1.0.17
- new appicon - smartRefreshIndicator (https://github.com/flutter/flutter/issues/65356#issuecomment-2410727567)
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 14 KiB |
@ -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),
|
||||||
),
|
),
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
],
|
],
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
28
lib/utils/SmartRefreshIndicator.dart
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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
|
||||||
|