v1.4.8+69
All checks were successful
Flutter Schmutter / build (push) Successful in 3m39s

This commit is contained in:
2025-06-24 03:02:39 +02:00
parent 39fadc009f
commit ba7505c2b3
4 changed files with 144 additions and 85 deletions

View File

@ -14,6 +14,7 @@ class MediaController extends GetxController {
RxBool loading = false.obs;
RxBool atEnd = false.obs;
RxBool atStart = false.obs;
Rxn<String> errorMessage = Rxn<String>();
RxInt typeIndex = 0.obs;
RxInt modeIndex = 0.obs;
@ -22,12 +23,10 @@ class MediaController extends GetxController {
void setTypeIndex(int idx) {
typeIndex.value = idx;
fetchInitial();
}
void setModeIndex(int idx) {
modeIndex.value = idx;
fetchInitial();
}
void setTag(String? newTag, {bool reload = true}) {
@ -37,6 +36,10 @@ class MediaController extends GetxController {
}
}
void toggleRandom() {
random.value = random.value == 0 ? 1 : 0;
}
Future<List<Favorite>?> toggleFavorite(
MediaItem item,
bool isFavorite,
@ -51,6 +54,7 @@ class MediaController extends GetxController {
Future<Feed?> _fetchItems({int? older, int? newer}) async {
if (loading.value) return null;
loading.value = true;
errorMessage.value = null;
try {
return await _api.fetchItems(
older: older,
@ -61,11 +65,10 @@ class MediaController extends GetxController {
tag: tag.value,
);
} catch (e) {
Get.snackbar(
'Fehler beim Laden',
'Die Daten konnten nicht abgerufen werden. Wo Internet?',
snackPosition: SnackPosition.BOTTOM,
);
final String errorText =
'Die Daten konnten nicht abgerufen werden. Wo Internet?';
errorMessage.value = errorText;
Get.snackbar('Fehler beim Laden', errorText);
return null;
} finally {
loading.value = false;
@ -73,7 +76,7 @@ class MediaController extends GetxController {
}
Future<void> fetchInitial({int? id}) async {
final result = await _fetchItems(older: id);
final Feed? result = await _fetchItems(older: id);
if (result != null) {
items.assignAll(result.items);
atEnd.value = result.atEnd;
@ -83,7 +86,7 @@ class MediaController extends GetxController {
Future<void> fetchMore() async {
if (items.isEmpty || atEnd.value) return;
final result = await _fetchItems(older: items.last.id);
final Feed? result = await _fetchItems(older: items.last.id);
if (result != null) {
final Set<int> existingIds = items.map((e) => e.id).toSet();
final List<MediaItem> newItems = result.items
@ -95,10 +98,9 @@ class MediaController extends GetxController {
}
}
Future<int> fetchNewer() async {
if (items.isEmpty || atStart.value) return 0;
final oldLength = items.length;
final result = await _fetchItems(newer: items.first.id);
Future<void> fetchNewer() async {
if (items.isEmpty || atStart.value) return;
final Feed? result = await _fetchItems(newer: items.first.id);
if (result != null) {
final Set<int> existingIds = items.map((e) => e.id).toSet();
final List<MediaItem> newItems = result.items
@ -107,9 +109,8 @@ class MediaController extends GetxController {
items.insertAll(0, newItems);
items.refresh();
atStart.value = result.atStart;
return items.length - oldLength;
}
return 0;
return;
}
Future<void> handleRefresh() async {
@ -122,15 +123,11 @@ class MediaController extends GetxController {
}
Future<void> handleLoading() async {
if (loading.value) return;
if (!loading.value && !atEnd.value) {
await fetchMore();
}
}
void toggleRandom() {
random.value = random.value == 1 ? 0 : 1;
fetchInitial();
}
bool get isRandomEnabled => random.value == 1;
}

View File

@ -29,11 +29,27 @@ class _MediaGrid extends State<MediaGrid> {
late final _MediaGridAppBar _appBar;
late final _MediaGridBody _body;
Worker? _filterWorker;
@override
void initState() {
super.initState();
_mediaController.fetchInitial();
_filterWorker = everAll(
[
_mediaController.typeIndex,
_mediaController.modeIndex,
_mediaController.tag,
_mediaController.random,
],
(_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_refreshController.requestRefresh();
});
},
);
_appBar = _MediaGridAppBar(mediaController: _mediaController);
_body = _MediaGridBody(
refreshController: _refreshController,
@ -45,6 +61,7 @@ class _MediaGrid extends State<MediaGrid> {
@override
void dispose() {
_filterWorker?.dispose();
_scrollController.dispose();
_refreshController.dispose();
super.dispose();
@ -52,19 +69,55 @@ class _MediaGrid extends State<MediaGrid> {
@override
Widget build(BuildContext context) {
return Obx(
() => Scaffold(
return Obx(() {
if (_mediaController.loading.value && _mediaController.items.isEmpty) {
return Scaffold(
appBar: _appBar,
body: const Center(child: CircularProgressIndicator()),
);
}
if (_mediaController.errorMessage.value != null &&
_mediaController.items.isEmpty) {
return Scaffold(
appBar: _appBar,
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 60),
const SizedBox(height: 16),
Text(
'${_mediaController.errorMessage.value}',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _mediaController.fetchInitial(),
child: const Text('Erneut versuchen'),
),
],
),
),
),
);
}
return Scaffold(
endDrawer: const EndDrawer(),
endDrawerEnableOpenDragGesture:
_settingsController.drawerSwipeEnabled.value,
bottomNavigationBar: FilterBar(scrollController: _scrollController),
bottomNavigationBar: FilterBar(),
appBar: _appBar,
body: _body,
persistentFooterButtons: _mediaController.tag.value != null
? [TagFooter()]
: null,
),
);
);
});
}
}
@ -78,7 +131,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
return AppBar(
title: InkWell(
onTap: () {
mediaController.setTag(null);
if (mediaController.tag.value != null) {
mediaController.setTag(null);
}
},
child: Row(
mainAxisSize: MainAxisSize.min,
@ -106,7 +161,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
? Icons.shuffle_on_outlined
: Icons.shuffle,
),
onPressed: mediaController.toggleRandom,
onPressed: () {
mediaController.toggleRandom();
},
),
),
IconButton(
@ -134,63 +191,72 @@ class _MediaGridBody extends StatelessWidget {
final SettingsController settingsController;
final ScrollController scrollController;
@override
Widget build(BuildContext context) {
return PullexRefresh(
controller: refreshController,
enablePullDown: true,
enablePullUp: true,
header: const WaterDropHeader(),
onRefresh: () async {
try {
await mediaController.handleRefresh();
} finally {
refreshController.refreshCompleted();
if (mediaController.items.isEmpty && !mediaController.loading.value) {
return const Center(
child: Text(
'Keine f0cks gefunden.\n\nVersuch mal andere Filter.',
textAlign: TextAlign.center,
),
);
}
return NotificationListener<ScrollNotification>(
onNotification: (scrollInfo) {
if (!mediaController.loading.value &&
!mediaController.atEnd.value &&
scrollInfo.metrics.pixels >=
scrollInfo.metrics.maxScrollExtent - 600) {
mediaController.handleLoading();
}
return true;
},
onLoading: () async {
try {
await mediaController.handleLoading();
} finally {
refreshController.loadComplete();
}
},
child: Obx(
() => GridView.builder(
addAutomaticKeepAlives: false,
controller: scrollController,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: const EdgeInsets.all(4),
itemCount: mediaController.items.length,
gridDelegate: settingsController.crossAxisCount.value == 0
? const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 1,
)
: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: settingsController.crossAxisCount.value,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 1,
child: PullexRefresh(
controller: refreshController,
onRefresh: () async {
try {
await mediaController.handleRefresh();
} finally {
refreshController.refreshCompleted();
}
},
header: const WaterDropHeader(),
child: Obx(
() => GridView.builder(
addAutomaticKeepAlives: false,
controller: scrollController,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(4),
itemCount: mediaController.items.length,
gridDelegate: settingsController.crossAxisCount.value == 0
? const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 1,
)
: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: settingsController.crossAxisCount.value,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 1,
),
itemBuilder: (context, index) {
final MediaItem item = mediaController.items[index];
return Hero(
tag: 'media_${item.id}',
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
key: ValueKey(item.id),
onTap: () => Get.toNamed('/${item.id}'),
child: MediaTile(item: item),
),
),
itemBuilder: (context, index) {
final MediaItem item = mediaController.items[index];
return Hero(
tag: 'media_${item.id}',
child: Material(
type: MaterialType.transparency,
child: GestureDetector(
key: ValueKey(item.id),
onTap: () => Get.toNamed('/${item.id}'),
child: MediaTile(item: item),
),
),
);
},
);
},
),
),
),
);

View File

@ -5,9 +5,7 @@ import 'package:get/get.dart';
import 'package:f0ckapp/controller/mediacontroller.dart';
class FilterBar extends StatelessWidget {
final ScrollController scrollController;
const FilterBar({super.key, required this.scrollController});
const FilterBar({super.key});
@override
Widget build(BuildContext context) {
@ -32,7 +30,6 @@ class FilterBar extends StatelessWidget {
onChanged: (String? newValue) {
if (newValue != null) {
c.setTypeIndex(mediaTypes.indexOf(newValue));
scrollController.jumpTo(0);
}
},
),
@ -51,7 +48,6 @@ class FilterBar extends StatelessWidget {
onChanged: (String? newValue) {
if (newValue != null) {
c.setModeIndex(mediaModes.indexOf(newValue));
scrollController.jumpTo(0);
}
},
),

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
# 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.4.7+68
version: 1.4.8+69
environment:
sdk: ^3.9.0-100.2.beta