- new appicon
- smartRefreshIndicator (https://github.com/flutter/flutter/issues/65356#issuecomment-2410727567)
This commit is contained in:
Flummi 2025-06-03 10:03:20 +02:00
parent e24a2122a5
commit 800df72a33
48 changed files with 206 additions and 185 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -15,6 +15,7 @@ class F0ckApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23), scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23),
), ),

View File

@ -13,7 +13,7 @@ class MediaGrid extends StatefulWidget {
class _MediaGridState extends State<MediaGrid> { class _MediaGridState extends State<MediaGrid> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
final String _version = '1.0.15'; final String _version = '1.0.17';
List<MediaItem> mediaItems = []; List<MediaItem> mediaItems = [];
bool isLoading = false; bool isLoading = false;
Timer? _debounceTimer; Timer? _debounceTimer;

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:f0ckapp/models/mediaitem.dart'; import 'package:f0ckapp/models/mediaitem.dart';
import 'package:f0ckapp/services/api.dart'; import 'package:f0ckapp/services/api.dart';
import 'package:f0ckapp/widgets/video_widget.dart'; import 'package:f0ckapp/widgets/video_widget.dart';
import 'package:f0ckapp/utils/SmartRefreshIndicator.dart';
class DetailView extends StatefulWidget { class DetailView extends StatefulWidget {
final int initialItemId; final int initialItemId;
@ -26,23 +27,23 @@ class DetailView extends StatefulWidget {
class _DetailViewState extends State<DetailView> { class _DetailViewState extends State<DetailView> {
late PageController _pageController; late PageController _pageController;
late List<MediaItem> mediaItems; late List<MediaItem> mediaItems;
final List<String> _modes = ["sfw", "nsfw", "untagged", "all"];
int currentItemId = 0; int currentItemId = 0;
bool isLoading = false; bool isLoading = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
mediaItems = widget.mediaItems; mediaItems = widget.mediaItems;
final initialIndex = mediaItems.indexWhere( final initialIndex = mediaItems.indexWhere(
(item) => item.id == widget.initialItemId, (item) => item.id == widget.initialItemId,
); );
_pageController = PageController(initialPage: initialIndex); _pageController = PageController(initialPage: initialIndex);
currentItemId = mediaItems[initialIndex].id; currentItemId = mediaItems[initialIndex].id;
_pageController.addListener(() { _pageController.addListener(_onPageScroll);
}
void _onPageScroll() {
final newIndex = _pageController.page?.round(); final newIndex = _pageController.page?.round();
if (newIndex != null && newIndex < mediaItems.length) { if (newIndex != null && newIndex < mediaItems.length) {
setState(() => currentItemId = mediaItems[newIndex].id); setState(() => currentItemId = mediaItems[newIndex].id);
@ -52,7 +53,6 @@ class _DetailViewState extends State<DetailView> {
_pageController.position.maxScrollExtent - 100) { _pageController.position.maxScrollExtent - 100) {
_loadMoreMedia(); _loadMoreMedia();
} }
});
} }
Future<void> _loadMoreMedia() async { Future<void> _loadMoreMedia() async {
@ -66,15 +66,11 @@ class _DetailViewState extends State<DetailView> {
mode: widget.mode, mode: widget.mode,
random: widget.random, random: widget.random,
); );
if (mounted) { if (mounted && newMedia.isNotEmpty) {
setState(() => mediaItems.addAll(newMedia)); setState(() => mediaItems.addAll(newMedia));
} }
} catch (e) { } catch (e) {
if (mounted) { _showError("Fehler beim Laden weiterer Medien: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fehler beim Laden weiterer Medien: $e')),
);
}
} finally { } finally {
setState(() => isLoading = false); setState(() => isLoading = false);
} }
@ -84,22 +80,22 @@ class _DetailViewState extends State<DetailView> {
try { try {
final updatedItem = await fetchMediaDetail(currentItemId); final updatedItem = await fetchMediaDetail(currentItemId);
if (mounted) { if (mounted) {
setState(() { final index = mediaItems.indexWhere((item) => item.id == currentItemId);
final index = mediaItems.indexWhere(
(item) => item.id == currentItemId,
);
if (index != -1) { if (index != -1) {
mediaItems[index] = updatedItem; setState(() => mediaItems[index] = updatedItem);
} }
});
} }
} catch (e) { } catch (e) {
if (mounted) { _showError("Fehler beim Aktualisieren des Items: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fehler beim Aktualisieren des Items: $e')),
);
} }
} }
void _showError(String message) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
} }
@override @override
@ -109,70 +105,60 @@ class _DetailViewState extends State<DetailView> {
appBar: AppBar( appBar: AppBar(
backgroundColor: const Color(0xFF2B2B2B), backgroundColor: const Color(0xFF2B2B2B),
foregroundColor: Colors.white, foregroundColor: Colors.white,
title: Text( title: Text('f0ck #$currentItemId (${widget.type})'),
'f0ck #$currentItemId (${widget.type}, ${_modes[widget.mode]})',
),
centerTitle: true, centerTitle: true,
), ),
body: RefreshIndicator( body: PageView.builder(
onRefresh: _refreshMediaItem,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
child: PageView.builder(
controller: _pageController, controller: _pageController,
itemCount: mediaItems.length, itemCount: mediaItems.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = mediaItems[index]; final MediaItem item = mediaItems[index];
return Column( return Scaffold(
body: SafeArea(
child: SmartRefreshIndicator(
onRefresh: _refreshMediaItem,
child: _buildMediaItem(item)
),
),
);
},
),
);
}
Widget _buildMediaItem(MediaItem item) {
return SingleChildScrollView(
child: Column(
children: [ children: [
if (item.mime.startsWith('image')) if (item.mime.startsWith('image'))
Image.network(item.mediaUrl, fit: BoxFit.contain) Image.network(
item.mediaUrl,
fit: BoxFit.contain,
)
else else
VideoWidget(details: item), VideoWidget(details: item),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
item.mime, item.mime,
style: const TextStyle( style: const TextStyle(color: Colors.white, fontSize: 18),
color: Colors.white,
fontSize: 18,
),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Wrap( Wrap(
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
spacing: 5.0, spacing: 5.0,
children: item.tags.map((tag) { 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);
}
return Chip( return Chip(
label: Text(tag.tag), label: Text(tag.tag),
backgroundColor: tagColor, backgroundColor: {
1: Colors.green,
2: Colors.red
}[tag.id] ?? const Color(0xFF090909),
labelStyle: const TextStyle(color: Colors.white), labelStyle: const TextStyle(color: Colors.white),
); );
}).toList(), }).toList(),
), ),
const SizedBox(height: 40), const SizedBox(height: 20),
], ],
);
},
),
),
],
),
),
), ),
); );
} }

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
class SmartRefreshIndicator extends StatelessWidget {
final Future<void> Function() onRefresh;
final Widget child;
const SmartRefreshIndicator({
super.key,
required this.onRefresh,
required this.child,
});
@override
Widget build(context) {
return LayoutBuilder(
builder: (context, constraints) => RefreshIndicator(
onRefresh: onRefresh,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: child,
),
),
),
);
}
}

View File

@ -52,7 +52,9 @@ class _VideoWidgetState extends State<VideoWidget> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: isAudio ? 1.0 : _controller.value.aspectRatio, aspectRatio: _controller.value.isInitialized
? _controller.value.aspectRatio
: 9 / 16,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@ -66,8 +68,11 @@ class _VideoWidgetState extends State<VideoWidget> {
}, },
child: isAudio child: isAudio
? Image.network(widget.details.coverUrl, fit: BoxFit.cover) ? Image.network(widget.details.coverUrl, fit: BoxFit.cover)
: VideoPlayer(_controller), : _controller.value.isInitialized
? VideoPlayer(_controller)
: Center(child: CircularProgressIndicator()),
), ),
if (_controller.value.isInitialized)
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Stack( child: Stack(
@ -81,7 +86,7 @@ class _VideoWidgetState extends State<VideoWidget> {
colors: VideoProgressColors( colors: VideoProgressColors(
playedColor: Colors.red, playedColor: Colors.red,
bufferedColor: Colors.grey, bufferedColor: Colors.grey,
backgroundColor: Colors.black.withValues(alpha: 0.5), backgroundColor: Colors.black.withAlpha(128),
), ),
), ),
), ),
@ -108,6 +113,7 @@ class _VideoWidgetState extends State<VideoWidget> {
], ],
), ),
), ),
if (_controller.value.isInitialized)
SizedBox( SizedBox(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.15 version: 1.0.17
environment: environment:
sdk: ^3.9.0-172.0.dev sdk: ^3.9.0-172.0.dev