mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Browse enhanced photos
This commit is contained in:
parent
39840c8e45
commit
ef4daf552b
9 changed files with 853 additions and 7 deletions
131
app/lib/bloc/scan_local_dir.dart
Normal file
131
app/lib/bloc/scan_local_dir.dart
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
import 'package:nc_photos/event/event.dart';
|
||||||
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
import 'package:nc_photos/use_case/scan_local_dir.dart';
|
||||||
|
|
||||||
|
abstract class ScanLocalDirBlocEvent {
|
||||||
|
const ScanLocalDirBlocEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBlocQuery extends ScanLocalDirBlocEvent {
|
||||||
|
const ScanLocalDirBlocQuery(this.relativePaths);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "$runtimeType {"
|
||||||
|
"relativePaths: ${relativePaths.toReadableString()}, "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
final List<String> relativePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScanLocalDirBlocFileDeleted extends ScanLocalDirBlocEvent {
|
||||||
|
const _ScanLocalDirBlocFileDeleted(this.files);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "$runtimeType {"
|
||||||
|
"files: ${files.map((f) => f.logTag).toReadableString()}, "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
final List<LocalFile> files;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ScanLocalDirBlocState {
|
||||||
|
const ScanLocalDirBlocState(this.files);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "$runtimeType {"
|
||||||
|
"files: List {length: ${files.length}}, "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
final List<LocalFile> files;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBlocInit extends ScanLocalDirBlocState {
|
||||||
|
const ScanLocalDirBlocInit() : super(const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBlocLoading extends ScanLocalDirBlocState {
|
||||||
|
const ScanLocalDirBlocLoading(List<LocalFile> files) : super(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBlocSuccess extends ScanLocalDirBlocState {
|
||||||
|
const ScanLocalDirBlocSuccess(List<LocalFile> files) : super(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBlocFailure extends ScanLocalDirBlocState {
|
||||||
|
const ScanLocalDirBlocFailure(List<LocalFile> files, this.exception)
|
||||||
|
: super(files);
|
||||||
|
|
||||||
|
@override
|
||||||
|
toString() => "$runtimeType {"
|
||||||
|
"super: ${super.toString()}, "
|
||||||
|
"exception: $exception, "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
final dynamic exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanLocalDirBloc
|
||||||
|
extends Bloc<ScanLocalDirBlocEvent, ScanLocalDirBlocState> {
|
||||||
|
ScanLocalDirBloc() : super(const ScanLocalDirBlocInit()) {
|
||||||
|
on<ScanLocalDirBlocQuery>(_onScanLocalDirBlocQuery);
|
||||||
|
on<_ScanLocalDirBlocFileDeleted>(_onScanLocalDirBlocFileDeleted);
|
||||||
|
|
||||||
|
_fileDeletedEventListener.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
close() {
|
||||||
|
_fileDeletedEventListener.end();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onScanLocalDirBlocQuery(
|
||||||
|
ScanLocalDirBlocQuery event, Emitter<ScanLocalDirBlocState> emit) async {
|
||||||
|
final shouldEmitIntermediate = state.files.isEmpty;
|
||||||
|
try {
|
||||||
|
emit(ScanLocalDirBlocLoading(state.files));
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
final products = <LocalFile>[];
|
||||||
|
for (final p in event.relativePaths) {
|
||||||
|
if (shouldEmitIntermediate) {
|
||||||
|
emit(ScanLocalDirBlocLoading(products));
|
||||||
|
}
|
||||||
|
final files = await ScanLocalDir(c)(p);
|
||||||
|
products.addAll(files);
|
||||||
|
}
|
||||||
|
emit(ScanLocalDirBlocSuccess(products));
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"[_onScanLocalDirBlocQuery] Exception while request", e, stackTrace);
|
||||||
|
emit(ScanLocalDirBlocFailure(state.files, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onScanLocalDirBlocFileDeleted(
|
||||||
|
_ScanLocalDirBlocFileDeleted event,
|
||||||
|
Emitter<ScanLocalDirBlocState> emit) async {
|
||||||
|
final newFiles = state.files
|
||||||
|
.where((f) => !event.files.any((d) => d.compareIdentity(f)))
|
||||||
|
.toList();
|
||||||
|
if (newFiles.length != state.files.length) {
|
||||||
|
emit(ScanLocalDirBlocSuccess(newFiles));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onFileDeletedEvent(LocalFileDeletedEvent ev) {
|
||||||
|
if (state is ScanLocalDirBlocInit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
add(_ScanLocalDirBlocFileDeleted(ev.files));
|
||||||
|
}
|
||||||
|
|
||||||
|
late final _fileDeletedEventListener =
|
||||||
|
AppEventListener<LocalFileDeletedEvent>(_onFileDeletedEvent);
|
||||||
|
|
||||||
|
static final _log = Logger("bloc.scan_local_dir.ScanLocalDirBloc");
|
||||||
|
}
|
|
@ -11,8 +11,11 @@ bool isSupportedMime(String mime) => _supportedFormatMimes.contains(mime);
|
||||||
|
|
||||||
bool isSupportedFormat(File file) => isSupportedMime(file.contentType ?? "");
|
bool isSupportedFormat(File file) => isSupportedMime(file.contentType ?? "");
|
||||||
|
|
||||||
|
bool isSupportedImageMime(String mime) =>
|
||||||
|
isSupportedMime(mime) && mime.startsWith("image/") == true;
|
||||||
|
|
||||||
bool isSupportedImageFormat(File file) =>
|
bool isSupportedImageFormat(File file) =>
|
||||||
isSupportedFormat(file) && file.contentType?.startsWith("image/") == true;
|
isSupportedImageMime(file.contentType ?? "");
|
||||||
|
|
||||||
bool isSupportedVideoFormat(File file) =>
|
bool isSupportedVideoFormat(File file) =>
|
||||||
isSupportedFormat(file) && file.contentType?.startsWith("video/") == true;
|
isSupportedFormat(file) && file.contentType?.startsWith("video/") == true;
|
||||||
|
|
|
@ -1179,6 +1179,14 @@
|
||||||
"@enhanceLowLightTitle": {
|
"@enhanceLowLightTitle": {
|
||||||
"description": "Enhance a photo taken in low-light environment"
|
"description": "Enhance a photo taken in low-light environment"
|
||||||
},
|
},
|
||||||
|
"collectionEnhancedPhotosLabel": "Enhanced photos",
|
||||||
|
"@collectionEnhancedPhotosLabel": {
|
||||||
|
"description": "List photos enhanced by the app"
|
||||||
|
},
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent": "Selected items will be deleted permanently from this device.\n\nThis action is nonreversible",
|
||||||
|
"@deletePermanentlyLocalConfirmationDialogContent": {
|
||||||
|
"description": "Make sure the user wants to delete the items from the current device"
|
||||||
|
},
|
||||||
|
|
||||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
|
|
|
@ -86,6 +86,8 @@
|
||||||
"metadataTaskPauseLowBatteryNotification",
|
"metadataTaskPauseLowBatteryNotification",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -190,6 +192,8 @@
|
||||||
"metadataTaskPauseLowBatteryNotification",
|
"metadataTaskPauseLowBatteryNotification",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -349,6 +353,8 @@
|
||||||
"metadataTaskPauseLowBatteryNotification",
|
"metadataTaskPauseLowBatteryNotification",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle",
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent",
|
||||||
"errorAlbumDowngrade"
|
"errorAlbumDowngrade"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -358,12 +364,16 @@
|
||||||
"backgroundServiceStopping",
|
"backgroundServiceStopping",
|
||||||
"metadataTaskPauseLowBatteryNotification",
|
"metadataTaskPauseLowBatteryNotification",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -372,7 +382,9 @@
|
||||||
"helpButtonLabel",
|
"helpButtonLabel",
|
||||||
"removeFromAlbumTooltip",
|
"removeFromAlbumTooltip",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
|
@ -398,16 +410,22 @@
|
||||||
"backgroundServiceStopping",
|
"backgroundServiceStopping",
|
||||||
"metadataTaskPauseLowBatteryNotification",
|
"metadataTaskPauseLowBatteryNotification",
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"enhanceTooltip",
|
"enhanceTooltip",
|
||||||
"enhanceLowLightTitle"
|
"enhanceLowLightTitle",
|
||||||
|
"collectionEnhancedPhotosLabel",
|
||||||
|
"deletePermanentlyLocalConfirmationDialogContent"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
369
app/lib/widget/enhanced_photo_browser.dart
Normal file
369
app/lib/widget/enhanced_photo_browser.dart
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/bloc/scan_local_dir.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/mobile/android/content_uri_image_provider.dart';
|
||||||
|
import 'package:nc_photos/pref.dart';
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/widget/empty_list_indicator.dart';
|
||||||
|
import 'package:nc_photos/widget/handler/delete_local_selection_handler.dart';
|
||||||
|
import 'package:nc_photos/widget/local_file_viewer.dart';
|
||||||
|
import 'package:nc_photos/widget/photo_list_util.dart' as photo_list_util;
|
||||||
|
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||||
|
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
|
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
|
||||||
|
|
||||||
|
class EnhancedPhotoBrowserArguments {
|
||||||
|
const EnhancedPhotoBrowserArguments(this.filename);
|
||||||
|
|
||||||
|
final String? filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnhancedPhotoBrowser extends StatefulWidget {
|
||||||
|
static const routeName = "/enhanced-photo-browser";
|
||||||
|
|
||||||
|
static Route buildRoute(EnhancedPhotoBrowserArguments args) =>
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EnhancedPhotoBrowser.fromArgs(args),
|
||||||
|
);
|
||||||
|
|
||||||
|
const EnhancedPhotoBrowser({
|
||||||
|
Key? key,
|
||||||
|
required this.filename,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
EnhancedPhotoBrowser.fromArgs(EnhancedPhotoBrowserArguments args, {Key? key})
|
||||||
|
: this(
|
||||||
|
key: key,
|
||||||
|
filename: args.filename,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
createState() => _EnhancedPhotoBrowserState();
|
||||||
|
|
||||||
|
final String? filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EnhancedPhotoBrowserState extends State<EnhancedPhotoBrowser>
|
||||||
|
with SelectableItemStreamListMixin<EnhancedPhotoBrowser> {
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_initBloc();
|
||||||
|
_thumbZoomLevel = Pref().getAlbumBrowserZoomLevelOr(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
build(BuildContext context) {
|
||||||
|
return AppTheme(
|
||||||
|
child: Scaffold(
|
||||||
|
body: BlocListener<ScanLocalDirBloc, ScanLocalDirBlocState>(
|
||||||
|
bloc: _bloc,
|
||||||
|
listener: (context, state) => _onStateChange(context, state),
|
||||||
|
child: BlocBuilder<ScanLocalDirBloc, ScanLocalDirBlocState>(
|
||||||
|
bloc: _bloc,
|
||||||
|
builder: (context, state) => _buildContent(context, state),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initBloc() {
|
||||||
|
if (_bloc.state is ScanLocalDirBlocInit) {
|
||||||
|
_log.info("[_initBloc] Initialize bloc");
|
||||||
|
_reqQuery();
|
||||||
|
} else {
|
||||||
|
// process the current state
|
||||||
|
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||||
|
setState(() {
|
||||||
|
_onStateChange(context, _bloc.state);
|
||||||
|
});
|
||||||
|
_reqQuery();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context, ScanLocalDirBlocState state) {
|
||||||
|
if (state is ScanLocalDirBlocSuccess && itemStreamListItems.isEmpty) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
AppBar(
|
||||||
|
title: Text(L10n.global().collectionEnhancedPhotosLabel),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: EmptyListIndicator(
|
||||||
|
icon: Icons.folder_outlined,
|
||||||
|
text: L10n.global().listEmptyText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
buildItemStreamListOuter(
|
||||||
|
context,
|
||||||
|
child: Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
colorScheme: Theme.of(context).colorScheme.copyWith(
|
||||||
|
secondary: AppTheme.getOverscrollIndicatorColor(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
_buildAppBar(context),
|
||||||
|
buildItemStreamList(
|
||||||
|
maxCrossAxisExtent: _thumbSize.toDouble(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (state is ScanLocalDirBlocLoading)
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: LinearProgressIndicator(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAppBar(BuildContext context) {
|
||||||
|
if (isSelectionMode) {
|
||||||
|
return _buildSelectionAppBar(context);
|
||||||
|
} else {
|
||||||
|
return _buildNormalAppBar(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNormalAppBar(BuildContext context) => SliverAppBar(
|
||||||
|
title: Text(L10n.global().collectionEnhancedPhotosLabel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSelectionAppBar(BuildContext context) {
|
||||||
|
return SelectionAppBar(
|
||||||
|
count: selectedListItems.length,
|
||||||
|
onClosePressed: () {
|
||||||
|
setState(() {
|
||||||
|
clearSelectedItems();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton<_SelectionMenuOption>(
|
||||||
|
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: _SelectionMenuOption.delete,
|
||||||
|
child: Text(L10n.global().deletePermanentlyTooltip),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (option) => _onSelectionMenuSelected(context, option),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onStateChange(BuildContext context, ScanLocalDirBlocState state) {
|
||||||
|
if (state is ScanLocalDirBlocInit) {
|
||||||
|
itemStreamListItems = [];
|
||||||
|
} else if (state is ScanLocalDirBlocLoading) {
|
||||||
|
_transformItems(state.files);
|
||||||
|
} else if (state is ScanLocalDirBlocSuccess) {
|
||||||
|
_transformItems(state.files);
|
||||||
|
if (_isFirstRun) {
|
||||||
|
_isFirstRun = false;
|
||||||
|
if (widget.filename != null) {
|
||||||
|
_openInitialImage(widget.filename!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (state is ScanLocalDirBlocFailure) {
|
||||||
|
_transformItems(state.files);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(state.exception is PermissionException
|
||||||
|
? L10n.global().errorNoStoragePermission
|
||||||
|
: exception_util.toUserString(state.exception)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSelectionMenuSelected(
|
||||||
|
BuildContext context, _SelectionMenuOption option) {
|
||||||
|
switch (option) {
|
||||||
|
case _SelectionMenuOption.delete:
|
||||||
|
_onSelectionDeletePressed(context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_log.shout("[_onSelectionMenuSelected] Unknown option: $option");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSelectionDeletePressed(BuildContext context) async {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(L10n.global().deletePermanentlyConfirmationDialogTitle),
|
||||||
|
content: Text(
|
||||||
|
L10n.global().deletePermanentlyLocalConfirmationDialogContent,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Text(L10n.global().confirmButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final selectedFiles = selectedListItems
|
||||||
|
.whereType<_FileListItem>()
|
||||||
|
.map((e) => e.file)
|
||||||
|
.toList();
|
||||||
|
setState(() {
|
||||||
|
clearSelectedItems();
|
||||||
|
});
|
||||||
|
await const DeleteLocalSelectionHandler()(selectedFiles: selectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onItemTap(int index) {
|
||||||
|
Navigator.pushNamed(context, LocalFileViewer.routeName,
|
||||||
|
arguments: LocalFileViewerArguments(_backingFiles, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _transformItems(List<LocalFile> files) {
|
||||||
|
// we use last modified here to keep newly enhanced photo at the top
|
||||||
|
_backingFiles =
|
||||||
|
files.stableSorted((a, b) => b.lastModified.compareTo(a.lastModified));
|
||||||
|
|
||||||
|
itemStreamListItems = () sync* {
|
||||||
|
for (int i = 0; i < _backingFiles.length; ++i) {
|
||||||
|
final f = _backingFiles[i];
|
||||||
|
if (file_util.isSupportedImageMime(f.mime ?? "")) {
|
||||||
|
yield _ImageListItem(
|
||||||
|
file: f,
|
||||||
|
onTap: () => _onItemTap(i),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
.toList();
|
||||||
|
_log.info("[_transformItems] Length: ${itemStreamListItems.length}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openInitialImage(String filename) {
|
||||||
|
final index = _backingFiles.indexWhere((f) => f.filename == filename);
|
||||||
|
if (index == -1) {
|
||||||
|
_log.severe("[openInitialImage] Filename not found: $filename");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.pushNamed(context, LocalFileViewer.routeName,
|
||||||
|
arguments: LocalFileViewerArguments(_backingFiles, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _reqQuery() {
|
||||||
|
_bloc.add(const ScanLocalDirBlocQuery(
|
||||||
|
["Download/Photos (for Nextcloud)/Enhanced Photos"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
final _bloc = ScanLocalDirBloc();
|
||||||
|
|
||||||
|
var _backingFiles = <LocalFile>[];
|
||||||
|
|
||||||
|
var _isFirstRun = true;
|
||||||
|
var _thumbZoomLevel = 0;
|
||||||
|
int get _thumbSize => photo_list_util.getThumbSize(_thumbZoomLevel);
|
||||||
|
|
||||||
|
static final _log =
|
||||||
|
Logger("widget.enhanced_photo_browser._EnhancedPhotoBrowserState");
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ListItem implements SelectableItem {
|
||||||
|
_ListItem({
|
||||||
|
VoidCallback? onTap,
|
||||||
|
}) : _onTap = onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get onTap => _onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get isSelectable => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
get staggeredTile => const StaggeredTile.count(1, 1);
|
||||||
|
|
||||||
|
final VoidCallback? _onTap;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _FileListItem extends _ListItem {
|
||||||
|
_FileListItem({
|
||||||
|
required this.file,
|
||||||
|
VoidCallback? onTap,
|
||||||
|
}) : super(onTap: onTap);
|
||||||
|
|
||||||
|
final LocalFile file;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ImageListItem extends _FileListItem {
|
||||||
|
_ImageListItem({
|
||||||
|
required LocalFile file,
|
||||||
|
VoidCallback? onTap,
|
||||||
|
}) : super(file: file, onTap: onTap);
|
||||||
|
|
||||||
|
@override
|
||||||
|
buildWidget(BuildContext context) {
|
||||||
|
final ImageProvider provider;
|
||||||
|
if (file is LocalUriFile) {
|
||||||
|
provider = ContentUriImage((file as LocalUriFile).uri);
|
||||||
|
} else {
|
||||||
|
throw ArgumentError("Invalid file");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: FittedBox(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
child: Container(
|
||||||
|
// arbitrary size here
|
||||||
|
constraints: BoxConstraints.tight(const Size(128, 128)),
|
||||||
|
color: AppTheme.getListItemBackgroundColor(context),
|
||||||
|
child: Image(
|
||||||
|
image: ResizeImage.resizeIfNeeded(k.photoThumbSize, null, provider),
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
errorBuilder: (context, e, stackTrace) {
|
||||||
|
return Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.image_not_supported,
|
||||||
|
size: 64,
|
||||||
|
color: Colors.white.withOpacity(.8),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _SelectionMenuOption {
|
||||||
|
delete,
|
||||||
|
}
|
51
app/lib/widget/handler/delete_local_selection_handler.dart
Normal file
51
app/lib/widget/handler/delete_local_selection_handler.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/debug_util.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/use_case/delete_local.dart';
|
||||||
|
|
||||||
|
class DeleteLocalSelectionHandler {
|
||||||
|
const DeleteLocalSelectionHandler();
|
||||||
|
|
||||||
|
/// Delete [selectedFiles] permanently from device
|
||||||
|
Future<int> call({
|
||||||
|
required List<LocalFile> selectedFiles,
|
||||||
|
bool isRemoveOpened = false,
|
||||||
|
}) async {
|
||||||
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
|
var failureCount = 0;
|
||||||
|
await DeleteLocal(c)(
|
||||||
|
selectedFiles,
|
||||||
|
onFailure: (file, e, stackTrace) {
|
||||||
|
if (e != null) {
|
||||||
|
_log.shout(
|
||||||
|
"[call] Failed while deleting file: ${logFilename(file.logTag)}",
|
||||||
|
e,
|
||||||
|
stackTrace);
|
||||||
|
}
|
||||||
|
++failureCount;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (failureCount == 0) {
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(L10n.global().deleteSelectedSuccessNotification),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content:
|
||||||
|
Text(L10n.global().deleteSelectedFailureNotification(failureCount)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return selectedFiles.length - failureCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final _log = Logger(
|
||||||
|
"widget.handler.delete_local_selection_handler.DeleteLocalSelectionHandler");
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import 'package:nc_photos/widget/album_search_delegate.dart';
|
||||||
import 'package:nc_photos/widget/archive_browser.dart';
|
import 'package:nc_photos/widget/archive_browser.dart';
|
||||||
import 'package:nc_photos/widget/builder/album_grid_item_builder.dart';
|
import 'package:nc_photos/widget/builder/album_grid_item_builder.dart';
|
||||||
import 'package:nc_photos/widget/dynamic_album_browser.dart';
|
import 'package:nc_photos/widget/dynamic_album_browser.dart';
|
||||||
|
import 'package:nc_photos/widget/enhanced_photo_browser.dart';
|
||||||
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
||||||
import 'package:nc_photos/widget/favorite_browser.dart';
|
import 'package:nc_photos/widget/favorite_browser.dart';
|
||||||
import 'package:nc_photos/widget/home_app_bar.dart';
|
import 'package:nc_photos/widget/home_app_bar.dart';
|
||||||
|
@ -37,6 +38,7 @@ import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
|
||||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/sharing_browser.dart';
|
import 'package:nc_photos/widget/sharing_browser.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_browser.dart';
|
import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||||
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
|
|
||||||
class HomeAlbums extends StatefulWidget {
|
class HomeAlbums extends StatefulWidget {
|
||||||
const HomeAlbums({
|
const HomeAlbums({
|
||||||
|
@ -285,6 +287,19 @@ class _HomeAlbumsState extends State<HomeAlbums>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SelectableItem _buildEnhancedPhotosItem(BuildContext context) {
|
||||||
|
return _ButtonListItem(
|
||||||
|
icon: Icons.auto_fix_high_outlined,
|
||||||
|
label: L10n.global().collectionEnhancedPhotosLabel,
|
||||||
|
onTap: () {
|
||||||
|
if (!isSelectionMode) {
|
||||||
|
Navigator.of(context).pushNamed(EnhancedPhotoBrowser.routeName,
|
||||||
|
arguments: const EnhancedPhotoBrowserArguments(null));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
SelectableItem _buildNewAlbumItem(BuildContext context) {
|
SelectableItem _buildNewAlbumItem(BuildContext context) {
|
||||||
return _ButtonListItem(
|
return _ButtonListItem(
|
||||||
icon: Icons.add,
|
icon: Icons.add,
|
||||||
|
@ -483,6 +498,7 @@ class _HomeAlbumsState extends State<HomeAlbums>
|
||||||
if (AccountPref.of(widget.account).isEnableFaceRecognitionAppOr())
|
if (AccountPref.of(widget.account).isEnableFaceRecognitionAppOr())
|
||||||
_buildPersonItem(context),
|
_buildPersonItem(context),
|
||||||
_buildSharingItem(context),
|
_buildSharingItem(context),
|
||||||
|
if (features.isSupportEnhancement) _buildEnhancedPhotosItem(context),
|
||||||
_buildArchiveItem(context),
|
_buildArchiveItem(context),
|
||||||
_buildTrashbinItem(context),
|
_buildTrashbinItem(context),
|
||||||
_buildNewAlbumItem(context),
|
_buildNewAlbumItem(context),
|
||||||
|
|
218
app/lib/widget/local_file_viewer.dart
Normal file
218
app/lib/widget/local_file_viewer.dart
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
import 'package:nc_photos/entity/local_file.dart';
|
||||||
|
import 'package:nc_photos/theme.dart';
|
||||||
|
import 'package:nc_photos/widget/handler/delete_local_selection_handler.dart';
|
||||||
|
import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
||||||
|
import 'package:nc_photos/widget/image_viewer.dart';
|
||||||
|
|
||||||
|
class LocalFileViewerArguments {
|
||||||
|
LocalFileViewerArguments(this.streamFiles, this.startIndex);
|
||||||
|
|
||||||
|
final List<LocalFile> streamFiles;
|
||||||
|
final int startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalFileViewer extends StatefulWidget {
|
||||||
|
static const routeName = "/local-file-viewer";
|
||||||
|
|
||||||
|
static Route buildRoute(LocalFileViewerArguments args) => MaterialPageRoute(
|
||||||
|
builder: (context) => LocalFileViewer.fromArgs(args),
|
||||||
|
);
|
||||||
|
|
||||||
|
const LocalFileViewer({
|
||||||
|
Key? key,
|
||||||
|
required this.streamFiles,
|
||||||
|
required this.startIndex,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
LocalFileViewer.fromArgs(LocalFileViewerArguments args, {Key? key})
|
||||||
|
: this(
|
||||||
|
key: key,
|
||||||
|
streamFiles: args.streamFiles,
|
||||||
|
startIndex: args.startIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
createState() => _LocalFileViewerState();
|
||||||
|
|
||||||
|
final List<LocalFile> streamFiles;
|
||||||
|
final int startIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LocalFileViewerState extends State<LocalFileViewer> {
|
||||||
|
@override
|
||||||
|
build(BuildContext context) {
|
||||||
|
return AppTheme(
|
||||||
|
child: Scaffold(
|
||||||
|
body: Builder(
|
||||||
|
builder: _buildContent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isShowVideoControl = !_isShowVideoControl;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(color: Colors.black),
|
||||||
|
if (!_isViewerLoaded ||
|
||||||
|
!_pageStates[_viewerController.currentPage]!.hasLoaded)
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
HorizontalPageViewer(
|
||||||
|
pageCount: widget.streamFiles.length,
|
||||||
|
pageBuilder: _buildPage,
|
||||||
|
initialPage: widget.startIndex,
|
||||||
|
controller: _viewerController,
|
||||||
|
viewportFraction: _viewportFraction,
|
||||||
|
canSwitchPage: _canSwitchPage,
|
||||||
|
),
|
||||||
|
_buildAppBar(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAppBar(BuildContext context) {
|
||||||
|
return Wrap(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
// + status bar height
|
||||||
|
height: kToolbarHeight + MediaQuery.of(context).padding.top,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment(0, -1),
|
||||||
|
end: Alignment(0, 1),
|
||||||
|
colors: [
|
||||||
|
Color.fromARGB(192, 0, 0, 0),
|
||||||
|
Color.fromARGB(0, 0, 0, 0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
foregroundColor: Colors.white.withOpacity(.87),
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton<_AppBarMenuOption>(
|
||||||
|
tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: _AppBarMenuOption.delete,
|
||||||
|
child: Text(L10n.global().deletePermanentlyTooltip),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (option) => _onMenuSelected(context, option),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onMenuSelected(BuildContext context, _AppBarMenuOption option) {
|
||||||
|
switch (option) {
|
||||||
|
case _AppBarMenuOption.delete:
|
||||||
|
_onDeletePressed(context);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_log.shout("[_onMenuSelected] Unknown option: $option");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDeletePressed(BuildContext context) async {
|
||||||
|
final file = widget.streamFiles[_viewerController.currentPage];
|
||||||
|
_log.info("[_onDeletePressed] Deleting file: ${file.logTag}");
|
||||||
|
final count = await const DeleteLocalSelectionHandler()(
|
||||||
|
selectedFiles: [file],
|
||||||
|
isRemoveOpened: true,
|
||||||
|
);
|
||||||
|
if (count > 0) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPage(BuildContext context, int index) {
|
||||||
|
if (_pageStates[index] == null) {
|
||||||
|
_pageStates[index] = _PageState();
|
||||||
|
}
|
||||||
|
return FractionallySizedBox(
|
||||||
|
widthFactor: 1 / _viewportFraction,
|
||||||
|
child: _buildItemView(context, index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItemView(BuildContext context, int index) {
|
||||||
|
final file = widget.streamFiles[index];
|
||||||
|
if (file_util.isSupportedImageMime(file.mime ?? "")) {
|
||||||
|
return _buildImageView(context, index);
|
||||||
|
} else {
|
||||||
|
_log.shout("[_buildItemView] Unknown file format: ${file.mime}");
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImageView(BuildContext context, int index) => LocalImageViewer(
|
||||||
|
file: widget.streamFiles[index],
|
||||||
|
canZoom: true,
|
||||||
|
onLoaded: () => _onImageLoaded(index),
|
||||||
|
onZoomStarted: () {
|
||||||
|
setState(() {
|
||||||
|
_isZoomed = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onZoomEnded: () {
|
||||||
|
setState(() {
|
||||||
|
_isZoomed = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
void _onImageLoaded(int index) {
|
||||||
|
if (_viewerController.currentPage == index &&
|
||||||
|
!_pageStates[index]!.hasLoaded) {
|
||||||
|
setState(() {
|
||||||
|
_pageStates[index]!.hasLoaded = true;
|
||||||
|
_isViewerLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get _canSwitchPage => !_isZoomed;
|
||||||
|
|
||||||
|
var _isShowVideoControl = true;
|
||||||
|
var _isZoomed = false;
|
||||||
|
|
||||||
|
final _viewerController = HorizontalPageViewerController();
|
||||||
|
bool _isViewerLoaded = false;
|
||||||
|
final _pageStates = <int, _PageState>{};
|
||||||
|
|
||||||
|
static final _log = Logger("widget.local_file_viewer._LocalFileViewerState");
|
||||||
|
|
||||||
|
static const _viewportFraction = 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PageState {
|
||||||
|
bool hasLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _AppBarMenuOption {
|
||||||
|
delete,
|
||||||
|
}
|
|
@ -16,8 +16,10 @@ import 'package:nc_photos/widget/album_share_outlier_browser.dart';
|
||||||
import 'package:nc_photos/widget/archive_browser.dart';
|
import 'package:nc_photos/widget/archive_browser.dart';
|
||||||
import 'package:nc_photos/widget/connect.dart';
|
import 'package:nc_photos/widget/connect.dart';
|
||||||
import 'package:nc_photos/widget/dynamic_album_browser.dart';
|
import 'package:nc_photos/widget/dynamic_album_browser.dart';
|
||||||
|
import 'package:nc_photos/widget/enhanced_photo_browser.dart';
|
||||||
import 'package:nc_photos/widget/favorite_browser.dart';
|
import 'package:nc_photos/widget/favorite_browser.dart';
|
||||||
import 'package:nc_photos/widget/home.dart';
|
import 'package:nc_photos/widget/home.dart';
|
||||||
|
import 'package:nc_photos/widget/local_file_viewer.dart';
|
||||||
import 'package:nc_photos/widget/people_browser.dart';
|
import 'package:nc_photos/widget/people_browser.dart';
|
||||||
import 'package:nc_photos/widget/person_browser.dart';
|
import 'package:nc_photos/widget/person_browser.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
|
@ -168,6 +170,8 @@ class _MyAppState extends State<MyApp>
|
||||||
route ??= _handleAlbumPickerRoute(settings);
|
route ??= _handleAlbumPickerRoute(settings);
|
||||||
route ??= _handleSmartAlbumBrowserRoute(settings);
|
route ??= _handleSmartAlbumBrowserRoute(settings);
|
||||||
route ??= _handleFavoriteBrowserRoute(settings);
|
route ??= _handleFavoriteBrowserRoute(settings);
|
||||||
|
route ??= _handleEnhancedPhotoBrowserRoute(settings);
|
||||||
|
route ??= _handleLocalFileViewerRoute(settings);
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,6 +502,34 @@ class _MyAppState extends State<MyApp>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Route<dynamic>? _handleEnhancedPhotoBrowserRoute(RouteSettings settings) {
|
||||||
|
try {
|
||||||
|
if (settings.name == EnhancedPhotoBrowser.routeName &&
|
||||||
|
settings.arguments != null) {
|
||||||
|
final args = settings.arguments as EnhancedPhotoBrowserArguments;
|
||||||
|
return EnhancedPhotoBrowser.buildRoute(args);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe(
|
||||||
|
"[_handleEnhancedPhotoBrowserRoute] Failed while handling route", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Route<dynamic>? _handleLocalFileViewerRoute(RouteSettings settings) {
|
||||||
|
try {
|
||||||
|
if (settings.name == LocalFileViewer.routeName &&
|
||||||
|
settings.arguments != null) {
|
||||||
|
final args = settings.arguments as LocalFileViewerArguments;
|
||||||
|
return LocalFileViewer.buildRoute(args);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_log.severe(
|
||||||
|
"[_handleLocalFileViewerRoute] Failed while handling route", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue