forked from immich-app/immich
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(mobile): video controls (immich-app#14086)
* refactor video controls * inline * make mute icon const * move placeholder to private widget * adjust text width, move volume button slightly right
- Loading branch information
1 parent
3eb3a60
commit 5c6f715
Showing
6 changed files
with
168 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:immich_mobile/constants/immich_colors.dart'; | ||
|
||
@pragma('vm:prefer-inline') | ||
String _formatDuration(Duration position) { | ||
final seconds = position.inSeconds.remainder(60).toString().padLeft(2, "0"); | ||
final minutes = position.inMinutes.remainder(60).toString().padLeft(2, "0"); | ||
if (position.inHours == 0) { | ||
return "$minutes:$seconds"; | ||
} | ||
final hours = position.inHours.toString().padLeft(2, '0'); | ||
return "$hours:$minutes:$seconds"; | ||
} | ||
|
||
class FormattedDuration extends StatelessWidget { | ||
final Duration data; | ||
const FormattedDuration(this.data, {super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return SizedBox( | ||
width: data.inHours > 0 ? 64 : 43, // use a fixed width to prevent jitter | ||
child: Text( | ||
_formatDuration(data), | ||
style: const TextStyle( | ||
fontSize: 14.0, | ||
color: whiteOpacity75, | ||
fontWeight: FontWeight.normal, | ||
), | ||
textAlign: TextAlign.center, | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,125 +1,35 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:immich_mobile/constants/immich_colors.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; | ||
import 'package:immich_mobile/widgets/asset_viewer/video_position.dart'; | ||
|
||
/// The video controls for the [videPlayerControlsProvider] | ||
/// The video controls for the [videoPlayerControlsProvider] | ||
class VideoControls extends ConsumerWidget { | ||
const VideoControls({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final duration = | ||
ref.watch(videoPlaybackValueProvider.select((v) => v.duration)); | ||
final position = | ||
ref.watch(videoPlaybackValueProvider.select((v) => v.position)); | ||
|
||
final isPortrait = | ||
MediaQuery.orientationOf(context) == Orientation.portrait; | ||
return AnimatedOpacity( | ||
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0, | ||
duration: const Duration(milliseconds: 100), | ||
child: OrientationBuilder( | ||
builder: (context, orientation) => Container( | ||
padding: EdgeInsets.symmetric( | ||
horizontal: orientation == Orientation.portrait ? 12.0 : 64.0, | ||
), | ||
color: Colors.black.withOpacity(0.4), | ||
child: Padding( | ||
padding: MediaQuery.of(context).orientation == Orientation.portrait | ||
? const EdgeInsets.symmetric(horizontal: 12.0) | ||
: const EdgeInsets.symmetric(horizontal: 64.0), | ||
child: Row( | ||
children: [ | ||
Text( | ||
_formatDuration(position), | ||
style: TextStyle( | ||
fontSize: 14.0, | ||
color: Colors.white.withOpacity(.75), | ||
fontWeight: FontWeight.normal, | ||
), | ||
), | ||
Expanded( | ||
child: Slider( | ||
value: duration == Duration.zero | ||
? 0.0 | ||
: min( | ||
position.inMicroseconds / | ||
duration.inMicroseconds * | ||
100, | ||
100, | ||
), | ||
min: 0, | ||
max: 100, | ||
thumbColor: Colors.white, | ||
activeColor: Colors.white, | ||
inactiveColor: Colors.white.withOpacity(0.75), | ||
onChanged: (position) { | ||
ref.read(videoPlayerControlsProvider.notifier).position = | ||
position; | ||
}, | ||
), | ||
), | ||
Text( | ||
_formatDuration(duration), | ||
style: TextStyle( | ||
fontSize: 14.0, | ||
color: Colors.white.withOpacity(.75), | ||
fontWeight: FontWeight.normal, | ||
), | ||
), | ||
IconButton( | ||
icon: Icon( | ||
ref.watch( | ||
videoPlayerControlsProvider.select((value) => value.mute), | ||
) | ||
? Icons.volume_off | ||
: Icons.volume_up, | ||
), | ||
onPressed: () => ref | ||
.read(videoPlayerControlsProvider.notifier) | ||
.toggleMute(), | ||
color: Colors.white, | ||
), | ||
], | ||
child: isPortrait | ||
? const ColoredBox( | ||
color: blackOpacity40, | ||
child: Padding( | ||
padding: EdgeInsets.symmetric(horizontal: 24.0), | ||
child: VideoPosition(), | ||
), | ||
) | ||
: const ColoredBox( | ||
color: blackOpacity40, | ||
child: Padding( | ||
padding: EdgeInsets.symmetric(horizontal: 128.0), | ||
child: VideoPosition(), | ||
), | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
|
||
String _formatDuration(Duration position) { | ||
final ms = position.inMilliseconds; | ||
|
||
int seconds = ms ~/ 1000; | ||
final int hours = seconds ~/ 3600; | ||
seconds = seconds % 3600; | ||
final minutes = seconds ~/ 60; | ||
seconds = seconds % 60; | ||
|
||
final hoursString = hours >= 10 | ||
? '$hours' | ||
: hours == 0 | ||
? '00' | ||
: '0$hours'; | ||
|
||
final minutesString = minutes >= 10 | ||
? '$minutes' | ||
: minutes == 0 | ||
? '00' | ||
: '0$minutes'; | ||
|
||
final secondsString = seconds >= 10 | ||
? '$seconds' | ||
: seconds == 0 | ||
? '00' | ||
: '0$seconds'; | ||
|
||
final formattedTime = | ||
'${hoursString == '00' ? '' : '$hoursString:'}$minutesString:$secondsString'; | ||
|
||
return formattedTime; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; | ||
|
||
class VideoMuteButton extends ConsumerWidget { | ||
const VideoMuteButton({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
return IconButton( | ||
icon: ref.watch( | ||
videoPlayerControlsProvider.select((value) => value.mute), | ||
) | ||
? const Icon(Icons.volume_off) | ||
: const Icon(Icons.volume_up), | ||
onPressed: () => | ||
ref.read(videoPlayerControlsProvider.notifier).toggleMute(), | ||
color: Colors.white, | ||
padding: const EdgeInsets.all(0), | ||
alignment: Alignment.centerRight, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||
import 'package:immich_mobile/constants/immich_colors.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provider.dart'; | ||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart'; | ||
import 'package:immich_mobile/widgets/asset_viewer/formatted_duration.dart'; | ||
import 'package:immich_mobile/widgets/asset_viewer/video_mute_button.dart'; | ||
|
||
class VideoPosition extends HookConsumerWidget { | ||
const VideoPosition({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context, WidgetRef ref) { | ||
final (position, duration) = ref.watch( | ||
videoPlaybackValueProvider.select((v) => (v.position, v.duration)), | ||
); | ||
final wasPlaying = useRef<bool>(true); | ||
return duration == Duration.zero | ||
? const _VideoPositionPlaceholder() | ||
: Row( | ||
children: [ | ||
FormattedDuration(position), | ||
Expanded( | ||
child: Slider( | ||
value: min( | ||
position.inMicroseconds / duration.inMicroseconds * 100, | ||
100, | ||
), | ||
min: 0, | ||
max: 100, | ||
thumbColor: Colors.white, | ||
activeColor: Colors.white, | ||
inactiveColor: whiteOpacity75, | ||
onChangeStart: (value) { | ||
final state = ref.read(videoPlaybackValueProvider).state; | ||
wasPlaying.value = state != VideoPlaybackState.paused; | ||
ref.read(videoPlayerControlsProvider.notifier).pause(); | ||
}, | ||
onChangeEnd: (value) { | ||
if (wasPlaying.value) { | ||
ref.read(videoPlayerControlsProvider.notifier).play(); | ||
} | ||
}, | ||
onChanged: (position) { | ||
ref.read(videoPlayerControlsProvider.notifier).position = | ||
position; | ||
}, | ||
), | ||
), | ||
FormattedDuration(duration), | ||
const VideoMuteButton(), | ||
], | ||
); | ||
} | ||
} | ||
|
||
class _VideoPositionPlaceholder extends StatelessWidget { | ||
const _VideoPositionPlaceholder(); | ||
|
||
static void _onChangedDummy(_) {} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return const Row( | ||
children: [ | ||
FormattedDuration(Duration.zero), | ||
Expanded( | ||
child: Slider( | ||
value: 0.0, | ||
min: 0, | ||
max: 100, | ||
thumbColor: Colors.white, | ||
activeColor: Colors.white, | ||
inactiveColor: whiteOpacity75, | ||
onChanged: _onChangedDummy, | ||
), | ||
), | ||
FormattedDuration(Duration.zero), | ||
VideoMuteButton(), | ||
], | ||
); | ||
} | ||
} |