mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
1772f720bf
fix(mobile): reset video controls hide timer when showing controls changes
The hide timer currently only resets when the status of the video
changes, but does not account for when the controls change. This means
that two things happen:
1. The hide timer does not reset when the controls become visible
again, the controls will stay visible forever or until the playback
status changes.
2. The hide timer will fire too quickly, and will hide the controls
much sooner than 5 seconds if the controls are hidden and then shown
again before the hide timer fires.
117 lines
4.4 KiB
Dart
117 lines
4.4 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/constants/colors.dart';
|
|
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
|
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
|
import 'package:immich_mobile/utils/hooks/timer_hook.dart';
|
|
import 'package:immich_mobile/extensions/duration_extensions.dart';
|
|
import 'package:immich_mobile/widgets/asset_viewer/animated_play_pause.dart';
|
|
|
|
class VideoControls extends HookConsumerWidget {
|
|
final String videoPlayerName;
|
|
|
|
static const List<Shadow> _controlShadows = [Shadow(color: Colors.black87, blurRadius: 6, offset: Offset(0, 1))];
|
|
|
|
const VideoControls({super.key, required this.videoPlayerName});
|
|
|
|
void _toggle(WidgetRef ref, bool isCasting) {
|
|
if (isCasting) {
|
|
ref.read(castProvider.notifier).toggle();
|
|
} else {
|
|
ref.read(videoPlayerProvider(videoPlayerName).notifier).toggle();
|
|
}
|
|
}
|
|
|
|
void _onSeek(WidgetRef ref, bool isCasting, double value) {
|
|
final seekTo = Duration(microseconds: value.toInt());
|
|
|
|
if (isCasting) {
|
|
ref.read(castProvider.notifier).seekTo(seekTo);
|
|
return;
|
|
}
|
|
|
|
ref.read(videoPlayerProvider(videoPlayerName).notifier).seekTo(seekTo);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final provider = videoPlayerProvider(videoPlayerName);
|
|
final cast = ref.watch(castProvider);
|
|
final isCasting = cast.isCasting;
|
|
|
|
final (position, duration) = isCasting
|
|
? ref.watch(castProvider.select((c) => (c.currentTime, c.duration)))
|
|
: ref.watch(provider.select((v) => (v.position, v.duration)));
|
|
|
|
final videoStatus = ref.watch(provider.select((v) => v.status));
|
|
final isPlaying = isCasting
|
|
? cast.castState == CastState.playing
|
|
: videoStatus == VideoPlaybackStatus.playing || videoStatus == VideoPlaybackStatus.buffering;
|
|
final isFinished = !isCasting && videoStatus == VideoPlaybackStatus.completed;
|
|
|
|
final hideTimer = useTimer(const Duration(seconds: 5), () {
|
|
if (!context.mounted) return;
|
|
if (ref.read(provider).status == VideoPlaybackStatus.playing) {
|
|
ref.read(assetViewerProvider.notifier).setControls(false);
|
|
}
|
|
});
|
|
|
|
ref.listen(assetViewerProvider.select((v) => v.showingControls), (prev, showing) {
|
|
if (showing && prev != showing) hideTimer.reset();
|
|
});
|
|
ref.listen(provider.select((v) => v.status), (_, __) => hideTimer.reset());
|
|
|
|
final notifier = ref.read(provider.notifier);
|
|
final isLoaded = duration != Duration.zero;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
|
|
child: Column(
|
|
spacing: 4,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
iconSize: 32,
|
|
padding: const EdgeInsets.all(12),
|
|
constraints: const BoxConstraints(),
|
|
icon: isFinished
|
|
? const Icon(Icons.replay, color: Colors.white, shadows: _controlShadows)
|
|
: AnimatedPlayPause(color: Colors.white, playing: isPlaying, shadows: _controlShadows),
|
|
onPressed: () => _toggle(ref, isCasting),
|
|
),
|
|
const Spacer(),
|
|
Text(
|
|
"${position.format()} / ${duration.format()}",
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w500,
|
|
fontFeatures: [FontFeature.tabularFigures()],
|
|
shadows: _controlShadows,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
],
|
|
),
|
|
Slider(
|
|
value: min(position.inMicroseconds.toDouble(), duration.inMicroseconds.toDouble()),
|
|
min: 0,
|
|
max: max(duration.inMicroseconds.toDouble(), 1),
|
|
thumbColor: Colors.white,
|
|
activeColor: Colors.white,
|
|
inactiveColor: whiteOpacity75,
|
|
padding: EdgeInsets.zero,
|
|
onChangeStart: (_) => notifier.hold(),
|
|
onChangeEnd: (_) => notifier.release(),
|
|
onChanged: isLoaded ? (value) => _onSeek(ref, isCasting, value) : null,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|