Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mobile): new video slider ui #14126

Merged
merged 1 commit into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mobile/lib/constants/immich_colors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const String defaultColorPresetName = "indigo";
const Color immichBrandColorLight = Color(0xFF4150AF);
const Color immichBrandColorDark = Color(0xFFACCBFA);
const Color whiteOpacity75 = Color.fromARGB((0.75 * 255) ~/ 1, 255, 255, 255);
const Color blackOpacity40 = Color.fromARGB((0.40 * 255) ~/ 1, 0, 0, 0);
const Color blackOpacity90 = Color.fromARGB((0.90 * 255) ~/ 1, 0, 0, 0);

final Map<ImmichColorPreset, ImmichTheme> _themePresetsMap = {
ImmichColorPreset.indigo: ImmichTheme(
Expand Down
75 changes: 44 additions & 31 deletions mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart';
Expand Down Expand Up @@ -327,39 +328,51 @@ class BottomGalleryBar extends ConsumerWidget {
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0,
child: Column(
children: [
Visibility(
visible: showVideoPlayerControls,
child: const VideoControls(),
child: DecoratedBox(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [blackOpacity90, Colors.transparent],
),
BottomNavigationBar(
backgroundColor: Colors.black.withOpacity(0.4),
unselectedIconTheme: const IconThemeData(color: Colors.white),
selectedIconTheme: const IconThemeData(color: Colors.white),
unselectedLabelStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
height: 2.3,
),
selectedLabelStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
height: 2.3,
),
unselectedFontSize: 14,
selectedFontSize: 14,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white,
showSelectedLabels: true,
showUnselectedLabels: true,
items:
albumActions.map((e) => e.keys.first).toList(growable: false),
onTap: (index) {
albumActions[index].values.first.call(index);
},
),
position: DecorationPosition.background,
child: Padding(
padding: EdgeInsets.only(top: 40.0),
child: Column(
children: [
if (showVideoPlayerControls) const VideoControls(),
BottomNavigationBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
unselectedIconTheme: const IconThemeData(color: Colors.white),
selectedIconTheme: const IconThemeData(color: Colors.white),
unselectedLabelStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
height: 2.3,
),
selectedLabelStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
height: 2.3,
),
unselectedFontSize: 14,
selectedFontSize: 14,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white,
showSelectedLabels: true,
showUnselectedLabels: true,
items: albumActions
.map((e) => e.keys.first)
.toList(growable: false),
onTap: (index) {
albumActions[index].values.first.call(index);
},
),
],
),
],
),
),
),
);
Expand Down
5 changes: 2 additions & 3 deletions mobile/lib/widgets/asset_viewer/formatted_duration.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/constants/immich_colors.dart';

@pragma('vm:prefer-inline')
String _formatDuration(Duration position) {
Expand All @@ -24,8 +23,8 @@ class FormattedDuration extends StatelessWidget {
_formatDuration(data),
style: const TextStyle(
fontSize: 14.0,
color: whiteOpacity75,
fontWeight: FontWeight.normal,
color: Colors.white,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
Expand Down
27 changes: 6 additions & 21 deletions mobile/lib/widgets/asset_viewer/video_controls.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
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/widgets/asset_viewer/video_position.dart';

/// The video controls for the [videoPlayerControlsProvider]
Expand All @@ -12,24 +10,11 @@ class VideoControls extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final isPortrait =
MediaQuery.orientationOf(context) == Orientation.portrait;
return AnimatedOpacity(
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0,
duration: const Duration(milliseconds: 100),
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(),
),
),
);
return isPortrait
? const VideoPosition()
: const Padding(
padding: EdgeInsets.symmetric(horizontal: 60.0),
child: VideoPosition(),
);
}
}
23 changes: 0 additions & 23 deletions mobile/lib/widgets/asset_viewer/video_mute_button.dart

This file was deleted.

110 changes: 67 additions & 43 deletions mobile/lib/widgets/asset_viewer/video_position.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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});
Expand All @@ -20,38 +19,52 @@ class VideoPosition extends HookConsumerWidget {
final wasPlaying = useRef<bool>(true);
return duration == Duration.zero
? const _VideoPositionPlaceholder()
: Row(
: Column(
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;
},
Padding(
// align with slider's inherent padding
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FormattedDuration(position),
FormattedDuration(duration),
],
),
),
FormattedDuration(duration),
const VideoMuteButton(),
Row(
children: [
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;
},
),
),
],
),
],
);
}
Expand All @@ -64,22 +77,33 @@ class _VideoPositionPlaceholder extends StatelessWidget {

@override
Widget build(BuildContext context) {
return const Row(
return const Column(
children: [
FormattedDuration(Duration.zero),
Expanded(
child: Slider(
value: 0.0,
min: 0,
max: 100,
thumbColor: Colors.white,
activeColor: Colors.white,
inactiveColor: whiteOpacity75,
onChanged: _onChangedDummy,
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
FormattedDuration(Duration.zero),
FormattedDuration(Duration.zero),
],
),
),
FormattedDuration(Duration.zero),
VideoMuteButton(),
Row(
children: [
Expanded(
child: Slider(
value: 0.0,
min: 0,
max: 100,
thumbColor: Colors.white,
activeColor: Colors.white,
inactiveColor: whiteOpacity75,
onChanged: _onChangedDummy,
),
),
],
),
],
);
}
Expand Down
Loading