diff --git a/lib/main.dart b/lib/main.dart index 6f5d799..83e18ff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,78 +1,47 @@ +import 'package:f0ckapp/providers/theme_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:f0ckapp/screens/mediagrid_screen.dart'; -import 'package:f0ckapp/screens/detailview_screen.dart'; -import 'package:f0ckapp/screens/settings_screen.dart'; +import 'package:get/get.dart'; + import 'package:f0ckapp/utils/appversion_util.dart'; -import 'package:f0ckapp/providers/theme_provider.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; +import 'package:f0ckapp/services/api_service.dart'; +import 'package:f0ckapp/screens/media_grid.dart'; +import 'package:f0ckapp/screens/settings_screen.dart'; +import 'package:f0ckapp/screens/detailview_screen.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await AppVersion.init(); + final ThemeController themeController = Get.put(ThemeController()); - runApp(ProviderScope(child: F0ckApp())); -} + final api = ApiService(); + await api.fetchMedia(); + Get.put(api); -class F0ckApp extends ConsumerWidget { - const F0ckApp({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final ThemeData theme = ref.watch(themeNotifierProvider); - - return MaterialApp( - debugShowCheckedModeBanner: false, - theme: theme, + runApp( + GetMaterialApp( + theme: themeController.currentTheme.value, initialRoute: '/', - routes: { - '/': (context) => const MediaGrid(), - '/settings': (context) => const SettingsPage(), - }, - onGenerateRoute: (RouteSettings settings) { - final String? name = settings.name; - if (name == null) { - return MaterialPageRoute( - builder: (_) => - const Scaffold(body: Center(child: Text('Ungültiger Link'))), - settings: settings, - ); - } - - final RegExp regExp = RegExp( - r'^(?:/tag/(?[^/]+))?(?:/(?image|audio|video))?(?:/(?\d+))?$', - ); - final RegExpMatch? match = regExp.firstMatch(name); - - if (match != null) { - final String? tag = match.namedGroup('tag'); - final String? mime = match.namedGroup('mime'); - final String? idStr = match.namedGroup('itemid'); - final int? itemId = idStr != null ? int.tryParse(idStr) : null; - const int preloadOffset = 50; - - if (itemId != null) { - return MaterialPageRoute( - builder: (context) => DetailView(initialItemId: itemId), - settings: settings, - ); - } - - return MaterialPageRoute( - builder: (context) => const MediaGrid(), - settings: settings, - ); - } - - return MaterialPageRoute( - builder: (context) => - const Scaffold(body: Center(child: Text('Ungültiger Link'))), - settings: settings, - ); - }, - ); - } + getPages: [ + GetPage(name: '/', page: () => MediaGrid()), + GetPage(name: '/settings', page: () => SettingsPage()), + GetPage( + name: '/:itemId', + page: () { + int? test = int.tryParse(Get.parameters['itemId']!); + if (test == null) { + return Scaffold(body: Center(child: Text('oof'))); + } + return DetailView(initialItemId: test); + }, + ), + ], + unknownRoute: GetPage( + name: '/notfound', + page: () => Center(child: Text('oof')), + ), + ), + ); } diff --git a/lib/providers/media_provider.dart b/lib/providers/media_provider.dart deleted file mode 100644 index 102a798..0000000 --- a/lib/providers/media_provider.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -import 'package:f0ckapp/models/mediaitem_model.dart'; -import 'package:f0ckapp/services/api_service.dart'; - -const List mediaTypes = ["alles", "image", "video", "audio"]; -const List mediaModes = ["sfw", "nsfw", "untagged", "all"]; -const _unsetTag = Object(); - -class MediaState { - final int typeIndex; - final int modeIndex; - final bool random; - final String? tag; - final int crossAxisCount; - final List mediaItems; - final bool isLoading; - final bool muted; - - const MediaState({ - this.typeIndex = 0, - this.modeIndex = 0, - this.random = false, - this.tag, - this.crossAxisCount = 0, - this.mediaItems = const [], - this.isLoading = false, - this.muted = false, - }); - - MediaState replace({ - int? typeIndex, - int? modeIndex, - bool? random, - Object? tag = _unsetTag, - int? crossAxisCount, - List? mediaItems, - bool? isLoading, - bool? muted, - }) { - return MediaState( - typeIndex: typeIndex ?? this.typeIndex, - modeIndex: modeIndex ?? this.modeIndex, - random: random ?? this.random, - tag: identical(tag, _unsetTag) ? this.tag : tag as String?, - crossAxisCount: crossAxisCount ?? this.crossAxisCount, - mediaItems: mediaItems ?? this.mediaItems, - isLoading: isLoading ?? this.isLoading, - muted: muted ?? this.muted, - ); - } -} - -class MediaNotifier extends StateNotifier { - final _storage = const FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ); - - MediaNotifier() : super(const MediaState()) { - _loadMutedState(); - } - - Future _loadMutedState() async { - final storedMuted = await _storage.read(key: 'muted'); - final isMuted = storedMuted == 'true'; - state = state.replace(muted: isMuted); - } - - Future _saveMutedState() async { - await _storage.write(key: 'muted', value: state.muted.toString()); - } - - void setType(String type) { - final newIndex = mediaTypes.indexOf(type); - state = state.replace(typeIndex: newIndex); - resetMedia(); - } - - void setMode(int modeIndex) { - state = state.replace(modeIndex: modeIndex); - resetMedia(); - } - - void toggleRandom() { - state = state.replace(random: !state.random); - resetMedia(); - } - - void setTag(String? tag) { - state = state.replace(tag: tag); - resetMedia(); - } - - void setCrossAxisCount(int count) { - state = state.replace(crossAxisCount: count); - } - - void resetMedia() { - state = state.replace(mediaItems: []); - loadMedia(); - } - - void addMediaItems(List newItems) { - final Set existingIds = state.mediaItems - .map((item) => item.id) - .toSet(); - final List filteredItems = newItems - .where((item) => !existingIds.contains(item.id)) - .toList(); - if (filteredItems.isNotEmpty) { - final List updated = List.from(state.mediaItems) - ..addAll(filteredItems); - state = state.replace(mediaItems: updated); - } - } - - List mergeMediaItems( - List current, - List incoming, - ) { - final existingIds = current.map((item) => item.id).toSet(); - final newItems = incoming - .where((item) => !existingIds.contains(item.id)) - .toList(); - return [...current, ...newItems]; - } - - Future loadMedia({int? id}) async { - if (state.isLoading) return; - state = state.replace(isLoading: true); - try { - final older = - id ?? (state.mediaItems.isNotEmpty ? state.mediaItems.last.id : null); - final newMedia = await fetchMedia( - older: older, - type: mediaTypes[state.typeIndex], - mode: state.modeIndex, - random: state.random, - tag: state.tag, - ); - - if (newMedia.isNotEmpty) { - state = state.replace( - mediaItems: mergeMediaItems(state.mediaItems, newMedia), - ); - } - } catch (e) { - print('Fehler beim Laden der Medien: $e'); - } finally { - state = state.replace(isLoading: false); - } - } - - void toggleMute() { - state = state.replace(muted: !state.muted); - _saveMutedState(); - } -} - -final mediaProvider = StateNotifierProvider( - (ref) => MediaNotifier(), -); diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index c7311c1..15770df 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -1,59 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - +import 'package:get/get.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -final Map themeMap = { - 'f0ck': f0ckTheme, - 'P1nk': p1nkTheme, // done - 'Orange': orangeTheme, - 'Amoled': amoledTheme, - 'Paper': paperTheme, - //'Iced': icedTheme, - 'f0ck95': f0ck95Theme, - 'f0ck95d': f0ck95dTheme, -}; - -class ThemeNotifier extends StateNotifier { - final FlutterSecureStorage secureStorage; - - ThemeNotifier({required this.secureStorage}) : super(f0ckTheme) { - _loadTheme(); - } - - Future _loadTheme() async { - try { - String? savedThemeName = await secureStorage.read(key: 'theme'); - if (savedThemeName != null && themeMap.containsKey(savedThemeName)) { - state = themeMap[savedThemeName]!; - } - } catch (error) { - debugPrint('Fehler beim Laden des Themes: $error'); - state = f0ckTheme; - } - } - - Future updateTheme(String themeName) async { - try { - await secureStorage.write(key: 'theme', value: themeName); - state = themeMap[themeName] ?? f0ckTheme; - } catch (error) { - debugPrint('Fehler beim Aktualisieren des Themes: $error'); - } - } -} - -final themeNotifierProvider = StateNotifierProvider(( - ref, -) { - return ThemeNotifier( - secureStorage: const FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), - ), - ); -}); +// Definiere deine Themes wie gehabt: final ThemeData f0ckTheme = ThemeData( brightness: Brightness.dark, primaryColor: const Color(0xFF9FFF00), @@ -237,7 +186,7 @@ final ThemeData f0ck95Theme = ThemeData( backgroundColor: const Color(0xFFE0E0E0), foregroundColor: Colors.black, elevation: 4, - centerTitle: true + centerTitle: true, ), textTheme: const TextTheme( bodyLarge: TextStyle(color: Colors.black), @@ -286,3 +235,56 @@ final ThemeData f0ck95dTheme = ThemeData( trackColor: WidgetStateProperty.all(const Color(0xFF424242)), ), ); + +class ThemeController extends GetxController { + final FlutterSecureStorage secureStorage = const FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); + + final Rx currentTheme = f0ckTheme.obs; + + final Map themeMap = { + 'f0ck': f0ckTheme, + 'P1nk': p1nkTheme, + 'Orange': orangeTheme, + 'Amoled': amoledTheme, + 'Paper': paperTheme, + 'f0ck95': f0ck95Theme, + 'f0ck95d': f0ck95dTheme, + }; + + @override + void onInit() { + super.onInit(); + _loadTheme(); + } + + Future _loadTheme() async { + try { + final String? savedThemeName = await secureStorage.read(key: 'theme'); + if (savedThemeName != null && themeMap.containsKey(savedThemeName)) { + currentTheme.value = themeMap[savedThemeName]!; + Get.changeTheme(currentTheme.value); + } + } catch (error) { + debugPrint('Fehler beim Laden des Themes: $error'); + currentTheme.value = f0ckTheme; + Get.changeTheme(f0ckTheme); + } + } + + Future updateTheme(String themeName) async { + try { + await secureStorage.write(key: 'theme', value: themeName); + if (themeMap.containsKey(themeName)) { + currentTheme.value = themeMap[themeName]!; + Get.changeTheme(currentTheme.value); + } else { + currentTheme.value = f0ckTheme; + Get.changeTheme(f0ckTheme); + } + } catch (error) { + debugPrint('Fehler beim Aktualisieren des Themes: $error'); + } + } +} diff --git a/lib/screens/detailview_screen.dart b/lib/screens/detailview_screen.dart index a5eedd6..c6de1e6 100644 --- a/lib/screens/detailview_screen.dart +++ b/lib/screens/detailview_screen.dart @@ -1,66 +1,103 @@ import 'dart:io'; -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'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import 'package:get/get.dart'; import 'package:share_plus/share_plus.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; +import 'package:f0ckapp/services/api_service.dart'; +import 'package:f0ckapp/widgets/end_drawer.dart'; +import 'package:f0ckapp/screens/fullscreen_screen.dart'; import 'package:f0ckapp/widgets/video_widget.dart'; -import 'package:f0ckapp/utils/smartrefreshindicator_util.dart'; -import 'package:f0ckapp/utils/pagetransformer_util.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; -class DetailView extends ConsumerStatefulWidget { +class DetailView extends StatefulWidget { final int initialItemId; - const DetailView({super.key, required this.initialItemId}); @override - ConsumerState createState() => _DetailViewState(); + State createState() => _DetailViewState(); } -class _DetailViewState extends ConsumerState { +class _DetailViewState extends State { + final ApiService apiService = Get.find(); PageController? _pageController; - bool isLoading = false; - int _currentIndex = 0; + Future? _loadingFuture; + int _currentPage = 0; @override void initState() { super.initState(); - } - - void _preloadAdjacentMedia(int index) async { - final mediaState = ref.read(mediaProvider); - for (int offset in [-1, 1]) { - final adjacentIndex = index + offset; - if (adjacentIndex >= 0 && adjacentIndex < mediaState.mediaItems.length) { - final url = mediaState.mediaItems[adjacentIndex].mediaUrl; - if (await DefaultCacheManager().getFileFromCache(url) == null) { - await DefaultCacheManager().downloadFile(url); - } - } + if (!_mediaItemExists(widget.initialItemId)) { + _loadingFuture = _fetchAndPreloadMedia(widget.initialItemId); + } else { + _initializePageController(); } } - Future _loadMoreMedia() async { - if (isLoading) return; - setState(() => isLoading = true); + bool _mediaItemExists(int id) { + return apiService.mediaItems.any((media) => media.id == id); + } + Future _fetchAndPreloadMedia(int targetId) async { try { - await ref.read(mediaProvider.notifier).loadMedia(); + await apiService.setTag(null); + await apiService.fetchMedia(id: targetId + 50, reset: false); + _initializePageController(); } catch (e) { - _showMsg("Fehler beim Laden der Medien: $e"); - } finally { - setState(() => isLoading = false); + _showMsg("Medien konnten nicht geladen werden"); } } + void _initializePageController() { + _currentPage = apiService.mediaItems.indexWhere( + (media) => media.id == widget.initialItemId, + ); + if (_currentPage < 0) { + _currentPage = 0; + } + _pageController = PageController(initialPage: _currentPage) + ..addListener(() { + setState(() { + _currentPage = _pageController!.page!.round(); + }); + }); + setState(() {}); + } + + @override + void dispose() { + _pageController?.dispose(); + super.dispose(); + } + + MediaItem? _findMediaItem() { + try { + return apiService.mediaItems.firstWhere( + (media) => media.id == widget.initialItemId, + ); + } catch (e) { + return null; + } + } + + Future _downloadMedia(MediaItem item) async { + final File file = await DefaultCacheManager().getSingleFile(item.mediaUrl); + final MethodChannel methodChannel = const MethodChannel('MediaShit'); + + bool? success = await methodChannel.invokeMethod('saveFile', { + 'filePath': file.path, + 'fileName': item.dest, + }); + + success == true + ? _showMsg('${item.dest} wurde in Downloads/fApp neigespeichert.') + : _showMsg('${item.dest} konnte nicht heruntergeladen werden.'); + } + void _showMsg(String message) { if (!mounted) return; ScaffoldMessenger.of(context) @@ -68,70 +105,55 @@ class _DetailViewState extends ConsumerState { ..showSnackBar(SnackBar(content: Text(message))); } - Future _downloadMedia() async { - final MediaState mediaState = ref.read(mediaProvider); - final MediaItem currentItem = mediaState.mediaItems[_currentIndex]; - final File file = await DefaultCacheManager().getSingleFile( - currentItem.mediaUrl, - ); - final MethodChannel methodChannel = const MethodChannel('MediaShit'); - - bool? success = await methodChannel.invokeMethod('saveFile', { - 'filePath': file.path, - 'fileName': currentItem.dest, - }); - - success == true - ? _showMsg( - '${currentItem.dest} wurde in Downloads/fApp neigespeichert.', - ) - : _showMsg('${currentItem.dest} konnte nicht heruntergeladen werden.'); - } - - @override - void dispose() { - _pageController?.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { - final MediaState mediaState = ref.watch(mediaProvider); - final int itemIndex = mediaState.mediaItems.indexWhere( - (item) => item.id == widget.initialItemId, - ); - - if (itemIndex == -1) { - Future.microtask(() { - ref.read(mediaProvider.notifier).loadMedia(id: widget.initialItemId + 50); - }); - return Scaffold( - appBar: AppBar(), - body: const Center(child: CircularProgressIndicator()), + if (_loadingFuture != null) { + return FutureBuilder( + future: _loadingFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Scaffold( + appBar: AppBar(title: const Text("Detail")), + body: const Center(child: CircularProgressIndicator()), + ); + } else { + MediaItem? item = _findMediaItem(); + if (item == null) { + return Scaffold( + appBar: AppBar(title: const Text("Detail")), + body: const Center(child: Text("f0ck nicht gefunden")), + ); + } + return _buildDetail(); + } + }, ); } - if (_pageController == null) { - _pageController = PageController(initialPage: itemIndex); - _currentIndex = itemIndex; - _pageController!.addListener(() { - setState(() => _currentIndex = _pageController!.page?.round() ?? 0); - }); - _preloadAdjacentMedia(itemIndex); + MediaItem? existingItem = _findMediaItem(); + if (existingItem == null) { + return Scaffold( + appBar: AppBar(title: const Text("Detail")), + body: const Center(child: Text("f0ck nicht gefunden")), + ); } + return _buildDetail(); + } + + Widget _buildDetail() { + final MediaItem currentItem = apiService.mediaItems[_currentPage]; return Scaffold( - endDrawer: EndDrawer(ref: ref), + endDrawer: const EndDrawer(), endDrawerEnableOpenDragGesture: false, - persistentFooterButtons: mediaState.tag != null + persistentFooterButtons: apiService.tag.value != null ? [ Center( child: InputChip( - label: Text(mediaState.tag!), + label: Text(apiService.tag.value!), onDeleted: () { - ref.read(mediaProvider.notifier).setTag(null); - //context.push('/', extra: true); - Navigator.pushNamed(context, '/'); + apiService.setTag(null); + Get.offAllNamed('/'); }, ), ), @@ -141,54 +163,53 @@ class _DetailViewState extends ConsumerState { slivers: [ SliverAppBar( floating: true, + pinned: true, snap: true, centerTitle: true, - title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'), + title: Text('f0ck #${currentItem.id.toString()}'), leading: IconButton( icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.canPop(context) ? Navigator.pop(context) : Navigator.pushNamed(context, '/'); - //context.canPop() ? context.pop() : context.go('/', extra: true); - }, + onPressed: () => Get.back(), ), actions: [ IconButton( icon: const Icon(Icons.fullscreen), onPressed: () { - final currentItem = mediaState.mediaItems[_currentIndex]; - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FullScreenMediaView(item: currentItem), - ), + Get.to( + FullScreenMediaView(item: currentItem), + fullscreenDialog: true, ); }, ), IconButton( icon: const Icon(Icons.download), - onPressed: _downloadMedia, + onPressed: () async { + await _downloadMedia(currentItem); + }, ), PopupMenuButton( onSelected: (value) async { - final item = mediaState.mediaItems[_currentIndex]; switch (value) { case 'media': File file = await DefaultCacheManager().getSingleFile( - item.mediaUrl, + currentItem.mediaUrl, ); Uint8List bytes = await file.readAsBytes(); final params = ShareParams( - files: [XFile.fromData(bytes, mimeType: item.mime)], + files: [ + XFile.fromData(bytes, mimeType: currentItem.mime), + ], ); await SharePlus.instance.share(params); break; case 'direct_link': await SharePlus.instance.share( - ShareParams(text: item.mediaUrl), + ShareParams(text: currentItem.mediaUrl), ); break; case 'post_link': await SharePlus.instance.share( - ShareParams(text: item.postUrl), + ShareParams(text: currentItem.postUrl), ); break; } @@ -228,72 +249,81 @@ class _DetailViewState extends ConsumerState { ), ], ), - SliverPadding( - padding: EdgeInsets.zero, - sliver: SliverFillRemaining( - child: PageTransformer( - controller: _pageController!, - pages: mediaState.mediaItems.map((item) { - int pageIndex = mediaState.mediaItems.indexOf(item); - return SafeArea( + SliverFillRemaining( + child: PageView.builder( + controller: _pageController, + itemCount: apiService.mediaItems.length, + itemBuilder: (context, index) { + final MediaItem pageItem = apiService.mediaItems[index]; + return AnimatedBuilder( + animation: _pageController!, + builder: (context, child) { + double value = 0; + if (_pageController!.position.haveDimensions) { + value = (_pageController!.page! - index).abs(); + } + double factor = Curves.easeOut.transform( + 1 - value.clamp(0.0, 1.0), + ); + double scale = 0.8 + factor * 0.2; + return Transform.scale(scale: scale, child: child); + }, + child: SafeArea( top: false, - child: SmartRefreshIndicator( - onRefresh: _loadMoreMedia, - child: _buildMediaItem(item, _currentIndex == pageIndex), + child: SingleChildScrollView( + child: Column( + children: [ + if (pageItem.mime.startsWith('image')) + CachedNetworkImage( + imageUrl: pageItem.mediaUrl, + fit: BoxFit.contain, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const Center(child: Icon(Icons.error)), + ) + else + VideoWidget( + details: pageItem, + isActive: index == _currentPage, + ), + const SizedBox(height: 10, width: double.infinity), + Wrap( + alignment: WrapAlignment.center, + spacing: 5.0, + children: pageItem.tags.map((tag) { + return ActionChip( + onPressed: () { + if (tag.tag == 'sfw' || tag.tag == 'nsfw') { + return; + } + apiService.setTag(tag.tag); + Get.offAllNamed('/'); + }, + label: Text(tag.tag), + backgroundColor: switch (tag.id) { + 1 => Colors.green, + 2 => Colors.red, + _ => const Color(0xFF090909), + }, + labelStyle: const TextStyle( + color: Colors.white, + ), + ); + }).toList(), + ), + const SizedBox(height: 20), + ], + ), ), - ); - }).toList(), - ), + ), + ); + }, ), ), ], ), ); } - - Widget _buildMediaItem(MediaItem item, bool isActive) { - final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier); - - return SingleChildScrollView( - child: Column( - children: [ - if (item.mime.startsWith('image')) - CachedNetworkImage( - imageUrl: item.mediaUrl, - fit: BoxFit.contain, - placeholder: (context, url) => - const Center(child: CircularProgressIndicator()), - errorWidget: (context, url, error) => - const Center(child: Icon(Icons.error)), - ) - else - VideoWidget(details: item, isActive: isActive), - const SizedBox(height: 10, width: double.infinity), - Wrap( - alignment: WrapAlignment.center, - spacing: 5.0, - children: item.tags.map((tag) { - return ActionChip( - onPressed: () { - if (tag.tag == 'sfw' || tag.tag == 'nsfw') return; - setState(() { - mediaNotifier.setTag(tag.tag); - Navigator.pushReplacementNamed(context, '/'); - }); - }, - label: Text(tag.tag), - backgroundColor: switch (tag.id) { - 1 => Colors.green, - 2 => Colors.red, - _ => const Color(0xFF090909), - }, - labelStyle: const TextStyle(color: Colors.white), - ); - }).toList(), - ), - const SizedBox(height: 20), - ], - ), - ); - } } diff --git a/lib/screens/media_grid.dart b/lib/screens/media_grid.dart new file mode 100644 index 0000000..edc084b --- /dev/null +++ b/lib/screens/media_grid.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; + +import 'package:f0ckapp/services/api_service.dart'; +import 'package:f0ckapp/widgets/media_tile.dart'; +import 'package:f0ckapp/widgets/end_drawer.dart'; +import 'package:f0ckapp/widgets/filter_bar.dart'; +import 'package:f0ckapp/utils/customsearchdelegate_util.dart'; + +class MediaGrid extends StatefulWidget { + const MediaGrid({super.key}); + + @override + State createState() => _MediaGrid(); +} + +class _MediaGrid extends State { + final ApiService apiService = Get.find(); + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(() async { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 300) { + await apiService.fetchMedia(); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + endDrawer: EndDrawer(), + persistentFooterButtons: [ + Obx(() { + if (apiService.tag.value != null) { + return Center( + child: InputChip( + label: Text(apiService.tag.value!), + onDeleted: () { + apiService.setTag(null); + Get.offAllNamed('/'); + }, + ), + ); + } else { + return SizedBox.shrink(); + } + }), + ], + body: CustomScrollView( + controller: _scrollController, + slivers: [ + SliverAppBar( + floating: true, + snap: true, + title: GestureDetector( + onTap: () async { + apiService.setTag(null); + }, + child: Row( + children: [ + Image.asset( + 'assets/images/f0ck_small.webp', + fit: BoxFit.fitHeight, + ), + const SizedBox(width: 10), + const Text('fApp', style: TextStyle(fontSize: 24)), + ], + ), + ), + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: () async { + await showSearch( + context: context, + delegate: CustomSearchDelegate(), + ); + }, + ), + Obx( + () => IconButton( + icon: Icon( + apiService.random.value + ? Icons.shuffle_on_outlined + : Icons.shuffle, + ), + onPressed: () { + apiService.toggleRandom(); + }, + ), + ), + Builder( + builder: (context) { + return IconButton( + icon: const Icon(Icons.menu), + onPressed: () { + Scaffold.of(context).openEndDrawer(); + }, + ); + }, + ), + ], + ), + SliverPadding( + padding: EdgeInsets.zero, + sliver: Obx( + () => SliverGrid( + delegate: SliverChildBuilderDelegate((context, index) { + return MediaTile(item: apiService.mediaItems[index]); + }, childCount: apiService.mediaItems.length), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + childAspectRatio: 1, + ), + ), + ), + ), + ], + ), + bottomNavigationBar: FilterBar(scrollController: _scrollController), + floatingActionButton: FloatingActionButton( + onPressed: () {}, + child: Icon(Icons.add), + ), + ); + } +} diff --git a/lib/screens/mediagrid_screen.dart b/lib/screens/mediagrid_screen.dart deleted file mode 100644 index f6ef25c..0000000 --- a/lib/screens/mediagrid_screen.dart +++ /dev/null @@ -1,162 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import 'package:f0ckapp/providers/media_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}); - - @override - ConsumerState createState() => _MediaGridState(); -} - -class _MediaGridState extends ConsumerState { - final ScrollController _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - Future.microtask(() { - ref.read(mediaProvider.notifier).loadMedia(); - }); - _scrollController.addListener(() { - if (_scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 200) { - ref.read(mediaProvider.notifier).loadMedia(); - } - }); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final MediaState mediaState = ref.watch(mediaProvider); - final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier); - - return Scaffold( - 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( - children: [ - Image.asset( - 'assets/images/f0ck_small.webp', - fit: BoxFit.fitHeight, - ), - const SizedBox(width: 10), - const Text('fApp', style: TextStyle(fontSize: 24)), - ], - ), - ), - actions: [ - IconButton( - icon: const Icon(Icons.search), - onPressed: () async { - await showSearch( - context: context, - delegate: CustomSearchDelegate(), - ); - }, - ), - IconButton( - icon: Icon( - mediaState.random - ? Icons.shuffle_on_outlined - : Icons.shuffle, - ), - onPressed: () { - mediaNotifier.toggleRandom(); - _scrollController.jumpTo(0); - }, - ), - Builder( - builder: (context) { - return IconButton( - icon: const Icon(Icons.menu), - onPressed: () { - Scaffold.of(context).openEndDrawer(); - }, - ); - }, - ), - ], - ), - SliverPadding( - padding: EdgeInsets.zero, - 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: mediaState.crossAxisCount == 0 - ? const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 150, - crossAxisSpacing: 5, - mainAxisSpacing: 5, - childAspectRatio: 1, - ) - : SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: mediaState.crossAxisCount, - crossAxisSpacing: 5, - mainAxisSpacing: 5, - childAspectRatio: 1, - ), - ), - ), - ], - ), - ), - bottomNavigationBar: FilterBar( - mediaNotifier: mediaNotifier, - mediaState: mediaState, - scrollController: _scrollController, - ), - endDrawer: EndDrawer(ref: ref), - endDrawerEnableOpenDragGesture: false, - persistentFooterButtons: mediaState.tag != null - ? [ - Center( - child: InputChip( - label: Text(mediaState.tag!), - onDeleted: () { - mediaNotifier.setTag(null); - _scrollController.jumpTo(0); - }, - ), - ), - ] - : null, - ); - } -} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index adca733..43a36bb 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,30 +1,32 @@ import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; import 'package:f0ckapp/widgets/end_drawer.dart'; +import 'package:get/get.dart'; -class SettingsPage extends ConsumerStatefulWidget { +class SettingsPage extends StatefulWidget { const SettingsPage({super.key}); @override - ConsumerState createState() => _SettingsPageState(); + State createState() => _SettingsPageState(); } -class _SettingsPageState extends ConsumerState { +class _SettingsPageState extends State { int _columns = 3; bool _drawerSwipeEnabled = true; + void _showMsg(String message, BuildContext context) { + ScaffoldMessenger.of(context) + ..removeCurrentSnackBar() + ..showSnackBar(SnackBar(content: Text(message))); + } + @override Widget build(BuildContext context) { - final MediaState mediaState = ref.watch(mediaProvider); - final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier); - return Scaffold( endDrawerEnableOpenDragGesture: _drawerSwipeEnabled, - endDrawer: EndDrawer(ref: ref), + endDrawer: EndDrawer(), body: CustomScrollView( slivers: [ SliverAppBar( @@ -34,13 +36,13 @@ class _SettingsPageState extends ConsumerState { leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { - Navigator.canPop(context) ? Navigator.pop(context) : Navigator.pushReplacementNamed(context, '/'); + Get.back(); }, ), ), SliverList( delegate: SliverChildListDelegate([ - Padding( + /*Padding( padding: const EdgeInsets.all(16.0), child: Text( "Anzahl der Spalten", @@ -68,7 +70,7 @@ class _SettingsPageState extends ConsumerState { } }, ), - ), + ),*/ const Divider(), SwitchListTile( title: const Text("Drawer per Geste öffnen"), @@ -84,14 +86,12 @@ class _SettingsPageState extends ConsumerState { ), const Divider(), ListTile( - title: const Text("Cache löschen"), + title: Text("Cache löschen"), trailing: ElevatedButton( onPressed: () async { await DefaultCacheManager().emptyCache(); if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Cache wurde geleert.")), - ); + _showMsg('Cache wurde geleert.', context); }, child: const Text("Löschen"), ), diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 6fa24c1..bb96f97 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -1,111 +1,94 @@ -import 'dart:async'; -import 'dart:convert'; +import 'package:flutter/material.dart'; -import 'package:http/http.dart' as http; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get/get.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; -import 'package:f0ckapp/models/suggestion_model.dart'; -final FlutterSecureStorage storage = const FlutterSecureStorage( - aOptions: AndroidOptions(encryptedSharedPreferences: true), -); +const List mediaTypes = ["alles", "image", "video", "audio"]; +const List mediaModes = ["sfw", "nsfw", "untagged", "all"]; -Future> fetchMedia({ - int? older, - 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, - 'mode': mode.toString(), - 'random': (random ? 1 : 0).toString(), - if (tag != null) 'tag': tag, - if (older != null) 'older': older.toString(), - }, - ); +class ApiService extends GetConnect { + RxList mediaItems = [].obs; + RxnString tag = RxnString(); + RxInt type = 0.obs; + RxInt mode = 0.obs; + RxBool random = false.obs; - final http.Response response = await http.get(url); - if (response.statusCode == 200) { - final List jsonList = jsonDecode(response.body); - return jsonList.map((item) => MediaItem.fromJson(item)).toList(); - } else { - throw Exception('Fehler beim Abrufen der Medien: ${response.statusCode}'); + bool _isFetching = false; + DateTime? _lastFetchTime; + final Duration _minFetchInterval = Duration(milliseconds: 500); + + @override + void onInit() { + super.onInit(); + everAll([tag, type, mode, random], (_) => fetchMedia(reset: true)); } -} -Future fetchMediaDetail(int itemId) async { - final Uri url = Uri.parse('https://api.f0ck.me/item/${itemId.toString()}'); - - final http.Response response = await http.get(url); - if (response.statusCode == 200) { - final Map jsonResponse = jsonDecode(response.body); - - return MediaItem.fromJson(jsonResponse); - } else { - throw Exception( - 'Fehler beim Abrufen der Media-Details: ${response.statusCode}', - ); + Future setTag(String? newTag) async { + tag.value = newTag; + return await fetchMedia(reset: true); } -} -Future> fetchSuggestions(String query) async { - final Uri uri = Uri.parse('https://api.f0ck.me/search/?q=$query'); - try { - final http.Response response = await http - .get(uri) - .timeout(const Duration(seconds: 5)); + Future setType(int newType) async { + type.value = newType >= 0 && newType < mediaTypes.length ? newType : 0; + return await fetchMedia(reset: true); + } - if (response.statusCode == 200) { - final dynamic decoded = jsonDecode(response.body); - if (decoded is List) { - final suggestions = decoded - .map((item) => Suggestion.fromJson(item as Map)) - .toList(); - suggestions.sort((a, b) => b.score.compareTo(a.score)); - return suggestions; - } else { - throw Exception('Unerwartetes Format: Es wurde eine Liste erwartet.'); + Future setMode(int newMode) async { + mode.value = newMode >= 0 && newMode < mediaModes.length ? newMode : 0; + return await fetchMedia(reset: true); + } + + Future toggleRandom() async { + random.value = !random.value; + return await fetchMedia(reset: true); + } + + Future fetchMedia({int? id, bool reset = false}) async { + if (!reset) { + if (_isFetching) return; + if (_lastFetchTime != null && + DateTime.now().difference(_lastFetchTime!) < _minFetchInterval) { + return; } - } else if (response.statusCode == 400) { - final dynamic error = jsonDecode(response.body); - final String message = error is Map - ? error['detail']?.toString() ?? 'Unbekannter Fehler.' - : 'Unbekannter Fehler.'; - throw Exception('Client-Fehler 400: $message'); - } else { - throw Exception( - 'Fehler beim Abrufen der Vorschläge: ${response.statusCode}', - ); } - } on TimeoutException { - throw Exception('Anfrage an die API hat zu lange gedauert.'); - } catch (e) { - throw Exception('Fehler bei der Verarbeitung der Anfrage: $e'); - } -} - -Future login(String username, String password) async { - final Uri url = Uri.parse('https://api.f0ck.me/login'); - - final http.Response response = await http.post( - url, - body: {'username': username, 'password': password}, - ); - - if (response.statusCode == 200) { - final dynamic data = jsonDecode(response.body); - final token = data['token']; - if (token != null) { - await storage.write(key: "token", value: token); - return true; - } else { - throw Exception('Token nicht im Response enthalten.'); - } - } else { - throw Exception('Login fehlgeschlagen: ${response.statusCode}'); + _lastFetchTime = DateTime.now(); + _isFetching = true; + + if (reset) mediaItems.clear(); + + final int? older = + id ?? (mediaItems.isNotEmpty ? mediaItems.last.id : null); + final Uri url = Uri.parse('https://api.f0ck.me/items/get').replace( + queryParameters: { + 'type': mediaTypes[type.value], + 'mode': mode.value.toString(), + 'random': (random.value ? 1 : 0).toString(), + if (tag.value != null) 'tag': tag.value, + if (older != null) 'older': older.toString(), + }, + ); + + try { + final Response response = await get(url.toString()); + + if (response.isOk && response.body is List) { + List newMedia = (response.body as List) + .map((json) => MediaItem.fromJson(json)) + .toList(); + + if (reset) { + mediaItems.assignAll(newMedia); + } else { + mediaItems.addAll(newMedia); + } + } else { + debugPrint('Fehler beim Laden der MediaItems: ${response.statusText}'); + } + } catch (e) { + debugPrint('Exception beim Laden der MediaItems: $e'); + } finally { + _isFetching = false; + } } } diff --git a/lib/utils/customsearchdelegate_util.dart b/lib/utils/customsearchdelegate_util.dart index 3bac980..40e5f37 100644 --- a/lib/utils/customsearchdelegate_util.dart +++ b/lib/utils/customsearchdelegate_util.dart @@ -1,11 +1,16 @@ import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; + import 'package:f0ckapp/services/api_service.dart'; import 'package:f0ckapp/models/suggestion_model.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; class CustomSearchDelegate extends SearchDelegate { + final ApiService apiService = Get.find(); Timer? _debounceTimer; List? _suggestions; bool _isLoading = false; @@ -42,13 +47,52 @@ class CustomSearchDelegate extends SearchDelegate { return Center(child: Text('Suchergebnisse für: "$query"')); } + Future> fetchSuggestions(String query) async { + final Uri uri = Uri.parse('https://api.f0ck.me/search/?q=$query'); + try { + final http.Response response = await http + .get(uri) + .timeout(const Duration(seconds: 5)); + + if (response.statusCode == 200) { + final dynamic decoded = jsonDecode(response.body); + if (decoded is List) { + final suggestions = decoded + .map((item) => Suggestion.fromJson(item as Map)) + .toList(); + suggestions.sort((a, b) => b.score.compareTo(a.score)); + return suggestions; + } else { + throw Exception('Unerwartetes Format: Es wurde eine Liste erwartet.'); + } + } else if (response.statusCode == 400) { + final dynamic error = jsonDecode(response.body); + final String message = error is Map + ? error['detail']?.toString() ?? 'Unbekannter Fehler.' + : 'Unbekannter Fehler.'; + throw Exception('Client-Fehler 400: $message'); + } else { + throw Exception( + 'Fehler beim Abrufen der Vorschläge: ${response.statusCode}', + ); + } + } on TimeoutException { + throw Exception('Anfrage an die API hat zu lange gedauert.'); + } catch (e) { + throw Exception('Fehler bei der Verarbeitung der Anfrage: $e'); + } + } + @override Widget buildSuggestions(BuildContext context) { return StatefulBuilder( builder: (BuildContext context, void Function(void Function()) setState) { if (query.isEmpty) { _debounceTimer?.cancel(); - return Container(padding: const EdgeInsets.all(16.0), child: const Text('')); + return Container( + padding: const EdgeInsets.all(16.0), + child: const Text(''), + ); } if (query != _lastFetchedQuery) { @@ -90,23 +134,19 @@ class CustomSearchDelegate extends SearchDelegate { return Center(child: const Text("Keine Ergebnisse gefunden.")); } - return Consumer( - builder: (BuildContext context, WidgetRef ref, Widget? child) { - return ListView.builder( - itemCount: _suggestions!.length, - itemBuilder: (BuildContext context, int index) { - final Suggestion suggestion = _suggestions![index]; - return ListTile( - title: Text(suggestion.tag), - subtitle: Text( - 'Getaggt: ${suggestion.tagged}x • Score: ${suggestion.score.toStringAsFixed(2)}', - style: TextStyle(fontSize: 12), - ), - onTap: () { - ref.read(mediaProvider.notifier).setTag(suggestion.tag); - close(context, suggestion.tag); - }, - ); + return ListView.builder( + itemCount: _suggestions!.length, + itemBuilder: (BuildContext context, int index) { + final Suggestion suggestion = _suggestions![index]; + return ListTile( + title: Text(suggestion.tag), + subtitle: Text( + 'Getaggt: ${suggestion.tagged}x • Score: ${suggestion.score.toStringAsFixed(2)}', + style: TextStyle(fontSize: 12), + ), + onTap: () async { + await apiService.setTag(suggestion.tag); + close(context, suggestion.tag); }, ); }, diff --git a/lib/utils/pagetransformer_util.dart b/lib/utils/pagetransformer_util.dart deleted file mode 100644 index 5956795..0000000 --- a/lib/utils/pagetransformer_util.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; - -class PageTransformer extends StatelessWidget { - final List pages; - final PageController controller; - - const PageTransformer({ - super.key, - required this.pages, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return PageView.builder( - controller: controller, - itemCount: pages.length, - itemBuilder: (context, index) { - return _buildPage(pages[index], index); - }, - ); - } - - Widget _buildPage(Widget page, int index) { - return AnimatedBuilder( - animation: controller, - builder: (context, child) { - double value = 1.0; - if (controller.position.haveDimensions) { - value = controller.page! - index; - value = (1 - (value.abs() * 0.5)).clamp(0.0, 1.0); - } - return Transform( - transform: Matrix4.identity()..scale(value, value), - alignment: Alignment.center, - child: child, - ); - }, - child: page, - ); - } -} diff --git a/lib/widgets/end_drawer.dart b/lib/widgets/end_drawer.dart index 94ff3da..ac71292 100644 --- a/lib/widgets/end_drawer.dart +++ b/lib/widgets/end_drawer.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:get/get.dart'; import 'package:f0ckapp/providers/theme_provider.dart'; +//import 'package:f0ckapp/services/api_service.dart'; import 'package:f0ckapp/utils/appversion_util.dart'; class EndDrawer extends StatelessWidget { - final WidgetRef ref; - - const EndDrawer({super.key, required this.ref}); + const EndDrawer({super.key}); void _showMsg(String message, BuildContext context) { ScaffoldMessenger.of(context) @@ -18,6 +17,9 @@ class EndDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + //final ApiService c = Get.find(); + final ThemeController themeController = Get.find(); + return Drawer( child: ListView( padding: EdgeInsets.zero, @@ -88,34 +90,32 @@ class EndDrawer extends StatelessWidget { 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(), - ), + child: Obx(() { + return Column( + children: themeController.themeMap.entries.map((entry) { + final String themeName = entry.key; + final ThemeData themeData = entry.value; + final bool isSelected = + themeController.currentTheme.value == themeData; + return ListTile( + title: Text(themeName), + selected: isSelected, + selectedTileColor: Colors.blue.withValues(alpha: 0.2), + onTap: () async { + await themeController.updateTheme(themeName); + }, + ); + }).toList(), + ); + }), ), ], ), ListTile( - title: const Text('Einstellungen'), + title: const Text('Settings'), onTap: () { - //context.go('/settings'); - Navigator.pushReplacementNamed(context, '/settings'); + Navigator.pop(context); + Get.toNamed('/settings'); }, ), ListTile( diff --git a/lib/widgets/filter_bar.dart b/lib/widgets/filter_bar.dart index 80d7124..89761e7 100644 --- a/lib/widgets/filter_bar.dart +++ b/lib/widgets/filter_bar.dart @@ -1,29 +1,29 @@ import 'package:flutter/material.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; +import 'package:get/get.dart'; + +import 'package:f0ckapp/services/api_service.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) { + final ApiService c = Get.find(); + return BottomAppBar( height: 50, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ const Text('type: '), - DropdownButton( - value: mediaTypes[mediaState.typeIndex], + Obx(() => DropdownButton( + value: mediaTypes[c.type.value], isDense: true, items: mediaTypes.map((String value) { return DropdownMenuItem( @@ -33,14 +33,14 @@ class FilterBar extends StatelessWidget { }).toList(), onChanged: (String? newValue) { if (newValue != null) { - mediaNotifier.setType(newValue); + c.setType(mediaTypes.indexOf(newValue)); scrollController.jumpTo(0); } }, - ), + )), const Text('mode: '), - DropdownButton( - value: mediaModes[mediaState.modeIndex], + Obx(() => DropdownButton( + value: mediaModes[c.mode.value], isDense: true, items: mediaModes.map((String value) { return DropdownMenuItem( @@ -50,11 +50,11 @@ class FilterBar extends StatelessWidget { }).toList(), onChanged: (String? newValue) { if (newValue != null) { - mediaNotifier.setMode(mediaModes.indexOf(newValue)); + c.setMode(mediaModes.indexOf(newValue)); scrollController.jumpTo(0); } }, - ), + )), ], ), ); diff --git a/lib/widgets/media_tile.dart b/lib/widgets/media_tile.dart index c141718..6a7f15e 100644 --- a/lib/widgets/media_tile.dart +++ b/lib/widgets/media_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:get/get.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; @@ -13,7 +14,8 @@ class MediaTile extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: () { - Navigator.pushNamed(context, '/${item.id}'); + //Navigator.pushNamed(context, '/${item.id}'); + Get.toNamed('/${item.id}'); }, child: Stack( fit: StackFit.expand, diff --git a/lib/widgets/video_widget.dart b/lib/widgets/video_widget.dart index 739aab5..a3fe4fa 100644 --- a/lib/widgets/video_widget.dart +++ b/lib/widgets/video_widget.dart @@ -4,13 +4,11 @@ import 'package:flutter/material.dart'; import 'package:cached_video_player_plus/cached_video_player_plus.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:f0ckapp/models/mediaitem_model.dart'; import 'package:f0ckapp/widgets/videooverlay_widget.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; -class VideoWidget extends ConsumerStatefulWidget { +class VideoWidget extends StatefulWidget { final MediaItem details; final bool isActive; final bool fullScreen; @@ -23,10 +21,10 @@ class VideoWidget extends ConsumerStatefulWidget { }); @override - ConsumerState createState() => _VideoWidgetState(); + State createState() => _VideoWidgetState(); } -class _VideoWidgetState extends ConsumerState { +class _VideoWidgetState extends State { late CachedVideoPlayerPlusController _controller; bool _showControls = false; Timer? _hideControlsTimer; @@ -51,8 +49,8 @@ class _VideoWidgetState extends ConsumerState { } _controller.setLooping(true); - final bool muted = ref.read(mediaProvider).muted; - _controller.setVolume(muted ? 0.0 : 1.0); + //final bool muted = ref.read(mediaProvider).muted; + //_controller.setVolume(muted ? 0.0 : 1.0); } @override @@ -88,11 +86,11 @@ class _VideoWidgetState extends ConsumerState { @override Widget build(BuildContext context) { - final bool muted = ref.watch(mediaProvider).muted; - if (_controller.value.isInitialized && - _controller.value.volume != (muted ? 0.0 : 1.0)) { - _controller.setVolume(muted ? 0.0 : 1.0); - } + //final bool muted = ref.watch(mediaProvider).muted; + //if (_controller.value.isInitialized && + // _controller.value.volume != (muted ? 0.0 : 1.0)) { + // _controller.setVolume(muted ? 0.0 : 1.0); + //} bool isAudio = widget.details.mime.startsWith('audio'); diff --git a/lib/widgets/videooverlay_widget.dart b/lib/widgets/videooverlay_widget.dart index d9beaf8..47b2b13 100644 --- a/lib/widgets/videooverlay_widget.dart +++ b/lib/widgets/videooverlay_widget.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cached_video_player_plus/cached_video_player_plus.dart'; -import 'package:f0ckapp/providers/media_provider.dart'; - -class VideoControlsOverlay extends ConsumerWidget { +class VideoControlsOverlay extends StatelessWidget { final CachedVideoPlayerPlusController controller; final VoidCallback button; @@ -16,25 +13,11 @@ class VideoControlsOverlay extends ConsumerWidget { }); @override - Widget build(BuildContext context, ref) { - final MediaState mediaState = ref.watch(mediaProvider); - final MediaNotifier mediaNotifier = ref.read(mediaProvider.notifier); + Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children: [ - Positioned( - right: 12, - bottom: 12, - child: _ControlButton( - mediaState.muted ? Icons.volume_off : Icons.volume_up, - () { - button(); - mediaNotifier.toggleMute(); - }, - size: 16, - ), - ), Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/pubspec.lock b/pubspec.lock index 9fd5cef..8131a21 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -158,14 +158,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - flutter_riverpod: - dependency: "direct main" - description: - name: flutter_riverpod - sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" - url: "https://pub.dev" - source: hosted - version: "2.6.1" flutter_secure_storage: dependency: "direct main" description: @@ -225,7 +217,7 @@ packages: source: sdk version: "0.0.0" get: - dependency: transitive + dependency: "direct main" description: name: get sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 @@ -432,14 +424,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - riverpod: - dependency: transitive - description: - name: riverpod - sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" - url: "https://pub.dev" - source: hosted - version: "2.6.1" rxdart: dependency: transitive description: @@ -533,14 +517,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" - state_notifier: - dependency: transitive - description: - name: state_notifier - sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb - url: "https://pub.dev" - source: hosted - version: "1.0.0" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f1ed678..ae96ba8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.23+53 +version: 1.2.0+54 environment: sdk: ^3.9.0-100.2.beta @@ -40,7 +40,7 @@ dependencies: package_info_plus: ^8.3.0 share_plus: ^11.0.0 flutter_secure_storage: ^9.2.4 - flutter_riverpod: ^2.6.1 + get: ^4.7.2 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index d5388bf..4217b77 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:f0ckapp/main.dart'; +//import 'package:f0ckapp/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(F0ckApp()); + //await tester.pumpWidget(F0ckApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);