mirror of
https://github.com/immich-app/immich.git
synced 2025-01-14 15:45:55 +02:00
WIP unravel stack index
This commit is contained in:
parent
f18872c18c
commit
adb541e627
@ -97,7 +97,7 @@ class _ChewieControllerHookState
|
||||
@override
|
||||
void initHook() async {
|
||||
super.initHook();
|
||||
unawaited(_initialize());
|
||||
_initialize().whenComplete(() => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -155,20 +155,18 @@ class _ChewieControllerHookState
|
||||
|
||||
await videoPlayerController!.initialize();
|
||||
|
||||
setState(() {
|
||||
chewieController = ChewieController(
|
||||
videoPlayerController: videoPlayerController!,
|
||||
controlsSafeAreaMinimum: hook.controlsSafeAreaMinimum,
|
||||
showOptions: hook.showOptions,
|
||||
showControlsOnInitialize: hook.showControlsOnInitialize,
|
||||
autoPlay: hook.autoPlay,
|
||||
allowFullScreen: hook.allowFullScreen,
|
||||
allowedScreenSleep: hook.allowedScreenSleep,
|
||||
showControls: hook.showControls,
|
||||
customControls: hook.customControls,
|
||||
placeholder: hook.placeholder,
|
||||
hideControlsTimer: hook.hideControlsTimer,
|
||||
);
|
||||
});
|
||||
chewieController = ChewieController(
|
||||
videoPlayerController: videoPlayerController!,
|
||||
controlsSafeAreaMinimum: hook.controlsSafeAreaMinimum,
|
||||
showOptions: hook.showOptions,
|
||||
showControlsOnInitialize: hook.showControlsOnInitialize,
|
||||
autoPlay: hook.autoPlay,
|
||||
allowFullScreen: hook.allowFullScreen,
|
||||
allowedScreenSleep: hook.allowedScreenSleep,
|
||||
showControls: hook.showControls,
|
||||
customControls: hook.customControls,
|
||||
placeholder: hook.placeholder,
|
||||
hideControlsTimer: hook.hideControlsTimer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'asset_stack.provider.g.dart';
|
||||
|
||||
class AssetStackNotifier extends StateNotifier<List<Asset>> {
|
||||
final Asset _asset;
|
||||
@ -49,3 +52,8 @@ final assetStackProvider =
|
||||
.sortByFileCreatedAtDesc()
|
||||
.findAll();
|
||||
});
|
||||
|
||||
@riverpod
|
||||
int assetStackIndex(AssetStackIndexRef ref, Asset asset) {
|
||||
return -1;
|
||||
}
|
||||
|
BIN
mobile/lib/modules/asset_viewer/providers/asset_stack.provider.g.dart
generated
Normal file
BIN
mobile/lib/modules/asset_viewer/providers/asset_stack.provider.g.dart
generated
Normal file
Binary file not shown.
357
mobile/lib/modules/asset_viewer/ui/bottom_gallery_bar.dart
Normal file
357
mobile/lib/modules/asset_viewer/ui/bottom_gallery_bar.dart
Normal file
@ -0,0 +1,357 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
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/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/services/asset_stack.service.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
|
||||
class BottomGalleryBar extends ConsumerWidget {
|
||||
final Asset asset;
|
||||
final bool showStack;
|
||||
final int stackIndex;
|
||||
final PageController controller;
|
||||
|
||||
const BottomGalleryBar({
|
||||
super.key,
|
||||
required this.showStack,
|
||||
required this.stackIndex,
|
||||
required this.asset,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
|
||||
|
||||
final stack = showStack && asset.stackChildrenCount > 0
|
||||
? ref.watch(assetStackStateProvider(asset))
|
||||
: <Asset>[];
|
||||
final stackElements = showStack ? [asset, ...stack] : <Asset>[];
|
||||
bool isParent = stackIndex == -1 || stackIndex == 0;
|
||||
|
||||
// !!!! itemsList and actionlist should always be in sync
|
||||
final itemsList = [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
|
||||
),
|
||||
label: 'control_bottom_app_bar_share'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_share'.tr(),
|
||||
),
|
||||
if (isOwner)
|
||||
asset.isArchived
|
||||
? BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.unarchive_rounded),
|
||||
label: 'control_bottom_app_bar_unarchive'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_unarchive'.tr(),
|
||||
)
|
||||
: BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.archive_outlined),
|
||||
label: 'control_bottom_app_bar_archive'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_archive'.tr(),
|
||||
),
|
||||
if (isOwner && stack.isNotEmpty)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.burst_mode_outlined),
|
||||
label: 'control_bottom_app_bar_stack'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_stack'.tr(),
|
||||
),
|
||||
if (isOwner)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
label: 'control_bottom_app_bar_delete'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_delete'.tr(),
|
||||
),
|
||||
if (!isOwner)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.download_outlined),
|
||||
label: 'download'.tr(),
|
||||
tooltip: 'download'.tr(),
|
||||
),
|
||||
];
|
||||
|
||||
void removeAssetFromStack() {
|
||||
if (stackIndex > 0 && showStack) {
|
||||
ref
|
||||
.read(assetStackStateProvider(asset).notifier)
|
||||
.removeChild(stackIndex - 1);
|
||||
stackIndex.value = stackIndex.value - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void handleDelete(Asset deleteAsset) async {
|
||||
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||
if (asset.isReadOnly) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_delete_err_read_only'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Future<bool> onDelete(bool force) async {
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
||||
{deleteAsset},
|
||||
force: force,
|
||||
);
|
||||
if (isDeleted && isParent) {
|
||||
if (totalAssets == 1) {
|
||||
// Handle only one asset
|
||||
context.popRoute();
|
||||
} else {
|
||||
// Go to next page otherwise
|
||||
controller.nextPage(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
// Asset is trashed
|
||||
if (isTrashEnabled && !isFromTrash) {
|
||||
final isDeleted = await onDelete(false);
|
||||
if (isDeleted) {
|
||||
// Can only trash assets stored in server. Local assets are always permanently removed for now
|
||||
if (context.mounted && deleteAsset.isRemote && isParent) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'Asset trashed',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
removeAssetFromStack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Asset is permanently removed
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return DeleteDialog(
|
||||
onDelete: () async {
|
||||
final isDeleted = await onDelete(true);
|
||||
if (isDeleted) {
|
||||
removeAssetFromStack();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showStackActionItems() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
enableDrag: false,
|
||||
builder: (BuildContext ctx) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isParent)
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.bookmark_border_outlined,
|
||||
size: 24,
|
||||
),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(assetStackServiceProvider)
|
||||
.updateStackParent(
|
||||
asset,
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_stack_use_as_main_asset",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.copy_all_outlined,
|
||||
size: 24,
|
||||
),
|
||||
onTap: () async {
|
||||
if (isParent) {
|
||||
await ref
|
||||
.read(assetStackServiceProvider)
|
||||
.updateStackParent(
|
||||
asset,
|
||||
stackElements
|
||||
.elementAt(1), // Next asset as parent
|
||||
);
|
||||
// Remove itself from stack
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
stackElements.elementAt(1),
|
||||
childrenToRemove: [asset],
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
} else {
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
asset,
|
||||
childrenToRemove: [
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
],
|
||||
);
|
||||
removeAssetFromStack();
|
||||
ctx.pop();
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_remove_from_stack",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.filter_none_outlined,
|
||||
size: 18,
|
||||
),
|
||||
onTap: () async {
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
asset,
|
||||
childrenToRemove: stack,
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_unstack",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
shareAsset() {
|
||||
if (asset.isOffline) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_share_err_offline'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
ref.read(imageViewerStateProvider.notifier).shareAsset(asset, context);
|
||||
}
|
||||
|
||||
handleArchive() {
|
||||
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
||||
if (isParent) {
|
||||
context.popRoute();
|
||||
return;
|
||||
}
|
||||
removeAssetFromStack();
|
||||
}
|
||||
|
||||
handleUpload(Asset asset) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return UploadDialog(
|
||||
onUpload: () {
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, [asset]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
handleDownload() {
|
||||
if (asset.isLocal) {
|
||||
return;
|
||||
}
|
||||
if (asset.isOffline) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_share_err_offline'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(imageViewerStateProvider.notifier).downloadAsset(
|
||||
asset,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
List<Function(int)> actionslist = [
|
||||
(_) => shareAsset(),
|
||||
if (isOwner) (_) => handleArchive(),
|
||||
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
||||
if (isOwner) (_) => handleDelete(),
|
||||
if (!isOwner) (_) => handleDownload(),
|
||||
];
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !ref.watch(showControlsProvider),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0,
|
||||
child: Column(
|
||||
children: [
|
||||
if (stack.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
bottom: 30,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: buildStackedChildren(),
|
||||
),
|
||||
),
|
||||
BottomNavigationBar(
|
||||
backgroundColor: Colors.black.withOpacity(0.4),
|
||||
unselectedIconTheme: const IconThemeData(color: Colors.white),
|
||||
selectedIconTheme: const IconThemeData(color: Colors.white),
|
||||
unselectedLabelStyle: const TextStyle(color: Colors.black),
|
||||
selectedLabelStyle: const TextStyle(color: Colors.black),
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
items: itemsList,
|
||||
onTap: (index) {
|
||||
if (index < actionslist.length) {
|
||||
actionslist[index].call(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
119
mobile/lib/modules/asset_viewer/ui/video_controls.dart
Normal file
119
mobile/lib/modules/asset_viewer/ui/video_controls.dart
Normal file
@ -0,0 +1,119 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_value_provider.dart';
|
||||
|
||||
/// The video controls for the [videPlayerControlsProvider]
|
||||
class VideoControls extends ConsumerWidget {
|
||||
const VideoControls({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(videoPlaybackValueProvider);
|
||||
print('player is $player');
|
||||
final duration = player.duration;
|
||||
final position = player.position;
|
||||
|
||||
return AnimatedOpacity(
|
||||
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
child: Container(
|
||||
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: player.duration == Duration.zero
|
||||
? 0.0
|
||||
: min(
|
||||
player.position.inMicroseconds /
|
||||
player.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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -15,12 +15,11 @@ import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart';
|
||||
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_value_provider.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/services/asset_stack.service.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/advanced_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/bottom_gallery_bar.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||
@ -94,16 +93,15 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
final isFromDto = currentAsset.id == Isar.autoIncrement;
|
||||
final album = ref.watch(currentAlbumProvider);
|
||||
|
||||
Asset asset() => stackIndex.value == -1
|
||||
Asset asset = stackIndex.value == -1
|
||||
? currentAsset
|
||||
: stackElements.elementAt(stackIndex.value);
|
||||
final isOwner = asset().ownerId == ref.watch(currentUserProvider)?.isarId;
|
||||
final isOwner = asset.ownerId == ref.watch(currentUserProvider)?.isarId;
|
||||
final isPartner = ref
|
||||
.watch(partnerSharedWithProvider)
|
||||
.map((e) => e.isarId)
|
||||
.contains(asset().ownerId);
|
||||
.contains(asset.ownerId);
|
||||
|
||||
bool isParent = stackIndex.value == -1 || stackIndex.value == 0;
|
||||
|
||||
// Listen provider to prevent autoDispose when navigating to other routes from within the gallery page
|
||||
ref.listen(currentAssetProvider, (_, __) {});
|
||||
@ -111,11 +109,11 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
() {
|
||||
// Delay state update to after the execution of build method
|
||||
Future.microtask(
|
||||
() => ref.read(currentAssetProvider.notifier).set(asset()),
|
||||
() => ref.read(currentAssetProvider.notifier).set(asset),
|
||||
);
|
||||
return null;
|
||||
},
|
||||
[asset()],
|
||||
[asset],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
@ -168,82 +166,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
child: ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting<bool>(AppSettingsEnum.advancedTroubleshooting)
|
||||
? AdvancedBottomSheet(assetDetail: asset())
|
||||
: ExifBottomSheet(asset: asset()),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void removeAssetFromStack() {
|
||||
if (stackIndex.value > 0 && showStack) {
|
||||
ref
|
||||
.read(assetStackStateProvider(currentAsset).notifier)
|
||||
.removeChild(stackIndex.value - 1);
|
||||
stackIndex.value = stackIndex.value - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void handleDelete(Asset deleteAsset) async {
|
||||
// Cannot delete readOnly / external assets. They are handled through library offline jobs
|
||||
if (asset().isReadOnly) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_delete_err_read_only'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
Future<bool> onDelete(bool force) async {
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(
|
||||
{deleteAsset},
|
||||
force: force,
|
||||
);
|
||||
if (isDeleted && isParent) {
|
||||
if (totalAssets == 1) {
|
||||
// Handle only one asset
|
||||
context.popRoute();
|
||||
} else {
|
||||
// Go to next page otherwise
|
||||
controller.nextPage(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.fastLinearToSlowEaseIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
// Asset is trashed
|
||||
if (isTrashEnabled && !isFromTrash) {
|
||||
final isDeleted = await onDelete(false);
|
||||
if (isDeleted) {
|
||||
// Can only trash assets stored in server. Local assets are always permanently removed for now
|
||||
if (context.mounted && deleteAsset.isRemote && isParent) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'Asset trashed',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
removeAssetFromStack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Asset is permanently removed
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return DeleteDialog(
|
||||
onDelete: () async {
|
||||
final isDeleted = await onDelete(true);
|
||||
if (isDeleted) {
|
||||
removeAssetFromStack();
|
||||
}
|
||||
},
|
||||
? AdvancedBottomSheet(assetDetail: asset)
|
||||
: ExifBottomSheet(asset: asset),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -293,63 +217,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
shareAsset() {
|
||||
if (asset().isOffline) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_share_err_offline'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
ref.read(imageViewerStateProvider.notifier).shareAsset(asset(), context);
|
||||
}
|
||||
|
||||
handleArchive(Asset asset) {
|
||||
ref.read(assetProvider.notifier).toggleArchive([asset]);
|
||||
if (isParent) {
|
||||
context.popRoute();
|
||||
return;
|
||||
}
|
||||
removeAssetFromStack();
|
||||
}
|
||||
|
||||
handleUpload(Asset asset) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext _) {
|
||||
return UploadDialog(
|
||||
onUpload: () {
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, [asset]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
handleDownload() {
|
||||
if (asset().isLocal) {
|
||||
return;
|
||||
}
|
||||
if (asset().isOffline) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 1,
|
||||
context: context,
|
||||
msg: 'asset_action_share_err_offline'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(imageViewerStateProvider.notifier).downloadAsset(
|
||||
asset(),
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
handleActivities() {
|
||||
if (album != null && album.shared && album.remoteId != null) {
|
||||
context.pushRoute(const ActivitiesRoute());
|
||||
@ -368,22 +235,22 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
isOwner: isOwner,
|
||||
isPartner: isPartner,
|
||||
isPlayingMotionVideo: isPlayingMotionVideo.value,
|
||||
asset: asset(),
|
||||
asset: asset,
|
||||
onMoreInfoPressed: showInfo,
|
||||
onFavorite: toggleFavorite,
|
||||
onUploadPressed:
|
||||
asset().isLocal ? () => handleUpload(asset()) : null,
|
||||
onDownloadPressed: asset().isLocal
|
||||
asset.isLocal ? () => handleUpload(asset) : null,
|
||||
onDownloadPressed: asset.isLocal
|
||||
? null
|
||||
: () =>
|
||||
ref.read(imageViewerStateProvider.notifier).downloadAsset(
|
||||
asset(),
|
||||
asset,
|
||||
context,
|
||||
),
|
||||
onToggleMotionVideo: (() {
|
||||
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
||||
}),
|
||||
onAddToAlbumPressed: () => addToAlbum(asset()),
|
||||
onAddToAlbumPressed: () => addToAlbum(asset),
|
||||
onActivitiesPressed: handleActivities,
|
||||
),
|
||||
),
|
||||
@ -391,313 +258,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildProgressBar() {
|
||||
final playerValue = ref.watch(videoPlaybackValueProvider);
|
||||
|
||||
return Expanded(
|
||||
child: Slider(
|
||||
value: playerValue.duration == Duration.zero
|
||||
? 0.0
|
||||
: min(
|
||||
playerValue.position.inMicroseconds /
|
||||
playerValue.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 buildPosition() {
|
||||
final position = ref
|
||||
.watch(videoPlaybackValueProvider.select((value) => value.position));
|
||||
|
||||
return Text(
|
||||
_formatDuration(position),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white.withOpacity(.75),
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Text buildDuration() {
|
||||
final duration = ref
|
||||
.watch(videoPlaybackValueProvider.select((value) => value.duration));
|
||||
|
||||
return Text(
|
||||
_formatDuration(duration),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white.withOpacity(.75),
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildMuteButton() {
|
||||
return 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,
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildStackedChildren() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
itemBuilder: (context, index) {
|
||||
final assetId = stackElements.elementAt(index).remoteId;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: GestureDetector(
|
||||
onTap: () => stackIndex.value = index,
|
||||
child: Container(
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: (stackIndex.value == -1 && index == 0) ||
|
||||
index == stackIndex.value
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: ImmichRemoteImageProvider(assetId: assetId!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showStackActionItems() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
enableDrag: false,
|
||||
builder: (BuildContext ctx) {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!isParent)
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.bookmark_border_outlined,
|
||||
size: 24,
|
||||
),
|
||||
onTap: () async {
|
||||
await ref
|
||||
.read(assetStackServiceProvider)
|
||||
.updateStackParent(
|
||||
currentAsset,
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_stack_use_as_main_asset",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.copy_all_outlined,
|
||||
size: 24,
|
||||
),
|
||||
onTap: () async {
|
||||
if (isParent) {
|
||||
await ref
|
||||
.read(assetStackServiceProvider)
|
||||
.updateStackParent(
|
||||
currentAsset,
|
||||
stackElements
|
||||
.elementAt(1), // Next asset as parent
|
||||
);
|
||||
// Remove itself from stack
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
stackElements.elementAt(1),
|
||||
childrenToRemove: [currentAsset],
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
} else {
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
currentAsset,
|
||||
childrenToRemove: [
|
||||
stackElements.elementAt(stackIndex.value),
|
||||
],
|
||||
);
|
||||
removeAssetFromStack();
|
||||
ctx.pop();
|
||||
}
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_remove_from_stack",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.filter_none_outlined,
|
||||
size: 18,
|
||||
),
|
||||
onTap: () async {
|
||||
await ref.read(assetStackServiceProvider).updateStack(
|
||||
currentAsset,
|
||||
childrenToRemove: stack,
|
||||
);
|
||||
ctx.pop();
|
||||
context.popRoute();
|
||||
},
|
||||
title: const Text(
|
||||
"viewer_unstack",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Migrate to a custom bottom bar and handle long press to delete
|
||||
Widget buildBottomBar() {
|
||||
// !!!! itemsList and actionlist should always be in sync
|
||||
final itemsList = [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
|
||||
),
|
||||
label: 'control_bottom_app_bar_share'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_share'.tr(),
|
||||
),
|
||||
if (isOwner)
|
||||
asset().isArchived
|
||||
? BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.unarchive_rounded),
|
||||
label: 'control_bottom_app_bar_unarchive'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_unarchive'.tr(),
|
||||
)
|
||||
: BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.archive_outlined),
|
||||
label: 'control_bottom_app_bar_archive'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_archive'.tr(),
|
||||
),
|
||||
if (isOwner && stack.isNotEmpty)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.burst_mode_outlined),
|
||||
label: 'control_bottom_app_bar_stack'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_stack'.tr(),
|
||||
),
|
||||
if (isOwner)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
label: 'control_bottom_app_bar_delete'.tr(),
|
||||
tooltip: 'control_bottom_app_bar_delete'.tr(),
|
||||
),
|
||||
if (!isOwner)
|
||||
BottomNavigationBarItem(
|
||||
icon: const Icon(Icons.download_outlined),
|
||||
label: 'download'.tr(),
|
||||
tooltip: 'download'.tr(),
|
||||
),
|
||||
];
|
||||
|
||||
List<Function(int)> actionslist = [
|
||||
(_) => shareAsset(),
|
||||
if (isOwner) (_) => handleArchive(asset()),
|
||||
if (isOwner && stack.isNotEmpty) (_) => showStackActionItems(),
|
||||
if (isOwner) (_) => handleDelete(asset()),
|
||||
if (!isOwner) (_) => handleDownload(),
|
||||
];
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !ref.watch(showControlsProvider),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
opacity: ref.watch(showControlsProvider) ? 1.0 : 0.0,
|
||||
child: Column(
|
||||
children: [
|
||||
if (stack.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
bottom: 30,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: buildStackedChildren(),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: !asset().isImage && !isPlayingMotionVideo.value,
|
||||
child: Container(
|
||||
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: [
|
||||
buildPosition(),
|
||||
buildProgressBar(),
|
||||
buildDuration(),
|
||||
buildMuteButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
BottomNavigationBar(
|
||||
backgroundColor: Colors.black.withOpacity(0.4),
|
||||
unselectedIconTheme: const IconThemeData(color: Colors.white),
|
||||
selectedIconTheme: const IconThemeData(color: Colors.white),
|
||||
unselectedLabelStyle: const TextStyle(color: Colors.black),
|
||||
selectedLabelStyle: const TextStyle(color: Colors.black),
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
items: itemsList,
|
||||
onTap: (index) {
|
||||
if (index < actionslist.length) {
|
||||
actionslist[index].call(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
@ -735,6 +297,44 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
});
|
||||
|
||||
Widget buildStackedChildren() {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
itemBuilder: (context, index) {
|
||||
final assetId = stackElements.elementAt(index).remoteId;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: GestureDetector(
|
||||
onTap: () => stackIndex.value = index,
|
||||
child: Container(
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: (stackIndex.value == -1 && index == 0) ||
|
||||
index == stackIndex.value
|
||||
? Border.all(
|
||||
color: Colors.white,
|
||||
width: 2,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: ImmichRemoteImageProvider(assetId: assetId!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (_) {
|
||||
@ -762,7 +362,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
ImmichThumbnail(
|
||||
asset: asset(),
|
||||
asset: asset,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
],
|
||||
@ -790,7 +390,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
},
|
||||
builder: (context, index) {
|
||||
final a =
|
||||
index == currentIndex.value ? asset() : loadAsset(index);
|
||||
index == currentIndex.value ? asset : loadAsset(index);
|
||||
final ImageProvider provider =
|
||||
ImmichImage.imageProvider(asset: a);
|
||||
|
||||
@ -870,44 +470,15 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: buildBottomBar(),
|
||||
child: Column(
|
||||
children: [
|
||||
buildStackedChildren(),
|
||||
BottomGalleryBar(
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/hooks/chewiew_controller_hook.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/video_controls.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/ui/video_player_controls.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/ui/delayed_loading_indicator.dart';
|
||||
@ -50,13 +51,16 @@ class VideoViewerPage extends HookWidget {
|
||||
);
|
||||
|
||||
// Loading
|
||||
final size = MediaQuery.of(context).size;
|
||||
print('showControls $showControls and isMotion $isMotionVideo');
|
||||
return PopScope(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (controller == null) {
|
||||
return Stack(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: controller == null,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (placeholder != null) placeholder!,
|
||||
const Positioned.fill(
|
||||
@ -67,18 +71,30 @@ class VideoViewerPage extends HookWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final size = MediaQuery.of(context).size;
|
||||
return SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Chewie(
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (controller != null)
|
||||
SizedBox(
|
||||
height: size.height,
|
||||
width: size.width,
|
||||
child: Chewie(
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
if (controller != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(
|
||||
width: size.width,
|
||||
child: Visibility(
|
||||
visible: true,
|
||||
child: const VideoControls(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
Binary file not shown.
@ -1393,7 +1393,7 @@ class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
|
||||
void Function()? onPaused,
|
||||
Widget? placeholder,
|
||||
bool showControls = true,
|
||||
Duration hideControlsTimer = const Duration(milliseconds: 1500),
|
||||
Duration hideControlsTimer = const Duration(seconds: 5),
|
||||
bool showDownloadingIndicator = true,
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
@ -1429,7 +1429,7 @@ class VideoViewerRouteArgs {
|
||||
this.onPaused,
|
||||
this.placeholder,
|
||||
this.showControls = true,
|
||||
this.hideControlsTimer = const Duration(milliseconds: 1500),
|
||||
this.hideControlsTimer = const Duration(seconds: 5),
|
||||
this.showDownloadingIndicator = true,
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user