v1.4.2+63
All checks were successful
Flutter Schmutter / build (push) Successful in 3m51s

This commit is contained in:
2025-06-21 16:28:57 +02:00
parent 73a44bb269
commit 7a88c23e57
10 changed files with 285 additions and 156 deletions

View File

@ -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),
),
),

View File

@ -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(

View File

@ -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),
_ControlButton(
controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
() {
onOverlayTap();
controller.value.isPlaying
? controller.pause()
: controller.play();
},
size: 64,
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,
),
),
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);
}),
],
),
),
_ControlButton(
widget.controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
() {
widget.onOverlayTap();
widget.controller.value.isPlaying
? widget.controller.pause()
: widget.controller.play();
},
size: 64,
),
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)}";
}

View File

@ -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,20 +95,35 @@ 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) {
_hideControlsTimer?.cancel();
_hideControlsTimer = Timer(const Duration(seconds: 2), () {
void _startHideControlsTimer() {
_hideControlsTimer?.cancel();
_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();
}
}