This commit is contained in:
		@@ -16,7 +16,7 @@ class MediaTile extends StatelessWidget {
 | 
			
		||||
          fit: StackFit.expand,
 | 
			
		||||
          children: [
 | 
			
		||||
            CachedNetworkImage(
 | 
			
		||||
              imageUrl: 'https://f0ck.me/t/${item.id}.webp',
 | 
			
		||||
              imageUrl: item.thumbnailUrl,
 | 
			
		||||
              fit: BoxFit.cover,
 | 
			
		||||
              placeholder: (context, url) => Container(color: Colors.grey[900]),
 | 
			
		||||
              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(
 | 
			
		||||
          widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
			
		||||
          () {
 | 
			
		||||
        IconButton(
 | 
			
		||||
          icon: Icon(
 | 
			
		||||
            widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
 | 
			
		||||
            size: 64,
 | 
			
		||||
          ),
 | 
			
		||||
          onPressed: () {
 | 
			
		||||
            widget.onOverlayTap();
 | 
			
		||||
            widget.controller.value.isPlaying
 | 
			
		||||
                ? widget.controller.pause()
 | 
			
		||||
                : widget.controller.play();
 | 
			
		||||
          },
 | 
			
		||||
          size: 64,
 | 
			
		||||
        ),
 | 
			
		||||
        Positioned(
 | 
			
		||||
          right: 12,
 | 
			
		||||
          bottom: 12,
 | 
			
		||||
          child: _ControlButton(
 | 
			
		||||
            widget.muted ? Icons.volume_off : Icons.volume_up,
 | 
			
		||||
            () {
 | 
			
		||||
          child: IconButton(
 | 
			
		||||
            icon: Icon(
 | 
			
		||||
              widget.muted ? Icons.volume_off : Icons.volume_up,
 | 
			
		||||
              size: 16,
 | 
			
		||||
            ),
 | 
			
		||||
            onPressed: () {
 | 
			
		||||
              widget.onOverlayTap();
 | 
			
		||||
              widget.onMuteToggle();
 | 
			
		||||
            },
 | 
			
		||||
            size: 16,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        Align(
 | 
			
		||||
          alignment: Alignment.bottomCenter,
 | 
			
		||||
          child: Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(bottom: 0),
 | 
			
		||||
            child: Stack(
 | 
			
		||||
              clipBehavior: Clip.none,
 | 
			
		||||
              children: [
 | 
			
		||||
                Positioned(
 | 
			
		||||
                  left: 10,
 | 
			
		||||
                  bottom: 12,
 | 
			
		||||
                  child: Text(
 | 
			
		||||
                    '${_formatDuration(widget.controller.value.position)} / ${_formatDuration(widget.controller.value.duration)}',
 | 
			
		||||
                    style: const TextStyle(color: Colors.white, fontSize: 12),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Listener(
 | 
			
		||||
                  onPointerDown: (_) {
 | 
			
		||||
                    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),
 | 
			
		||||
            child: LayoutBuilder(
 | 
			
		||||
              builder: (context, constraints) {
 | 
			
		||||
                return Stack(
 | 
			
		||||
                  clipBehavior: Clip.none,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Positioned(
 | 
			
		||||
                      left: 10,
 | 
			
		||||
                      bottom: 12,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        '${_formatDuration(widget.controller.value.position)} / ${_formatDuration(widget.controller.value.duration)}',
 | 
			
		||||
                        style: const TextStyle(
 | 
			
		||||
                          color: Colors.white,
 | 
			
		||||
                          fontSize: 12,
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
              ],
 | 
			
		||||
                    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)}";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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/widgets/video_controls_overlay.dart';
 | 
			
		||||
import 'package:f0ckapp/controller/settingscontroller.dart';
 | 
			
		||||
import 'package:f0ckapp/controller/mediacontroller.dart';
 | 
			
		||||
 | 
			
		||||
class VideoWidget extends StatefulWidget {
 | 
			
		||||
@@ -29,8 +30,9 @@ class VideoWidget extends StatefulWidget {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  final MediaController controller = Get.find<MediaController>();
 | 
			
		||||
  late CachedVideoPlayerPlusController _controller;
 | 
			
		||||
  final MediaController mediaController = Get.find<MediaController>();
 | 
			
		||||
  final SettingsController settingsController = Get.find<SettingsController>();
 | 
			
		||||
  late CachedVideoPlayerPlusController videoController;
 | 
			
		||||
  late Worker _muteWorker;
 | 
			
		||||
  late Worker _timerResetWorker;
 | 
			
		||||
  late Worker _hideControlsWorker;
 | 
			
		||||
@@ -41,12 +43,14 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _initController();
 | 
			
		||||
    _muteWorker = ever(controller.muted, (bool muted) {
 | 
			
		||||
      if (_controller.value.isInitialized) {
 | 
			
		||||
        _controller.setVolume(muted ? 0.0 : 1.0);
 | 
			
		||||
    _muteWorker = ever(settingsController.muted, (bool muted) {
 | 
			
		||||
      if (videoController.value.isInitialized) {
 | 
			
		||||
        videoController.setVolume(muted ? 0.0 : 1.0);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    _timerResetWorker = ever(controller.videoControlsTimerNotifier, (_) {
 | 
			
		||||
    _timerResetWorker = ever(settingsController.videoControlsTimerNotifier, (
 | 
			
		||||
      _,
 | 
			
		||||
    ) {
 | 
			
		||||
      if (widget.isActive && mounted) {
 | 
			
		||||
        if (!_showControls) {
 | 
			
		||||
          setState(() => _showControls = true);
 | 
			
		||||
@@ -54,7 +58,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
        _startHideControlsTimer();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    _hideControlsWorker = ever(controller.hideControlsNotifier, (_) {
 | 
			
		||||
    _hideControlsWorker = ever(settingsController.hideControlsNotifier, (_) {
 | 
			
		||||
      if (mounted && _showControls) {
 | 
			
		||||
        setState(() => _showControls = false);
 | 
			
		||||
        _hideControlsTimer?.cancel();
 | 
			
		||||
@@ -63,20 +67,18 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _initController() async {
 | 
			
		||||
    _controller = CachedVideoPlayerPlusController.networkUrl(
 | 
			
		||||
    videoController = CachedVideoPlayerPlusController.networkUrl(
 | 
			
		||||
      Uri.parse(widget.details.mediaUrl),
 | 
			
		||||
      videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
 | 
			
		||||
    );
 | 
			
		||||
    await _controller.initialize();
 | 
			
		||||
    await videoController.initialize();
 | 
			
		||||
    widget.onInitialized?.call();
 | 
			
		||||
    if (!mounted) return;
 | 
			
		||||
    setState(() {});
 | 
			
		||||
    _controller.addListener(() => setState(() {}));
 | 
			
		||||
    _controller.setLooping(true);
 | 
			
		||||
    _controller.setVolume(controller.muted.value ? 0.0 : 1.0);
 | 
			
		||||
    videoController.setLooping(true);
 | 
			
		||||
    videoController.setVolume(settingsController.muted.value ? 0.0 : 1.0);
 | 
			
		||||
 | 
			
		||||
    if (widget.isActive) {
 | 
			
		||||
      _controller.play();
 | 
			
		||||
      videoController.play();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -85,9 +87,9 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
    super.didUpdateWidget(oldWidget);
 | 
			
		||||
    if (widget.isActive != oldWidget.isActive) {
 | 
			
		||||
      if (widget.isActive) {
 | 
			
		||||
        _controller.play();
 | 
			
		||||
        videoController.play();
 | 
			
		||||
      } else {
 | 
			
		||||
        _controller.pause();
 | 
			
		||||
        videoController.pause();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -97,7 +99,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
    _muteWorker.dispose();
 | 
			
		||||
    _timerResetWorker.dispose();
 | 
			
		||||
    _hideControlsWorker.dispose();
 | 
			
		||||
    _controller.dispose();
 | 
			
		||||
    videoController.dispose();
 | 
			
		||||
    _hideControlsTimer?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
@@ -129,7 +131,7 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final bool muted = controller.muted.value;
 | 
			
		||||
    final bool muted = settingsController.muted.value;
 | 
			
		||||
    bool isAudio = widget.details.mime.startsWith('audio');
 | 
			
		||||
 | 
			
		||||
    Widget mediaContent;
 | 
			
		||||
@@ -144,36 +146,43 @@ class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      mediaContent = _controller.value.isInitialized
 | 
			
		||||
          ? CachedVideoPlayerPlus(_controller)
 | 
			
		||||
      mediaContent = videoController.value.isInitialized
 | 
			
		||||
          ? CachedVideoPlayerPlus(videoController)
 | 
			
		||||
          : const Center(child: CircularProgressIndicator());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return AspectRatio(
 | 
			
		||||
      aspectRatio: _controller.value.isInitialized
 | 
			
		||||
          ? _controller.value.aspectRatio
 | 
			
		||||
      aspectRatio: videoController.value.isInitialized
 | 
			
		||||
          ? videoController.value.aspectRatio
 | 
			
		||||
          : (isAudio ? 16 / 9 : 9 / 16),
 | 
			
		||||
      child: Stack(
 | 
			
		||||
        alignment: Alignment.center,
 | 
			
		||||
        children: [
 | 
			
		||||
          GestureDetector(onTap: _onTap, child: mediaContent),
 | 
			
		||||
          if (_controller.value.isInitialized && _showControls)
 | 
			
		||||
            Positioned.fill(
 | 
			
		||||
              child: GestureDetector(
 | 
			
		||||
                onTap: _onTap,
 | 
			
		||||
                child: Container(
 | 
			
		||||
                  color: Colors.black.withValues(alpha: 0.5),
 | 
			
		||||
                  child: VideoControlsOverlay(
 | 
			
		||||
                    controller: _controller,
 | 
			
		||||
                    onOverlayTap: () => _onTap(ctrlButton: true),
 | 
			
		||||
                    muted: muted,
 | 
			
		||||
                    onMuteToggle: () {
 | 
			
		||||
                      controller.toggleMuted();
 | 
			
		||||
                    },
 | 
			
		||||
          AnimatedBuilder(
 | 
			
		||||
            animation: videoController,
 | 
			
		||||
            builder: (context, child) {
 | 
			
		||||
              if (videoController.value.isInitialized && _showControls) {
 | 
			
		||||
                return Positioned.fill(
 | 
			
		||||
                  child: GestureDetector(
 | 
			
		||||
                    onTap: _onTap,
 | 
			
		||||
                    child: Container(
 | 
			
		||||
                      color: Colors.black.withValues(alpha: 0.5),
 | 
			
		||||
                      child: VideoControlsOverlay(
 | 
			
		||||
                        controller: videoController,
 | 
			
		||||
                        onOverlayTap: () => _onTap(ctrlButton: true),
 | 
			
		||||
                        muted: muted,
 | 
			
		||||
                        onMuteToggle: () {
 | 
			
		||||
                          settingsController.toggleMuted();
 | 
			
		||||
                        },
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
              return const SizedBox.shrink();
 | 
			
		||||
            },
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user