This commit is contained in:
		@@ -1,18 +1,14 @@
 | 
				
			|||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
import 'package:encrypt_shared_preferences/provider.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/models/feed.dart';
 | 
					import 'package:f0ckapp/models/feed.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/item.dart';
 | 
					import 'package:f0ckapp/models/item.dart';
 | 
				
			||||||
import 'package:f0ckapp/services/api.dart';
 | 
					import 'package:f0ckapp/services/api.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
 | 
					const List<String> mediaTypes = ["alles", "image", "video", "audio"];
 | 
				
			||||||
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
 | 
					const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MediaController extends GetxController {
 | 
					class MediaController extends GetxController {
 | 
				
			||||||
  final ApiService _api = Get.find<ApiService>();
 | 
					  final ApiService _api = Get.find<ApiService>();
 | 
				
			||||||
  final EncryptedSharedPreferencesAsync storage =
 | 
					 | 
				
			||||||
      EncryptedSharedPreferencesAsync.getInstance();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  RxList<MediaItem> items = <MediaItem>[].obs;
 | 
					  RxList<MediaItem> items = <MediaItem>[].obs;
 | 
				
			||||||
  RxBool loading = false.obs;
 | 
					  RxBool loading = false.obs;
 | 
				
			||||||
@@ -23,12 +19,6 @@ class MediaController extends GetxController {
 | 
				
			|||||||
  RxInt modeIndex = 0.obs;
 | 
					  RxInt modeIndex = 0.obs;
 | 
				
			||||||
  RxInt random = 0.obs;
 | 
					  RxInt random = 0.obs;
 | 
				
			||||||
  Rxn<String> tag = Rxn<String>(null);
 | 
					  Rxn<String> tag = Rxn<String>(null);
 | 
				
			||||||
  RxBool muted = false.obs;
 | 
					 | 
				
			||||||
  Rx<PageTransition> transitionType = PageTransition.opacity.obs;
 | 
					 | 
				
			||||||
  RxBool drawerSwipeEnabled = true.obs;
 | 
					 | 
				
			||||||
  RxInt crossAxisCount = 0.obs;
 | 
					 | 
				
			||||||
  RxInt videoControlsTimerNotifier = 0.obs;
 | 
					 | 
				
			||||||
  RxInt hideControlsNotifier = 0.obs;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void setTypeIndex(int idx) {
 | 
					  void setTypeIndex(int idx) {
 | 
				
			||||||
    typeIndex.value = idx;
 | 
					    typeIndex.value = idx;
 | 
				
			||||||
@@ -137,58 +127,10 @@ class MediaController extends GetxController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void toggleMuted() {
 | 
					 | 
				
			||||||
    muted.value = !muted.value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void setMuted(bool value) {
 | 
					 | 
				
			||||||
    muted.value = value;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> setTransitionType(PageTransition type) async {
 | 
					 | 
				
			||||||
    transitionType.value = type;
 | 
					 | 
				
			||||||
    await saveSettings();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> setCrossAxisCount(int value) async {
 | 
					 | 
				
			||||||
    crossAxisCount.value = value;
 | 
					 | 
				
			||||||
    await saveSettings();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> setDrawerSwipeEnabled(bool enabled) async {
 | 
					 | 
				
			||||||
    drawerSwipeEnabled.value = enabled;
 | 
					 | 
				
			||||||
    await saveSettings();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  void toggleRandom() {
 | 
					  void toggleRandom() {
 | 
				
			||||||
    random.value = random.value == 1 ? 0 : 1;
 | 
					    random.value = random.value == 1 ? 0 : 1;
 | 
				
			||||||
    fetchInitial();
 | 
					    fetchInitial();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void resetVideoControlsTimer() => videoControlsTimerNotifier.value++;
 | 
					 | 
				
			||||||
  void hideVideoControls() => hideControlsNotifier.value++;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  void onInit() async {
 | 
					 | 
				
			||||||
    super.onInit();
 | 
					 | 
				
			||||||
    await loadSettings();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> loadSettings() async {
 | 
					 | 
				
			||||||
    muted.value = await storage.getBoolean('muted') ?? false;
 | 
					 | 
				
			||||||
    crossAxisCount.value = await storage.getInt('crossAxisCount') ?? 0;
 | 
					 | 
				
			||||||
    drawerSwipeEnabled.value =
 | 
					 | 
				
			||||||
        await storage.getBoolean('drawerSwipeEnabled') ?? true;
 | 
					 | 
				
			||||||
    transitionType.value =
 | 
					 | 
				
			||||||
        PageTransition.values[await storage.getInt('transitionType') ?? 0];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Future<void> saveSettings() async {
 | 
					 | 
				
			||||||
    await storage.setBoolean('muted', muted.value);
 | 
					 | 
				
			||||||
    await storage.setInt('crossAxisCount', crossAxisCount.value);
 | 
					 | 
				
			||||||
    await storage.setBoolean('drawerSwipeEnabled', drawerSwipeEnabled.value);
 | 
					 | 
				
			||||||
    await storage.setInt('transitionType', transitionType.value.index);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  bool get isRandomEnabled => random.value == 1;
 | 
					  bool get isRandomEnabled => random.value == 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										81
									
								
								lib/controller/settingscontroller.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								lib/controller/settingscontroller.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import 'package:encrypt_shared_preferences/provider.dart';
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _StorageKeys {
 | 
				
			||||||
 | 
					  static const String muted = 'muted';
 | 
				
			||||||
 | 
					  static const String crossAxisCount = 'crossAxisCount';
 | 
				
			||||||
 | 
					  static const String drawerSwipeEnabled = 'drawerSwipeEnabled';
 | 
				
			||||||
 | 
					  static const String transitionType = 'transitionType';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SettingsController extends GetxController {
 | 
				
			||||||
 | 
					  final EncryptedSharedPreferencesAsync storage =
 | 
				
			||||||
 | 
					      EncryptedSharedPreferencesAsync.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RxBool muted = false.obs;
 | 
				
			||||||
 | 
					  Rx<PageTransition> transitionType = PageTransition.opacity.obs;
 | 
				
			||||||
 | 
					  RxBool drawerSwipeEnabled = true.obs;
 | 
				
			||||||
 | 
					  RxInt crossAxisCount = 0.obs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RxInt videoControlsTimerNotifier = 0.obs;
 | 
				
			||||||
 | 
					  RxInt hideControlsNotifier = 0.obs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onInit() {
 | 
				
			||||||
 | 
					    super.onInit();
 | 
				
			||||||
 | 
					    loadSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void toggleMuted() {
 | 
				
			||||||
 | 
					    muted.value = !muted.value;
 | 
				
			||||||
 | 
					    saveSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void setMuted(bool value) {
 | 
				
			||||||
 | 
					    muted.value = value;
 | 
				
			||||||
 | 
					    saveSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> setTransitionType(PageTransition type) async {
 | 
				
			||||||
 | 
					    transitionType.value = type;
 | 
				
			||||||
 | 
					    await saveSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> setCrossAxisCount(int value) async {
 | 
				
			||||||
 | 
					    crossAxisCount.value = value;
 | 
				
			||||||
 | 
					    await saveSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> setDrawerSwipeEnabled(bool enabled) async {
 | 
				
			||||||
 | 
					    drawerSwipeEnabled.value = enabled;
 | 
				
			||||||
 | 
					    await saveSettings();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void resetVideoControlsTimer() => videoControlsTimerNotifier.value++;
 | 
				
			||||||
 | 
					  void hideVideoControls() => hideControlsNotifier.value++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> loadSettings() async {
 | 
				
			||||||
 | 
					    muted.value = await storage.getBoolean(_StorageKeys.muted) ?? false;
 | 
				
			||||||
 | 
					    crossAxisCount.value =
 | 
				
			||||||
 | 
					        await storage.getInt(_StorageKeys.crossAxisCount) ?? 0;
 | 
				
			||||||
 | 
					    drawerSwipeEnabled.value =
 | 
				
			||||||
 | 
					        await storage.getBoolean(_StorageKeys.drawerSwipeEnabled) ?? true;
 | 
				
			||||||
 | 
					    transitionType.value = PageTransition
 | 
				
			||||||
 | 
					        .values[await storage.getInt(_StorageKeys.transitionType) ?? 0];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> saveSettings() async {
 | 
				
			||||||
 | 
					    await storage.setBoolean(_StorageKeys.muted, muted.value);
 | 
				
			||||||
 | 
					    await storage.setInt(_StorageKeys.crossAxisCount, crossAxisCount.value);
 | 
				
			||||||
 | 
					    await storage.setBoolean(
 | 
				
			||||||
 | 
					      _StorageKeys.drawerSwipeEnabled,
 | 
				
			||||||
 | 
					      drawerSwipeEnabled.value,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    await storage.setInt(
 | 
				
			||||||
 | 
					      _StorageKeys.transitionType,
 | 
				
			||||||
 | 
					      transitionType.value.index,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,7 @@ import 'package:encrypt_shared_preferences/provider.dart';
 | 
				
			|||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/services/api.dart';
 | 
					import 'package:f0ckapp/services/api.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/authcontroller.dart';
 | 
					import 'package:f0ckapp/controller/authcontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/localizationcontroller.dart';
 | 
					import 'package:f0ckapp/controller/localizationcontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/themecontroller.dart';
 | 
					import 'package:f0ckapp/controller/themecontroller.dart';
 | 
				
			||||||
@@ -24,6 +25,7 @@ void main() async {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Get.put(AuthController());
 | 
					  Get.put(AuthController());
 | 
				
			||||||
  Get.put(ApiService());
 | 
					  Get.put(ApiService());
 | 
				
			||||||
 | 
					  Get.put(SettingsController());
 | 
				
			||||||
  Get.put(MediaController());
 | 
					  Get.put(MediaController());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final ThemeController themeController = Get.put(ThemeController());
 | 
					  final ThemeController themeController = Get.put(ThemeController());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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/file.dart';
 | 
				
			||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
					import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					import 'package:pullex/pullex.dart';
 | 
				
			||||||
import 'package:share_plus/share_plus.dart';
 | 
					import 'package:share_plus/share_plus.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/services/api.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/tagfooter.dart';
 | 
					import 'package:f0ckapp/widgets/tagfooter.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
					import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/authcontroller.dart';
 | 
					import 'package:f0ckapp/controller/authcontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/actiontag.dart';
 | 
					 | 
				
			||||||
import 'package:f0ckapp/widgets/favoritesection.dart';
 | 
					import 'package:f0ckapp/widgets/favoritesection.dart';
 | 
				
			||||||
import 'package:f0ckapp/screens/fullscreen.dart';
 | 
					import 'package:f0ckapp/screens/fullscreen.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
					import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
					import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/item.dart';
 | 
					import 'package:f0ckapp/models/item.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/tagsection.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/video_widget.dart';
 | 
					import 'package:f0ckapp/widgets/video_widget.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum ShareAction { media, directLink, postLink }
 | 
					enum ShareAction { media, directLink, postLink }
 | 
				
			||||||
@@ -31,10 +34,11 @@ class MediaDetailScreen extends StatefulWidget {
 | 
				
			|||||||
class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
					class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			||||||
  PageController? _pageController;
 | 
					  PageController? _pageController;
 | 
				
			||||||
  final MediaController mediaController = Get.find<MediaController>();
 | 
					  final MediaController mediaController = Get.find<MediaController>();
 | 
				
			||||||
 | 
					  final SettingsController settingsController = Get.find<SettingsController>();
 | 
				
			||||||
  final AuthController authController = Get.find<AuthController>();
 | 
					  final AuthController authController = Get.find<AuthController>();
 | 
				
			||||||
  final RxInt _currentIndex = 0.obs;
 | 
					  final RxInt _currentIndex = 0.obs;
 | 
				
			||||||
  final MethodChannel _mediaSaverChannel = const MethodChannel('MediaShit');
 | 
					  final MethodChannel _mediaSaverChannel = const MethodChannel('MediaShit');
 | 
				
			||||||
  final Map<int, bool> _expandedTags = {};
 | 
					  final Map<int, PullexRefreshController> _refreshControllers = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bool _isLoading = true;
 | 
					  bool _isLoading = true;
 | 
				
			||||||
  bool _itemNotFound = false;
 | 
					  bool _itemNotFound = false;
 | 
				
			||||||
@@ -103,6 +107,31 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
      ..snackbar('hehe', message, snackPosition: SnackPosition.BOTTOM);
 | 
					      ..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) {
 | 
					  void _onPageChanged(int idx) {
 | 
				
			||||||
    if (idx != _currentIndex.value) {
 | 
					    if (idx != _currentIndex.value) {
 | 
				
			||||||
      _currentIndex.value = idx;
 | 
					      _currentIndex.value = idx;
 | 
				
			||||||
@@ -148,7 +177,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
          item.mediaUrl,
 | 
					          item.mediaUrl,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        final Uint8List bytes = await file.readAsBytes();
 | 
					        final Uint8List bytes = await file.readAsBytes();
 | 
				
			||||||
        final params = ShareParams(
 | 
					        final ShareParams params = ShareParams(
 | 
				
			||||||
          files: [XFile.fromData(bytes, mimeType: item.mime)],
 | 
					          files: [XFile.fromData(bytes, mimeType: item.mime)],
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        await SharePlus.instance.share(params);
 | 
					        await SharePlus.instance.share(params);
 | 
				
			||||||
@@ -175,6 +204,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void dispose() {
 | 
					  void dispose() {
 | 
				
			||||||
    _pageController?.dispose();
 | 
					    _pageController?.dispose();
 | 
				
			||||||
 | 
					    for (PullexRefreshController controller in _refreshControllers.values) {
 | 
				
			||||||
 | 
					      controller.dispose();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    super.dispose();
 | 
					    super.dispose();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -217,152 +249,125 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Obx(
 | 
					    return Obx(() {
 | 
				
			||||||
      () => PageView.builder(
 | 
					      if (mediaController.items.isEmpty ||
 | 
				
			||||||
        controller: _pageController!,
 | 
					          _currentIndex.value >= mediaController.items.length) {
 | 
				
			||||||
        itemCount: mediaController.items.length,
 | 
					        return Scaffold(
 | 
				
			||||||
        onPageChanged: _onPageChanged,
 | 
					          appBar: AppBar(title: const Text('Fehler')),
 | 
				
			||||||
        itemBuilder: (context, index) {
 | 
					          body: const Center(child: Text('Keine Items zum Anzeigen.')),
 | 
				
			||||||
          final MediaItem item = mediaController.items[index];
 | 
					        );
 | 
				
			||||||
          final bool isReady = _readyItemIds.contains(item.id);
 | 
					      }
 | 
				
			||||||
          final bool areTagsExpanded = _expandedTags[item.id] ?? false;
 | 
					      final MediaItem currentItem = mediaController.items[_currentIndex.value];
 | 
				
			||||||
          final List<Tag> allTags = item.tags ?? [];
 | 
					      return Scaffold(
 | 
				
			||||||
          final bool hasMoreTags = allTags.length > 5;
 | 
					        endDrawer: const EndDrawer(),
 | 
				
			||||||
          final List<Tag> tagsToShow = areTagsExpanded
 | 
					        endDrawerEnableOpenDragGesture:
 | 
				
			||||||
              ? allTags
 | 
					            settingsController.drawerSwipeEnabled.value,
 | 
				
			||||||
              : allTags.take(5).toList();
 | 
					        appBar: AppBar(
 | 
				
			||||||
 | 
					          title: Text('f0ck #${currentItem.id}'),
 | 
				
			||||||
          return Obx(
 | 
					          actions: [
 | 
				
			||||||
            () => Scaffold(
 | 
					            IconButton(
 | 
				
			||||||
              endDrawer: EndDrawer(),
 | 
					              icon: const Icon(Icons.fullscreen),
 | 
				
			||||||
              endDrawerEnableOpenDragGesture:
 | 
					              onPressed: () {
 | 
				
			||||||
                  mediaController.drawerSwipeEnabled.value,
 | 
					                Get.to(
 | 
				
			||||||
              appBar: AppBar(
 | 
					                  FullScreenMediaView(item: currentItem),
 | 
				
			||||||
                title: Text('f0ck #${item.id}'),
 | 
					                  fullscreenDialog: true,
 | 
				
			||||||
                actions: [
 | 
					                );
 | 
				
			||||||
                  IconButton(
 | 
					              },
 | 
				
			||||||
                    icon: const Icon(Icons.fullscreen),
 | 
					            ),
 | 
				
			||||||
                    onPressed: () {
 | 
					            IconButton(
 | 
				
			||||||
                      Get.to(
 | 
					              icon: const Icon(Icons.download),
 | 
				
			||||||
                        FullScreenMediaView(item: item),
 | 
					              onPressed: () async => await _downloadMedia(currentItem),
 | 
				
			||||||
                        fullscreenDialog: true,
 | 
					            ),
 | 
				
			||||||
                      );
 | 
					            PopupMenuButton<ShareAction>(
 | 
				
			||||||
                    },
 | 
					              onSelected: (value) => _handleShareAction(value, currentItem),
 | 
				
			||||||
                  ),
 | 
					              itemBuilder: (context) => _shareMenuItems,
 | 
				
			||||||
                  IconButton(
 | 
					              icon: const Icon(Icons.share),
 | 
				
			||||||
                    icon: const Icon(Icons.download),
 | 
					            ),
 | 
				
			||||||
                    onPressed: () async {
 | 
					            Builder(
 | 
				
			||||||
                      await _downloadMedia(item);
 | 
					              builder: (context) => IconButton(
 | 
				
			||||||
                    },
 | 
					                icon: const Icon(Icons.menu),
 | 
				
			||||||
                  ),
 | 
					                onPressed: () => Scaffold.of(context).openEndDrawer(),
 | 
				
			||||||
                  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();
 | 
					 | 
				
			||||||
                      },
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ],
 | 
					 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              body: Column(
 | 
					            ),
 | 
				
			||||||
                crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
					          ],
 | 
				
			||||||
                children: [
 | 
					        ),
 | 
				
			||||||
                  AnimatedBuilder(
 | 
					        body: PageView.builder(
 | 
				
			||||||
                    animation: _pageController!,
 | 
					          controller: _pageController!,
 | 
				
			||||||
                    builder: (context, child) {
 | 
					          itemCount: mediaController.items.length,
 | 
				
			||||||
                      return buildAnimatedTransition(
 | 
					          onPageChanged: _onPageChanged,
 | 
				
			||||||
                        context: context,
 | 
					          itemBuilder: (context, index) {
 | 
				
			||||||
                        pageController: _pageController!,
 | 
					            final MediaItem item = mediaController.items[index];
 | 
				
			||||||
                        index: index,
 | 
					            final bool isReady = _readyItemIds.contains(item.id);
 | 
				
			||||||
                        controller: mediaController,
 | 
					            final ScrollController scrollController = ScrollController();
 | 
				
			||||||
                        child: child!,
 | 
					            final PullexRefreshController refreshController =
 | 
				
			||||||
                      );
 | 
					                _refreshControllers.putIfAbsent(
 | 
				
			||||||
                    },
 | 
					                  item.id,
 | 
				
			||||||
                    child: Obx(
 | 
					                  () => PullexRefreshController(),
 | 
				
			||||||
                      () => _buildMedia(item, index == _currentIndex.value),
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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(
 | 
					                  if (isReady)
 | 
				
			||||||
                    child: GestureDetector(
 | 
					                    SliverToBoxAdapter(
 | 
				
			||||||
                      onTap: () => mediaController.hideVideoControls(),
 | 
					                      child: GestureDetector(
 | 
				
			||||||
                      behavior: HitTestBehavior.translucent,
 | 
					                        onTap: () => settingsController.hideVideoControls(),
 | 
				
			||||||
                      child: Visibility(
 | 
					                        behavior: HitTestBehavior.translucent,
 | 
				
			||||||
                        visible: isReady,
 | 
					                        child: Padding(
 | 
				
			||||||
                        child: SingleChildScrollView(
 | 
					                          padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
                          child: Padding(
 | 
					                          child: Column(
 | 
				
			||||||
                            padding: const EdgeInsets.all(16.0),
 | 
					                            crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
                            child: Column(
 | 
					                            children: [
 | 
				
			||||||
                              crossAxisAlignment: CrossAxisAlignment.center,
 | 
					                              TagSection(tags: item.tags ?? []),
 | 
				
			||||||
                              children: [
 | 
					                              Obx(
 | 
				
			||||||
                                Wrap(
 | 
					                                () => Visibility(
 | 
				
			||||||
                                  spacing: 6.0,
 | 
					                                  visible: authController.isLoggedIn,
 | 
				
			||||||
                                  runSpacing: 4.0,
 | 
					                                  child: Padding(
 | 
				
			||||||
                                  alignment: WrapAlignment.center,
 | 
					                                    padding: const EdgeInsets.only(top: 20.0),
 | 
				
			||||||
                                  children: [
 | 
					                                    child: FavoriteSection(
 | 
				
			||||||
                                    ...tagsToShow.map(
 | 
					                                      item: item,
 | 
				
			||||||
                                      (tag) => ActionTag(
 | 
					                                      index: index,
 | 
				
			||||||
                                        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,
 | 
					 | 
				
			||||||
                                      ),
 | 
					 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                  ),
 | 
					                                  ),
 | 
				
			||||||
                                ),
 | 
					                                ),
 | 
				
			||||||
                              ],
 | 
					                              ),
 | 
				
			||||||
                            ),
 | 
					                            ],
 | 
				
			||||||
                          ),
 | 
					                          ),
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
 | 
					                  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,
 | 
				
			||||||
      ),
 | 
					      );
 | 
				
			||||||
    );
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import 'package:f0ckapp/utils/customsearchdelegate.dart';
 | 
				
			|||||||
import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
					import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/filter_bar.dart';
 | 
					import 'package:f0ckapp/widgets/filter_bar.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/media_tile.dart';
 | 
					import 'package:f0ckapp/widgets/media_tile.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
					import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MediaGrid extends StatefulWidget {
 | 
					class MediaGrid extends StatefulWidget {
 | 
				
			||||||
@@ -20,6 +21,7 @@ class MediaGrid extends StatefulWidget {
 | 
				
			|||||||
class _MediaGrid extends State<MediaGrid> {
 | 
					class _MediaGrid extends State<MediaGrid> {
 | 
				
			||||||
  final ScrollController _scrollController = ScrollController();
 | 
					  final ScrollController _scrollController = ScrollController();
 | 
				
			||||||
  final MediaController _mediaController = Get.put(MediaController());
 | 
					  final MediaController _mediaController = Get.put(MediaController());
 | 
				
			||||||
 | 
					  final SettingsController _settingsController = Get.put(SettingsController());
 | 
				
			||||||
  final PullexRefreshController _refreshController = PullexRefreshController(
 | 
					  final PullexRefreshController _refreshController = PullexRefreshController(
 | 
				
			||||||
    initialRefresh: false,
 | 
					    initialRefresh: false,
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
@@ -35,6 +37,7 @@ class _MediaGrid extends State<MediaGrid> {
 | 
				
			|||||||
    _body = _MediaGridBody(
 | 
					    _body = _MediaGridBody(
 | 
				
			||||||
      refreshController: _refreshController,
 | 
					      refreshController: _refreshController,
 | 
				
			||||||
      mediaController: _mediaController,
 | 
					      mediaController: _mediaController,
 | 
				
			||||||
 | 
					      settingsController: _settingsController,
 | 
				
			||||||
      scrollController: _scrollController,
 | 
					      scrollController: _scrollController,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -52,7 +55,7 @@ class _MediaGrid extends State<MediaGrid> {
 | 
				
			|||||||
      () => Scaffold(
 | 
					      () => Scaffold(
 | 
				
			||||||
        endDrawer: const EndDrawer(),
 | 
					        endDrawer: const EndDrawer(),
 | 
				
			||||||
        endDrawerEnableOpenDragGesture:
 | 
					        endDrawerEnableOpenDragGesture:
 | 
				
			||||||
            _mediaController.drawerSwipeEnabled.value,
 | 
					            _settingsController.drawerSwipeEnabled.value,
 | 
				
			||||||
        bottomNavigationBar: FilterBar(scrollController: _scrollController),
 | 
					        bottomNavigationBar: FilterBar(scrollController: _scrollController),
 | 
				
			||||||
        appBar: _appBar,
 | 
					        appBar: _appBar,
 | 
				
			||||||
        body: _body,
 | 
					        body: _body,
 | 
				
			||||||
@@ -121,11 +124,13 @@ class _MediaGridBody extends StatelessWidget {
 | 
				
			|||||||
  const _MediaGridBody({
 | 
					  const _MediaGridBody({
 | 
				
			||||||
    required this.refreshController,
 | 
					    required this.refreshController,
 | 
				
			||||||
    required this.mediaController,
 | 
					    required this.mediaController,
 | 
				
			||||||
 | 
					    required this.settingsController,
 | 
				
			||||||
    required this.scrollController,
 | 
					    required this.scrollController,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final PullexRefreshController refreshController;
 | 
					  final PullexRefreshController refreshController;
 | 
				
			||||||
  final MediaController mediaController;
 | 
					  final MediaController mediaController;
 | 
				
			||||||
 | 
					  final SettingsController settingsController;
 | 
				
			||||||
  final ScrollController scrollController;
 | 
					  final ScrollController scrollController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -134,7 +139,7 @@ class _MediaGridBody extends StatelessWidget {
 | 
				
			|||||||
      controller: refreshController,
 | 
					      controller: refreshController,
 | 
				
			||||||
      enablePullDown: true,
 | 
					      enablePullDown: true,
 | 
				
			||||||
      enablePullUp: true,
 | 
					      enablePullUp: true,
 | 
				
			||||||
      header: const MaterialHeader(),
 | 
					      header: const WaterDropHeader(),
 | 
				
			||||||
      onRefresh: () async {
 | 
					      onRefresh: () async {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          await mediaController.handleRefresh();
 | 
					          await mediaController.handleRefresh();
 | 
				
			||||||
@@ -157,7 +162,7 @@ class _MediaGridBody extends StatelessWidget {
 | 
				
			|||||||
          shrinkWrap: true,
 | 
					          shrinkWrap: true,
 | 
				
			||||||
          padding: const EdgeInsets.all(4),
 | 
					          padding: const EdgeInsets.all(4),
 | 
				
			||||||
          itemCount: mediaController.items.length,
 | 
					          itemCount: mediaController.items.length,
 | 
				
			||||||
          gridDelegate: mediaController.crossAxisCount.value == 0
 | 
					          gridDelegate: settingsController.crossAxisCount.value == 0
 | 
				
			||||||
              ? const SliverGridDelegateWithMaxCrossAxisExtent(
 | 
					              ? const SliverGridDelegateWithMaxCrossAxisExtent(
 | 
				
			||||||
                  maxCrossAxisExtent: 150,
 | 
					                  maxCrossAxisExtent: 150,
 | 
				
			||||||
                  crossAxisSpacing: 5,
 | 
					                  crossAxisSpacing: 5,
 | 
				
			||||||
@@ -165,7 +170,7 @@ class _MediaGridBody extends StatelessWidget {
 | 
				
			|||||||
                  childAspectRatio: 1,
 | 
					                  childAspectRatio: 1,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
              : SliverGridDelegateWithFixedCrossAxisCount(
 | 
					              : SliverGridDelegateWithFixedCrossAxisCount(
 | 
				
			||||||
                  crossAxisCount: mediaController.crossAxisCount.value,
 | 
					                  crossAxisCount: settingsController.crossAxisCount.value,
 | 
				
			||||||
                  crossAxisSpacing: 5,
 | 
					                  crossAxisSpacing: 5,
 | 
				
			||||||
                  mainAxisSpacing: 5,
 | 
					                  mainAxisSpacing: 5,
 | 
				
			||||||
                  childAspectRatio: 1,
 | 
					                  childAspectRatio: 1,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
				
			|||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/controller/localizationcontroller.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';
 | 
					import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SettingsPage extends StatefulWidget {
 | 
					class SettingsPage extends StatefulWidget {
 | 
				
			||||||
@@ -15,8 +15,8 @@ class SettingsPage extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _SettingsPageState extends State<SettingsPage> {
 | 
					class _SettingsPageState extends State<SettingsPage> {
 | 
				
			||||||
  final MediaController controller = Get.find();
 | 
					  final SettingsController settingsController = Get.find<SettingsController>();
 | 
				
			||||||
  final LocalizationController localizationController = Get.find();
 | 
					  final LocalizationController localizationController = Get.find<LocalizationController>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
@@ -34,7 +34,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
				
			|||||||
                title: Text('settings_numberofcolumns_title'.tr),
 | 
					                title: Text('settings_numberofcolumns_title'.tr),
 | 
				
			||||||
                trailing: Obx(
 | 
					                trailing: Obx(
 | 
				
			||||||
                  () => DropdownButton<int>(
 | 
					                  () => DropdownButton<int>(
 | 
				
			||||||
                    value: controller.crossAxisCount.value,
 | 
					                    value: settingsController.crossAxisCount.value,
 | 
				
			||||||
                    dropdownColor: const Color.fromARGB(255, 43, 43, 43),
 | 
					                    dropdownColor: const Color.fromARGB(255, 43, 43, 43),
 | 
				
			||||||
                    iconEnabledColor: Colors.white,
 | 
					                    iconEnabledColor: Colors.white,
 | 
				
			||||||
                    items: [0, 3, 4, 5].map((int value) {
 | 
					                    items: [0, 3, 4, 5].map((int value) {
 | 
				
			||||||
@@ -51,7 +51,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
				
			|||||||
                    }).toList(),
 | 
					                    }).toList(),
 | 
				
			||||||
                    onChanged: (int? newValue) async {
 | 
					                    onChanged: (int? newValue) async {
 | 
				
			||||||
                      if (newValue != null) {
 | 
					                      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),
 | 
					                title: Text('settings_pageanimation_title'.tr),
 | 
				
			||||||
                trailing: Obx(
 | 
					                trailing: Obx(
 | 
				
			||||||
                  () => DropdownButton<PageTransition>(
 | 
					                  () => DropdownButton<PageTransition>(
 | 
				
			||||||
                    value: controller.transitionType.value,
 | 
					                    value: settingsController.transitionType.value,
 | 
				
			||||||
                    dropdownColor: const Color.fromARGB(255, 43, 43, 43),
 | 
					                    dropdownColor: const Color.fromARGB(255, 43, 43, 43),
 | 
				
			||||||
                    iconEnabledColor: Colors.white,
 | 
					                    iconEnabledColor: Colors.white,
 | 
				
			||||||
                    items: PageTransition.values.map((PageTransition type) {
 | 
					                    items: PageTransition.values.map((PageTransition type) {
 | 
				
			||||||
@@ -91,7 +91,7 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
				
			|||||||
                    }).toList(),
 | 
					                    }).toList(),
 | 
				
			||||||
                    onChanged: (PageTransition? newValue) async {
 | 
					                    onChanged: (PageTransition? newValue) async {
 | 
				
			||||||
                      if (newValue != null) {
 | 
					                      if (newValue != null) {
 | 
				
			||||||
                        await controller.setTransitionType(newValue);
 | 
					                        await settingsController.setTransitionType(newValue);
 | 
				
			||||||
                      }
 | 
					                      }
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
@@ -102,9 +102,9 @@ class _SettingsPageState extends State<SettingsPage> {
 | 
				
			|||||||
              SwitchListTile(
 | 
					              SwitchListTile(
 | 
				
			||||||
                title: Text('settings_drawer_title'.tr),
 | 
					                title: Text('settings_drawer_title'.tr),
 | 
				
			||||||
                subtitle: Text('settings_drawer_subtitle'.tr),
 | 
					                subtitle: Text('settings_drawer_subtitle'.tr),
 | 
				
			||||||
                value: controller.drawerSwipeEnabled.value,
 | 
					                value: settingsController.drawerSwipeEnabled.value,
 | 
				
			||||||
                onChanged: (bool value) async {
 | 
					                onChanged: (bool value) async {
 | 
				
			||||||
                  await controller.setDrawerSwipeEnabled(value);
 | 
					                  await settingsController.setDrawerSwipeEnabled(value);
 | 
				
			||||||
                  setState(() {});
 | 
					                  setState(() {});
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,11 @@
 | 
				
			|||||||
import 'package:encrypt_shared_preferences/provider.dart';
 | 
					 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/authcontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/item.dart';
 | 
					import 'package:f0ckapp/models/item.dart';
 | 
				
			||||||
import 'package:f0ckapp/models/feed.dart';
 | 
					import 'package:f0ckapp/models/feed.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ApiService extends GetConnect {
 | 
					class ApiService extends GetConnect {
 | 
				
			||||||
  final EncryptedSharedPreferencesAsync storage =
 | 
					  final AuthController _authController = Get.find<AuthController>();
 | 
				
			||||||
      EncryptedSharedPreferencesAsync.getInstance();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<Feed> fetchItems({
 | 
					  Future<Feed> fetchItems({
 | 
				
			||||||
    int? older,
 | 
					    int? older,
 | 
				
			||||||
@@ -16,7 +15,7 @@ class ApiService extends GetConnect {
 | 
				
			|||||||
    int random = 0,
 | 
					    int random = 0,
 | 
				
			||||||
    String? tag,
 | 
					    String? tag,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
    String? token = await storage.getString('token');
 | 
					    String? token = _authController.token.value;
 | 
				
			||||||
    final params = <String, String>{
 | 
					    final params = <String, String>{
 | 
				
			||||||
      'type': type.toString(),
 | 
					      'type': type.toString(),
 | 
				
			||||||
      'mode': mode.toString(),
 | 
					      'mode': mode.toString(),
 | 
				
			||||||
@@ -42,18 +41,40 @@ class ApiService extends GetConnect {
 | 
				
			|||||||
      feed.items.sort((a, b) => b.id.compareTo(a.id));
 | 
					      feed.items.sort((a, b) => b.id.compareTo(a.id));
 | 
				
			||||||
      return feed;
 | 
					      return feed;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      if (Get.isSnackbarOpen == false) {
 | 
					      if (!Get.isSnackbarOpen) {
 | 
				
			||||||
        Get.snackbar('Fehler', 'Fehler beim Laden der Items');
 | 
					        Get.snackbar('Fehler', 'Fehler beim Laden der Items');
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      throw Exception('Fehler beim Laden der Items');
 | 
					      throw Exception('Fehler beim Laden der Items');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<MediaItem> fetchItemById(int itemId) async {
 | 
				
			||||||
 | 
					    String? token = _authController.token.value;
 | 
				
			||||||
 | 
					    final Map<String, String> headers = <String, String>{};
 | 
				
			||||||
 | 
					    if (token != null && token.isNotEmpty) {
 | 
				
			||||||
 | 
					      headers['Authorization'] = 'Bearer $token';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final Response<dynamic> response = await get(
 | 
				
			||||||
 | 
					      'https://api.f0ck.me/item/$itemId',
 | 
				
			||||||
 | 
					      headers: headers,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (response.status.code == 200 && response.body is Map<String, dynamic>) {
 | 
				
			||||||
 | 
					      return MediaItem.fromJson(response.body as Map<String, dynamic>);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      if (!Get.isSnackbarOpen) {
 | 
				
			||||||
 | 
					        Get.snackbar('Fehler', 'Fehler beim Laden des Items');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      throw Exception('Fehler beim Laden des Items');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<List<Favorite>?> toggleFavorite(
 | 
					  Future<List<Favorite>?> toggleFavorite(
 | 
				
			||||||
    MediaItem item,
 | 
					    MediaItem item,
 | 
				
			||||||
    bool isFavorite,
 | 
					    bool isFavorite,
 | 
				
			||||||
  ) async {
 | 
					  ) async {
 | 
				
			||||||
    String? token = await storage.getString('token');
 | 
					    String? token = _authController.token.value;
 | 
				
			||||||
    if (token == null || token.isEmpty) return null;
 | 
					    if (token == null || token.isEmpty) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final Map<String, String> headers = {
 | 
					    final Map<String, String> headers = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum PageTransition { opacity, scale, slide, rotate, flip }
 | 
					enum PageTransition { opacity, scale, slide, rotate, flip }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,17 +11,17 @@ Widget buildAnimatedTransition({
 | 
				
			|||||||
  required Widget child,
 | 
					  required Widget child,
 | 
				
			||||||
  required PageController pageController,
 | 
					  required PageController pageController,
 | 
				
			||||||
  required int index,
 | 
					  required int index,
 | 
				
			||||||
  required MediaController controller,
 | 
					 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
 | 
					  final SettingsController settingsController = Get.find<SettingsController>();
 | 
				
			||||||
  final double value = pageController.position.haveDimensions
 | 
					  final double value = pageController.position.haveDimensions
 | 
				
			||||||
      ? pageController.page! - index
 | 
					      ? pageController.page! - index
 | 
				
			||||||
      : 0;
 | 
					      : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  switch (controller.transitionType.value) {
 | 
					  switch (settingsController.transitionType.value) {
 | 
				
			||||||
    case PageTransition.opacity:
 | 
					    case PageTransition.opacity:
 | 
				
			||||||
      return Opacity(
 | 
					      return Opacity(
 | 
				
			||||||
        opacity: Curves.easeOut.transform(1 - value.abs().clamp(0.0, 1.0)),
 | 
					        opacity: Curves.easeOut.transform(1 - value.abs().clamp(0.0, 1.0)),
 | 
				
			||||||
        child: Transform(transform: Matrix4.identity(), child: child),
 | 
					        child: child,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    case PageTransition.scale:
 | 
					    case PageTransition.scale:
 | 
				
			||||||
      return Transform.scale(
 | 
					      return Transform.scale(
 | 
				
			||||||
@@ -29,10 +31,7 @@ Widget buildAnimatedTransition({
 | 
				
			|||||||
        child: child,
 | 
					        child: child,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    case PageTransition.slide:
 | 
					    case PageTransition.slide:
 | 
				
			||||||
      return Transform.translate(
 | 
					      return child;
 | 
				
			||||||
        offset: Offset(300 * value.abs(), 0),
 | 
					 | 
				
			||||||
        child: child,
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    case PageTransition.rotate:
 | 
					    case PageTransition.rotate:
 | 
				
			||||||
      return Opacity(
 | 
					      return Opacity(
 | 
				
			||||||
        opacity: (1 - value.abs()).clamp(0.0, 1.0),
 | 
					        opacity: (1 - value.abs()).clamp(0.0, 1.0),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ class MediaTile extends StatelessWidget {
 | 
				
			|||||||
          fit: StackFit.expand,
 | 
					          fit: StackFit.expand,
 | 
				
			||||||
          children: [
 | 
					          children: [
 | 
				
			||||||
            CachedNetworkImage(
 | 
					            CachedNetworkImage(
 | 
				
			||||||
              imageUrl: 'https://f0ck.me/t/${item.id}.webp',
 | 
					              imageUrl: item.thumbnailUrl,
 | 
				
			||||||
              fit: BoxFit.cover,
 | 
					              fit: BoxFit.cover,
 | 
				
			||||||
              placeholder: (context, url) => Container(color: Colors.grey[900]),
 | 
					              placeholder: (context, url) => Container(color: Colors.grey[900]),
 | 
				
			||||||
              errorWidget: (context, url, error) =>
 | 
					              errorWidget: (context, url, error) =>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										62
									
								
								lib/widgets/tagsection.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								lib/widgets/tagsection.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/item.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/actiontag.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TagSection extends StatefulWidget {
 | 
				
			||||||
 | 
					  final List<Tag> tags;
 | 
				
			||||||
 | 
					  const TagSection({super.key, required this.tags});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<TagSection> createState() => _TagSectionState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _TagSectionState extends State<TagSection> {
 | 
				
			||||||
 | 
					  bool _areTagsExpanded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final MediaController mediaController = Get.find<MediaController>();
 | 
				
			||||||
 | 
					    final bool hasMoreTags = widget.tags.length > 5;
 | 
				
			||||||
 | 
					    final List<Tag> tagsToShow = _areTagsExpanded
 | 
				
			||||||
 | 
					        ? widget.tags
 | 
				
			||||||
 | 
					        : widget.tags.take(5).toList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Column(
 | 
				
			||||||
 | 
					      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(() => _areTagsExpanded = !_areTagsExpanded);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            child: Text(
 | 
				
			||||||
 | 
					              _areTagsExpanded
 | 
				
			||||||
 | 
					                  ? 'Weniger anzeigen'
 | 
				
			||||||
 | 
					                  : 'Alle ${widget.tags.length} Tags anzeigen',
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -94,81 +94,99 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        _ControlButton(
 | 
					        IconButton(
 | 
				
			||||||
          widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
					          icon: Icon(
 | 
				
			||||||
          () {
 | 
					            widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
				
			||||||
 | 
					            size: 64,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          onPressed: () {
 | 
				
			||||||
            widget.onOverlayTap();
 | 
					            widget.onOverlayTap();
 | 
				
			||||||
            widget.controller.value.isPlaying
 | 
					            widget.controller.value.isPlaying
 | 
				
			||||||
                ? widget.controller.pause()
 | 
					                ? widget.controller.pause()
 | 
				
			||||||
                : widget.controller.play();
 | 
					                : widget.controller.play();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          size: 64,
 | 
					 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        Positioned(
 | 
					        Positioned(
 | 
				
			||||||
          right: 12,
 | 
					          right: 12,
 | 
				
			||||||
          bottom: 12,
 | 
					          bottom: 12,
 | 
				
			||||||
          child: _ControlButton(
 | 
					          child: IconButton(
 | 
				
			||||||
            widget.muted ? Icons.volume_off : Icons.volume_up,
 | 
					            icon: Icon(
 | 
				
			||||||
            () {
 | 
					              widget.muted ? Icons.volume_off : Icons.volume_up,
 | 
				
			||||||
 | 
					              size: 16,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
              widget.onOverlayTap();
 | 
					              widget.onOverlayTap();
 | 
				
			||||||
              widget.onMuteToggle();
 | 
					              widget.onMuteToggle();
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            size: 16,
 | 
					 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        Align(
 | 
					        Align(
 | 
				
			||||||
          alignment: Alignment.bottomCenter,
 | 
					          alignment: Alignment.bottomCenter,
 | 
				
			||||||
          child: Padding(
 | 
					          child: Padding(
 | 
				
			||||||
            padding: const EdgeInsets.only(bottom: 0),
 | 
					            padding: const EdgeInsets.only(bottom: 0),
 | 
				
			||||||
            child: Stack(
 | 
					            child: LayoutBuilder(
 | 
				
			||||||
              clipBehavior: Clip.none,
 | 
					              builder: (context, constraints) {
 | 
				
			||||||
              children: [
 | 
					                return Stack(
 | 
				
			||||||
                Positioned(
 | 
					                  clipBehavior: Clip.none,
 | 
				
			||||||
                  left: 10,
 | 
					                  children: [
 | 
				
			||||||
                  bottom: 12,
 | 
					                    Positioned(
 | 
				
			||||||
                  child: Text(
 | 
					                      left: 10,
 | 
				
			||||||
                    '${_formatDuration(widget.controller.value.position)} / ${_formatDuration(widget.controller.value.duration)}',
 | 
					                      bottom: 12,
 | 
				
			||||||
                    style: const TextStyle(color: Colors.white, fontSize: 12),
 | 
					                      child: Text(
 | 
				
			||||||
                  ),
 | 
					                        '${_formatDuration(widget.controller.value.position)} / ${_formatDuration(widget.controller.value.duration)}',
 | 
				
			||||||
                ),
 | 
					                        style: const TextStyle(
 | 
				
			||||||
                Listener(
 | 
					                          color: Colors.white,
 | 
				
			||||||
                  onPointerDown: (_) {
 | 
					                          fontSize: 12,
 | 
				
			||||||
                    widget.onOverlayTap();
 | 
					                        ),
 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                  child: VideoProgressIndicator(
 | 
					 | 
				
			||||||
                    widget.controller,
 | 
					 | 
				
			||||||
                    allowScrubbing: true,
 | 
					 | 
				
			||||||
                    padding: const EdgeInsets.only(top: 25.0),
 | 
					 | 
				
			||||||
                    colors: const VideoProgressColors(
 | 
					 | 
				
			||||||
                      playedColor: Colors.red,
 | 
					 | 
				
			||||||
                      backgroundColor: Colors.grey,
 | 
					 | 
				
			||||||
                      bufferedColor: Colors.white54,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                if (widget.controller.value.duration.inMilliseconds > 0)
 | 
					 | 
				
			||||||
                  Positioned(
 | 
					 | 
				
			||||||
                    left:
 | 
					 | 
				
			||||||
                        (widget.controller.value.position.inMilliseconds /
 | 
					 | 
				
			||||||
                                widget
 | 
					 | 
				
			||||||
                                    .controller
 | 
					 | 
				
			||||||
                                    .value
 | 
					 | 
				
			||||||
                                    .duration
 | 
					 | 
				
			||||||
                                    .inMilliseconds) *
 | 
					 | 
				
			||||||
                            MediaQuery.of(context).size.width -
 | 
					 | 
				
			||||||
                        6,
 | 
					 | 
				
			||||||
                    bottom: -4,
 | 
					 | 
				
			||||||
                    child: Container(
 | 
					 | 
				
			||||||
                      width: 12,
 | 
					 | 
				
			||||||
                      height: 12,
 | 
					 | 
				
			||||||
                      decoration: BoxDecoration(
 | 
					 | 
				
			||||||
                        shape: BoxShape.circle,
 | 
					 | 
				
			||||||
                        color: Colors.red,
 | 
					 | 
				
			||||||
                        border: Border.all(color: Colors.red, width: 2),
 | 
					 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                    Listener(
 | 
				
			||||||
              ],
 | 
					                      onPointerDown: (_) {
 | 
				
			||||||
 | 
					                        widget.onOverlayTap();
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                      child: VideoProgressIndicator(
 | 
				
			||||||
 | 
					                        widget.controller,
 | 
				
			||||||
 | 
					                        allowScrubbing: true,
 | 
				
			||||||
 | 
					                        padding: const EdgeInsets.only(top: 25.0),
 | 
				
			||||||
 | 
					                        colors: VideoProgressColors(
 | 
				
			||||||
 | 
					                          playedColor: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
 | 
					                          backgroundColor: Theme.of(
 | 
				
			||||||
 | 
					                            context,
 | 
				
			||||||
 | 
					                          ).colorScheme.surface.withValues(alpha: 0.5),
 | 
				
			||||||
 | 
					                          bufferedColor: Theme.of(
 | 
				
			||||||
 | 
					                            context,
 | 
				
			||||||
 | 
					                          ).colorScheme.secondary.withValues(alpha: 0.5),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    if (widget.controller.value.duration.inMilliseconds > 0)
 | 
				
			||||||
 | 
					                      Positioned(
 | 
				
			||||||
 | 
					                        left:
 | 
				
			||||||
 | 
					                            (widget.controller.value.position.inMilliseconds /
 | 
				
			||||||
 | 
					                                    widget
 | 
				
			||||||
 | 
					                                        .controller
 | 
				
			||||||
 | 
					                                        .value
 | 
				
			||||||
 | 
					                                        .duration
 | 
				
			||||||
 | 
					                                        .inMilliseconds) *
 | 
				
			||||||
 | 
					                                constraints.maxWidth -
 | 
				
			||||||
 | 
					                            6,
 | 
				
			||||||
 | 
					                        bottom: -4,
 | 
				
			||||||
 | 
					                        child: Container(
 | 
				
			||||||
 | 
					                          width: 12,
 | 
				
			||||||
 | 
					                          height: 12,
 | 
				
			||||||
 | 
					                          decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                            shape: BoxShape.circle,
 | 
				
			||||||
 | 
					                            color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
 | 
					                            border: Border.all(
 | 
				
			||||||
 | 
					                              color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
 | 
					                              width: 2,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                          ),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
@@ -182,25 +200,3 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
    return "${twoDigits(duration.inMinutes % 60)}:${twoDigits(duration.inSeconds % 60)}";
 | 
					    return "${twoDigits(duration.inMinutes % 60)}:${twoDigits(duration.inSeconds % 60)}";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
class _ControlButton extends StatelessWidget {
 | 
					 | 
				
			||||||
  final IconData icon;
 | 
					 | 
				
			||||||
  final VoidCallback onPressed;
 | 
					 | 
				
			||||||
  final double size;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const _ControlButton(this.icon, this.onPressed, {this.size = 24});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					 | 
				
			||||||
    return Container(
 | 
					 | 
				
			||||||
      decoration: BoxDecoration(
 | 
					 | 
				
			||||||
        shape: BoxShape.circle,
 | 
					 | 
				
			||||||
        color: Colors.black.withValues(alpha: 0.4),
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
      child: IconButton(
 | 
					 | 
				
			||||||
        icon: Icon(icon, size: size),
 | 
					 | 
				
			||||||
        onPressed: onPressed,
 | 
					 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ import 'package:get/get.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/models/item.dart';
 | 
					import 'package:f0ckapp/models/item.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/video_controls_overlay.dart';
 | 
					import 'package:f0ckapp/widgets/video_controls_overlay.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
					import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VideoWidget extends StatefulWidget {
 | 
					class VideoWidget extends StatefulWidget {
 | 
				
			||||||
@@ -29,8 +30,9 @@ class VideoWidget extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _VideoWidgetState extends State<VideoWidget> {
 | 
					class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			||||||
  final MediaController controller = Get.find<MediaController>();
 | 
					  final MediaController mediaController = Get.find<MediaController>();
 | 
				
			||||||
  late CachedVideoPlayerPlusController _controller;
 | 
					  final SettingsController settingsController = Get.find<SettingsController>();
 | 
				
			||||||
 | 
					  late CachedVideoPlayerPlusController videoController;
 | 
				
			||||||
  late Worker _muteWorker;
 | 
					  late Worker _muteWorker;
 | 
				
			||||||
  late Worker _timerResetWorker;
 | 
					  late Worker _timerResetWorker;
 | 
				
			||||||
  late Worker _hideControlsWorker;
 | 
					  late Worker _hideControlsWorker;
 | 
				
			||||||
@@ -41,12 +43,14 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
    super.initState();
 | 
					    super.initState();
 | 
				
			||||||
    _initController();
 | 
					    _initController();
 | 
				
			||||||
    _muteWorker = ever(controller.muted, (bool muted) {
 | 
					    _muteWorker = ever(settingsController.muted, (bool muted) {
 | 
				
			||||||
      if (_controller.value.isInitialized) {
 | 
					      if (videoController.value.isInitialized) {
 | 
				
			||||||
        _controller.setVolume(muted ? 0.0 : 1.0);
 | 
					        videoController.setVolume(muted ? 0.0 : 1.0);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    _timerResetWorker = ever(controller.videoControlsTimerNotifier, (_) {
 | 
					    _timerResetWorker = ever(settingsController.videoControlsTimerNotifier, (
 | 
				
			||||||
 | 
					      _,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
      if (widget.isActive && mounted) {
 | 
					      if (widget.isActive && mounted) {
 | 
				
			||||||
        if (!_showControls) {
 | 
					        if (!_showControls) {
 | 
				
			||||||
          setState(() => _showControls = true);
 | 
					          setState(() => _showControls = true);
 | 
				
			||||||
@@ -54,7 +58,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
        _startHideControlsTimer();
 | 
					        _startHideControlsTimer();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    _hideControlsWorker = ever(controller.hideControlsNotifier, (_) {
 | 
					    _hideControlsWorker = ever(settingsController.hideControlsNotifier, (_) {
 | 
				
			||||||
      if (mounted && _showControls) {
 | 
					      if (mounted && _showControls) {
 | 
				
			||||||
        setState(() => _showControls = false);
 | 
					        setState(() => _showControls = false);
 | 
				
			||||||
        _hideControlsTimer?.cancel();
 | 
					        _hideControlsTimer?.cancel();
 | 
				
			||||||
@@ -63,20 +67,18 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _initController() async {
 | 
					  Future<void> _initController() async {
 | 
				
			||||||
    _controller = CachedVideoPlayerPlusController.networkUrl(
 | 
					    videoController = CachedVideoPlayerPlusController.networkUrl(
 | 
				
			||||||
      Uri.parse(widget.details.mediaUrl),
 | 
					      Uri.parse(widget.details.mediaUrl),
 | 
				
			||||||
      videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
 | 
					      videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    await _controller.initialize();
 | 
					    await videoController.initialize();
 | 
				
			||||||
    widget.onInitialized?.call();
 | 
					    widget.onInitialized?.call();
 | 
				
			||||||
    if (!mounted) return;
 | 
					    if (!mounted) return;
 | 
				
			||||||
    setState(() {});
 | 
					    videoController.setLooping(true);
 | 
				
			||||||
    _controller.addListener(() => setState(() {}));
 | 
					    videoController.setVolume(settingsController.muted.value ? 0.0 : 1.0);
 | 
				
			||||||
    _controller.setLooping(true);
 | 
					 | 
				
			||||||
    _controller.setVolume(controller.muted.value ? 0.0 : 1.0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (widget.isActive) {
 | 
					    if (widget.isActive) {
 | 
				
			||||||
      _controller.play();
 | 
					      videoController.play();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,9 +87,9 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
    super.didUpdateWidget(oldWidget);
 | 
					    super.didUpdateWidget(oldWidget);
 | 
				
			||||||
    if (widget.isActive != oldWidget.isActive) {
 | 
					    if (widget.isActive != oldWidget.isActive) {
 | 
				
			||||||
      if (widget.isActive) {
 | 
					      if (widget.isActive) {
 | 
				
			||||||
        _controller.play();
 | 
					        videoController.play();
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        _controller.pause();
 | 
					        videoController.pause();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -97,7 +99,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
    _muteWorker.dispose();
 | 
					    _muteWorker.dispose();
 | 
				
			||||||
    _timerResetWorker.dispose();
 | 
					    _timerResetWorker.dispose();
 | 
				
			||||||
    _hideControlsWorker.dispose();
 | 
					    _hideControlsWorker.dispose();
 | 
				
			||||||
    _controller.dispose();
 | 
					    videoController.dispose();
 | 
				
			||||||
    _hideControlsTimer?.cancel();
 | 
					    _hideControlsTimer?.cancel();
 | 
				
			||||||
    super.dispose();
 | 
					    super.dispose();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -129,7 +131,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    final bool muted = controller.muted.value;
 | 
					    final bool muted = settingsController.muted.value;
 | 
				
			||||||
    bool isAudio = widget.details.mime.startsWith('audio');
 | 
					    bool isAudio = widget.details.mime.startsWith('audio');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Widget mediaContent;
 | 
					    Widget mediaContent;
 | 
				
			||||||
@@ -144,36 +146,43 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      mediaContent = _controller.value.isInitialized
 | 
					      mediaContent = videoController.value.isInitialized
 | 
				
			||||||
          ? CachedVideoPlayerPlus(_controller)
 | 
					          ? CachedVideoPlayerPlus(videoController)
 | 
				
			||||||
          : const Center(child: CircularProgressIndicator());
 | 
					          : const Center(child: CircularProgressIndicator());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return AspectRatio(
 | 
					    return AspectRatio(
 | 
				
			||||||
      aspectRatio: _controller.value.isInitialized
 | 
					      aspectRatio: videoController.value.isInitialized
 | 
				
			||||||
          ? _controller.value.aspectRatio
 | 
					          ? videoController.value.aspectRatio
 | 
				
			||||||
          : (isAudio ? 16 / 9 : 9 / 16),
 | 
					          : (isAudio ? 16 / 9 : 9 / 16),
 | 
				
			||||||
      child: Stack(
 | 
					      child: Stack(
 | 
				
			||||||
        alignment: Alignment.center,
 | 
					        alignment: Alignment.center,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          GestureDetector(onTap: _onTap, child: mediaContent),
 | 
					          GestureDetector(onTap: _onTap, child: mediaContent),
 | 
				
			||||||
          if (_controller.value.isInitialized && _showControls)
 | 
					          AnimatedBuilder(
 | 
				
			||||||
            Positioned.fill(
 | 
					            animation: videoController,
 | 
				
			||||||
              child: GestureDetector(
 | 
					            builder: (context, child) {
 | 
				
			||||||
                onTap: _onTap,
 | 
					              if (videoController.value.isInitialized && _showControls) {
 | 
				
			||||||
                child: Container(
 | 
					                return Positioned.fill(
 | 
				
			||||||
                  color: Colors.black.withValues(alpha: 0.5),
 | 
					                  child: GestureDetector(
 | 
				
			||||||
                  child: VideoControlsOverlay(
 | 
					                    onTap: _onTap,
 | 
				
			||||||
                    controller: _controller,
 | 
					                    child: Container(
 | 
				
			||||||
                    onOverlayTap: () => _onTap(ctrlButton: true),
 | 
					                      color: Colors.black.withValues(alpha: 0.5),
 | 
				
			||||||
                    muted: muted,
 | 
					                      child: VideoControlsOverlay(
 | 
				
			||||||
                    onMuteToggle: () {
 | 
					                        controller: videoController,
 | 
				
			||||||
                      controller.toggleMuted();
 | 
					                        onOverlayTap: () => _onTap(ctrlButton: true),
 | 
				
			||||||
                    },
 | 
					                        muted: muted,
 | 
				
			||||||
 | 
					                        onMuteToggle: () {
 | 
				
			||||||
 | 
					                          settingsController.toggleMuted();
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                );
 | 
				
			||||||
              ),
 | 
					              }
 | 
				
			||||||
            ),
 | 
					              return const SizedBox.shrink();
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.3+64
 | 
					version: 1.4.4+65
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.9.0-100.2.beta
 | 
					  sdk: ^3.9.0-100.2.beta
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user