From e24a2122a570a6c633ebab0dc646b27d2bd6d016 Mon Sep 17 00:00:00 2001 From: Flummi Date: Mon, 2 Jun 2025 16:14:38 +0200 Subject: [PATCH] 1.0.15 + random --- lib/mediagrid.dart | 76 +++++++++++++++--- lib/screens/detailview.dart | 147 +++++++++++++++++++++------------- lib/services/api.dart | 32 +++++--- lib/widgets/video_widget.dart | 9 +-- pubspec.yaml | 2 +- 5 files changed, 183 insertions(+), 83 deletions(-) diff --git a/lib/mediagrid.dart b/lib/mediagrid.dart index 8e6f821..2363c24 100644 --- a/lib/mediagrid.dart +++ b/lib/mediagrid.dart @@ -13,12 +13,16 @@ class MediaGrid extends StatefulWidget { class _MediaGridState extends State { final ScrollController _scrollController = ScrollController(); + final String _version = '1.0.15'; List mediaItems = []; bool isLoading = false; Timer? _debounceTimer; Completer? _navigationCompleter; - int _crossAxisCount = 3; - String _selectedMode = "alles"; + int _crossAxisCount = 0; + String _selectedType = 'alles'; + int _selectedMode = 0; + bool _random = false; + final List _modes = ["sfw", "nsfw", "untagged", "all"]; @override void initState() { @@ -37,6 +41,17 @@ class _MediaGridState extends State { _debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia); } + int _calculateCrossAxisCount(BuildContext context) { + if (_crossAxisCount != 0) { + return _crossAxisCount; + } + + double screenWidth = MediaQuery.of(context).size.width; + int columnCount = (screenWidth / 110).clamp(3, 5).toInt(); + + return columnCount; + } + Future _loadMedia() async { if (isLoading) return; setState(() => isLoading = true); @@ -44,7 +59,9 @@ class _MediaGridState extends State { try { final newMedia = await fetchMedia( older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null, + type: _selectedType, mode: _selectedMode, + random: _random, ); if (mounted) { setState(() => mediaItems.addAll(newMedia)); @@ -63,7 +80,12 @@ class _MediaGridState extends State { Future _refreshMedia() async { setState(() => isLoading = true); try { - final freshMedia = await fetchMedia(older: null, mode: _selectedMode); + final freshMedia = await fetchMedia( + older: null, + type: _selectedType, + mode: _selectedMode, + random: _random, + ); if (mounted) { setState(() { mediaItems.clear(); @@ -93,7 +115,9 @@ class _MediaGridState extends State { builder: (context) => DetailView( initialItemId: item.id, mediaItems: mediaItems, + type: _selectedType, mode: _selectedMode, + random: _random, ), ), ); @@ -113,12 +137,24 @@ class _MediaGridState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + centerTitle: true, backgroundColor: const Color.fromARGB(255, 43, 43, 43), foregroundColor: const Color.fromARGB(255, 255, 255, 255), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [const Text('f0cks')], - ), + children: [ + Text('f0ck v$_version'), + Checkbox( + value: _random, + onChanged: (bool? value) { + setState(() { + _random = !_random; + _refreshMedia(); + }); + }, + ) + ] + ) ), bottomNavigationBar: BottomAppBar( color: const Color.fromARGB(255, 43, 43, 43), @@ -126,9 +162,10 @@ class _MediaGridState extends State { padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, children: [ DropdownButton( - value: _selectedMode, + value: _selectedType, dropdownColor: const Color.fromARGB(255, 43, 43, 43), iconEnabledColor: Colors.white, items: ["alles", "image", "video", "audio"].map((String value) { @@ -140,7 +177,26 @@ class _MediaGridState extends State { onChanged: (String? newValue) { if (newValue != null) { setState(() { - _selectedMode = newValue; + _selectedType = newValue; + _refreshMedia(); + }); + } + }, + ), + DropdownButton( + value: _modes[_selectedMode], + dropdownColor: const Color.fromARGB(255, 43, 43, 43), + iconEnabledColor: Colors.white, + items: _modes.map((String value) { + return DropdownMenuItem( + value: value, + child: Text(value, style: TextStyle(color: Colors.white)), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + setState(() { + _selectedMode = _modes.indexOf(newValue); _refreshMedia(); }); } @@ -150,11 +206,11 @@ class _MediaGridState extends State { value: _crossAxisCount, dropdownColor: const Color.fromARGB(255, 43, 43, 43), iconEnabledColor: Colors.white, - items: [3, 4].map((int value) { + items: [0, 3, 4].map((int value) { return DropdownMenuItem( value: value, child: Text( - '$value Spalten', + value == 0 ? 'auto' : '$value Spalten', style: TextStyle(color: Colors.white), ), ); @@ -176,7 +232,7 @@ class _MediaGridState extends State { child: GridView.builder( controller: _scrollController, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: _crossAxisCount, + crossAxisCount: _calculateCrossAxisCount(context), crossAxisSpacing: 5.0, mainAxisSpacing: 5.0, ), diff --git a/lib/screens/detailview.dart b/lib/screens/detailview.dart index 762747b..74edb68 100644 --- a/lib/screens/detailview.dart +++ b/lib/screens/detailview.dart @@ -6,13 +6,17 @@ import 'package:f0ckapp/widgets/video_widget.dart'; class DetailView extends StatefulWidget { final int initialItemId; final List mediaItems; - final String mode; + final String type; + final int mode; + final bool random; const DetailView({ super.key, required this.initialItemId, required this.mediaItems, + required this.type, required this.mode, + required this.random, }); @override @@ -22,12 +26,14 @@ class DetailView extends StatefulWidget { class _DetailViewState extends State { late PageController _pageController; late List mediaItems; + final List _modes = ["sfw", "nsfw", "untagged", "all"]; int currentItemId = 0; bool isLoading = false; @override void initState() { super.initState(); + mediaItems = widget.mediaItems; final initialIndex = mediaItems.indexWhere( (item) => item.id == widget.initialItemId, @@ -56,7 +62,9 @@ class _DetailViewState extends State { try { final newMedia = await fetchMedia( older: mediaItems.last.id.toString(), + type: widget.type, mode: widget.mode, + random: widget.random, ); if (mounted) { setState(() => mediaItems.addAll(newMedia)); @@ -72,72 +80,99 @@ class _DetailViewState extends State { } } + Future _refreshMediaItem() async { + try { + final updatedItem = await fetchMediaDetail(currentItemId); + if (mounted) { + setState(() { + final index = mediaItems.indexWhere( + (item) => item.id == currentItemId, + ); + if (index != -1) { + mediaItems[index] = updatedItem; + } + }); + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Fehler beim Aktualisieren des Items: $e')), + ); + } + } + } + @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color(0xFF171717), + backgroundColor: const Color(0xFF171717), appBar: AppBar( backgroundColor: const Color(0xFF2B2B2B), foregroundColor: Colors.white, - title: Text('f0ck #$currentItemId (${widget.mode})'), + title: Text( + 'f0ck #$currentItemId (${widget.type}, ${_modes[widget.mode]})', + ), centerTitle: true, ), - body: PageView.builder( - controller: _pageController, - itemCount: mediaItems.length + (isLoading ? 1 : 0), - itemBuilder: (context, index) { - if (index >= mediaItems.length) { - return const Center(child: CircularProgressIndicator()); - } + body: RefreshIndicator( + onRefresh: _refreshMediaItem, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height, + child: PageView.builder( + controller: _pageController, + itemCount: mediaItems.length, + itemBuilder: (context, index) { + final item = mediaItems[index]; + return Column( + children: [ + if (item.mime.startsWith('image')) + Image.network(item.mediaUrl, fit: BoxFit.contain) + else + VideoWidget(details: item), + const SizedBox(height: 20), + Text( + item.mime, + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + const SizedBox(height: 10), + Wrap( + alignment: WrapAlignment.center, + spacing: 5.0, + children: item.tags.map((tag) { + Color tagColor; + switch (tag.id) { + case 1: + tagColor = Colors.green; + break; + case 2: + tagColor = Colors.red; + break; + default: + tagColor = const Color(0xFF090909); + } - final item = mediaItems[index]; - return SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (item.mime.startsWith('image')) ...[ - Image.network( - item.mediaUrl, - fit: BoxFit.contain, - ), - ] else ...[ - VideoWidget(details: item), - ], - const SizedBox(height: 20), - Text( - item.mime, - style: const TextStyle(color: Colors.white, fontSize: 18), - ), - const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.center, - spacing: 5.0, - children: item.tags.map((tag) { - Color tagColor; - switch (tag.id) { - case 1: - tagColor = Colors.green; - break; - case 2: - tagColor = Colors.red; - break; - default: - tagColor = Color(0xFF090909); - } - - return Chip( - label: Text(tag.tag), - backgroundColor: tagColor, - labelStyle: const TextStyle(color: Colors.white), + return Chip( + label: Text(tag.tag), + backgroundColor: tagColor, + labelStyle: const TextStyle(color: Colors.white), + ); + }).toList(), + ), + const SizedBox(height: 40), + ], ); - }).toList(), + }, ), - const SizedBox(height: 40), - ], - ), - ); - }, + ), + ], + ), + ), ), ); } diff --git a/lib/services/api.dart b/lib/services/api.dart index ecc9501..1f23d96 100644 --- a/lib/services/api.dart +++ b/lib/services/api.dart @@ -2,14 +2,21 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:f0ckapp/models/mediaitem.dart'; -//import 'package:f0ckapp/models/mediaitem_detail.dart'; -Future> fetchMedia({String? older, String? mode}) async { - final Uri url = Uri.parse('https://api.f0ck.me/items/get') - .replace(queryParameters: { - 'mode': mode ?? 'image', +Future> fetchMedia({ + String? older, + String? type, + int? mode, + bool? random, +}) 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(), if (older != null) 'older': older, - }); + }, + ); final response = await http.get(url); if (response.statusCode == 200) { @@ -20,14 +27,17 @@ Future> fetchMedia({String? older, String? mode}) async { } } -/*Future fetchMediaDetail(int itemId) async { - final Uri url = Uri.parse('https://f0ck.me/api/v2/item/${itemId.toString()}'); +Future fetchMediaDetail(int itemId) async { + final Uri url = Uri.parse('https://api.f0ck.me/item/${itemId.toString()}'); final response = await http.get(url); if (response.statusCode == 200) { final Map jsonResponse = jsonDecode(response.body); - return MediaItemDetail.fromJson(jsonResponse['rows']); + + return MediaItem.fromJson(jsonResponse); } else { - throw Exception('Fehler beim Abrufen der Media-Details: ${response.statusCode}'); + throw Exception( + 'Fehler beim Abrufen der Media-Details: ${response.statusCode}', + ); } -}*/ +} diff --git a/lib/widgets/video_widget.dart b/lib/widgets/video_widget.dart index aa1aff9..4738fb4 100644 --- a/lib/widgets/video_widget.dart +++ b/lib/widgets/video_widget.dart @@ -20,7 +20,9 @@ class _VideoWidgetState extends State { } Future _initController() async { - _controller = VideoPlayerController.networkUrl(Uri.parse(widget.details.mediaUrl)); + _controller = VideoPlayerController.networkUrl( + Uri.parse(widget.details.mediaUrl), + ); await _controller.initialize(); setState(() {}); @@ -63,10 +65,7 @@ class _VideoWidgetState extends State { }); }, child: isAudio - ? Image.network( - widget.details.coverUrl, - fit: BoxFit.cover, - ) + ? Image.network(widget.details.coverUrl, fit: BoxFit.cover) : VideoPlayer(_controller), ), Align( diff --git a/pubspec.yaml b/pubspec.yaml index cbc53cb..d37cd99 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.0.12 +version: 1.0.15 environment: sdk: ^3.9.0-172.0.dev