v1.4.4+65
All checks were successful
Flutter Schmutter / build (push) Successful in 3m35s

This commit is contained in:
2025-06-22 03:02:18 +02:00
parent 7f0743808a
commit 95f6dcfe2b
13 changed files with 461 additions and 339 deletions

View File

@ -5,17 +5,20 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/file.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:get/get.dart';
import 'package:pullex/pullex.dart';
import 'package:share_plus/share_plus.dart';
import 'package:f0ckapp/services/api.dart';
import 'package:f0ckapp/widgets/tagfooter.dart';
import 'package:f0ckapp/utils/animatedtransition.dart';
import 'package:f0ckapp/controller/authcontroller.dart';
import 'package:f0ckapp/widgets/actiontag.dart';
import 'package:f0ckapp/widgets/favoritesection.dart';
import 'package:f0ckapp/screens/fullscreen.dart';
import 'package:f0ckapp/widgets/end_drawer.dart';
import 'package:f0ckapp/controller/settingscontroller.dart';
import 'package:f0ckapp/controller/mediacontroller.dart';
import 'package:f0ckapp/models/item.dart';
import 'package:f0ckapp/widgets/tagsection.dart';
import 'package:f0ckapp/widgets/video_widget.dart';
enum ShareAction { media, directLink, postLink }
@ -31,10 +34,11 @@ class MediaDetailScreen extends StatefulWidget {
class _MediaDetailScreenState extends State<MediaDetailScreen> {
PageController? _pageController;
final MediaController mediaController = Get.find<MediaController>();
final SettingsController settingsController = Get.find<SettingsController>();
final AuthController authController = Get.find<AuthController>();
final RxInt _currentIndex = 0.obs;
final MethodChannel _mediaSaverChannel = const MethodChannel('MediaShit');
final Map<int, bool> _expandedTags = {};
final Map<int, PullexRefreshController> _refreshControllers = {};
bool _isLoading = true;
bool _itemNotFound = false;
@ -103,6 +107,31 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
..snackbar('hehe', message, snackPosition: SnackPosition.BOTTOM);
}
Future<void> _onRefresh(
int itemId,
PullexRefreshController controller,
) async {
if (mediaController.loading.value) {
controller.refreshCompleted();
return;
}
try {
final MediaItem item = await ApiService().fetchItemById(itemId);
final int index = mediaController.items.indexWhere(
(item) => item.id == itemId,
);
if (index != -1) {
mediaController.items[index] = item;
mediaController.items.refresh();
}
controller.refreshCompleted();
} catch (e) {
_showMsg('Fehler beim Aktualisieren: $e');
controller.refreshFailed();
}
}
void _onPageChanged(int idx) {
if (idx != _currentIndex.value) {
_currentIndex.value = idx;
@ -148,7 +177,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
item.mediaUrl,
);
final Uint8List bytes = await file.readAsBytes();
final params = ShareParams(
final ShareParams params = ShareParams(
files: [XFile.fromData(bytes, mimeType: item.mime)],
);
await SharePlus.instance.share(params);
@ -175,6 +204,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
@override
void dispose() {
_pageController?.dispose();
for (PullexRefreshController controller in _refreshControllers.values) {
controller.dispose();
}
super.dispose();
}
@ -217,152 +249,125 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
);
}
return Obx(
() => PageView.builder(
controller: _pageController!,
itemCount: mediaController.items.length,
onPageChanged: _onPageChanged,
itemBuilder: (context, index) {
final MediaItem item = mediaController.items[index];
final bool isReady = _readyItemIds.contains(item.id);
final bool areTagsExpanded = _expandedTags[item.id] ?? false;
final List<Tag> allTags = item.tags ?? [];
final bool hasMoreTags = allTags.length > 5;
final List<Tag> tagsToShow = areTagsExpanded
? allTags
: allTags.take(5).toList();
return Obx(
() => Scaffold(
endDrawer: EndDrawer(),
endDrawerEnableOpenDragGesture:
mediaController.drawerSwipeEnabled.value,
appBar: AppBar(
title: Text('f0ck #${item.id}'),
actions: [
IconButton(
icon: const Icon(Icons.fullscreen),
onPressed: () {
Get.to(
FullScreenMediaView(item: item),
fullscreenDialog: true,
);
},
),
IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
await _downloadMedia(item);
},
),
PopupMenuButton<ShareAction>(
onSelected: (value) => _handleShareAction(value, item),
itemBuilder: (context) => _shareMenuItems,
icon: const Icon(Icons.share),
),
Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),
),
],
return Obx(() {
if (mediaController.items.isEmpty ||
_currentIndex.value >= mediaController.items.length) {
return Scaffold(
appBar: AppBar(title: const Text('Fehler')),
body: const Center(child: Text('Keine Items zum Anzeigen.')),
);
}
final MediaItem currentItem = mediaController.items[_currentIndex.value];
return Scaffold(
endDrawer: const EndDrawer(),
endDrawerEnableOpenDragGesture:
settingsController.drawerSwipeEnabled.value,
appBar: AppBar(
title: Text('f0ck #${currentItem.id}'),
actions: [
IconButton(
icon: const Icon(Icons.fullscreen),
onPressed: () {
Get.to(
FullScreenMediaView(item: currentItem),
fullscreenDialog: true,
);
},
),
IconButton(
icon: const Icon(Icons.download),
onPressed: () async => await _downloadMedia(currentItem),
),
PopupMenuButton<ShareAction>(
onSelected: (value) => _handleShareAction(value, currentItem),
itemBuilder: (context) => _shareMenuItems,
icon: const Icon(Icons.share),
),
Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openEndDrawer(),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: _pageController!,
builder: (context, child) {
return buildAnimatedTransition(
context: context,
pageController: _pageController!,
index: index,
controller: mediaController,
child: child!,
);
},
child: Obx(
() => _buildMedia(item, index == _currentIndex.value),
),
],
),
body: PageView.builder(
controller: _pageController!,
itemCount: mediaController.items.length,
onPageChanged: _onPageChanged,
itemBuilder: (context, index) {
final MediaItem item = mediaController.items[index];
final bool isReady = _readyItemIds.contains(item.id);
final ScrollController scrollController = ScrollController();
final PullexRefreshController refreshController =
_refreshControllers.putIfAbsent(
item.id,
() => PullexRefreshController(),
);
return PullexRefresh(
onRefresh: () => _onRefresh(item.id, refreshController),
header: const WaterDropHeader(),
controller: refreshController,
child: CustomScrollView(
controller: scrollController,
slivers: [
SliverToBoxAdapter(
child: AnimatedBuilder(
animation: _pageController!,
builder: (context, child) {
return buildAnimatedTransition(
context: context,
pageController: _pageController!,
index: index,
child: child!,
);
},
child: Obx(
() => _buildMedia(item, index == _currentIndex.value),
),
),
),
Expanded(
child: GestureDetector(
onTap: () => mediaController.hideVideoControls(),
behavior: HitTestBehavior.translucent,
child: Visibility(
visible: isReady,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Wrap(
spacing: 6.0,
runSpacing: 4.0,
alignment: WrapAlignment.center,
children: [
...tagsToShow.map(
(tag) => ActionTag(
tag,
(tag.tag == 'sfw' || tag.tag == 'nsfw')
? (onTagTap) => {}
: (onTagTap) {
mediaController.setTag(
onTagTap,
);
Get.offAllNamed('/');
},
),
),
],
),
if (hasMoreTags)
TextButton(
onPressed: () {
setState(
() => _expandedTags[item.id] =
!areTagsExpanded,
);
},
child: Text(
areTagsExpanded
? 'Weniger anzeigen'
: 'Alle ${allTags.length} Tags anzeigen',
),
),
Obx(
() => Visibility(
visible: authController.isLoggedIn,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: FavoriteSection(
item: item,
index: index,
),
if (isReady)
SliverToBoxAdapter(
child: GestureDetector(
onTap: () => settingsController.hideVideoControls(),
behavior: HitTestBehavior.translucent,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TagSection(tags: item.tags ?? []),
Obx(
() => Visibility(
visible: authController.isLoggedIn,
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: FavoriteSection(
item: item,
index: index,
),
),
),
],
),
),
],
),
),
),
),
const SliverToBoxAdapter(
child: SafeArea(child: SizedBox.shrink()),
),
const SafeArea(child: SizedBox.shrink()),
],
),
persistentFooterButtons: mediaController.tag.value != null
? [TagFooter()]
: null,
),
);
},
),
);
);
},
),
persistentFooterButtons: mediaController.tag.value != null
? [TagFooter()]
: null,
);
});
}
}

View File

@ -8,6 +8,7 @@ import 'package:f0ckapp/utils/customsearchdelegate.dart';
import 'package:f0ckapp/widgets/end_drawer.dart';
import 'package:f0ckapp/widgets/filter_bar.dart';
import 'package:f0ckapp/widgets/media_tile.dart';
import 'package:f0ckapp/controller/settingscontroller.dart';
import 'package:f0ckapp/controller/mediacontroller.dart';
class MediaGrid extends StatefulWidget {
@ -20,6 +21,7 @@ class MediaGrid extends StatefulWidget {
class _MediaGrid extends State<MediaGrid> {
final ScrollController _scrollController = ScrollController();
final MediaController _mediaController = Get.put(MediaController());
final SettingsController _settingsController = Get.put(SettingsController());
final PullexRefreshController _refreshController = PullexRefreshController(
initialRefresh: false,
);
@ -35,6 +37,7 @@ class _MediaGrid extends State<MediaGrid> {
_body = _MediaGridBody(
refreshController: _refreshController,
mediaController: _mediaController,
settingsController: _settingsController,
scrollController: _scrollController,
);
}
@ -52,7 +55,7 @@ class _MediaGrid extends State<MediaGrid> {
() => Scaffold(
endDrawer: const EndDrawer(),
endDrawerEnableOpenDragGesture:
_mediaController.drawerSwipeEnabled.value,
_settingsController.drawerSwipeEnabled.value,
bottomNavigationBar: FilterBar(scrollController: _scrollController),
appBar: _appBar,
body: _body,
@ -121,11 +124,13 @@ class _MediaGridBody extends StatelessWidget {
const _MediaGridBody({
required this.refreshController,
required this.mediaController,
required this.settingsController,
required this.scrollController,
});
final PullexRefreshController refreshController;
final MediaController mediaController;
final SettingsController settingsController;
final ScrollController scrollController;
@override
@ -134,7 +139,7 @@ class _MediaGridBody extends StatelessWidget {
controller: refreshController,
enablePullDown: true,
enablePullUp: true,
header: const MaterialHeader(),
header: const WaterDropHeader(),
onRefresh: () async {
try {
await mediaController.handleRefresh();
@ -157,7 +162,7 @@ class _MediaGridBody extends StatelessWidget {
shrinkWrap: true,
padding: const EdgeInsets.all(4),
itemCount: mediaController.items.length,
gridDelegate: mediaController.crossAxisCount.value == 0
gridDelegate: settingsController.crossAxisCount.value == 0
? const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
crossAxisSpacing: 5,
@ -165,7 +170,7 @@ class _MediaGridBody extends StatelessWidget {
childAspectRatio: 1,
)
: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: mediaController.crossAxisCount.value,
crossAxisCount: settingsController.crossAxisCount.value,
crossAxisSpacing: 5,
mainAxisSpacing: 5,
childAspectRatio: 1,

View File

@ -4,7 +4,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:get/get.dart';
import 'package:f0ckapp/controller/localizationcontroller.dart';
import 'package:f0ckapp/controller/mediacontroller.dart';
import 'package:f0ckapp/controller/settingscontroller.dart';
import 'package:f0ckapp/utils/animatedtransition.dart';
class SettingsPage extends StatefulWidget {
@ -15,8 +15,8 @@ class SettingsPage extends StatefulWidget {
}
class _SettingsPageState extends State<SettingsPage> {
final MediaController controller = Get.find();
final LocalizationController localizationController = Get.find();
final SettingsController settingsController = Get.find<SettingsController>();
final LocalizationController localizationController = Get.find<LocalizationController>();
@override
Widget build(BuildContext context) {
@ -34,7 +34,7 @@ class _SettingsPageState extends State<SettingsPage> {
title: Text('settings_numberofcolumns_title'.tr),
trailing: Obx(
() => DropdownButton<int>(
value: controller.crossAxisCount.value,
value: settingsController.crossAxisCount.value,
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
iconEnabledColor: Colors.white,
items: [0, 3, 4, 5].map((int value) {
@ -51,7 +51,7 @@ class _SettingsPageState extends State<SettingsPage> {
}).toList(),
onChanged: (int? newValue) async {
if (newValue != null) {
await controller.setCrossAxisCount(newValue);
await settingsController.setCrossAxisCount(newValue);
}
},
),
@ -62,7 +62,7 @@ class _SettingsPageState extends State<SettingsPage> {
title: Text('settings_pageanimation_title'.tr),
trailing: Obx(
() => DropdownButton<PageTransition>(
value: controller.transitionType.value,
value: settingsController.transitionType.value,
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
iconEnabledColor: Colors.white,
items: PageTransition.values.map((PageTransition type) {
@ -91,7 +91,7 @@ class _SettingsPageState extends State<SettingsPage> {
}).toList(),
onChanged: (PageTransition? newValue) async {
if (newValue != null) {
await controller.setTransitionType(newValue);
await settingsController.setTransitionType(newValue);
}
},
),
@ -102,9 +102,9 @@ class _SettingsPageState extends State<SettingsPage> {
SwitchListTile(
title: Text('settings_drawer_title'.tr),
subtitle: Text('settings_drawer_subtitle'.tr),
value: controller.drawerSwipeEnabled.value,
value: settingsController.drawerSwipeEnabled.value,
onChanged: (bool value) async {
await controller.setDrawerSwipeEnabled(value);
await settingsController.setDrawerSwipeEnabled(value);
setState(() {});
},
),