This commit is contained in:
		@@ -4,14 +4,14 @@ import 'package:get/get.dart';
 | 
			
		||||
import 'package:encrypt_shared_preferences/provider.dart';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/models/user.dart';
 | 
			
		||||
 | 
			
		||||
class AuthController extends GetxController {
 | 
			
		||||
  final EncryptedSharedPreferencesAsync storage =
 | 
			
		||||
      EncryptedSharedPreferencesAsync.getInstance();
 | 
			
		||||
 | 
			
		||||
  RxnString token = RxnString();
 | 
			
		||||
  RxnInt userId = RxnInt();
 | 
			
		||||
  RxnString avatarUrl = RxnString();
 | 
			
		||||
  RxnString username = RxnString();
 | 
			
		||||
  Rxn<User> user = Rxn<User>();
 | 
			
		||||
  RxBool isLoading = false.obs;
 | 
			
		||||
  RxnString error = RxnString();
 | 
			
		||||
 | 
			
		||||
@@ -47,9 +47,7 @@ class AuthController extends GetxController {
 | 
			
		||||
      } catch (_) {}
 | 
			
		||||
    }
 | 
			
		||||
    token.value = null;
 | 
			
		||||
    userId.value = null;
 | 
			
		||||
    avatarUrl.value = null;
 | 
			
		||||
    username.value = null;
 | 
			
		||||
    user.value = null;
 | 
			
		||||
    await storage.remove('token');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -66,11 +64,7 @@ class AuthController extends GetxController {
 | 
			
		||||
        final dynamic data = json.decode(response.body);
 | 
			
		||||
        if (data['token'] != null) {
 | 
			
		||||
          await saveToken(data['token']);
 | 
			
		||||
          userId.value = data['userid'];
 | 
			
		||||
          avatarUrl.value = data['avatar'] != null
 | 
			
		||||
              ? 'https://f0ck.me/t/${data['avatar']}.webp'
 | 
			
		||||
              : null;
 | 
			
		||||
          this.username.value = data['user'];
 | 
			
		||||
          user.value = User.fromJson(data);
 | 
			
		||||
          return true;
 | 
			
		||||
        } else {
 | 
			
		||||
          error.value = 'Kein Token erhalten';
 | 
			
		||||
@@ -95,13 +89,7 @@ class AuthController extends GetxController {
 | 
			
		||||
      );
 | 
			
		||||
      if (response.statusCode == 200) {
 | 
			
		||||
        final dynamic data = json.decode(response.body);
 | 
			
		||||
        userId.value = data['userid'] != null
 | 
			
		||||
            ? int.tryParse(data['userid'].toString())
 | 
			
		||||
            : null;
 | 
			
		||||
        avatarUrl.value = data['avatar'] != null
 | 
			
		||||
            ? 'https://f0ck.me/t/${data['avatar']}.webp'
 | 
			
		||||
            : null;
 | 
			
		||||
        username.value = data['user'];
 | 
			
		||||
        user.value = User.fromJson(data);
 | 
			
		||||
      } else {
 | 
			
		||||
        await logout();
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,8 @@ class MediaController extends GetxController {
 | 
			
		||||
  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) {
 | 
			
		||||
    typeIndex.value = idx;
 | 
			
		||||
@@ -163,6 +165,9 @@ class MediaController extends GetxController {
 | 
			
		||||
    fetchInitial();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void resetVideoControlsTimer() => videoControlsTimerNotifier.value++;
 | 
			
		||||
  void hideVideoControls() => hideControlsNotifier.value++;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void onInit() async {
 | 
			
		||||
    super.onInit();
 | 
			
		||||
 
 | 
			
		||||
@@ -186,7 +186,6 @@ final ThemeData f0ck95Theme = ThemeData(
 | 
			
		||||
    backgroundColor: const Color(0xFFE0E0E0),
 | 
			
		||||
    foregroundColor: Colors.black,
 | 
			
		||||
    elevation: 4,
 | 
			
		||||
    centerTitle: true,
 | 
			
		||||
  ),
 | 
			
		||||
  textTheme: const TextTheme(
 | 
			
		||||
    bodyLarge: TextStyle(color: Colors.black),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								lib/models/user.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								lib/models/user.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
class User {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final String username;
 | 
			
		||||
  final String? avatarUrl;
 | 
			
		||||
 | 
			
		||||
  User({required this.id, required this.username, this.avatarUrl});
 | 
			
		||||
 | 
			
		||||
  factory User.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    return User(
 | 
			
		||||
      id: json['userid'],
 | 
			
		||||
      username: json['user'],
 | 
			
		||||
      avatarUrl: json['avatar'] != null
 | 
			
		||||
          ? 'https://f0ck.me/t/${json['avatar']}.webp'
 | 
			
		||||
          : null,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -32,8 +32,9 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
  PageController? _pageController;
 | 
			
		||||
  final MediaController mediaController = Get.find<MediaController>();
 | 
			
		||||
  final AuthController authController = Get.find<AuthController>();
 | 
			
		||||
  final _currentIndex = 0.obs;
 | 
			
		||||
  final _mediaSaverChannel = const MethodChannel('MediaShit');
 | 
			
		||||
  final RxInt _currentIndex = 0.obs;
 | 
			
		||||
  final MethodChannel _mediaSaverChannel = const MethodChannel('MediaShit');
 | 
			
		||||
  final Map<int, bool> _expandedTags = {};
 | 
			
		||||
 | 
			
		||||
  bool _isLoading = true;
 | 
			
		||||
  bool _itemNotFound = false;
 | 
			
		||||
@@ -105,7 +106,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
  void _onPageChanged(int idx) {
 | 
			
		||||
    if (idx != _currentIndex.value) {
 | 
			
		||||
      _currentIndex.value = idx;
 | 
			
		||||
      final item = mediaController.items[idx];
 | 
			
		||||
      final MediaItem item = mediaController.items[idx];
 | 
			
		||||
      if (item.mime.startsWith('image/') && !_readyItemIds.contains(item.id)) {
 | 
			
		||||
        setState(() => _readyItemIds.add(item.id));
 | 
			
		||||
      }
 | 
			
		||||
@@ -224,8 +225,15 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
        itemBuilder: (context, index) {
 | 
			
		||||
          final MediaItem item = mediaController.items[index];
 | 
			
		||||
          final bool isReady = _readyItemIds.contains(item.id);
 | 
			
		||||
          final bool areTagsExpanded = _expandedTags[item.id] ?? false;
 | 
			
		||||
          final List<Tag> allTags = item.tags ?? [];
 | 
			
		||||
          final bool hasMoreTags = allTags.length > 5;
 | 
			
		||||
          final List<Tag> tagsToShow = areTagsExpanded
 | 
			
		||||
              ? allTags
 | 
			
		||||
              : allTags.take(5).toList();
 | 
			
		||||
 | 
			
		||||
          return Scaffold(
 | 
			
		||||
          return Obx(
 | 
			
		||||
            () => Scaffold(
 | 
			
		||||
              endDrawer: EndDrawer(),
 | 
			
		||||
              endDrawerEnableOpenDragGesture:
 | 
			
		||||
                  mediaController.drawerSwipeEnabled.value,
 | 
			
		||||
@@ -262,8 +270,7 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            body: SingleChildScrollView(
 | 
			
		||||
              child: Column(
 | 
			
		||||
              body: Column(
 | 
			
		||||
                crossAxisAlignment: CrossAxisAlignment.stretch,
 | 
			
		||||
                children: [
 | 
			
		||||
                  AnimatedBuilder(
 | 
			
		||||
@@ -281,10 +288,15 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
                      () => _buildMedia(item, index == _currentIndex.value),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  const SizedBox(height: 16),
 | 
			
		||||
                  if (isReady)
 | 
			
		||||
                    Padding(
 | 
			
		||||
                      padding: const EdgeInsets.symmetric(horizontal: 16.0),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: GestureDetector(
 | 
			
		||||
                      onTap: () => mediaController.hideVideoControls(),
 | 
			
		||||
                      behavior: HitTestBehavior.translucent,
 | 
			
		||||
                      child: Visibility(
 | 
			
		||||
                        visible: isReady,
 | 
			
		||||
                        child: SingleChildScrollView(
 | 
			
		||||
                          child: Padding(
 | 
			
		||||
                            padding: const EdgeInsets.all(16.0),
 | 
			
		||||
                            child: Column(
 | 
			
		||||
                              crossAxisAlignment: CrossAxisAlignment.center,
 | 
			
		||||
                              children: [
 | 
			
		||||
@@ -293,20 +305,35 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
                                  runSpacing: 4.0,
 | 
			
		||||
                                  alignment: WrapAlignment.center,
 | 
			
		||||
                                  children: [
 | 
			
		||||
                              ...item.tags?.map(
 | 
			
		||||
                                    ...tagsToShow.map(
 | 
			
		||||
                                      (tag) => ActionTag(
 | 
			
		||||
                                        tag,
 | 
			
		||||
                                        (tag.tag == 'sfw' || tag.tag == 'nsfw')
 | 
			
		||||
                                            ? (onTagTap) => {}
 | 
			
		||||
                                            : (onTagTap) {
 | 
			
		||||
                                              mediaController.setTag(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,
 | 
			
		||||
@@ -321,15 +348,18 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
 | 
			
		||||
                                ),
 | 
			
		||||
                              ],
 | 
			
		||||
                            ),
 | 
			
		||||
                    )
 | 
			
		||||
                  else
 | 
			
		||||
                    const SizedBox.shrink(),
 | 
			
		||||
                ],
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  const SafeArea(child: SizedBox.shrink()),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
              persistentFooterButtons: mediaController.tag.value != null
 | 
			
		||||
                  ? [TagFooter()]
 | 
			
		||||
                  : null,
 | 
			
		||||
            ),
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
 
 | 
			
		||||
@@ -28,11 +28,11 @@ class EndDrawer extends StatelessWidget {
 | 
			
		||||
        children: [
 | 
			
		||||
          Obx(() {
 | 
			
		||||
            if (authController.token.value != null &&
 | 
			
		||||
                authController.avatarUrl.value != null) {
 | 
			
		||||
                authController.user.value?.avatarUrl != null) {
 | 
			
		||||
              return DrawerHeader(
 | 
			
		||||
                decoration: BoxDecoration(
 | 
			
		||||
                  image: DecorationImage(
 | 
			
		||||
                    image: NetworkImage(authController.avatarUrl.value!),
 | 
			
		||||
                    image: NetworkImage(authController.user.value!.avatarUrl!),
 | 
			
		||||
                    fit: BoxFit.cover,
 | 
			
		||||
                    alignment: Alignment.topCenter,
 | 
			
		||||
                  ),
 | 
			
		||||
@@ -58,11 +58,11 @@ class EndDrawer extends StatelessWidget {
 | 
			
		||||
                padding: const EdgeInsets.all(16.0),
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (authController.username.value != null)
 | 
			
		||||
                    if (authController.user.value?.username != null)
 | 
			
		||||
                      Padding(
 | 
			
		||||
                        padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          'Hamlo ${authController.username.value!}',
 | 
			
		||||
                          'Hamlo ${authController.user.value?.username}',
 | 
			
		||||
                          style: const TextStyle(fontWeight: FontWeight.bold),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class FavoriteSection extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final bool isFavorite =
 | 
			
		||||
        item.favorites?.any((f) => f.userId == authController.userId.value) ??
 | 
			
		||||
        item.favorites?.any((f) => f.userId == authController.user.value?.id) ??
 | 
			
		||||
        false;
 | 
			
		||||
 | 
			
		||||
    return Row(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,10 @@
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
 | 
			
		||||
 | 
			
		||||
class VideoControlsOverlay extends StatelessWidget {
 | 
			
		||||
class VideoControlsOverlay extends StatefulWidget {
 | 
			
		||||
  final CachedVideoPlayerPlusController controller;
 | 
			
		||||
  final VoidCallback onOverlayTap;
 | 
			
		||||
  final bool muted;
 | 
			
		||||
@@ -16,51 +18,102 @@ class VideoControlsOverlay extends StatelessWidget {
 | 
			
		||||
    required this.onMuteToggle,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<VideoControlsOverlay> createState() => _VideoControlsOverlayState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _VideoControlsOverlayState extends State<VideoControlsOverlay> {
 | 
			
		||||
  bool _showSeekIndicator = false;
 | 
			
		||||
  bool _isRewinding = false;
 | 
			
		||||
  Timer? _hideTimer;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _hideTimer?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _handleDoubleTap(TapDownDetails details) {
 | 
			
		||||
    final screenWidth = MediaQuery.of(context).size.width;
 | 
			
		||||
    final isRewind = details.globalPosition.dx < screenWidth / 2;
 | 
			
		||||
 | 
			
		||||
    Future(() {
 | 
			
		||||
      if (isRewind) {
 | 
			
		||||
        final newPosition =
 | 
			
		||||
            widget.controller.value.position - const Duration(seconds: 10);
 | 
			
		||||
        widget.controller.seekTo(
 | 
			
		||||
          newPosition < Duration.zero ? Duration.zero : newPosition,
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        final newPosition =
 | 
			
		||||
            widget.controller.value.position + const Duration(seconds: 10);
 | 
			
		||||
        final duration = widget.controller.value.duration;
 | 
			
		||||
        widget.controller.seekTo(
 | 
			
		||||
          newPosition > duration ? duration : newPosition,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    _hideTimer?.cancel();
 | 
			
		||||
    setState(() {
 | 
			
		||||
      _showSeekIndicator = true;
 | 
			
		||||
      _isRewinding = isRewind;
 | 
			
		||||
    });
 | 
			
		||||
    _hideTimer = Timer(const Duration(milliseconds: 500), () {
 | 
			
		||||
      setState(() => _showSeekIndicator = false);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Stack(
 | 
			
		||||
      alignment: Alignment.center,
 | 
			
		||||
      children: [
 | 
			
		||||
        Row(
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
          children: [
 | 
			
		||||
            _ControlButton(Icons.replay_10, () {
 | 
			
		||||
              onOverlayTap();
 | 
			
		||||
              Duration newPosition =
 | 
			
		||||
                  controller.value.position - const Duration(seconds: 10);
 | 
			
		||||
              if (newPosition < Duration.zero) newPosition = Duration.zero;
 | 
			
		||||
              controller.seekTo(newPosition);
 | 
			
		||||
            }),
 | 
			
		||||
            const SizedBox(width: 40),
 | 
			
		||||
        GestureDetector(
 | 
			
		||||
          onTap: widget.onOverlayTap,
 | 
			
		||||
          onDoubleTapDown: _handleDoubleTap,
 | 
			
		||||
          child: Container(color: Colors.transparent),
 | 
			
		||||
        ),
 | 
			
		||||
        AnimatedOpacity(
 | 
			
		||||
          opacity: _showSeekIndicator ? 1.0 : 0.0,
 | 
			
		||||
          duration: const Duration(milliseconds: 200),
 | 
			
		||||
          child: Align(
 | 
			
		||||
            alignment: _isRewinding
 | 
			
		||||
                ? Alignment.centerLeft
 | 
			
		||||
                : Alignment.centerRight,
 | 
			
		||||
            child: Padding(
 | 
			
		||||
              padding: const EdgeInsets.symmetric(horizontal: 40.0),
 | 
			
		||||
              child: Icon(
 | 
			
		||||
                _isRewinding
 | 
			
		||||
                    ? Icons.fast_rewind_rounded
 | 
			
		||||
                    : Icons.fast_forward_rounded,
 | 
			
		||||
                color: Colors.white70,
 | 
			
		||||
                size: 60,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        _ControlButton(
 | 
			
		||||
              controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
			
		||||
          widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
			
		||||
          () {
 | 
			
		||||
                onOverlayTap();
 | 
			
		||||
                controller.value.isPlaying
 | 
			
		||||
                    ? controller.pause()
 | 
			
		||||
                    : controller.play();
 | 
			
		||||
            widget.onOverlayTap();
 | 
			
		||||
            widget.controller.value.isPlaying
 | 
			
		||||
                ? widget.controller.pause()
 | 
			
		||||
                : widget.controller.play();
 | 
			
		||||
          },
 | 
			
		||||
          size: 64,
 | 
			
		||||
        ),
 | 
			
		||||
            const SizedBox(width: 40),
 | 
			
		||||
            _ControlButton(Icons.forward_10, () {
 | 
			
		||||
              onOverlayTap();
 | 
			
		||||
              Duration newPosition =
 | 
			
		||||
                  controller.value.position + const Duration(seconds: 10);
 | 
			
		||||
              if (newPosition > controller.value.duration) {
 | 
			
		||||
                newPosition = controller.value.duration;
 | 
			
		||||
              }
 | 
			
		||||
              controller.seekTo(newPosition);
 | 
			
		||||
            }),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
        Positioned(
 | 
			
		||||
          right: 12,
 | 
			
		||||
          bottom: 12,
 | 
			
		||||
          child: _ControlButton(muted ? Icons.volume_off : Icons.volume_up, () {
 | 
			
		||||
            onOverlayTap();
 | 
			
		||||
            onMuteToggle();
 | 
			
		||||
          }, size: 16),
 | 
			
		||||
          child: _ControlButton(
 | 
			
		||||
            widget.muted ? Icons.volume_off : Icons.volume_up,
 | 
			
		||||
            () {
 | 
			
		||||
              widget.onOverlayTap();
 | 
			
		||||
              widget.onMuteToggle();
 | 
			
		||||
            },
 | 
			
		||||
            size: 16,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Align(
 | 
			
		||||
          alignment: Alignment.bottomCenter,
 | 
			
		||||
@@ -73,16 +126,16 @@ class VideoControlsOverlay extends StatelessWidget {
 | 
			
		||||
                  left: 10,
 | 
			
		||||
                  bottom: 12,
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    '${_formatDuration(controller.value.position)} / ${_formatDuration(controller.value.duration)}',
 | 
			
		||||
                    '${_formatDuration(widget.controller.value.position)} / ${_formatDuration(widget.controller.value.duration)}',
 | 
			
		||||
                    style: const TextStyle(color: Colors.white, fontSize: 12),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Listener(
 | 
			
		||||
                  onPointerDown: (_) {
 | 
			
		||||
                    onOverlayTap();
 | 
			
		||||
                    widget.onOverlayTap();
 | 
			
		||||
                  },
 | 
			
		||||
                  child: VideoProgressIndicator(
 | 
			
		||||
                    controller,
 | 
			
		||||
                    widget.controller,
 | 
			
		||||
                    allowScrubbing: true,
 | 
			
		||||
                    padding: const EdgeInsets.only(top: 25.0),
 | 
			
		||||
                    colors: const VideoProgressColors(
 | 
			
		||||
@@ -92,11 +145,15 @@ class VideoControlsOverlay extends StatelessWidget {
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                if (controller.value.duration.inMilliseconds > 0)
 | 
			
		||||
                if (widget.controller.value.duration.inMilliseconds > 0)
 | 
			
		||||
                  Positioned(
 | 
			
		||||
                    left:
 | 
			
		||||
                        (controller.value.position.inMilliseconds /
 | 
			
		||||
                                controller.value.duration.inMilliseconds) *
 | 
			
		||||
                        (widget.controller.value.position.inMilliseconds /
 | 
			
		||||
                                widget
 | 
			
		||||
                                    .controller
 | 
			
		||||
                                    .value
 | 
			
		||||
                                    .duration
 | 
			
		||||
                                    .inMilliseconds) *
 | 
			
		||||
                            MediaQuery.of(context).size.width -
 | 
			
		||||
                        6,
 | 
			
		||||
                    bottom: -4,
 | 
			
		||||
@@ -118,7 +175,8 @@ class VideoControlsOverlay extends StatelessWidget {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String _formatDuration(Duration duration) {
 | 
			
		||||
  String _formatDuration(Duration? duration) {
 | 
			
		||||
    if (duration == null) return '00:00';
 | 
			
		||||
    String twoDigits(int n) => n.toString().padLeft(2, '0');
 | 
			
		||||
    return "${twoDigits(duration.inMinutes % 60)}:${twoDigits(duration.inSeconds % 60)}";
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,8 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  final MediaController controller = Get.find<MediaController>();
 | 
			
		||||
  late CachedVideoPlayerPlusController _controller;
 | 
			
		||||
  late Worker _muteWorker;
 | 
			
		||||
  late Worker _timerResetWorker;
 | 
			
		||||
  late Worker _hideControlsWorker;
 | 
			
		||||
  bool _showControls = false;
 | 
			
		||||
  Timer? _hideControlsTimer;
 | 
			
		||||
 | 
			
		||||
@@ -44,11 +46,26 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
        _controller.setVolume(muted ? 0.0 : 1.0);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    _timerResetWorker = ever(controller.videoControlsTimerNotifier, (_) {
 | 
			
		||||
      if (widget.isActive && mounted) {
 | 
			
		||||
        if (!_showControls) {
 | 
			
		||||
          setState(() => _showControls = true);
 | 
			
		||||
        }
 | 
			
		||||
        _startHideControlsTimer();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    _hideControlsWorker = ever(controller.hideControlsNotifier, (_) {
 | 
			
		||||
      if (mounted && _showControls) {
 | 
			
		||||
        setState(() => _showControls = false);
 | 
			
		||||
        _hideControlsTimer?.cancel();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _initController() async {
 | 
			
		||||
    _controller = CachedVideoPlayerPlusController.networkUrl(
 | 
			
		||||
      Uri.parse(widget.details.mediaUrl),
 | 
			
		||||
      videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
 | 
			
		||||
    );
 | 
			
		||||
    await _controller.initialize();
 | 
			
		||||
    widget.onInitialized?.call();
 | 
			
		||||
@@ -78,21 +95,36 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _muteWorker.dispose();
 | 
			
		||||
    _timerResetWorker.dispose();
 | 
			
		||||
    _hideControlsWorker.dispose();
 | 
			
		||||
    _controller.dispose();
 | 
			
		||||
    _hideControlsTimer?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _onTap({bool ctrlButton = false}) {
 | 
			
		||||
    if (!ctrlButton) {
 | 
			
		||||
      setState(() => _showControls = !_showControls);
 | 
			
		||||
    }
 | 
			
		||||
    if (_showControls) {
 | 
			
		||||
  void _startHideControlsTimer() {
 | 
			
		||||
    _hideControlsTimer?.cancel();
 | 
			
		||||
      _hideControlsTimer = Timer(const Duration(seconds: 2), () {
 | 
			
		||||
    _hideControlsTimer = Timer(const Duration(seconds: 3), () {
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        setState(() => _showControls = false);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _onTap({bool ctrlButton = false}) {
 | 
			
		||||
    if (ctrlButton) {
 | 
			
		||||
      _startHideControlsTimer();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final bool newShowState = !_showControls;
 | 
			
		||||
    setState(() => _showControls = newShowState);
 | 
			
		||||
 | 
			
		||||
    if (newShowState) {
 | 
			
		||||
      _startHideControlsTimer();
 | 
			
		||||
    } else {
 | 
			
		||||
      _hideControlsTimer?.cancel();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
			
		||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
			
		||||
# of the product and file versions while build-number is used as the build suffix.
 | 
			
		||||
version: 1.4.1+62
 | 
			
		||||
version: 1.4.2+63
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: ^3.9.0-100.2.beta
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user