This commit is contained in:
@ -14,6 +14,7 @@ class MediaController extends GetxController {
|
|||||||
RxBool loading = false.obs;
|
RxBool loading = false.obs;
|
||||||
RxBool atEnd = false.obs;
|
RxBool atEnd = false.obs;
|
||||||
RxBool atStart = false.obs;
|
RxBool atStart = false.obs;
|
||||||
|
Rxn<String> errorMessage = Rxn<String>();
|
||||||
|
|
||||||
RxInt typeIndex = 0.obs;
|
RxInt typeIndex = 0.obs;
|
||||||
RxInt modeIndex = 0.obs;
|
RxInt modeIndex = 0.obs;
|
||||||
@ -22,12 +23,10 @@ class MediaController extends GetxController {
|
|||||||
|
|
||||||
void setTypeIndex(int idx) {
|
void setTypeIndex(int idx) {
|
||||||
typeIndex.value = idx;
|
typeIndex.value = idx;
|
||||||
fetchInitial();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setModeIndex(int idx) {
|
void setModeIndex(int idx) {
|
||||||
modeIndex.value = idx;
|
modeIndex.value = idx;
|
||||||
fetchInitial();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTag(String? newTag, {bool reload = true}) {
|
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(
|
Future<List<Favorite>?> toggleFavorite(
|
||||||
MediaItem item,
|
MediaItem item,
|
||||||
bool isFavorite,
|
bool isFavorite,
|
||||||
@ -51,6 +54,7 @@ class MediaController extends GetxController {
|
|||||||
Future<Feed?> _fetchItems({int? older, int? newer}) async {
|
Future<Feed?> _fetchItems({int? older, int? newer}) async {
|
||||||
if (loading.value) return null;
|
if (loading.value) return null;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
errorMessage.value = null;
|
||||||
try {
|
try {
|
||||||
return await _api.fetchItems(
|
return await _api.fetchItems(
|
||||||
older: older,
|
older: older,
|
||||||
@ -61,11 +65,10 @@ class MediaController extends GetxController {
|
|||||||
tag: tag.value,
|
tag: tag.value,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Get.snackbar(
|
final String errorText =
|
||||||
'Fehler beim Laden',
|
'Die Daten konnten nicht abgerufen werden. Wo Internet?';
|
||||||
'Die Daten konnten nicht abgerufen werden. Wo Internet?',
|
errorMessage.value = errorText;
|
||||||
snackPosition: SnackPosition.BOTTOM,
|
Get.snackbar('Fehler beim Laden', errorText);
|
||||||
);
|
|
||||||
return null;
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@ -73,7 +76,7 @@ class MediaController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchInitial({int? id}) async {
|
Future<void> fetchInitial({int? id}) async {
|
||||||
final result = await _fetchItems(older: id);
|
final Feed? result = await _fetchItems(older: id);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
items.assignAll(result.items);
|
items.assignAll(result.items);
|
||||||
atEnd.value = result.atEnd;
|
atEnd.value = result.atEnd;
|
||||||
@ -83,7 +86,7 @@ class MediaController extends GetxController {
|
|||||||
|
|
||||||
Future<void> fetchMore() async {
|
Future<void> fetchMore() async {
|
||||||
if (items.isEmpty || atEnd.value) return;
|
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) {
|
if (result != null) {
|
||||||
final Set<int> existingIds = items.map((e) => e.id).toSet();
|
final Set<int> existingIds = items.map((e) => e.id).toSet();
|
||||||
final List<MediaItem> newItems = result.items
|
final List<MediaItem> newItems = result.items
|
||||||
@ -95,10 +98,9 @@ class MediaController extends GetxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> fetchNewer() async {
|
Future<void> fetchNewer() async {
|
||||||
if (items.isEmpty || atStart.value) return 0;
|
if (items.isEmpty || atStart.value) return;
|
||||||
final oldLength = items.length;
|
final Feed? result = await _fetchItems(newer: items.first.id);
|
||||||
final result = await _fetchItems(newer: items.first.id);
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
final Set<int> existingIds = items.map((e) => e.id).toSet();
|
final Set<int> existingIds = items.map((e) => e.id).toSet();
|
||||||
final List<MediaItem> newItems = result.items
|
final List<MediaItem> newItems = result.items
|
||||||
@ -107,9 +109,8 @@ class MediaController extends GetxController {
|
|||||||
items.insertAll(0, newItems);
|
items.insertAll(0, newItems);
|
||||||
items.refresh();
|
items.refresh();
|
||||||
atStart.value = result.atStart;
|
atStart.value = result.atStart;
|
||||||
return items.length - oldLength;
|
|
||||||
}
|
}
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleRefresh() async {
|
Future<void> handleRefresh() async {
|
||||||
@ -122,15 +123,11 @@ class MediaController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleLoading() async {
|
Future<void> handleLoading() async {
|
||||||
|
if (loading.value) return;
|
||||||
if (!loading.value && !atEnd.value) {
|
if (!loading.value && !atEnd.value) {
|
||||||
await fetchMore();
|
await fetchMore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleRandom() {
|
|
||||||
random.value = random.value == 1 ? 0 : 1;
|
|
||||||
fetchInitial();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isRandomEnabled => random.value == 1;
|
bool get isRandomEnabled => random.value == 1;
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,27 @@ class _MediaGrid extends State<MediaGrid> {
|
|||||||
|
|
||||||
late final _MediaGridAppBar _appBar;
|
late final _MediaGridAppBar _appBar;
|
||||||
late final _MediaGridBody _body;
|
late final _MediaGridBody _body;
|
||||||
|
Worker? _filterWorker;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_mediaController.fetchInitial();
|
_mediaController.fetchInitial();
|
||||||
|
|
||||||
|
_filterWorker = everAll(
|
||||||
|
[
|
||||||
|
_mediaController.typeIndex,
|
||||||
|
_mediaController.modeIndex,
|
||||||
|
_mediaController.tag,
|
||||||
|
_mediaController.random,
|
||||||
|
],
|
||||||
|
(_) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_refreshController.requestRefresh();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
_appBar = _MediaGridAppBar(mediaController: _mediaController);
|
_appBar = _MediaGridAppBar(mediaController: _mediaController);
|
||||||
_body = _MediaGridBody(
|
_body = _MediaGridBody(
|
||||||
refreshController: _refreshController,
|
refreshController: _refreshController,
|
||||||
@ -45,6 +61,7 @@ class _MediaGrid extends State<MediaGrid> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_filterWorker?.dispose();
|
||||||
_scrollController.dispose();
|
_scrollController.dispose();
|
||||||
_refreshController.dispose();
|
_refreshController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -52,19 +69,55 @@ class _MediaGrid extends State<MediaGrid> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(
|
return Obx(() {
|
||||||
() => Scaffold(
|
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(),
|
endDrawer: const EndDrawer(),
|
||||||
endDrawerEnableOpenDragGesture:
|
endDrawerEnableOpenDragGesture:
|
||||||
_settingsController.drawerSwipeEnabled.value,
|
_settingsController.drawerSwipeEnabled.value,
|
||||||
bottomNavigationBar: FilterBar(scrollController: _scrollController),
|
bottomNavigationBar: FilterBar(),
|
||||||
appBar: _appBar,
|
appBar: _appBar,
|
||||||
body: _body,
|
body: _body,
|
||||||
persistentFooterButtons: _mediaController.tag.value != null
|
persistentFooterButtons: _mediaController.tag.value != null
|
||||||
? [TagFooter()]
|
? [TagFooter()]
|
||||||
: null,
|
: null,
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +131,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
return AppBar(
|
return AppBar(
|
||||||
title: InkWell(
|
title: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
mediaController.setTag(null);
|
if (mediaController.tag.value != null) {
|
||||||
|
mediaController.setTag(null);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -106,7 +161,9 @@ class _MediaGridAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
? Icons.shuffle_on_outlined
|
? Icons.shuffle_on_outlined
|
||||||
: Icons.shuffle,
|
: Icons.shuffle,
|
||||||
),
|
),
|
||||||
onPressed: mediaController.toggleRandom,
|
onPressed: () {
|
||||||
|
mediaController.toggleRandom();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -134,63 +191,72 @@ class _MediaGridBody extends StatelessWidget {
|
|||||||
final SettingsController settingsController;
|
final SettingsController settingsController;
|
||||||
final ScrollController scrollController;
|
final ScrollController scrollController;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PullexRefresh(
|
if (mediaController.items.isEmpty && !mediaController.loading.value) {
|
||||||
controller: refreshController,
|
return const Center(
|
||||||
enablePullDown: true,
|
child: Text(
|
||||||
enablePullUp: true,
|
'Keine f0cks gefunden.\n\nVersuch mal andere Filter.',
|
||||||
header: const WaterDropHeader(),
|
textAlign: TextAlign.center,
|
||||||
onRefresh: () async {
|
),
|
||||||
try {
|
);
|
||||||
await mediaController.handleRefresh();
|
}
|
||||||
} finally {
|
return NotificationListener<ScrollNotification>(
|
||||||
refreshController.refreshCompleted();
|
onNotification: (scrollInfo) {
|
||||||
|
if (!mediaController.loading.value &&
|
||||||
|
!mediaController.atEnd.value &&
|
||||||
|
scrollInfo.metrics.pixels >=
|
||||||
|
scrollInfo.metrics.maxScrollExtent - 600) {
|
||||||
|
mediaController.handleLoading();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
onLoading: () async {
|
child: PullexRefresh(
|
||||||
try {
|
controller: refreshController,
|
||||||
await mediaController.handleLoading();
|
onRefresh: () async {
|
||||||
} finally {
|
try {
|
||||||
refreshController.loadComplete();
|
await mediaController.handleRefresh();
|
||||||
}
|
} finally {
|
||||||
},
|
refreshController.refreshCompleted();
|
||||||
child: Obx(
|
}
|
||||||
() => GridView.builder(
|
},
|
||||||
addAutomaticKeepAlives: false,
|
header: const WaterDropHeader(),
|
||||||
controller: scrollController,
|
child: Obx(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
() => GridView.builder(
|
||||||
shrinkWrap: true,
|
addAutomaticKeepAlives: false,
|
||||||
padding: const EdgeInsets.all(4),
|
controller: scrollController,
|
||||||
itemCount: mediaController.items.length,
|
shrinkWrap: true,
|
||||||
gridDelegate: settingsController.crossAxisCount.value == 0
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
? const SliverGridDelegateWithMaxCrossAxisExtent(
|
padding: const EdgeInsets.all(4),
|
||||||
maxCrossAxisExtent: 150,
|
itemCount: mediaController.items.length,
|
||||||
crossAxisSpacing: 5,
|
gridDelegate: settingsController.crossAxisCount.value == 0
|
||||||
mainAxisSpacing: 5,
|
? const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||||
childAspectRatio: 1,
|
maxCrossAxisExtent: 150,
|
||||||
)
|
crossAxisSpacing: 5,
|
||||||
: SliverGridDelegateWithFixedCrossAxisCount(
|
mainAxisSpacing: 5,
|
||||||
crossAxisCount: settingsController.crossAxisCount.value,
|
childAspectRatio: 1,
|
||||||
crossAxisSpacing: 5,
|
)
|
||||||
mainAxisSpacing: 5,
|
: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
childAspectRatio: 1,
|
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -5,9 +5,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:f0ckapp/controller/mediacontroller.dart';
|
import 'package:f0ckapp/controller/mediacontroller.dart';
|
||||||
|
|
||||||
class FilterBar extends StatelessWidget {
|
class FilterBar extends StatelessWidget {
|
||||||
final ScrollController scrollController;
|
const FilterBar({super.key});
|
||||||
|
|
||||||
const FilterBar({super.key, required this.scrollController});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -32,7 +30,6 @@ class FilterBar extends StatelessWidget {
|
|||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
c.setTypeIndex(mediaTypes.indexOf(newValue));
|
c.setTypeIndex(mediaTypes.indexOf(newValue));
|
||||||
scrollController.jumpTo(0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -51,7 +48,6 @@ class FilterBar extends StatelessWidget {
|
|||||||
onChanged: (String? newValue) {
|
onChanged: (String? newValue) {
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
c.setModeIndex(mediaModes.indexOf(newValue));
|
c.setModeIndex(mediaModes.indexOf(newValue));
|
||||||
scrollController.jumpTo(0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -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.4.7+68
|
version: 1.4.8+69
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.0-100.2.beta
|
sdk: ^3.9.0-100.2.beta
|
||||||
|
Reference in New Issue
Block a user