This commit is contained in:
		@@ -115,6 +115,17 @@ class MediaNotifier extends StateNotifier<MediaState> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  List<MediaItem> mergeMediaItems(
 | 
				
			||||||
 | 
					    List<MediaItem> current,
 | 
				
			||||||
 | 
					    List<MediaItem> incoming,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    final existingIds = current.map((item) => item.id).toSet();
 | 
				
			||||||
 | 
					    final newItems = incoming
 | 
				
			||||||
 | 
					        .where((item) => !existingIds.contains(item.id))
 | 
				
			||||||
 | 
					        .toList();
 | 
				
			||||||
 | 
					    return [...current, ...newItems];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> loadMedia({int? id}) async {
 | 
					  Future<void> loadMedia({int? id}) async {
 | 
				
			||||||
    if (state.isLoading) return;
 | 
					    if (state.isLoading) return;
 | 
				
			||||||
    state = state.replace(isLoading: true);
 | 
					    state = state.replace(isLoading: true);
 | 
				
			||||||
@@ -128,8 +139,11 @@ class MediaNotifier extends StateNotifier<MediaState> {
 | 
				
			|||||||
        random: state.random,
 | 
					        random: state.random,
 | 
				
			||||||
        tag: state.tag,
 | 
					        tag: state.tag,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (newMedia.isNotEmpty) {
 | 
					      if (newMedia.isNotEmpty) {
 | 
				
			||||||
        addMediaItems(newMedia);
 | 
					        state = state.replace(
 | 
				
			||||||
 | 
					          mediaItems: mergeMediaItems(state.mediaItems, newMedia),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      print('Fehler beim Laden der Medien: $e');
 | 
					      print('Fehler beim Laden der Medien: $e');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
 | 
				
			|||||||
import 'dart:typed_data';
 | 
					import 'dart:typed_data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/screens/fullscreen_screen.dart';
 | 
					import 'package:f0ckapp/screens/fullscreen_screen.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
@@ -72,7 +73,9 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
				
			|||||||
  Future<void> _downloadMedia() async {
 | 
					  Future<void> _downloadMedia() async {
 | 
				
			||||||
    final MediaState mediaState = ref.read(mediaProvider);
 | 
					    final MediaState mediaState = ref.read(mediaProvider);
 | 
				
			||||||
    final MediaItem currentItem = mediaState.mediaItems[_currentIndex];
 | 
					    final MediaItem currentItem = mediaState.mediaItems[_currentIndex];
 | 
				
			||||||
    final File file = await DefaultCacheManager().getSingleFile(currentItem.mediaUrl);
 | 
					    final File file = await DefaultCacheManager().getSingleFile(
 | 
				
			||||||
 | 
					      currentItem.mediaUrl,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
    final MethodChannel methodChannel = const MethodChannel('MediaShit');
 | 
					    final MethodChannel methodChannel = const MethodChannel('MediaShit');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool? success = await methodChannel.invokeMethod<bool>('saveFile', {
 | 
					    bool? success = await methodChannel.invokeMethod<bool>('saveFile', {
 | 
				
			||||||
@@ -81,7 +84,9 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    success == true
 | 
					    success == true
 | 
				
			||||||
        ? _showMsg('${currentItem.dest} wurde in Downloads/fApp neigespeichert.')
 | 
					        ? _showMsg(
 | 
				
			||||||
 | 
					            '${currentItem.dest} wurde in Downloads/fApp neigespeichert.',
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
        : _showMsg('${currentItem.dest} konnte nicht heruntergeladen werden.');
 | 
					        : _showMsg('${currentItem.dest} konnte nicht heruntergeladen werden.');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,7 +107,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
				
			|||||||
      Future.microtask(() {
 | 
					      Future.microtask(() {
 | 
				
			||||||
        ref
 | 
					        ref
 | 
				
			||||||
            .read(mediaProvider.notifier)
 | 
					            .read(mediaProvider.notifier)
 | 
				
			||||||
            .loadMedia(id: widget.initialItemId + 50);
 | 
					            .loadMedia(/*id: widget.initialItemId + 50*/);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      return Scaffold(
 | 
					      return Scaffold(
 | 
				
			||||||
        appBar: AppBar(),
 | 
					        appBar: AppBar(),
 | 
				
			||||||
@@ -198,8 +203,19 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
            icon: const Icon(Icons.share),
 | 
					            icon: const Icon(Icons.share),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
 | 
					          Builder(
 | 
				
			||||||
 | 
					            builder: (context) {
 | 
				
			||||||
 | 
					              return IconButton(
 | 
				
			||||||
 | 
					                icon: const Icon(Icons.menu),
 | 
				
			||||||
 | 
					                onPressed: () {
 | 
				
			||||||
 | 
					                  Scaffold.of(context).openEndDrawer();
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
					      endDrawer: EndDrawer(ref: ref),
 | 
				
			||||||
      body: Stack(
 | 
					      body: Stack(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          PageTransformer(
 | 
					          PageTransformer(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,12 @@
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
					 | 
				
			||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
import 'package:go_router/go_router.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/models/mediaitem_model.dart';
 | 
					 | 
				
			||||||
import 'package:f0ckapp/providers/media_provider.dart';
 | 
					import 'package:f0ckapp/providers/media_provider.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/appversion_util.dart';
 | 
					 | 
				
			||||||
import 'package:f0ckapp/providers/theme_provider.dart';
 | 
					 | 
				
			||||||
import 'package:f0ckapp/utils/customsearchdelegate_util.dart';
 | 
					import 'package:f0ckapp/utils/customsearchdelegate_util.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/media_tile.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/filter_bar.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MediaGrid extends ConsumerStatefulWidget {
 | 
					class MediaGrid extends ConsumerStatefulWidget {
 | 
				
			||||||
  const MediaGrid({super.key});
 | 
					  const MediaGrid({super.key});
 | 
				
			||||||
@@ -19,16 +17,6 @@ class MediaGrid extends ConsumerStatefulWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
					class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			||||||
  final ScrollController _scrollController = ScrollController();
 | 
					  final ScrollController _scrollController = ScrollController();
 | 
				
			||||||
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  final TextEditingController _usernameController = TextEditingController();
 | 
					 | 
				
			||||||
  final TextEditingController _passwordController = TextEditingController();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  int _calculateCrossAxisCount(BuildContext context, int defaultCount) {
 | 
					 | 
				
			||||||
    return defaultCount == 0
 | 
					 | 
				
			||||||
        ? (MediaQuery.of(context).size.width / 110).clamp(3, 5).toInt()
 | 
					 | 
				
			||||||
        : defaultCount;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
@@ -47,8 +35,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void dispose() {
 | 
					  void dispose() {
 | 
				
			||||||
    _scrollController.dispose();
 | 
					    _scrollController.dispose();
 | 
				
			||||||
    _usernameController.dispose();
 | 
					 | 
				
			||||||
    _passwordController.dispose();
 | 
					 | 
				
			||||||
    super.dispose();
 | 
					    super.dispose();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,23 +44,33 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
    final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier);
 | 
					    final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
      key: _scaffoldKey,
 | 
					      body: RefreshIndicator(
 | 
				
			||||||
      appBar: AppBar(
 | 
					        onRefresh: () async {
 | 
				
			||||||
 | 
					          mediaNotifier.setTag(null);
 | 
				
			||||||
 | 
					          _scrollController.jumpTo(0);
 | 
				
			||||||
 | 
					          await mediaNotifier.loadMedia();
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        child: CustomScrollView(
 | 
				
			||||||
 | 
					          controller: _scrollController,
 | 
				
			||||||
 | 
					          slivers: [
 | 
				
			||||||
 | 
					            SliverAppBar(
 | 
				
			||||||
 | 
					              floating: true,
 | 
				
			||||||
 | 
					              snap: true,
 | 
				
			||||||
              title: GestureDetector(
 | 
					              title: GestureDetector(
 | 
				
			||||||
 | 
					                onTap: () {
 | 
				
			||||||
 | 
					                  mediaNotifier.setTag(null);
 | 
				
			||||||
 | 
					                  _scrollController.jumpTo(0);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                child: Row(
 | 
					                child: Row(
 | 
				
			||||||
            spacing: 10,
 | 
					 | 
				
			||||||
                  children: [
 | 
					                  children: [
 | 
				
			||||||
                    Image.asset(
 | 
					                    Image.asset(
 | 
				
			||||||
                      'assets/images/f0ck_small.webp',
 | 
					                      'assets/images/f0ck_small.webp',
 | 
				
			||||||
                      fit: BoxFit.fitHeight,
 | 
					                      fit: BoxFit.fitHeight,
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
              Text('fApp', style: TextStyle(fontSize: 24)),
 | 
					                    const SizedBox(width: 10),
 | 
				
			||||||
 | 
					                    const Text('fApp', style: TextStyle(fontSize: 24)),
 | 
				
			||||||
                  ],
 | 
					                  ],
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
          onTap: () {
 | 
					 | 
				
			||||||
            mediaNotifier.setTag(null);
 | 
					 | 
				
			||||||
            _scrollController.jumpTo(0);
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              actions: [
 | 
					              actions: [
 | 
				
			||||||
                IconButton(
 | 
					                IconButton(
 | 
				
			||||||
@@ -88,167 +84,58 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
                ),
 | 
					                ),
 | 
				
			||||||
                IconButton(
 | 
					                IconButton(
 | 
				
			||||||
                  icon: Icon(
 | 
					                  icon: Icon(
 | 
				
			||||||
              mediaState.random ? Icons.shuffle_on_outlined : Icons.shuffle,
 | 
					                    mediaState.random
 | 
				
			||||||
 | 
					                        ? Icons.shuffle_on_outlined
 | 
				
			||||||
 | 
					                        : Icons.shuffle,
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  onPressed: () {
 | 
					                  onPressed: () {
 | 
				
			||||||
                    mediaNotifier.toggleRandom();
 | 
					                    mediaNotifier.toggleRandom();
 | 
				
			||||||
                    _scrollController.jumpTo(0);
 | 
					                    _scrollController.jumpTo(0);
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
          IconButton(
 | 
					                Builder(
 | 
				
			||||||
 | 
					                  builder: (context) {
 | 
				
			||||||
 | 
					                    return IconButton(
 | 
				
			||||||
                      icon: const Icon(Icons.menu),
 | 
					                      icon: const Icon(Icons.menu),
 | 
				
			||||||
                      onPressed: () {
 | 
					                      onPressed: () {
 | 
				
			||||||
              _scaffoldKey.currentState?.openEndDrawer();
 | 
					                        Scaffold.of(context).openEndDrawer();
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
      bottomNavigationBar: BottomAppBar(
 | 
					            SliverPadding(
 | 
				
			||||||
        height: 50,
 | 
					              padding: const EdgeInsets.all(5.0),
 | 
				
			||||||
        child: Row(
 | 
					              sliver: SliverGrid(
 | 
				
			||||||
          mainAxisAlignment: MainAxisAlignment.spaceAround,
 | 
					                delegate: SliverChildBuilderDelegate(
 | 
				
			||||||
          children: [
 | 
					                  (context, index) {
 | 
				
			||||||
            const Text('type: '),
 | 
					                    if (index >= mediaState.mediaItems.length) {
 | 
				
			||||||
            DropdownButton<String>(
 | 
					                      return const Center(child: CircularProgressIndicator());
 | 
				
			||||||
              value: mediaTypes[mediaState.typeIndex],
 | 
					 | 
				
			||||||
              isDense: true,
 | 
					 | 
				
			||||||
              items: mediaTypes.map((String value) {
 | 
					 | 
				
			||||||
                return DropdownMenuItem<String>(
 | 
					 | 
				
			||||||
                  value: value,
 | 
					 | 
				
			||||||
                  child: Text(value),
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
              }).toList(),
 | 
					 | 
				
			||||||
              onChanged: (String? newValue) {
 | 
					 | 
				
			||||||
                if (newValue != null) {
 | 
					 | 
				
			||||||
                  mediaNotifier.setType(newValue);
 | 
					 | 
				
			||||||
                  _scrollController.jumpTo(0);
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    return MediaTile(item: mediaState.mediaItems[index]);
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
 | 
					                  childCount:
 | 
				
			||||||
 | 
					                      mediaState.mediaItems.length +
 | 
				
			||||||
 | 
					                      (mediaState.isLoading ? 1 : 0),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
 | 
				
			||||||
 | 
					                  maxCrossAxisExtent: 150,
 | 
				
			||||||
 | 
					                  crossAxisSpacing: 5,
 | 
				
			||||||
 | 
					                  mainAxisSpacing: 5,
 | 
				
			||||||
 | 
					                  childAspectRatio: 1,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            const Text('mode: '),
 | 
					 | 
				
			||||||
            DropdownButton<String>(
 | 
					 | 
				
			||||||
              value: mediaModes[mediaState.modeIndex],
 | 
					 | 
				
			||||||
              isDense: true,
 | 
					 | 
				
			||||||
              items: mediaModes.map((String value) {
 | 
					 | 
				
			||||||
                return DropdownMenuItem<String>(
 | 
					 | 
				
			||||||
                  value: value,
 | 
					 | 
				
			||||||
                  child: Text(value),
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
              }).toList(),
 | 
					 | 
				
			||||||
              onChanged: (String? newValue) {
 | 
					 | 
				
			||||||
                if (newValue != null) {
 | 
					 | 
				
			||||||
                  mediaNotifier.setMode(mediaModes.indexOf(newValue));
 | 
					 | 
				
			||||||
                  _scrollController.jumpTo(0);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      endDrawer: Drawer(
 | 
					      bottomNavigationBar: FilterBar(
 | 
				
			||||||
        child: ListView(
 | 
					        mediaNotifier: mediaNotifier,
 | 
				
			||||||
          padding: EdgeInsets.zero,
 | 
					        mediaState: mediaState,
 | 
				
			||||||
          children: [
 | 
					        scrollController: _scrollController,
 | 
				
			||||||
            DrawerHeader(
 | 
					 | 
				
			||||||
              decoration: const BoxDecoration(
 | 
					 | 
				
			||||||
                image: DecorationImage(
 | 
					 | 
				
			||||||
                  image: AssetImage('assets/images/menu.webp'),
 | 
					 | 
				
			||||||
                  fit: BoxFit.cover,
 | 
					 | 
				
			||||||
                  alignment: Alignment.topCenter,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
              child: null,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            /*ExpansionTile(
 | 
					 | 
				
			||||||
              title: const Text('Login'),
 | 
					 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                Padding(
 | 
					 | 
				
			||||||
                  padding: const EdgeInsets.all(16.0),
 | 
					 | 
				
			||||||
                  child: Column(
 | 
					 | 
				
			||||||
                    children: [
 | 
					 | 
				
			||||||
                      TextField(
 | 
					 | 
				
			||||||
                        readOnly: true,
 | 
					 | 
				
			||||||
                        controller: _usernameController,
 | 
					 | 
				
			||||||
                        decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                          labelText: 'Benutzername',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      const SizedBox(height: 10),
 | 
					 | 
				
			||||||
                      TextField(
 | 
					 | 
				
			||||||
                        readOnly: true,
 | 
					 | 
				
			||||||
                        controller: _passwordController,
 | 
					 | 
				
			||||||
                        obscureText: true,
 | 
					 | 
				
			||||||
                        decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                          labelText: 'Passwort',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      const SizedBox(height: 20),
 | 
					 | 
				
			||||||
                      ElevatedButton(
 | 
					 | 
				
			||||||
                        onPressed: () async {
 | 
					 | 
				
			||||||
                          ScaffoldMessenger.of(context).showSnackBar(
 | 
					 | 
				
			||||||
                            const SnackBar(
 | 
					 | 
				
			||||||
                              content: Text("noch nicht implementiert lol"),
 | 
					 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                            final success = await login(
 | 
					 | 
				
			||||||
                              _usernameController.text,
 | 
					 | 
				
			||||||
                              _passwordController.text,
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            if (success) {
 | 
					 | 
				
			||||||
                              Navigator.pop(context);
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                              ScaffoldMessenger.of(context).showSnackBar(
 | 
					 | 
				
			||||||
                                SnackBar(content: Text("Login fehlgeschlagen!")),
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                          );
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        child: const Text('Login'),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),*/
 | 
					 | 
				
			||||||
            ExpansionTile(
 | 
					 | 
				
			||||||
              title: const Text('Theme'),
 | 
					 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                Padding(
 | 
					 | 
				
			||||||
                  padding: const EdgeInsets.symmetric(horizontal: 16.0),
 | 
					 | 
				
			||||||
                  child: Column(
 | 
					 | 
				
			||||||
                    children: themeMap.entries.map((entry) {
 | 
					 | 
				
			||||||
                      final String themeName = entry.key;
 | 
					 | 
				
			||||||
                      final ThemeData themeData = entry.value;
 | 
					 | 
				
			||||||
                      final ThemeData currentTheme = ref.watch(themeNotifierProvider);
 | 
					 | 
				
			||||||
                      final bool isSelected = currentTheme == themeData;
 | 
					 | 
				
			||||||
                      return ListTile(
 | 
					 | 
				
			||||||
                        title: Text(themeName),
 | 
					 | 
				
			||||||
                        selected: isSelected,
 | 
					 | 
				
			||||||
                        selectedTileColor: Colors.blue.withValues(alpha: 0.2),
 | 
					 | 
				
			||||||
                        onTap: () async {
 | 
					 | 
				
			||||||
                          await ref
 | 
					 | 
				
			||||||
                              .read(themeNotifierProvider.notifier)
 | 
					 | 
				
			||||||
                              .updateTheme(themeName);
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
                    }).toList(),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            ListTile(
 | 
					 | 
				
			||||||
              title: Text('v${AppVersion.version}'),
 | 
					 | 
				
			||||||
              onTap: () {
 | 
					 | 
				
			||||||
                ScaffoldMessenger.of(context).showSnackBar(
 | 
					 | 
				
			||||||
                  const SnackBar(
 | 
					 | 
				
			||||||
                    content: Text('jooong lass das, hier ist nichts'),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ],
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 | 
					      endDrawer: EndDrawer(ref: ref),
 | 
				
			||||||
      persistentFooterButtons: mediaState.tag != null
 | 
					      persistentFooterButtons: mediaState.tag != null
 | 
				
			||||||
          ? [
 | 
					          ? [
 | 
				
			||||||
              Center(
 | 
					              Center(
 | 
				
			||||||
@@ -262,62 +149,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
				
			|||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
          : null,
 | 
					          : null,
 | 
				
			||||||
      body: RefreshIndicator(
 | 
					 | 
				
			||||||
        onRefresh: () async {
 | 
					 | 
				
			||||||
          mediaNotifier.resetMedia();
 | 
					 | 
				
			||||||
          _scrollController.jumpTo(0);
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        child: GridView.builder(
 | 
					 | 
				
			||||||
          key: const PageStorageKey('mediaGrid'),
 | 
					 | 
				
			||||||
          controller: _scrollController,
 | 
					 | 
				
			||||||
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | 
					 | 
				
			||||||
            crossAxisCount: _calculateCrossAxisCount(
 | 
					 | 
				
			||||||
              context,
 | 
					 | 
				
			||||||
              mediaState.crossAxisCount,
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            crossAxisSpacing: 5.0,
 | 
					 | 
				
			||||||
            mainAxisSpacing: 5.0,
 | 
					 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          itemCount:
 | 
					 | 
				
			||||||
              mediaState.mediaItems.length + (mediaState.isLoading ? 1 : 0),
 | 
					 | 
				
			||||||
          itemBuilder: (BuildContext context, int index) {
 | 
					 | 
				
			||||||
            if (index >= mediaState.mediaItems.length) {
 | 
					 | 
				
			||||||
              return const Center(child: CircularProgressIndicator());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            final MediaItem item = mediaState.mediaItems[index];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return InkWell(
 | 
					 | 
				
			||||||
              onTap: () async {
 | 
					 | 
				
			||||||
                context.push('/${item.id}', extra: true);
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              child: Stack(
 | 
					 | 
				
			||||||
                fit: StackFit.expand,
 | 
					 | 
				
			||||||
                children: <Widget>[
 | 
					 | 
				
			||||||
                  CachedNetworkImage(
 | 
					 | 
				
			||||||
                    imageUrl: item.thumbnailUrl,
 | 
					 | 
				
			||||||
                    fit: BoxFit.cover,
 | 
					 | 
				
			||||||
                    placeholder: (context, url) => const SizedBox.shrink(),
 | 
					 | 
				
			||||||
                    errorWidget: (context, url, error) =>
 | 
					 | 
				
			||||||
                        const Icon(Icons.error),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  Align(
 | 
					 | 
				
			||||||
                    alignment: Alignment.bottomRight,
 | 
					 | 
				
			||||||
                    child: Icon(
 | 
					 | 
				
			||||||
                      Icons.square,
 | 
					 | 
				
			||||||
                      color: switch (item.mode) {
 | 
					 | 
				
			||||||
                        1 => Colors.green,
 | 
					 | 
				
			||||||
                        2 => Colors.red,
 | 
					 | 
				
			||||||
                        _ => Colors.yellow,
 | 
					 | 
				
			||||||
                      },
 | 
					 | 
				
			||||||
                      size: 15.0,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,16 +13,16 @@ final FlutterSecureStorage storage = const FlutterSecureStorage(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Future<List<MediaItem>> fetchMedia({
 | 
					Future<List<MediaItem>> fetchMedia({
 | 
				
			||||||
  int? older,
 | 
					  int? older,
 | 
				
			||||||
  String? type,
 | 
					  String type = 'image',
 | 
				
			||||||
  int? mode,
 | 
					  int mode = 0,
 | 
				
			||||||
  bool? random,
 | 
					  bool random = false,
 | 
				
			||||||
  String? tag,
 | 
					  String? tag,
 | 
				
			||||||
}) async {
 | 
					}) async {
 | 
				
			||||||
  final Uri url = Uri.parse('https://api.f0ck.me/items/get').replace(
 | 
					  final Uri url = Uri.parse('https://api.f0ck.me/items/get').replace(
 | 
				
			||||||
    queryParameters: {
 | 
					    queryParameters: {
 | 
				
			||||||
      'type': type ?? 'image',
 | 
					      'type': type,
 | 
				
			||||||
      'mode': (mode ?? 0).toString(),
 | 
					      'mode': mode.toString(),
 | 
				
			||||||
      'random': (random! ? 1 : 0).toString(),
 | 
					      'random': (random ? 1 : 0).toString(),
 | 
				
			||||||
      if (tag != null) 'tag': tag,
 | 
					      if (tag != null) 'tag': tag,
 | 
				
			||||||
      if (older != null) 'older': older.toString(),
 | 
					      if (older != null) 'older': older.toString(),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -61,14 +61,14 @@ Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (response.statusCode == 200) {
 | 
					    if (response.statusCode == 200) {
 | 
				
			||||||
      final dynamic decoded = jsonDecode(response.body);
 | 
					      final dynamic decoded = jsonDecode(response.body);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (decoded is List) {
 | 
					      if (decoded is List) {
 | 
				
			||||||
        return decoded
 | 
					        final suggestions = decoded
 | 
				
			||||||
            .map((item) => Suggestion.fromJson(item as Map<String, dynamic>))
 | 
					            .map((item) => Suggestion.fromJson(item as Map<String, dynamic>))
 | 
				
			||||||
            .toList()
 | 
					            .toList();
 | 
				
			||||||
          ..sort((a, b) => b.score.compareTo(a.score));
 | 
					        suggestions.sort((a, b) => b.score.compareTo(a.score));
 | 
				
			||||||
 | 
					        return suggestions;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        throw Exception('Unerwartetes Format: Erwartet wurde eine Liste.');
 | 
					        throw Exception('Unerwartetes Format: Es wurde eine Liste erwartet.');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else if (response.statusCode == 400) {
 | 
					    } else if (response.statusCode == 400) {
 | 
				
			||||||
      final dynamic error = jsonDecode(response.body);
 | 
					      final dynamic error = jsonDecode(response.body);
 | 
				
			||||||
@@ -84,7 +84,7 @@ Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
				
			|||||||
  } on TimeoutException {
 | 
					  } on TimeoutException {
 | 
				
			||||||
    throw Exception('Anfrage an die API hat zu lange gedauert.');
 | 
					    throw Exception('Anfrage an die API hat zu lange gedauert.');
 | 
				
			||||||
  } catch (e) {
 | 
					  } catch (e) {
 | 
				
			||||||
    throw Exception('Fehler beim Verarbeiten der Anfrage: $e');
 | 
					    throw Exception('Fehler bei der Verarbeitung der Anfrage: $e');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,12 +98,14 @@ Future<bool> login(String username, String password) async {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  if (response.statusCode == 200) {
 | 
					  if (response.statusCode == 200) {
 | 
				
			||||||
    final dynamic data = jsonDecode(response.body);
 | 
					    final dynamic data = jsonDecode(response.body);
 | 
				
			||||||
    final dynamic token = data['token'];
 | 
					    final token = data['token'];
 | 
				
			||||||
 | 
					    if (token != null) {
 | 
				
			||||||
      await storage.write(key: "token", value: token);
 | 
					      await storage.write(key: "token", value: token);
 | 
				
			||||||
 | 
					 | 
				
			||||||
      return true;
 | 
					      return true;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
    return false;
 | 
					      throw Exception('Token nicht im Response enthalten.');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    throw Exception('Login fehlgeschlagen: ${response.statusCode}');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										122
									
								
								lib/widgets/end_drawer.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								lib/widgets/end_drawer.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/providers/theme_provider.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/utils/appversion_util.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EndDrawer extends StatelessWidget {
 | 
				
			||||||
 | 
					  final WidgetRef ref;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const EndDrawer({super.key, required this.ref});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Drawer(
 | 
				
			||||||
 | 
					      child: ListView(
 | 
				
			||||||
 | 
					        padding: EdgeInsets.zero,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          DrawerHeader(
 | 
				
			||||||
 | 
					            decoration: const BoxDecoration(
 | 
				
			||||||
 | 
					              image: DecorationImage(
 | 
				
			||||||
 | 
					                image: AssetImage('assets/images/menu.webp'),
 | 
				
			||||||
 | 
					                fit: BoxFit.cover,
 | 
				
			||||||
 | 
					                alignment: Alignment.topCenter,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            child: null,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          /*ExpansionTile(
 | 
				
			||||||
 | 
					              title: const Text('Login'),
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Padding(
 | 
				
			||||||
 | 
					                  padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                  child: Column(
 | 
				
			||||||
 | 
					                    children: [
 | 
				
			||||||
 | 
					                      TextField(
 | 
				
			||||||
 | 
					                        readOnly: true,
 | 
				
			||||||
 | 
					                        controller: _usernameController,
 | 
				
			||||||
 | 
					                        decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                          labelText: 'Benutzername',
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      const SizedBox(height: 10),
 | 
				
			||||||
 | 
					                      TextField(
 | 
				
			||||||
 | 
					                        readOnly: true,
 | 
				
			||||||
 | 
					                        controller: _passwordController,
 | 
				
			||||||
 | 
					                        obscureText: true,
 | 
				
			||||||
 | 
					                        decoration: const InputDecoration(
 | 
				
			||||||
 | 
					                          labelText: 'Passwort',
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                      const SizedBox(height: 20),
 | 
				
			||||||
 | 
					                      ElevatedButton(
 | 
				
			||||||
 | 
					                        onPressed: () async {
 | 
				
			||||||
 | 
					                          ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
 | 
					                            const SnackBar(
 | 
				
			||||||
 | 
					                              content: Text("noch nicht implementiert lol"),
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            final success = await login(
 | 
				
			||||||
 | 
					                              _usernameController.text,
 | 
				
			||||||
 | 
					                              _passwordController.text,
 | 
				
			||||||
 | 
					                            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (success) {
 | 
				
			||||||
 | 
					                              Navigator.pop(context);
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
 | 
					                              ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
 | 
					                                SnackBar(content: Text("Login fehlgeschlagen!")),
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        child: const Text('Login'),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            ),*/
 | 
				
			||||||
 | 
					          ExpansionTile(
 | 
				
			||||||
 | 
					            title: const Text('Theme'),
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.symmetric(horizontal: 16.0),
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  children: themeMap.entries.map((entry) {
 | 
				
			||||||
 | 
					                    final String themeName = entry.key;
 | 
				
			||||||
 | 
					                    final ThemeData themeData = entry.value;
 | 
				
			||||||
 | 
					                    final ThemeData currentTheme = ref.watch(
 | 
				
			||||||
 | 
					                      themeNotifierProvider,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    final bool isSelected = currentTheme == themeData;
 | 
				
			||||||
 | 
					                    return ListTile(
 | 
				
			||||||
 | 
					                      title: Text(themeName),
 | 
				
			||||||
 | 
					                      selected: isSelected,
 | 
				
			||||||
 | 
					                      selectedTileColor: Colors.blue.withValues(alpha: 0.2),
 | 
				
			||||||
 | 
					                      onTap: () async {
 | 
				
			||||||
 | 
					                        await ref
 | 
				
			||||||
 | 
					                            .read(themeNotifierProvider.notifier)
 | 
				
			||||||
 | 
					                            .updateTheme(themeName);
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  }).toList(),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          ListTile(
 | 
				
			||||||
 | 
					            title: Text('v${AppVersion.version}'),
 | 
				
			||||||
 | 
					            onTap: () {
 | 
				
			||||||
 | 
					              ScaffoldMessenger.of(context).showSnackBar(
 | 
				
			||||||
 | 
					                const SnackBar(
 | 
				
			||||||
 | 
					                  content: Text('jooong lass das, hier ist nichts'),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										62
									
								
								lib/widgets/filter_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/widgets/filter_bar.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/providers/media_provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FilterBar extends StatelessWidget {
 | 
				
			||||||
 | 
					  final MediaState mediaState;
 | 
				
			||||||
 | 
					  final MediaNotifier mediaNotifier;
 | 
				
			||||||
 | 
					  final ScrollController scrollController;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const FilterBar({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.mediaState,
 | 
				
			||||||
 | 
					    required this.mediaNotifier,
 | 
				
			||||||
 | 
					    required this.scrollController,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return BottomAppBar(
 | 
				
			||||||
 | 
					      height: 50,
 | 
				
			||||||
 | 
					      child: Row(
 | 
				
			||||||
 | 
					        mainAxisAlignment: MainAxisAlignment.spaceAround,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          const Text('type: '),
 | 
				
			||||||
 | 
					          DropdownButton<String>(
 | 
				
			||||||
 | 
					            value: mediaTypes[mediaState.typeIndex],
 | 
				
			||||||
 | 
					            isDense: true,
 | 
				
			||||||
 | 
					            items: mediaTypes.map((String value) {
 | 
				
			||||||
 | 
					              return DropdownMenuItem<String>(
 | 
				
			||||||
 | 
					                value: value,
 | 
				
			||||||
 | 
					                child: Text(value),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }).toList(),
 | 
				
			||||||
 | 
					            onChanged: (String? newValue) {
 | 
				
			||||||
 | 
					              if (newValue != null) {
 | 
				
			||||||
 | 
					                mediaNotifier.setType(newValue);
 | 
				
			||||||
 | 
					                scrollController.jumpTo(0);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          const Text('mode: '),
 | 
				
			||||||
 | 
					          DropdownButton<String>(
 | 
				
			||||||
 | 
					            value: mediaModes[mediaState.modeIndex],
 | 
				
			||||||
 | 
					            isDense: true,
 | 
				
			||||||
 | 
					            items: mediaModes.map((String value) {
 | 
				
			||||||
 | 
					              return DropdownMenuItem<String>(
 | 
				
			||||||
 | 
					                value: value,
 | 
				
			||||||
 | 
					                child: Text(value),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }).toList(),
 | 
				
			||||||
 | 
					            onChanged: (String? newValue) {
 | 
				
			||||||
 | 
					              if (newValue != null) {
 | 
				
			||||||
 | 
					                mediaNotifier.setMode(mediaModes.indexOf(newValue));
 | 
				
			||||||
 | 
					                scrollController.jumpTo(0);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								lib/widgets/media_tile.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								lib/widgets/media_tile.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					import 'package:go_router/go_router.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/mediaitem_model.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MediaTile extends StatelessWidget {
 | 
				
			||||||
 | 
					  final MediaItem item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const MediaTile({super.key, required this.item});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return InkWell(
 | 
				
			||||||
 | 
					      onTap: () {
 | 
				
			||||||
 | 
					        context.push('/${item.id}', extra: true);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      child: Stack(
 | 
				
			||||||
 | 
					        fit: StackFit.expand,
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          Hero(
 | 
				
			||||||
 | 
					            tag: 'media-${item.id}',
 | 
				
			||||||
 | 
					            child: CachedNetworkImage(
 | 
				
			||||||
 | 
					              imageUrl: item.thumbnailUrl,
 | 
				
			||||||
 | 
					              fit: BoxFit.cover,
 | 
				
			||||||
 | 
					              errorWidget: (context, url, error) => const Icon(Icons.error),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          Align(
 | 
				
			||||||
 | 
					            alignment: Alignment.bottomRight,
 | 
				
			||||||
 | 
					            child: Icon(
 | 
				
			||||||
 | 
					              Icons.square,
 | 
				
			||||||
 | 
					              color: switch (item.mode) {
 | 
				
			||||||
 | 
					                1 => Colors.green,
 | 
				
			||||||
 | 
					                2 => Colors.red,
 | 
				
			||||||
 | 
					                _ => Colors.yellow,
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              size: 15.0,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
				
			|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
					# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
				
			||||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
					# In Windows, build-name is used as the major, minor, and patch parts
 | 
				
			||||||
# of the product and file versions while build-number is used as the build suffix.
 | 
					# of the product and file versions while build-number is used as the build suffix.
 | 
				
			||||||
version: 1.1.19+49
 | 
					version: 1.1.20+50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.9.0-100.2.beta
 | 
					  sdk: ^3.9.0-100.2.beta
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user