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 {
 | 
			
		||||
    if (state.isLoading) return;
 | 
			
		||||
    state = state.replace(isLoading: true);
 | 
			
		||||
@@ -128,8 +139,11 @@ class MediaNotifier extends StateNotifier<MediaState> {
 | 
			
		||||
        random: state.random,
 | 
			
		||||
        tag: state.tag,
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (newMedia.isNotEmpty) {
 | 
			
		||||
        addMediaItems(newMedia);
 | 
			
		||||
        state = state.replace(
 | 
			
		||||
          mediaItems: mergeMediaItems(state.mediaItems, newMedia),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Fehler beim Laden der Medien: $e');
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/screens/fullscreen_screen.dart';
 | 
			
		||||
import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
 | 
			
		||||
@@ -72,7 +73,9 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
			
		||||
  Future<void> _downloadMedia() async {
 | 
			
		||||
    final MediaState mediaState = ref.read(mediaProvider);
 | 
			
		||||
    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');
 | 
			
		||||
 | 
			
		||||
    bool? success = await methodChannel.invokeMethod<bool>('saveFile', {
 | 
			
		||||
@@ -81,7 +84,9 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -102,7 +107,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
			
		||||
      Future.microtask(() {
 | 
			
		||||
        ref
 | 
			
		||||
            .read(mediaProvider.notifier)
 | 
			
		||||
            .loadMedia(id: widget.initialItemId + 50);
 | 
			
		||||
            .loadMedia(/*id: widget.initialItemId + 50*/);
 | 
			
		||||
      });
 | 
			
		||||
      return Scaffold(
 | 
			
		||||
        appBar: AppBar(),
 | 
			
		||||
@@ -198,8 +203,19 @@ class _DetailViewState extends ConsumerState<DetailView> {
 | 
			
		||||
            ],
 | 
			
		||||
            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(
 | 
			
		||||
        children: [
 | 
			
		||||
          PageTransformer(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,12 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:cached_network_image/cached_network_image.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/utils/appversion_util.dart';
 | 
			
		||||
import 'package:f0ckapp/providers/theme_provider.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 {
 | 
			
		||||
  const MediaGrid({super.key});
 | 
			
		||||
@@ -19,16 +17,6 @@ class MediaGrid extends ConsumerStatefulWidget {
 | 
			
		||||
 | 
			
		||||
class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
			
		||||
  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
 | 
			
		||||
  void initState() {
 | 
			
		||||
@@ -47,8 +35,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _scrollController.dispose();
 | 
			
		||||
    _usernameController.dispose();
 | 
			
		||||
    _passwordController.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -58,23 +44,33 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
			
		||||
    final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier);
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      key: _scaffoldKey,
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
      body: RefreshIndicator(
 | 
			
		||||
        onRefresh: () async {
 | 
			
		||||
          mediaNotifier.setTag(null);
 | 
			
		||||
          _scrollController.jumpTo(0);
 | 
			
		||||
          await mediaNotifier.loadMedia();
 | 
			
		||||
        },
 | 
			
		||||
        child: CustomScrollView(
 | 
			
		||||
          controller: _scrollController,
 | 
			
		||||
          slivers: [
 | 
			
		||||
            SliverAppBar(
 | 
			
		||||
              floating: true,
 | 
			
		||||
              snap: true,
 | 
			
		||||
              title: GestureDetector(
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  mediaNotifier.setTag(null);
 | 
			
		||||
                  _scrollController.jumpTo(0);
 | 
			
		||||
                },
 | 
			
		||||
                child: Row(
 | 
			
		||||
            spacing: 10,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Image.asset(
 | 
			
		||||
                      'assets/images/f0ck_small.webp',
 | 
			
		||||
                      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: [
 | 
			
		||||
                IconButton(
 | 
			
		||||
@@ -88,167 +84,58 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
			
		||||
                ),
 | 
			
		||||
                IconButton(
 | 
			
		||||
                  icon: Icon(
 | 
			
		||||
              mediaState.random ? Icons.shuffle_on_outlined : Icons.shuffle,
 | 
			
		||||
                    mediaState.random
 | 
			
		||||
                        ? Icons.shuffle_on_outlined
 | 
			
		||||
                        : Icons.shuffle,
 | 
			
		||||
                  ),
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
                    mediaNotifier.toggleRandom();
 | 
			
		||||
                    _scrollController.jumpTo(0);
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
          IconButton(
 | 
			
		||||
                Builder(
 | 
			
		||||
                  builder: (context) {
 | 
			
		||||
                    return IconButton(
 | 
			
		||||
                      icon: const Icon(Icons.menu),
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
              _scaffoldKey.currentState?.openEndDrawer();
 | 
			
		||||
                        Scaffold.of(context).openEndDrawer();
 | 
			
		||||
                      },
 | 
			
		||||
                    );
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ),
 | 
			
		||||
      bottomNavigationBar: 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);
 | 
			
		||||
            SliverPadding(
 | 
			
		||||
              padding: const EdgeInsets.all(5.0),
 | 
			
		||||
              sliver: SliverGrid(
 | 
			
		||||
                delegate: SliverChildBuilderDelegate(
 | 
			
		||||
                  (context, index) {
 | 
			
		||||
                    if (index >= mediaState.mediaItems.length) {
 | 
			
		||||
                      return const Center(child: CircularProgressIndicator());
 | 
			
		||||
                    }
 | 
			
		||||
                    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(
 | 
			
		||||
        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'),
 | 
			
		||||
                  ),
 | 
			
		||||
                );
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      bottomNavigationBar: FilterBar(
 | 
			
		||||
        mediaNotifier: mediaNotifier,
 | 
			
		||||
        mediaState: mediaState,
 | 
			
		||||
        scrollController: _scrollController,
 | 
			
		||||
      ),
 | 
			
		||||
      endDrawer: EndDrawer(ref: ref),
 | 
			
		||||
      persistentFooterButtons: mediaState.tag != null
 | 
			
		||||
          ? [
 | 
			
		||||
              Center(
 | 
			
		||||
@@ -262,62 +149,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
 | 
			
		||||
              ),
 | 
			
		||||
            ]
 | 
			
		||||
          : 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({
 | 
			
		||||
  int? older,
 | 
			
		||||
  String? type,
 | 
			
		||||
  int? mode,
 | 
			
		||||
  bool? random,
 | 
			
		||||
  String type = 'image',
 | 
			
		||||
  int mode = 0,
 | 
			
		||||
  bool random = false,
 | 
			
		||||
  String? tag,
 | 
			
		||||
}) async {
 | 
			
		||||
  final Uri url = Uri.parse('https://api.f0ck.me/items/get').replace(
 | 
			
		||||
    queryParameters: {
 | 
			
		||||
      'type': type ?? 'image',
 | 
			
		||||
      'mode': (mode ?? 0).toString(),
 | 
			
		||||
      'random': (random! ? 1 : 0).toString(),
 | 
			
		||||
      'type': type,
 | 
			
		||||
      'mode': mode.toString(),
 | 
			
		||||
      'random': (random ? 1 : 0).toString(),
 | 
			
		||||
      if (tag != null) 'tag': tag,
 | 
			
		||||
      if (older != null) 'older': older.toString(),
 | 
			
		||||
    },
 | 
			
		||||
@@ -61,14 +61,14 @@ Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
			
		||||
 | 
			
		||||
    if (response.statusCode == 200) {
 | 
			
		||||
      final dynamic decoded = jsonDecode(response.body);
 | 
			
		||||
 | 
			
		||||
      if (decoded is List) {
 | 
			
		||||
        return decoded
 | 
			
		||||
        final suggestions = decoded
 | 
			
		||||
            .map((item) => Suggestion.fromJson(item as Map<String, dynamic>))
 | 
			
		||||
            .toList()
 | 
			
		||||
          ..sort((a, b) => b.score.compareTo(a.score));
 | 
			
		||||
            .toList();
 | 
			
		||||
        suggestions.sort((a, b) => b.score.compareTo(a.score));
 | 
			
		||||
        return suggestions;
 | 
			
		||||
      } else {
 | 
			
		||||
        throw Exception('Unerwartetes Format: Erwartet wurde eine Liste.');
 | 
			
		||||
        throw Exception('Unerwartetes Format: Es wurde eine Liste erwartet.');
 | 
			
		||||
      }
 | 
			
		||||
    } else if (response.statusCode == 400) {
 | 
			
		||||
      final dynamic error = jsonDecode(response.body);
 | 
			
		||||
@@ -84,7 +84,7 @@ Future<List<Suggestion>> fetchSuggestions(String query) async {
 | 
			
		||||
  } on TimeoutException {
 | 
			
		||||
    throw Exception('Anfrage an die API hat zu lange gedauert.');
 | 
			
		||||
  } 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) {
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    } 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
 | 
			
		||||
# 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.
 | 
			
		||||
version: 1.1.19+49
 | 
			
		||||
version: 1.1.20+50
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: ^3.9.0-100.2.beta
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user