This commit is contained in:
		@@ -7,6 +7,7 @@ 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:pullex/pullex.dart';
 | 
				
			||||||
import 'package:share_plus/share_plus.dart';
 | 
					import 'package:share_plus/share_plus.dart';
 | 
				
			||||||
 | 
					import 'package:timeago/timeago.dart' as timeago;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/services/api.dart';
 | 
					import 'package:f0ckapp/services/api.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/tagfooter.dart';
 | 
					import 'package:f0ckapp/widgets/tagfooter.dart';
 | 
				
			||||||
@@ -43,8 +44,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  bool _isLoading = true;
 | 
					  bool _isLoading = true;
 | 
				
			||||||
  bool _itemNotFound = false;
 | 
					  bool _itemNotFound = false;
 | 
				
			||||||
  final Set<int> _readyItemIds = {};
 | 
					  final RxSet<int> _readyItemIds = <int>{}.obs;
 | 
				
			||||||
  final Map<int, bool> _showFavoriteAnimation = {};
 | 
					  final Rxn<int> _animatingFavoriteId = Rxn<int>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final List<PopupMenuEntry<ShareAction>> _shareMenuItems = const [
 | 
					  final List<PopupMenuEntry<ShareAction>> _shareMenuItems = const [
 | 
				
			||||||
    PopupMenuItem(
 | 
					    PopupMenuItem(
 | 
				
			||||||
@@ -67,6 +68,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
    super.initState();
 | 
					    super.initState();
 | 
				
			||||||
 | 
					    timeago.setLocaleMessages('de', timeago.DeMessages());
 | 
				
			||||||
    _loadInitialItem();
 | 
					    _loadInitialItem();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,7 +141,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
      _currentIndex.value = idx;
 | 
					      _currentIndex.value = idx;
 | 
				
			||||||
      final MediaItem item = mediaController.items[idx];
 | 
					      final MediaItem item = mediaController.items[idx];
 | 
				
			||||||
      if (item.mime.startsWith('image/') && !_readyItemIds.contains(item.id)) {
 | 
					      if (item.mime.startsWith('image/') && !_readyItemIds.contains(item.id)) {
 | 
				
			||||||
        setState(() => _readyItemIds.add(item.id));
 | 
					        _readyItemIds.add(item.id);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -271,11 +273,10 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
      mediaController.items.refresh();
 | 
					      mediaController.items.refresh();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!mounted) return;
 | 
					    _animatingFavoriteId.value = item.id;
 | 
				
			||||||
    setState(() => _showFavoriteAnimation[item.id] = true);
 | 
					 | 
				
			||||||
    Future.delayed(const Duration(milliseconds: 700), () {
 | 
					    Future.delayed(const Duration(milliseconds: 700), () {
 | 
				
			||||||
      if (mounted) {
 | 
					      if (_animatingFavoriteId.value == item.id) {
 | 
				
			||||||
        setState(() => _showFavoriteAnimation[item.id] = false);
 | 
					        _animatingFavoriteId.value = null;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -287,15 +288,12 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
        false;
 | 
					        false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (item.mime.startsWith('image/')) {
 | 
					    if (item.mime.startsWith('image/')) {
 | 
				
			||||||
      mediaWidget = GestureDetector(
 | 
					      mediaWidget = CachedNetworkImage(
 | 
				
			||||||
        onDoubleTap: () => _handleFavoriteToggle(item, isFavorite),
 | 
					 | 
				
			||||||
        child: CachedNetworkImage(
 | 
					 | 
				
			||||||
        imageUrl: item.mediaUrl,
 | 
					        imageUrl: item.mediaUrl,
 | 
				
			||||||
        fit: BoxFit.contain,
 | 
					        fit: BoxFit.contain,
 | 
				
			||||||
        placeholder: (context, url) =>
 | 
					        placeholder: (context, url) =>
 | 
				
			||||||
            const Center(child: CircularProgressIndicator()),
 | 
					            const Center(child: CircularProgressIndicator()),
 | 
				
			||||||
        errorWidget: (c, e, s) => const Icon(Icons.broken_image, size: 100),
 | 
					        errorWidget: (c, e, s) => const Icon(Icons.broken_image, size: 100),
 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else if (item.mime.startsWith('video/') ||
 | 
					    } else if (item.mime.startsWith('video/') ||
 | 
				
			||||||
        item.mime.startsWith('audio/')) {
 | 
					        item.mime.startsWith('audio/')) {
 | 
				
			||||||
@@ -309,10 +307,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
        isActive: isActive,
 | 
					        isActive: isActive,
 | 
				
			||||||
        onInitialized: () {
 | 
					        onInitialized: () {
 | 
				
			||||||
          if (mounted && !_readyItemIds.contains(item.id)) {
 | 
					          if (mounted && !_readyItemIds.contains(item.id)) {
 | 
				
			||||||
            setState(() => _readyItemIds.add(item.id));
 | 
					            _readyItemIds.add(item.id);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onDoubleTap: () => _handleFavoriteToggle(item, isFavorite),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      mediaWidget = const Icon(Icons.help_outline, size: 100);
 | 
					      mediaWidget = const Icon(Icons.help_outline, size: 100);
 | 
				
			||||||
@@ -323,12 +320,17 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
      child: Stack(
 | 
					      child: Stack(
 | 
				
			||||||
        alignment: Alignment.center,
 | 
					        alignment: Alignment.center,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          mediaWidget,
 | 
					          GestureDetector(
 | 
				
			||||||
          AnimatedOpacity(
 | 
					            onDoubleTap: () => _handleFavoriteToggle(item, isFavorite),
 | 
				
			||||||
            opacity: _showFavoriteAnimation[item.id] ?? false ? 1.0 : 0.0,
 | 
					            child: mediaWidget,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          Obx(() {
 | 
				
			||||||
 | 
					            final showAnimation = _animatingFavoriteId.value == item.id;
 | 
				
			||||||
 | 
					            return AnimatedOpacity(
 | 
				
			||||||
 | 
					              opacity: showAnimation ? 1.0 : 0.0,
 | 
				
			||||||
              duration: const Duration(milliseconds: 200),
 | 
					              duration: const Duration(milliseconds: 200),
 | 
				
			||||||
              child: AnimatedScale(
 | 
					              child: AnimatedScale(
 | 
				
			||||||
              scale: _showFavoriteAnimation[item.id] ?? false ? 1.0 : 0.5,
 | 
					                scale: showAnimation ? 1.0 : 0.5,
 | 
				
			||||||
                duration: const Duration(milliseconds: 400),
 | 
					                duration: const Duration(milliseconds: 400),
 | 
				
			||||||
                curve: Curves.easeOutBack,
 | 
					                curve: Curves.easeOutBack,
 | 
				
			||||||
                child: Icon(
 | 
					                child: Icon(
 | 
				
			||||||
@@ -337,44 +339,44 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
                  size: 100,
 | 
					                  size: 100,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
          ),
 | 
					            );
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  PreferredSizeWidget _buildAppBar(BuildContext context) {
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					    return AppBar(
 | 
				
			||||||
 | 
					      title: Obx(() {
 | 
				
			||||||
        if (_isLoading) {
 | 
					        if (_isLoading) {
 | 
				
			||||||
      return Scaffold(
 | 
					          return Text('Lade f0ck #${widget.initialId}...');
 | 
				
			||||||
        appBar: AppBar(title: Text('Lade f0ck #${widget.initialId}...')),
 | 
					 | 
				
			||||||
        body: const Center(child: CircularProgressIndicator()),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (_itemNotFound ||
 | 
				
			||||||
    if (_itemNotFound) {
 | 
					            mediaController.items.isEmpty ||
 | 
				
			||||||
      return Scaffold(
 | 
					 | 
				
			||||||
        appBar: AppBar(title: const Text('Fehler')),
 | 
					 | 
				
			||||||
        body: const Center(child: Text('f0ck nicht gefunden.')),
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Obx(() {
 | 
					 | 
				
			||||||
      if (mediaController.items.isEmpty ||
 | 
					 | 
				
			||||||
            _currentIndex.value >= mediaController.items.length) {
 | 
					            _currentIndex.value >= mediaController.items.length) {
 | 
				
			||||||
        return Scaffold(
 | 
					          return const Text('Fehler');
 | 
				
			||||||
          appBar: AppBar(title: const Text('Fehler')),
 | 
					 | 
				
			||||||
          body: const Center(child: Text('Keine Items zum Anzeigen.')),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      final MediaItem currentItem = mediaController.items[_currentIndex.value];
 | 
					        final MediaItem currentItem =
 | 
				
			||||||
      return Scaffold(
 | 
					            mediaController.items[_currentIndex.value];
 | 
				
			||||||
        endDrawer: const EndDrawer(),
 | 
					        return Text('f0ck #${currentItem.id}');
 | 
				
			||||||
        endDrawerEnableOpenDragGesture:
 | 
					      }),
 | 
				
			||||||
            settingsController.drawerSwipeEnabled.value,
 | 
					 | 
				
			||||||
        appBar: AppBar(
 | 
					 | 
				
			||||||
          title: Text('f0ck #${currentItem.id}'),
 | 
					 | 
				
			||||||
      actions: [
 | 
					      actions: [
 | 
				
			||||||
 | 
					        Obx(() {
 | 
				
			||||||
 | 
					          final bool showActions =
 | 
				
			||||||
 | 
					              !_isLoading &&
 | 
				
			||||||
 | 
					              !_itemNotFound &&
 | 
				
			||||||
 | 
					              mediaController.items.isNotEmpty &&
 | 
				
			||||||
 | 
					              _currentIndex.value < mediaController.items.length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (!showActions) {
 | 
				
			||||||
 | 
					            return const SizedBox.shrink();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          final MediaItem currentItem =
 | 
				
			||||||
 | 
					              mediaController.items[_currentIndex.value];
 | 
				
			||||||
 | 
					          return Row(
 | 
				
			||||||
 | 
					            mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
              IconButton(
 | 
					              IconButton(
 | 
				
			||||||
                icon: const Icon(Icons.fullscreen),
 | 
					                icon: const Icon(Icons.fullscreen),
 | 
				
			||||||
                onPressed: () => _handleFullScreen(currentItem),
 | 
					                onPressed: () => _handleFullScreen(currentItem),
 | 
				
			||||||
@@ -388,6 +390,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
                itemBuilder: (context) => _shareMenuItems,
 | 
					                itemBuilder: (context) => _shareMenuItems,
 | 
				
			||||||
                icon: const Icon(Icons.share),
 | 
					                icon: const Icon(Icons.share),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
        Builder(
 | 
					        Builder(
 | 
				
			||||||
          builder: (context) => IconButton(
 | 
					          builder: (context) => IconButton(
 | 
				
			||||||
            icon: const Icon(Icons.menu),
 | 
					            icon: const Icon(Icons.menu),
 | 
				
			||||||
@@ -395,22 +400,37 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
        ),
 | 
					    );
 | 
				
			||||||
        body: Stack(
 | 
					  }
 | 
				
			||||||
          children: [
 | 
					
 | 
				
			||||||
            PageView.builder(
 | 
					  Widget _buildBody(BuildContext context) {
 | 
				
			||||||
 | 
					    if (_isLoading) {
 | 
				
			||||||
 | 
					      return const Center(child: CircularProgressIndicator());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (_itemNotFound) {
 | 
				
			||||||
 | 
					      return const Center(child: Text('f0ck nicht gefunden.'));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Obx(() {
 | 
				
			||||||
 | 
					      if (mediaController.items.isEmpty) {
 | 
				
			||||||
 | 
					        return const Center(child: Text('Keine Items zum Anzeigen.'));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return PageView.builder(
 | 
				
			||||||
        controller: _pageController!,
 | 
					        controller: _pageController!,
 | 
				
			||||||
        itemCount: mediaController.items.length,
 | 
					        itemCount: mediaController.items.length,
 | 
				
			||||||
        onPageChanged: _onPageChanged,
 | 
					        onPageChanged: _onPageChanged,
 | 
				
			||||||
        itemBuilder: (context, index) {
 | 
					        itemBuilder: (context, index) {
 | 
				
			||||||
 | 
					          if (index >= mediaController.items.length) {
 | 
				
			||||||
 | 
					            return const SizedBox.shrink();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          final MediaItem item = mediaController.items[index];
 | 
					          final MediaItem item = mediaController.items[index];
 | 
				
			||||||
                final bool isReady = _readyItemIds.contains(item.id);
 | 
					          final PullexRefreshController refreshController = _refreshControllers
 | 
				
			||||||
                final PullexRefreshController refreshController =
 | 
					              .putIfAbsent(item.id, () => PullexRefreshController());
 | 
				
			||||||
                    _refreshControllers.putIfAbsent(
 | 
					 | 
				
			||||||
                      item.id,
 | 
					 | 
				
			||||||
                      () => PullexRefreshController(),
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return Obx(() {
 | 
				
			||||||
 | 
					            final bool isReady = _readyItemIds.contains(item.id);
 | 
				
			||||||
            return PullexRefresh(
 | 
					            return PullexRefresh(
 | 
				
			||||||
              onRefresh: () => _onRefresh(item.id, refreshController),
 | 
					              onRefresh: () => _onRefresh(item.id, refreshController),
 | 
				
			||||||
              header: const WaterDropHeader(),
 | 
					              header: const WaterDropHeader(),
 | 
				
			||||||
@@ -429,20 +449,52 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
                        );
 | 
					                        );
 | 
				
			||||||
                      },
 | 
					                      },
 | 
				
			||||||
                      child: Obx(
 | 
					                      child: Obx(
 | 
				
			||||||
                            () =>
 | 
					                        () => _buildMedia(item, index == _currentIndex.value),
 | 
				
			||||||
                                _buildMedia(item, index == _currentIndex.value),
 | 
					 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                  if (isReady)
 | 
					                  if (isReady)
 | 
				
			||||||
                        SliverFillRemaining(
 | 
					                    SliverToBoxAdapter(
 | 
				
			||||||
                          hasScrollBody: false,
 | 
					 | 
				
			||||||
                          fillOverscroll: true,
 | 
					 | 
				
			||||||
                      child: Padding(
 | 
					                      child: Padding(
 | 
				
			||||||
                        padding: const EdgeInsets.all(16.0),
 | 
					                        padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
                        child: Column(
 | 
					                        child: Column(
 | 
				
			||||||
                          crossAxisAlignment: CrossAxisAlignment.center,
 | 
					                          crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
                              children: [TagSection(tags: item.tags ?? [])],
 | 
					                          children: [
 | 
				
			||||||
 | 
					                            TagSection(tags: item.tags ?? []),
 | 
				
			||||||
 | 
					                            Obx(() {
 | 
				
			||||||
 | 
					                              if (!authController.isLoggedIn) {
 | 
				
			||||||
 | 
					                                return const SizedBox.shrink();
 | 
				
			||||||
 | 
					                              }
 | 
				
			||||||
 | 
					                              final TextStyle? infoTextStyle = Theme.of(
 | 
				
			||||||
 | 
					                                context,
 | 
				
			||||||
 | 
					                              ).textTheme.bodySmall;
 | 
				
			||||||
 | 
					                              return Padding(
 | 
				
			||||||
 | 
					                                padding: const EdgeInsets.only(top: 24.0),
 | 
				
			||||||
 | 
					                                child: Column(
 | 
				
			||||||
 | 
					                                  children: [
 | 
				
			||||||
 | 
					                                    FavoriteSection(item: item, index: index),
 | 
				
			||||||
 | 
					                                    const SizedBox(height: 16),
 | 
				
			||||||
 | 
					                                    Text(
 | 
				
			||||||
 | 
					                                      "Dateigröße: ${(item.size / 1024).toStringAsFixed(1)} KB",
 | 
				
			||||||
 | 
					                                      style: infoTextStyle,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                    Text(
 | 
				
			||||||
 | 
					                                      "Typ: ${item.mime}",
 | 
				
			||||||
 | 
					                                      style: infoTextStyle,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                    Text(
 | 
				
			||||||
 | 
					                                      "ID: ${item.id}",
 | 
				
			||||||
 | 
					                                      style: infoTextStyle,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                    Text(
 | 
				
			||||||
 | 
					                                      "Hochgeladen: ${timeago.format(DateTime.fromMillisecondsSinceEpoch(item.stamp * 1000), locale: 'de')}",
 | 
				
			||||||
 | 
					                                      style: infoTextStyle,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  ],
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            }),
 | 
				
			||||||
 | 
					                          ],
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
@@ -452,58 +504,23 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            Obx(() {
 | 
					 | 
				
			||||||
              if (!authController.isLoggedIn) {
 | 
					 | 
				
			||||||
                return const SizedBox.shrink();
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              final MediaItem currentItem =
 | 
					 | 
				
			||||||
                  mediaController.items[_currentIndex.value];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              final bool hasSoftButtons =
 | 
					 | 
				
			||||||
                  MediaQuery.of(context).padding.bottom > 24.0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
              return DraggableScrollableSheet(
 | 
					 | 
				
			||||||
                initialChildSize: hasSoftButtons ? 0.11 : 0.2,
 | 
					 | 
				
			||||||
                minChildSize: hasSoftButtons ? 0.11 : 0.2,
 | 
					 | 
				
			||||||
                maxChildSize: hasSoftButtons ? 0.245 : 0.2,
 | 
					 | 
				
			||||||
                snap: true,
 | 
					 | 
				
			||||||
                builder: (context, scrollController) => ListView(
 | 
					 | 
				
			||||||
                  controller: scrollController,
 | 
					 | 
				
			||||||
                  padding: const EdgeInsets.only(left: 16, right: 16),
 | 
					 | 
				
			||||||
                  children: [
 | 
					 | 
				
			||||||
                    FavoriteSection(
 | 
					 | 
				
			||||||
                      item: currentItem,
 | 
					 | 
				
			||||||
                      index: _currentIndex.value,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    const SizedBox(height: 16),
 | 
					 | 
				
			||||||
                    Text(
 | 
					 | 
				
			||||||
                      "Dateigröße: ${(currentItem.size / 1024).toStringAsFixed(1)} KB",
 | 
					 | 
				
			||||||
                      style: Theme.of(context).textTheme.bodySmall,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    Text(
 | 
					 | 
				
			||||||
                      "Typ: ${currentItem.mime}",
 | 
					 | 
				
			||||||
                      style: Theme.of(context).textTheme.bodySmall,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    Text(
 | 
					 | 
				
			||||||
                      "ID: ${currentItem.id}",
 | 
					 | 
				
			||||||
                      style: Theme.of(context).textTheme.bodySmall,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                    Text(
 | 
					 | 
				
			||||||
                      "Hochgeladen am: ${DateTime.fromMillisecondsSinceEpoch(currentItem.stamp * 1000)}",
 | 
					 | 
				
			||||||
                      style: Theme.of(context).textTheme.bodySmall,
 | 
					 | 
				
			||||||
                    ),
 | 
					 | 
				
			||||||
                  ],
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
            }),
 | 
					    });
 | 
				
			||||||
          ],
 | 
					  }
 | 
				
			||||||
        ),
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      endDrawer: const EndDrawer(),
 | 
				
			||||||
 | 
					      endDrawerEnableOpenDragGesture:
 | 
				
			||||||
 | 
					          settingsController.drawerSwipeEnabled.value,
 | 
				
			||||||
 | 
					      appBar: _buildAppBar(context),
 | 
				
			||||||
 | 
					      body: _buildBody(context),
 | 
				
			||||||
      persistentFooterButtons: mediaController.tag.value != null
 | 
					      persistentFooterButtons: mediaController.tag.value != null
 | 
				
			||||||
          ? [TagFooter()]
 | 
					          ? [TagFooter()]
 | 
				
			||||||
          : null,
 | 
					          : null,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
 | 
					import 'package:cached_video_player_plus/cached_video_player_plus.dart';
 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
					import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VideoControlsOverlay extends StatefulWidget {
 | 
					class VideoControlsOverlay extends StatefulWidget {
 | 
				
			||||||
  final CachedVideoPlayerPlusController controller;
 | 
					  final CachedVideoPlayerPlusController controller;
 | 
				
			||||||
  final VoidCallback? onDoubleTap;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const VideoControlsOverlay({
 | 
					  const VideoControlsOverlay({
 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
    required this.controller,
 | 
					    required this.controller,
 | 
				
			||||||
    this.onDoubleTap,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -121,22 +122,23 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return GestureDetector(
 | 
					    return Stack(
 | 
				
			||||||
      behavior: HitTestBehavior.opaque,
 | 
					      children: [
 | 
				
			||||||
 | 
					        Positioned.fill(
 | 
				
			||||||
 | 
					          child: GestureDetector(
 | 
				
			||||||
            onTap: _toggleControlsVisibility,
 | 
					            onTap: _toggleControlsVisibility,
 | 
				
			||||||
            onHorizontalDragStart: _controlsVisible ? _onHorizontalDragStart : null,
 | 
					            onHorizontalDragStart: _controlsVisible ? _onHorizontalDragStart : null,
 | 
				
			||||||
            onHorizontalDragUpdate: _controlsVisible ? _onHorizontalDragUpdate : null,
 | 
					            onHorizontalDragUpdate: _controlsVisible ? _onHorizontalDragUpdate : null,
 | 
				
			||||||
            onHorizontalDragEnd: _controlsVisible ? _onHorizontalDragEnd : null,
 | 
					            onHorizontalDragEnd: _controlsVisible ? _onHorizontalDragEnd : null,
 | 
				
			||||||
            onHorizontalDragCancel: _controlsVisible ? _onHorizontalDragCancel : null,
 | 
					            onHorizontalDragCancel: _controlsVisible ? _onHorizontalDragCancel : null,
 | 
				
			||||||
      child: Stack(
 | 
					            child: AnimatedContainer(
 | 
				
			||||||
        alignment: Alignment.center,
 | 
					 | 
				
			||||||
        children: [
 | 
					 | 
				
			||||||
          AnimatedContainer(
 | 
					 | 
				
			||||||
              duration: const Duration(milliseconds: 300),
 | 
					              duration: const Duration(milliseconds: 300),
 | 
				
			||||||
              color: _controlsVisible && !_isScrubbing
 | 
					              color: _controlsVisible && !_isScrubbing
 | 
				
			||||||
                  ? Colors.black.withValues(alpha: 0.5)
 | 
					                  ? Colors.black.withValues(alpha: 0.5)
 | 
				
			||||||
                  : Colors.transparent,
 | 
					                  : Colors.transparent,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
        AnimatedOpacity(
 | 
					        AnimatedOpacity(
 | 
				
			||||||
          opacity: _isScrubbing ? 1.0 : 0.0,
 | 
					          opacity: _isScrubbing ? 1.0 : 0.0,
 | 
				
			||||||
          duration: const Duration(milliseconds: 200),
 | 
					          duration: const Duration(milliseconds: 200),
 | 
				
			||||||
@@ -151,7 +153,6 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      ),
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -192,7 +193,7 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  Widget _buildBottomBar() {
 | 
					  Widget _buildBottomBar() {
 | 
				
			||||||
    return Padding(
 | 
					    return Padding(
 | 
				
			||||||
      padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
 | 
					      padding: const EdgeInsets.fromLTRB(12, 0, 12, 5),
 | 
				
			||||||
      child: Row(
 | 
					      child: Row(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          IconButton(
 | 
					          IconButton(
 | 
				
			||||||
@@ -201,7 +202,7 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
                  ? Icons.pause
 | 
					                  ? Icons.pause
 | 
				
			||||||
                  : Icons.play_arrow,
 | 
					                  : Icons.play_arrow,
 | 
				
			||||||
              color: Theme.of(context).colorScheme.primary,
 | 
					              color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
              size: 20,
 | 
					              size: 24,
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            onPressed: _handlePlayPause,
 | 
					            onPressed: _handlePlayPause,
 | 
				
			||||||
            constraints: const BoxConstraints(),
 | 
					            constraints: const BoxConstraints(),
 | 
				
			||||||
@@ -243,7 +244,7 @@ class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
				
			|||||||
                    ? Icons.volume_off
 | 
					                    ? Icons.volume_off
 | 
				
			||||||
                    : Icons.volume_up,
 | 
					                    : Icons.volume_up,
 | 
				
			||||||
                color: Theme.of(context).colorScheme.primary,
 | 
					                color: Theme.of(context).colorScheme.primary,
 | 
				
			||||||
                size: 20,
 | 
					                size: 24,
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
              onPressed: () {
 | 
					              onPressed: () {
 | 
				
			||||||
                _settingsController.toggleMuted();
 | 
					                _settingsController.toggleMuted();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,6 @@ class VideoWidget extends StatefulWidget {
 | 
				
			|||||||
  final bool fullScreen;
 | 
					  final bool fullScreen;
 | 
				
			||||||
  final VoidCallback? onInitialized;
 | 
					  final VoidCallback? onInitialized;
 | 
				
			||||||
  final Duration? initialPosition;
 | 
					  final Duration? initialPosition;
 | 
				
			||||||
  final VoidCallback? onDoubleTap;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const VideoWidget({
 | 
					  const VideoWidget({
 | 
				
			||||||
    super.key,
 | 
					    super.key,
 | 
				
			||||||
@@ -26,7 +25,6 @@ class VideoWidget extends StatefulWidget {
 | 
				
			|||||||
    this.fullScreen = false,
 | 
					    this.fullScreen = false,
 | 
				
			||||||
    this.onInitialized,
 | 
					    this.onInitialized,
 | 
				
			||||||
    this.initialPosition,
 | 
					    this.initialPosition,
 | 
				
			||||||
    this.onDoubleTap,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -58,7 +56,6 @@ class VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
    await videoController.initialize();
 | 
					    await videoController.initialize();
 | 
				
			||||||
    if (!mounted) return;
 | 
					    if (!mounted) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Rebuild the widget to reflect the initialized state
 | 
					 | 
				
			||||||
    setState(() {});
 | 
					    setState(() {});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (widget.initialPosition != null) {
 | 
					    if (widget.initialPosition != null) {
 | 
				
			||||||
@@ -133,10 +130,7 @@ class VideoWidgetState extends State<VideoWidget> {
 | 
				
			|||||||
          mediaContent,
 | 
					          mediaContent,
 | 
				
			||||||
          if (isInitialized)
 | 
					          if (isInitialized)
 | 
				
			||||||
            Positioned.fill(
 | 
					            Positioned.fill(
 | 
				
			||||||
              child: VideoControlsOverlay(
 | 
					              child: VideoControlsOverlay(controller: videoController),
 | 
				
			||||||
                controller: videoController,
 | 
					 | 
				
			||||||
                onDoubleTap: widget.onDoubleTap,
 | 
					 | 
				
			||||||
              ),
 | 
					 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -264,6 +264,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "4.1.2"
 | 
					    version: "4.1.2"
 | 
				
			||||||
 | 
					  intl:
 | 
				
			||||||
 | 
					    dependency: transitive
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: intl
 | 
				
			||||||
 | 
					      sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "0.20.2"
 | 
				
			||||||
  js:
 | 
					  js:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
@@ -661,6 +669,14 @@ packages:
 | 
				
			|||||||
      url: "https://pub.dev"
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
    source: hosted
 | 
					    source: hosted
 | 
				
			||||||
    version: "0.7.6"
 | 
					    version: "0.7.6"
 | 
				
			||||||
 | 
					  timeago:
 | 
				
			||||||
 | 
					    dependency: "direct main"
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      name: timeago
 | 
				
			||||||
 | 
					      sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e
 | 
				
			||||||
 | 
					      url: "https://pub.dev"
 | 
				
			||||||
 | 
					    source: hosted
 | 
				
			||||||
 | 
					    version: "3.7.1"
 | 
				
			||||||
  typed_data:
 | 
					  typed_data:
 | 
				
			||||||
    dependency: transitive
 | 
					    dependency: transitive
 | 
				
			||||||
    description:
 | 
					    description:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.8+69
 | 
					version: 1.4.9+70
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.9.0-100.2.beta
 | 
					  sdk: ^3.9.0-100.2.beta
 | 
				
			||||||
@@ -37,6 +37,7 @@ dependencies:
 | 
				
			|||||||
  share_plus: ^11.0.0
 | 
					  share_plus: ^11.0.0
 | 
				
			||||||
  flutter_cache_manager: ^3.4.1
 | 
					  flutter_cache_manager: ^3.4.1
 | 
				
			||||||
  pullex: ^1.0.0
 | 
					  pullex: ^1.0.0
 | 
				
			||||||
 | 
					  timeago: ^3.7.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dev_dependencies:
 | 
					dev_dependencies:
 | 
				
			||||||
  flutter_test:
 | 
					  flutter_test:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user