various fixes

This commit is contained in:
Thibault Deckers 2023-01-19 19:41:54 +01:00
parent a5cf5ca555
commit 7bf83b4892
80 changed files with 165 additions and 193 deletions

View file

@ -172,7 +172,18 @@ open class MainActivity : FlutterActivity() {
mediaSessionHandler.dispose() mediaSessionHandler.dispose()
mediaStoreChangeStreamHandler.dispose() mediaStoreChangeStreamHandler.dispose()
settingsChangeStreamHandler.dispose() settingsChangeStreamHandler.dispose()
super.onDestroy() try {
super.onDestroy()
} catch (e: Exception) {
// on Android 11, app may crash as follows:
// `Fatal Exception:`
// `java.lang.RuntimeException: Unable to destroy activity {deckers.thibault.aves/deckers.thibault.aves.MainActivity}:`
// `java.lang.IllegalArgumentException: NetworkCallback was not registered`
// related to this error:
// `Package android does not belong to 10162`
// cf https://issuetracker.google.com/issues/175055271
Log.e(LOG_TAG, "failed while destroying activity", e)
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {

View file

@ -30,6 +30,7 @@ import 'package:latlong2/latlong.dart';
final Settings settings = Settings._private(); final Settings settings = Settings._private();
class Settings extends ChangeNotifier { class Settings extends ChangeNotifier {
final List<StreamSubscription> _subscriptions = [];
final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change'); final EventChannel _platformSettingsChangeChannel = const OptionalEventChannel('deckers.thibault/aves/settings_change');
final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast(); final StreamController<SettingsChangedEvent> _updateStreamController = StreamController.broadcast();
@ -209,7 +210,10 @@ class Settings extends ChangeNotifier {
await settingsStore.init(); await settingsStore.init();
_appliedLocale = null; _appliedLocale = null;
if (monitorPlatformSettings) { if (monitorPlatformSettings) {
_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChanged(event as Map?)); _subscriptions
..forEach((sub) => sub.cancel())
..clear();
_subscriptions.add(_platformSettingsChangeChannel.receiveBroadcastStream().listen((event) => _onPlatformSettingsChanged(event as Map?)));
} }
} }

View file

@ -6,6 +6,7 @@ import 'package:aves/widgets/viewer/video/controller.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
abstract class MediaSessionService { abstract class MediaSessionService {
Stream<MediaCommandEvent> get mediaCommands; Stream<MediaCommandEvent> get mediaCommands;
@ -15,14 +16,22 @@ abstract class MediaSessionService {
Future<void> release(); Future<void> release();
} }
class PlatformMediaSessionService implements MediaSessionService { class PlatformMediaSessionService implements MediaSessionService, Disposable {
static const _platformObject = MethodChannel('deckers.thibault/aves/media_session'); static const _platformObject = MethodChannel('deckers.thibault/aves/media_session');
final List<StreamSubscription> _subscriptions = [];
final EventChannel _mediaCommandChannel = const OptionalEventChannel('deckers.thibault/aves/media_command'); final EventChannel _mediaCommandChannel = const OptionalEventChannel('deckers.thibault/aves/media_command');
final StreamController _streamController = StreamController.broadcast(); final StreamController _streamController = StreamController.broadcast();
PlatformMediaSessionService() { PlatformMediaSessionService() {
_mediaCommandChannel.receiveBroadcastStream().listen((event) => _onMediaCommand(event as Map?)); _subscriptions.add(_mediaCommandChannel.receiveBroadcastStream().listen((event) => _onMediaCommand(event as Map?)));
}
@override
FutureOr onDispose() {
_subscriptions
..forEach((sub) => sub.cancel())
..clear();
} }
@override @override

View file

@ -2,8 +2,16 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:permission_handler/permission_handler.dart';
class Constants { class Constants {
static const storagePermissions = [
Permission.storage,
// for media access on Android >=13
Permission.photos,
Permission.videos,
];
static const separator = ''; static const separator = '';
// `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`) // `Color(0x00FFFFFF)` is different from `Color(0x00000000)` (or `Colors.transparent`)

View file

@ -102,8 +102,7 @@ class _AppReferenceState extends State<AppReference> {
} }
void _goToPolicyPage() { void _goToPolicyPage() {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: PolicyPage.routeName), settings: const RouteSettings(name: PolicyPage.routeName),
builder: (context) => const PolicyPage(), builder: (context) => const PolicyPage(),

View file

@ -80,8 +80,7 @@ class _LicensesState extends State<Licenses> {
Center( Center(
child: AvesOutlinedButton( child: AvesOutlinedButton(
label: context.l10n.aboutLicensesShowAllButtonLabel, label: context.l10n.aboutLicensesShowAllButtonLabel,
onPressed: () => Navigator.push( onPressed: () => Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => Theme( builder: (context) => Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(

View file

@ -678,8 +678,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
} }
void _goToSearch() { void _goToSearch() {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
SearchPageRoute( SearchPageRoute(
delegate: CollectionSearchDelegate( delegate: CollectionSearchDelegate(
searchFieldLabel: context.l10n.searchCollectionFieldHint, searchFieldLabel: context.l10n.searchCollectionFieldHint,

View file

@ -13,6 +13,7 @@ import 'package:aves/model/source/section_keys.dart';
import 'package:aves/ref/mime_types.dart'; import 'package:aves/ref/mime_types.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/app_bar.dart'; import 'package:aves/widgets/collection/app_bar.dart';
import 'package:aves/widgets/collection/draggable_thumb_label.dart'; import 'package:aves/widgets/collection/draggable_thumb_label.dart';
import 'package:aves/widgets/collection/grid/list_details_theme.dart'; import 'package:aves/widgets/collection/grid/list_details_theme.dart';
@ -642,5 +643,5 @@ class _CollectionScrollViewState extends State<_CollectionScrollView> with Widge
return crumbs; return crumbs;
} }
Future<bool> get _isStoragePermissionGranted => Permission.storage.status.then((status) => status.isGranted); Future<bool> get _isStoragePermissionGranted => Future.wait(Constants.storagePermissions.map((v) => v.status)).then((v) => v.any((status) => status.isGranted));
} }

View file

@ -342,8 +342,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
Future<void> _rename(BuildContext context) async { Future<void> _rename(BuildContext context) async {
final entries = _getTargetItems(context).toList(); final entries = _getTargetItems(context).toList();
final pattern = await Navigator.push<NamingPattern>( final pattern = await Navigator.maybeOf(context)?.push<NamingPattern>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: RenameEntrySetPage.routeName), settings: const RouteSettings(name: RenameEntrySetPage.routeName),
builder: (context) => RenameEntrySetPage( builder: (context) => RenameEntrySetPage(
@ -468,7 +467,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
const CancelButton(), const CancelButton(),
if (supported.isNotEmpty) if (supported.isNotEmpty)
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(l10n.continueButtonLabel), child: Text(l10n.continueButtonLabel),
), ),
], ],
@ -523,8 +522,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final editableEntries = await _getEditableItems(context, entries, canEdit: (entry) => entry.canEditLocation); final editableEntries = await _getEditableItems(context, entries, canEdit: (entry) => entry.canEditLocation);
if (editableEntries == null || editableEntries.isEmpty) return null; if (editableEntries == null || editableEntries.isEmpty) return null;
final location = await Navigator.push( final location = await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: LocationPickPage.routeName), settings: const RouteSettings(name: LocationPickPage.routeName),
builder: (context) => LocationPickPage( builder: (context) => LocationPickPage(
@ -548,7 +546,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],
@ -621,8 +619,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
filters: collection.filters, filters: collection.filters,
fixedSelection: entries.where((entry) => entry.hasGps).toList(), fixedSelection: entries.where((entry) => entry.hasGps).toList(),
); );
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: MapPage.routeName), settings: const RouteSettings(name: MapPage.routeName),
builder: (context) => MapPage(collection: mapCollection), builder: (context) => MapPage(collection: mapCollection),
@ -635,8 +632,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final collection = context.read<CollectionLens>(); final collection = context.read<CollectionLens>();
final entries = _getTargetItems(context); final entries = _getTargetItems(context);
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: SlideshowPage.routeName), settings: const RouteSettings(name: SlideshowPage.routeName),
builder: (context) { builder: (context) {
@ -656,8 +652,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final collection = context.read<CollectionLens>(); final collection = context.read<CollectionLens>();
final entries = _getTargetItems(context); final entries = _getTargetItems(context);
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: StatsPage.routeName), settings: const RouteSettings(name: StatsPage.routeName),
builder: (context) => StatsPage( builder: (context) => StatsPage(
@ -672,8 +667,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
void _goToSearch(BuildContext context) { void _goToSearch(BuildContext context) {
final collection = context.read<CollectionLens>(); final collection = context.read<CollectionLens>();
Navigator.push( Navigator.maybeOf(context)?.push(
context,
SearchPageRoute( SearchPageRoute(
delegate: CollectionSearchDelegate( delegate: CollectionSearchDelegate(
searchFieldLabel: context.l10n.searchCollectionFieldHint, searchFieldLabel: context.l10n.searchCollectionFieldHint,

View file

@ -51,7 +51,7 @@ class InteractiveTile extends StatelessWidget {
selection.toggleSelection(entry); selection.toggleSelection(entry);
break; break;
case AppMode.pickMediaInternal: case AppMode.pickMediaInternal:
Navigator.pop(context, entry); Navigator.maybeOf(context)?.pop(entry);
break; break;
case AppMode.pickCollectionFiltersExternal: case AppMode.pickCollectionFiltersExternal:
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
@ -81,8 +81,7 @@ class InteractiveTile extends StatelessWidget {
} }
void _goToViewer(BuildContext context) { void _goToViewer(BuildContext context) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
TransparentMaterialPageRoute( TransparentMaterialPageRoute(
settings: const RouteSettings(name: EntryViewerPage.routeName), settings: const RouteSettings(name: EntryViewerPage.routeName),
pageBuilder: (context, a, sa) { pageBuilder: (context, a, sa) {

View file

@ -82,8 +82,7 @@ mixin EntryEditorMixin {
final filters = <CollectionFilter>{...v.tags.map(TagFilter.new)}; final filters = <CollectionFilter>{...v.tags.map(TagFilter.new)};
return MapEntry(v, filters); return MapEntry(v, filters);
})); }));
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: TagEditorPage.routeName), settings: const RouteSettings(name: TagEditorPage.routeName),
builder: (context) => TagEditorPage( builder: (context) => TagEditorPage(
@ -128,7 +127,7 @@ mixin EntryEditorMixin {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],

View file

@ -330,8 +330,7 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
targetFilters.removeWhere((f) => f is AlbumFilter); targetFilters.removeWhere((f) => f is AlbumFilter);
targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum))); targetFilters.add(AlbumFilter(destinationAlbum, source.getAlbumDisplayName(context, destinationAlbum)));
} }
unawaited(Navigator.pushAndRemoveUntil( unawaited(Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -145,7 +145,7 @@ mixin FeedbackMixin {
itemCount: itemCount, itemCount: itemCount,
onCancel: onCancel, onCancel: onCancel,
onDone: (processed) { onDone: (processed) {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
onDone?.call(processed); onDone?.call(processed);
}, },
), ),

View file

@ -56,7 +56,7 @@ mixin PermissionAwareMixin {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(MaterialLocalizations.of(context).okButtonLabel), child: Text(MaterialLocalizations.of(context).okButtonLabel),
), ),
], ],

View file

@ -96,7 +96,7 @@ class _ColorPickerDialogState extends State<ColorPickerDialog> {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, color), onPressed: () => Navigator.maybeOf(context)?.pop(color),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],

View file

@ -16,8 +16,7 @@ class TvNavigationPopHandler {
return true; return true;
} }
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
_getHomeRoute(), _getHomeRoute(),
(route) => false, (route) => false,
); );

View file

@ -37,7 +37,7 @@ class MapButtonPanel extends StatelessWidget {
if (!settings.useTvLayout) { if (!settings.useTvLayout) {
navigationButton = MapOverlayButton( navigationButton = MapOverlayButton(
icon: const BackButtonIcon(), icon: const BackButtonIcon(),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.maybeOf(context)?.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip, tooltip: MaterialLocalizations.of(context).backButtonTooltip,
); );
} }

View file

@ -61,7 +61,7 @@ abstract class AvesSearchDelegate extends SearchDelegate {
void goBack(BuildContext context) { void goBack(BuildContext context) {
clean(); clean();
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
} }
void clean() { void clean() {

View file

@ -196,8 +196,7 @@ class _AppDebugPageState extends State<AppDebugPage> {
); );
break; break;
case AppDebugAction.greenScreen: case AppDebugAction.greenScreen:
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const Scaffold( builder: (context) => const Scaffold(
backgroundColor: Colors.green, backgroundColor: Colors.green,

View file

@ -41,7 +41,7 @@ class _MediaStoreScanDirDialogState extends State<MediaStoreScanDirDialog> {
} }
}); });
} }
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
}, },
child: const Text('Scan'), child: const Text('Scan'),
) )

View file

@ -108,8 +108,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
final _collection = widget.collection; final _collection = widget.collection;
if (_collection == null) return; if (_collection == null) return;
final entry = await Navigator.push<AvesEntry>( final entry = await Navigator.maybeOf(context)?.push<AvesEntry>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ItemPickPage.routeName), settings: const RouteSettings(name: ItemPickPage.routeName),
builder: (context) { builder: (context) {
@ -142,7 +141,7 @@ class _AddShortcutDialogState extends State<AddShortcutDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
if (_isValidNotifier.value) { if (_isValidNotifier.value) {
Navigator.pop(context, Tuple2<AvesEntry?, String>(_coverEntry, _nameController.text)); Navigator.maybeOf(context)?.pop(Tuple2<AvesEntry?, String>(_coverEntry, _nameController.text));
} }
} }
} }

View file

@ -116,7 +116,7 @@ class _AvesConfirmationDialogState extends State<_AvesConfirmationDialog> {
if (_skip.value) { if (_skip.value) {
_skipConfirmation(widget.type); _skipConfirmation(widget.type);
} }
Navigator.pop(context, true); Navigator.maybeOf(context)?.pop(true);
}, },
child: Text(widget.confirmationButtonLabel), child: Text(widget.confirmationButtonLabel),
), ),

View file

@ -166,7 +166,7 @@ class CancelButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextButton( return TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.maybeOf(context)?.pop(),
child: Text(MaterialLocalizations.of(context).cancelButtonLabel), child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
); );
} }
@ -178,7 +178,7 @@ class OkButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextButton( return TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.maybeOf(context)?.pop(),
child: Text(MaterialLocalizations.of(context).okButtonLabel), child: Text(MaterialLocalizations.of(context).okButtonLabel),
); );
} }

View file

@ -89,7 +89,7 @@ class _AvesSelectionDialogState<T> extends State<AvesSelectionDialog<T>> {
const CancelButton(), const CancelButton(),
if (needConfirmation) if (needConfirmation)
TextButton( TextButton(
onPressed: () => Navigator.pop(context, _selectedValue), onPressed: () => Navigator.maybeOf(context)?.pop(_selectedValue),
child: Text(confirmationButtonLabel), child: Text(confirmationButtonLabel),
), ),
], ],
@ -129,7 +129,7 @@ class SelectionRadioListTile<T> extends StatelessWidget {
if (needConfirmation) { if (needConfirmation) {
setGroupValue(v as T); setGroupValue(v as T);
} else { } else {
Navigator.pop(context, v); Navigator.maybeOf(context)?.pop(v);
} }
}, },
reselectable: true, reselectable: true,

View file

@ -106,5 +106,5 @@ class _DurationDialogState extends State<DurationDialog> {
); );
} }
void _submit(BuildContext context) => Navigator.pop(context, _minutes.value * secondsInMinute + _seconds.value); void _submit(BuildContext context) => Navigator.maybeOf(context)?.pop(_minutes.value * secondsInMinute + _seconds.value);
} }

View file

@ -329,8 +329,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
final _collection = widget.collection; final _collection = widget.collection;
if (_collection == null) return; if (_collection == null) return;
final entry = await Navigator.push<AvesEntry>( final entry = await Navigator.maybeOf(context)?.push<AvesEntry>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ItemPickPage.routeName), settings: const RouteSettings(name: ItemPickPage.routeName),
builder: (context) => ItemPickPage( builder: (context) => ItemPickPage(
@ -384,7 +383,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
if (_isValidNotifier.value) { if (_isValidNotifier.value) {
Navigator.pop(context, _getModifier()); Navigator.maybeOf(context)?.pop(_getModifier());
} }
} }
} }

View file

@ -103,6 +103,6 @@ class _EditEntryTitleDescriptionDialogState extends State<EditEntryTitleDescript
final text = _fieldController(field).text; final text = _fieldController(field).text;
return MapEntry(field, text.isEmpty ? null : text); return MapEntry(field, text.isEmpty ? null : text);
})); }));
return Navigator.pop<Map<DescriptionField, String?>>(context, modifier); return Navigator.maybeOf(context)?.pop<Map<DescriptionField, String?>>(modifier);
} }
} }

View file

@ -180,8 +180,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(), fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(),
) )
: null; : null;
final latLng = await Navigator.push( final latLng = await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: LocationPickPage.routeName), settings: const RouteSettings(name: LocationPickPage.routeName),
builder: (context) => LocationPickPage( builder: (context) => LocationPickPage(
@ -222,8 +221,7 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
final _collection = widget.collection; final _collection = widget.collection;
if (_collection == null) return; if (_collection == null) return;
final entry = await Navigator.push<AvesEntry>( final entry = await Navigator.maybeOf(context)?.push<AvesEntry>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ItemPickPage.routeName), settings: const RouteSettings(name: ItemPickPage.routeName),
builder: (context) => ItemPickPage( builder: (context) => ItemPickPage(
@ -330,16 +328,16 @@ class _EditEntryLocationDialogState extends State<EditEntryLocationDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
switch (_action) { switch (_action) {
case LocationEditAction.chooseOnMap: case LocationEditAction.chooseOnMap:
Navigator.pop(context, _mapCoordinates); Navigator.maybeOf(context)?.pop(_mapCoordinates);
break; break;
case LocationEditAction.copyItem: case LocationEditAction.copyItem:
Navigator.pop(context, _copyItemSource.latLng); Navigator.maybeOf(context)?.pop(_copyItemSource.latLng);
break; break;
case LocationEditAction.setCustom: case LocationEditAction.setCustom:
Navigator.pop(context, _parseLatLng()); Navigator.maybeOf(context)?.pop(_parseLatLng());
break; break;
case LocationEditAction.remove: case LocationEditAction.remove:
Navigator.pop(context, ExtraAvesEntryMetadataEdition.removalLocation); Navigator.maybeOf(context)?.pop(ExtraAvesEntryMetadataEdition.removalLocation);
break; break;
} }
} }

View file

@ -126,7 +126,7 @@ class _EditEntryRatingDialogState extends State<EditEntryRatingDialog> {
entryRating = 0; entryRating = 0;
break; break;
} }
Navigator.pop(context, entryRating); Navigator.maybeOf(context)?.pop(entryRating);
} }
} }

View file

@ -136,5 +136,5 @@ class _RemoveEntryMetadataDialogState extends State<RemoveEntryMetadataDialog> {
void _validate() => _isValidNotifier.value = _types.isNotEmpty; void _validate() => _isValidNotifier.value = _types.isNotEmpty;
void _submit(BuildContext context) => Navigator.pop(context, _types); void _submit(BuildContext context) => Navigator.maybeOf(context)?.pop(_types);
} }

View file

@ -88,7 +88,7 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
if (_isValidNotifier.value) { if (_isValidNotifier.value) {
Navigator.pop(context, newName); Navigator.maybeOf(context)?.pop(newName);
} }
} }
} }

View file

@ -185,7 +185,7 @@ class _RenameEntrySetPageState extends State<RenameEntrySetPage> {
label: l10n.entryActionRename, label: l10n.entryActionRename,
onPressed: () { onPressed: () {
settings.entryRenamingPattern = _patternTextController.text; settings.entryRenamingPattern = _patternTextController.text;
Navigator.pop<NamingPattern>(context, _namingPatternNotifier.value); Navigator.maybeOf(context)?.pop<NamingPattern>(_namingPatternNotifier.value);
}, },
), ),
), ),

View file

@ -130,7 +130,7 @@ class _ExportEntryDialogState extends State<ExportEntryDialog> {
height: height, height: height,
) )
: null; : null;
Navigator.pop(context, options); Navigator.maybeOf(context)?.pop(options);
} }
: null, : null,
child: Text(l10n.applyButtonLabel), child: Text(l10n.applyButtonLabel),

View file

@ -161,7 +161,7 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
final entry = _isCustomEntry ? _customEntry : null; final entry = _isCustomEntry ? _customEntry : null;
final package = _isCustomPackage ? _customPackage : null; final package = _isCustomPackage ? _customPackage : null;
final color = _isCustomColor ? _customColor : null; final color = _isCustomColor ? _customColor : null;
return Navigator.pop(context, Tuple3<AvesEntry?, String?, Color?>(entry, package, color)); return Navigator.maybeOf(context)?.pop(Tuple3<AvesEntry?, String?, Color?>(entry, package, color));
}, },
child: Text(l10n.applyButtonLabel), child: Text(l10n.applyButtonLabel),
) )
@ -340,8 +340,7 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
} }
Future<void> _pickEntry() async { Future<void> _pickEntry() async {
final entry = await Navigator.push<AvesEntry>( final entry = await Navigator.maybeOf(context)?.push<AvesEntry>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ItemPickPage.routeName), settings: const RouteSettings(name: ItemPickPage.routeName),
builder: (context) => ItemPickPage( builder: (context) => ItemPickPage(
@ -361,8 +360,7 @@ class _CoverSelectionDialogState extends State<CoverSelectionDialog> {
} }
Future<void> _pickPackage() async { Future<void> _pickPackage() async {
final package = await Navigator.push<String>( final package = await Navigator.maybeOf(context)?.push<String>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: AppPickPage.routeName), settings: const RouteSettings(name: AppPickPage.routeName),
builder: (context) => AppPickPage( builder: (context) => AppPickPage(

View file

@ -159,7 +159,7 @@ class _CreateAlbumDialogState extends State<CreateAlbumDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
if (_isValidNotifier.value) { if (_isValidNotifier.value) {
Navigator.pop(context, _buildAlbumPath(_nameController.text)); Navigator.maybeOf(context)?.pop(_buildAlbumPath(_nameController.text));
} }
} }
} }

View file

@ -88,7 +88,7 @@ class _RenameAlbumDialogState extends State<RenameAlbumDialog> {
void _submit(BuildContext context) { void _submit(BuildContext context) {
if (_isValidNotifier.value) { if (_isValidNotifier.value) {
Navigator.pop(context, _nameController.text); Navigator.maybeOf(context)?.pop(_nameController.text);
} }
} }
} }

View file

@ -35,8 +35,7 @@ Future<String?> pickAlbum({
// source may not be fully initialized in view mode // source may not be fully initialized in view mode
await source.init(); await source.init();
} }
final filter = await Navigator.push( final filter = await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute<AlbumFilter>( MaterialPageRoute<AlbumFilter>(
settings: const RouteSettings(name: _AlbumPickPage.routeName), settings: const RouteSettings(name: _AlbumPickPage.routeName),
builder: (context) => _AlbumPickPage(source: source, moveType: moveType), builder: (context) => _AlbumPickPage(source: source, moveType: moveType),
@ -188,7 +187,7 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
// wait for the dialog to hide as applying the change may block the UI // wait for the dialog to hide as applying the change may block the UI
await Future.delayed(Durations.dialogTransitionAnimation * timeDilation); await Future.delayed(Durations.dialogTransitionAnimation * timeDilation);
if (newAlbum != null && newAlbum.isNotEmpty) { if (newAlbum != null && newAlbum.isNotEmpty) {
Navigator.pop<AlbumFilter>(context, AlbumFilter(newAlbum, source.getAlbumDisplayName(context, newAlbum))); Navigator.maybeOf(context)?.pop<AlbumFilter>(AlbumFilter(newAlbum, source.getAlbumDisplayName(context, newAlbum)));
} }
}, },
tooltip: context.l10n.createAlbumTooltip, tooltip: context.l10n.createAlbumTooltip,

View file

@ -83,7 +83,7 @@ class _AppPickPageState extends State<AppPickPage> {
return ReselectableRadioListTile<String?>( return ReselectableRadioListTile<String?>(
value: '', value: '',
groupValue: _selectedValue, groupValue: _selectedValue,
onChanged: (v) => Navigator.pop(context, v), onChanged: (v) => Navigator.maybeOf(context)?.pop(v),
reselectable: true, reselectable: true,
title: Text( title: Text(
context.l10n.appPickDialogNone, context.l10n.appPickDialogNone,
@ -100,7 +100,7 @@ class _AppPickPageState extends State<AppPickPage> {
return ReselectableRadioListTile<String?>( return ReselectableRadioListTile<String?>(
value: package.packageName, value: package.packageName,
groupValue: _selectedValue, groupValue: _selectedValue,
onChanged: (v) => Navigator.pop(context, v), onChanged: (v) => Navigator.maybeOf(context)?.pop(v),
reselectable: true, reselectable: true,
title: Text.rich( title: Text.rich(
TextSpan( TextSpan(

View file

@ -118,7 +118,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
const SizedBox(height: 8), const SizedBox(height: 8),
AvesOutlinedButton( AvesOutlinedButton(
label: context.l10n.locationPickerUseThisLocationButton, label: context.l10n.locationPickerUseThisLocationButton,
onPressed: () => Navigator.pop(context, _dotLocationNotifier.value), onPressed: () => Navigator.maybeOf(context)?.pop(_dotLocationNotifier.value),
), ),
], ],
), ),

View file

@ -150,7 +150,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
key: const Key('button-apply'), key: const Key('button-apply'),
onPressed: () { onPressed: () {
tileExtentController.setUserPreferredColumnCount(_columnCountNotifier.value); tileExtentController.setUserPreferredColumnCount(_columnCountNotifier.value);
Navigator.pop(context, Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort)); Navigator.maybeOf(context)?.pop(Tuple4(_selectedSort, _selectedGroup, _selectedLayout, _reverseSort));
}, },
child: Text(l10n.applyButtonLabel), child: Text(l10n.applyButtonLabel),
) )

View file

@ -66,5 +66,5 @@ class _VideoSpeedDialogState extends State<VideoSpeedDialog> {
); );
} }
void _submit(BuildContext context) => Navigator.pop(context, _speed); void _submit(BuildContext context) => Navigator.maybeOf(context)?.pop(_speed);
} }

View file

@ -153,7 +153,7 @@ class _VideoStreamSelectionDialogState extends State<VideoStreamSelectionDialog>
]; ];
} }
void _submit(BuildContext context) => Navigator.pop(context, { void _submit(BuildContext context) => Navigator.maybeOf(context)?.pop({
StreamType.video: _currentVideo, StreamType.video: _currentVideo,
StreamType.audio: _currentAudio, StreamType.audio: _currentAudio,
StreamType.text: _currentText, StreamType.text: _currentText,

View file

@ -41,7 +41,7 @@ class _WallpaperSettingsDialogState extends State<WallpaperSettingsDialog> {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, Tuple2<WallpaperTarget, bool>(_selectedTarget, _useScrollEffect)), onPressed: () => Navigator.maybeOf(context)?.pop(Tuple2<WallpaperTarget, bool>(_selectedTarget, _useScrollEffect)),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],

View file

@ -191,8 +191,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
highlightInfo.trackItem(FilterGridItem(filter, null), highlightItem: filter); highlightInfo.trackItem(FilterGridItem(filter, null), highlightItem: filter);
} else { } else {
highlightInfo.set(filter); highlightInfo.set(filter);
await Navigator.pushAndRemoveUntil( await Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: AlbumListPage.routeName), settings: const RouteSettings(name: AlbumListPage.routeName),
builder: (_) => const AlbumListPage(), builder: (_) => const AlbumListPage(),
@ -239,7 +238,7 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate<AlbumFilter> with
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(l10n.deleteButtonLabel), child: Text(l10n.deleteButtonLabel),
), ),
], ],

View file

@ -40,8 +40,7 @@ class ChipActionDelegate {
WidgetBuilder pageBuilder, WidgetBuilder pageBuilder,
) { ) {
context.read<HighlightInfo>().set(filter); context.read<HighlightInfo>().set(filter);
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: routeName), settings: RouteSettings(name: routeName),
builder: pageBuilder, builder: pageBuilder,
@ -58,7 +57,7 @@ class ChipActionDelegate {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.hideButtonLabel), child: Text(context.l10n.hideButtonLabel),
), ),
], ],

View file

@ -248,8 +248,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
source: context.read<CollectionSource>(), source: context.read<CollectionSource>(),
fixedSelection: _selectedEntries(context, filters).where((entry) => entry.hasGps).toList(), fixedSelection: _selectedEntries(context, filters).where((entry) => entry.hasGps).toList(),
); );
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: MapPage.routeName), settings: const RouteSettings(name: MapPage.routeName),
builder: (context) => MapPage(collection: mapCollection), builder: (context) => MapPage(collection: mapCollection),
@ -259,8 +258,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
} }
void _goToSlideshow(BuildContext context, Set<T> filters) { void _goToSlideshow(BuildContext context, Set<T> filters) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: SlideshowPage.routeName), settings: const RouteSettings(name: SlideshowPage.routeName),
builder: (context) { builder: (context) {
@ -276,8 +274,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
} }
void _goToStats(BuildContext context, Set<T> filters) { void _goToStats(BuildContext context, Set<T> filters) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: StatsPage.routeName), settings: const RouteSettings(name: StatsPage.routeName),
builder: (context) { builder: (context) {
@ -291,8 +288,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
} }
void _goToSearch(BuildContext context) { void _goToSearch(BuildContext context) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
SearchPageRoute( SearchPageRoute(
delegate: CollectionSearchDelegate( delegate: CollectionSearchDelegate(
searchFieldLabel: context.l10n.searchCollectionFieldHint, searchFieldLabel: context.l10n.searchCollectionFieldHint,
@ -310,7 +306,7 @@ abstract class ChipSetActionDelegate<T extends CollectionFilter> with FeedbackMi
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.hideButtonLabel), child: Text(context.l10n.hideButtonLabel),
), ),
], ],

View file

@ -422,8 +422,7 @@ class _FilterGridAppBarState<T extends CollectionFilter, CSAD extends ChipSetAct
} }
void _goToSearch() { void _goToSearch() {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
SearchPageRoute( SearchPageRoute(
delegate: CollectionSearchDelegate( delegate: CollectionSearchDelegate(
searchFieldLabel: context.l10n.searchCollectionFieldHint, searchFieldLabel: context.l10n.searchCollectionFieldHint,

View file

@ -61,7 +61,7 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
} }
break; break;
case AppMode.pickFilterInternal: case AppMode.pickFilterInternal:
Navigator.pop<T>(context, filter); Navigator.maybeOf(context)?.pop<T>(filter);
break; break;
case AppMode.pickMediaInternal: case AppMode.pickMediaInternal:
case AppMode.screenSaver: case AppMode.screenSaver:
@ -96,8 +96,7 @@ class _InteractiveFilterTileState<T extends CollectionFilter> extends State<Inte
} }
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return; if (!mounted) return;
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -16,6 +16,7 @@ import 'package:aves/services/global_search.dart';
import 'package:aves/services/intent_service.dart'; import 'package:aves/services/intent_service.dart';
import 'package:aves/services/widget_service.dart'; import 'package:aves/services/widget_service.dart';
import 'package:aves/utils/android_file_utils.dart'; import 'package:aves/utils/android_file_utils.dart';
import 'package:aves/utils/constants.dart';
import 'package:aves/widgets/collection/collection_page.dart'; import 'package:aves/widgets/collection/collection_page.dart';
import 'package:aves/widgets/common/behaviour/routes.dart'; import 'package:aves/widgets/common/behaviour/routes.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
@ -95,10 +96,7 @@ class _HomePageState extends State<HomePage> {
// do not check whether permission was granted, because some app stores // do not check whether permission was granted, because some app stores
// hide in some countries apps that force quit on permission denial // hide in some countries apps that force quit on permission denial
await [ await [
Permission.storage, ...Constants.storagePermissions,
// for media access on Android >=13
Permission.photos,
Permission.videos,
// to access media with unredacted metadata with scoped storage (Android >=10) // to access media with unredacted metadata with scoped storage (Android >=10)
Permission.accessMediaLocation, Permission.accessMediaLocation,
].request(); ].request();
@ -239,8 +237,7 @@ class _HomePageState extends State<HomePage> {
// `pushReplacement` is not enough in some edge cases // `pushReplacement` is not enough in some edge cases
// e.g. when opening the viewer in `view` mode should replace a viewer in `main` mode // e.g. when opening the viewer in `view` mode should replace a viewer in `main` mode
unawaited(Navigator.pushAndRemoveUntil( unawaited(Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
await _getRedirectRoute(appMode), await _getRedirectRoute(appMode),
(route) => false, (route) => false,
)); ));

View file

@ -456,8 +456,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
void _goToViewer(AvesEntry? initialEntry) { void _goToViewer(AvesEntry? initialEntry) {
if (initialEntry == null) return; if (initialEntry == null) return;
Navigator.push( Navigator.maybeOf(context)?.push(
context,
TransparentMaterialPageRoute( TransparentMaterialPageRoute(
settings: const RouteSettings(name: EntryViewerPage.routeName), settings: const RouteSettings(name: EntryViewerPage.routeName),
pageBuilder: (context, a, sa) { pageBuilder: (context, a, sa) {
@ -476,8 +475,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main; final isMainMode = context.read<ValueNotifier<AppMode>>().value == AppMode.main;
if (!isMainMode) return; if (!isMainMode) return;
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -105,10 +105,9 @@ class _AppDrawerState extends State<AppDrawer> {
Widget _buildHeader(BuildContext context) { Widget _buildHeader(BuildContext context) {
Future<void> goTo(String routeName, WidgetBuilder pageBuilder) async { Future<void> goTo(String routeName, WidgetBuilder pageBuilder) async {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
await Future.delayed(Durations.drawerTransitionAnimation); await Future.delayed(Durations.drawerTransitionAnimation);
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: routeName), settings: RouteSettings(name: routeName),
builder: pageBuilder, builder: pageBuilder,

View file

@ -57,9 +57,8 @@ class CollectionNavTile extends StatelessWidget {
} }
void _goToCollection(BuildContext context) { void _goToCollection(BuildContext context) {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -47,16 +47,15 @@ class PageNavTile extends StatelessWidget {
) )
: null, : null,
onTap: () { onTap: () {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
final route = routeBuilder(context, routeName); final route = routeBuilder(context, routeName);
if (topLevel) { if (topLevel) {
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
route, route,
(route) => false, (route) => false,
); );
} else { } else {
Navigator.push(context, route); Navigator.maybeOf(context)?.push(route);
} }
}, },
selected: context.currentRouteName == routeName, selected: context.currentRouteName == routeName,

View file

@ -135,8 +135,7 @@ class _AppBottomNavBarState extends State<AppBottomNavBar> {
void _goTo(BuildContext context, List<AvesBottomNavItem> items, int index) { void _goTo(BuildContext context, List<AvesBottomNavItem> items, int index) {
final item = items[index]; final item = items[index];
final routeName = item.route; final routeName = item.route;
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: routeName), settings: RouteSettings(name: routeName),
builder: (context) { builder: (context) {

View file

@ -249,16 +249,14 @@ class _TvRailState extends State<TvRail> {
); );
void _goTo(String routeName) { void _goTo(String routeName) {
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
PageNavTile.routeBuilder(context, routeName), PageNavTile.routeBuilder(context, routeName),
(route) => false, (route) => false,
); );
} }
void _goToCollection(BuildContext context, CollectionFilter? filter) { void _goToCollection(BuildContext context, CollectionFilter? filter) {
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -320,8 +320,7 @@ class CollectionSearchDelegate extends AvesSearchDelegate {
void _jumpToCollectionPage(BuildContext context, Set<CollectionFilter> filters) { void _jumpToCollectionPage(BuildContext context, Set<CollectionFilter> filters) {
clean(); clean();
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -56,7 +56,7 @@ class _AppExportItemSelectionDialogState extends State<AppExportItemSelectionDia
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: _selectedItems.isEmpty ? null : () => Navigator.pop(context, _selectedItems), onPressed: _selectedItems.isEmpty ? null : () => Navigator.maybeOf(context)?.pop(_selectedItems),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],

View file

@ -24,8 +24,7 @@ class SettingsSubPageTile extends StatelessWidget {
return ListTile( return ListTile(
title: Text(title), title: Text(title),
onTap: () { onTap: () {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: RouteSettings(name: routeName), settings: RouteSettings(name: routeName),
builder: builder, builder: builder,

View file

@ -125,7 +125,7 @@ class SettingsTileDisplayForceTvLayout extends SettingsTile {
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(l10n.applyButtonLabel), child: Text(l10n.applyButtonLabel),
), ),
], ],

View file

@ -60,7 +60,7 @@ class _LocaleSelectionPageState extends State<LocaleSelectionPage> {
key: Key(value.toString()), key: Key(value.toString()),
value: value, value: value,
groupValue: _selectedValue, groupValue: _selectedValue,
onChanged: (v) => Navigator.pop(context, v), onChanged: (v) => Navigator.maybeOf(context)?.pop(v),
reselectable: true, reselectable: true,
title: Text( title: Text(
title, title,

View file

@ -25,8 +25,7 @@ class LocaleTile extends StatelessWidget {
}, },
), ),
onTap: () async { onTap: () async {
final value = await Navigator.push<Locale>( final value = await Navigator.maybeOf(context)?.push<Locale>(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: LocaleSelectionPage.routeName), settings: const RouteSettings(name: LocaleSelectionPage.routeName),
builder: (context) => const LocaleSelectionPage(), builder: (context) => const LocaleSelectionPage(),

View file

@ -129,7 +129,7 @@ class _FilePickerPageState extends State<FilePickerPage> {
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: AvesOutlinedButton( child: AvesOutlinedButton(
label: l10n.filePickerUseThisFolder, label: l10n.filePickerUseThisFolder,
onPressed: () => Navigator.pop(context, currentDirectoryPath), onPressed: () => Navigator.maybeOf(context)?.pop(currentDirectoryPath),
), ),
), ),
], ],
@ -165,7 +165,7 @@ class _FilePickerPageState extends State<FilePickerPage> {
leading: Icon(icon), leading: Icon(icon),
title: Text(v.getDescription(context)), title: Text(v.getDescription(context)),
onTap: () async { onTap: () async {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
await Future.delayed(Durations.drawerTransitionAnimation); await Future.delayed(Durations.drawerTransitionAnimation);
_goTo(v.path); _goTo(v.path);
setState(() {}); setState(() {});

View file

@ -144,8 +144,7 @@ class _HiddenPaths extends StatelessWidget {
icon: const Icon(AIcons.add), icon: const Icon(AIcons.add),
label: context.l10n.addPathTooltip, label: context.l10n.addPathTooltip,
onPressed: () async { onPressed: () async {
final path = await Navigator.push( final path = await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute<String>( MaterialPageRoute<String>(
settings: const RouteSettings(name: FilePickerPage.routeName), settings: const RouteSettings(name: FilePickerPage.routeName),
builder: (context) => const FilePickerPage(), builder: (context) => const FilePickerPage(),

View file

@ -284,8 +284,7 @@ class _SettingsPageState extends State<SettingsPage> with FeedbackMixin {
} }
void _goToSearch(BuildContext context) { void _goToSearch(BuildContext context) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
SearchPageRoute( SearchPageRoute(
delegate: SettingsSearchDelegate( delegate: SettingsSearchDelegate(
searchFieldLabel: context.l10n.settingsSearchFieldLabel, searchFieldLabel: context.l10n.settingsSearchFieldLabel,

View file

@ -253,8 +253,7 @@ class _StatsPageState extends State<StatsPage> {
final totalEntryCount = entries.length; final totalEntryCount = entries.length;
final hasMore = maxRowCount != null && entryCountMap.length > maxRowCount; final hasMore = maxRowCount != null && entryCountMap.length > maxRowCount;
final onHeaderPressed = hasMore final onHeaderPressed = hasMore
? () => Navigator.push( ? () => Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: StatsTopPage.routeName), settings: const RouteSettings(name: StatsTopPage.routeName),
builder: (context) => StatsTopPage( builder: (context) => StatsTopPage(
@ -334,12 +333,11 @@ class _StatsPageState extends State<StatsPage> {
// even when the target is a child of an `AnimatedList`. // even when the target is a child of an `AnimatedList`.
// Do not use `WidgetsBinding.instance.addPostFrameCallback`, // Do not use `WidgetsBinding.instance.addPostFrameCallback`,
// as it may not trigger if there is no subsequent build. // as it may not trigger if there is no subsequent build.
Future.delayed(const Duration(milliseconds: 100), () => Navigator.popUntil(context, (route) => route.settings.name == CollectionPage.routeName)); Future.delayed(const Duration(milliseconds: 100), () => Navigator.maybeOf(context)?.popUntil((route) => route.settings.name == CollectionPage.routeName));
} }
void _jumpToCollectionPage(BuildContext context, CollectionFilter filter) { void _jumpToCollectionPage(BuildContext context, CollectionFilter filter) {
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -460,8 +460,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
// local context may be deactivated when action is triggered after navigation // local context may be deactivated when action is triggered after navigation
final context = AvesApp.navigatorKey.currentContext; final context = AvesApp.navigatorKey.currentContext;
if (context != null) { if (context != null) {
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(
@ -521,8 +520,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
bool _isMainMode(BuildContext context) => context.read<ValueNotifier<AppMode>>().value == AppMode.main; bool _isMainMode(BuildContext context) => context.read<ValueNotifier<AppMode>>().value == AppMode.main;
void _goToSourceViewer(BuildContext context, AvesEntry targetEntry) { void _goToSourceViewer(BuildContext context, AvesEntry targetEntry) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: SourceViewerPage.routeName), settings: const RouteSettings(name: SourceViewerPage.routeName),
builder: (context) => SourceViewerPage( builder: (context) => SourceViewerPage(
@ -540,8 +538,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
} }
void _goToDebug(BuildContext context, AvesEntry targetEntry) { void _goToDebug(BuildContext context, AvesEntry targetEntry) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ViewerDebugPage.routeName), settings: const RouteSettings(name: ViewerDebugPage.routeName),
builder: (context) => ViewerDebugPage(entry: targetEntry), builder: (context) => ViewerDebugPage(entry: targetEntry),

View file

@ -234,7 +234,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
actions: [ actions: [
const CancelButton(), const CancelButton(),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.applyButtonLabel), child: Text(context.l10n.applyButtonLabel),
), ),
], ],
@ -262,8 +262,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
listenToSource: true, listenToSource: true,
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).where((entry) => entry != targetEntry).toList(), fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).where((entry) => entry != targetEntry).toList(),
); );
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: MapPage.routeName), settings: const RouteSettings(name: MapPage.routeName),
builder: (context) => MapPage( builder: (context) => MapPage(
@ -276,8 +275,7 @@ class EntryInfoActionDelegate with FeedbackMixin, PermissionAwareMixin, EntryEdi
} }
void _goToDebug(BuildContext context, AvesEntry targetEntry) { void _goToDebug(BuildContext context, AvesEntry targetEntry) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: ViewerDebugPage.routeName), settings: const RouteSettings(name: ViewerDebugPage.routeName),
builder: (context) => ViewerDebugPage(entry: targetEntry), builder: (context) => ViewerDebugPage(entry: targetEntry),

View file

@ -72,8 +72,7 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
} }
void _openTempEntry(BuildContext context, AvesEntry tempEntry) { void _openTempEntry(BuildContext context, AvesEntry tempEntry) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
TransparentMaterialPageRoute( TransparentMaterialPageRoute(
settings: const RouteSettings(name: EntryViewerPage.routeName), settings: const RouteSettings(name: EntryViewerPage.routeName),
pageBuilder: (context, a, sa) => EntryViewerPage( pageBuilder: (context, a, sa) => EntryViewerPage(

View file

@ -233,7 +233,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
actions: { actions: {
_ShowPreviousIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(-1, animate: false)), _ShowPreviousIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(-1, animate: false)),
_ShowNextIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(1, animate: false)), _ShowNextIntent: CallbackAction<Intent>(onInvoke: (intent) => _goToHorizontalPage(1, animate: false)),
_LeaveIntent: CallbackAction<Intent>(onInvoke: (intent) => Navigator.pop(context)), _LeaveIntent: CallbackAction<Intent>(onInvoke: (intent) => Navigator.maybeOf(context)?.pop()),
_ShowInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => ShowInfoPageNotification().dispatch(context)), _ShowInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => ShowInfoPageNotification().dispatch(context)),
TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)), TvShowLessInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context)),
_TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)), _TvShowMoreInfoIntent: CallbackAction<Intent>(onInvoke: (intent) => TvShowMoreInfoNotification().dispatch(context)),
@ -320,7 +320,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
await _entry.catalog(background: false, force: false, persist: true); await _entry.catalog(background: false, force: false, persist: true);
await _entry.locate(background: false, force: false, geocoderLocale: settings.appliedLocale); await _entry.locate(background: false, force: false, geocoderLocale: settings.appliedLocale);
} else { } else {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
} }
// needed to refresh when entry changes but the page does not (e.g. on page deletion) // needed to refresh when entry changes but the page does not (e.g. on page deletion)

View file

@ -503,8 +503,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
if (baseCollection == null) return; if (baseCollection == null) return;
_onLeave(); _onLeave();
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(
@ -602,7 +601,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
} }
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
} else { } else {
_leaveViewer(); _leaveViewer();
} }
@ -639,7 +638,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
if (Navigator.canPop(context)) { if (Navigator.canPop(context)) {
void pop() { void pop() {
_onLeave(); _onLeave();
Navigator.pop(context); Navigator.maybeOf(context)?.pop();
} }
// closing hero, with viewer as source // closing hero, with viewer as source

View file

@ -30,7 +30,7 @@ class InfoSearchDelegate extends SearchDelegate {
icon: AnimatedIcons.menu_arrow, icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation, progress: transitionAnimation,
), ),
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.maybeOf(context)?.pop(),
tooltip: MaterialLocalizations.of(context).backButtonTooltip, tooltip: MaterialLocalizations.of(context).backButtonTooltip,
); );
} }

View file

@ -137,8 +137,7 @@ class _LocationSectionState extends State<LocationSection> {
listenToSource: true, listenToSource: true,
fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(), fixedSelection: baseCollection.sortedEntries.where((entry) => entry.hasGps).toList(),
); );
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: MapPage.routeName), settings: const RouteSettings(name: MapPage.routeName),
builder: (context) => MapPage( builder: (context) => MapPage(

View file

@ -93,8 +93,7 @@ class MetadataDirTile extends StatelessWidget {
'Metadata': InfoRowGroup.linkSpanBuilder( 'Metadata': InfoRowGroup.linkSpanBuilder(
linkText: (context) => context.l10n.viewerInfoViewXmlLinkText, linkText: (context) => context.l10n.viewerInfoViewXmlLinkText,
onTap: (context) { onTap: (context) {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: SourceViewerPage.routeName), settings: const RouteSettings(name: SourceViewerPage.routeName),
builder: (context) => SourceViewerPage( builder: (context) => SourceViewerPage(

View file

@ -27,8 +27,7 @@ class PanoramaOverlay extends StatelessWidget {
onPressed: () async { onPressed: () async {
final info = await metadataFetchService.getPanoramaInfo(entry); final info = await metadataFetchService.getPanoramaInfo(entry);
if (info != null) { if (info != null) {
unawaited(Navigator.push( unawaited(Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: PanoramaPage.routeName), settings: const RouteSettings(name: PanoramaPage.routeName),
builder: (context) => PanoramaPage( builder: (context) => PanoramaPage(

View file

@ -123,8 +123,7 @@ class _SlideshowPageState extends State<SlideshowPage> {
final album = entry.directory; final album = entry.directory;
final uri = entry.uri; final uri = entry.uri;
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(

View file

@ -70,11 +70,11 @@ abstract class AvesVideoController {
content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))), content: Text(context.l10n.videoResumeDialogMessage(formatFriendlyDuration(Duration(milliseconds: resumeTime)))),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.pop(context), onPressed: () => Navigator.maybeOf(context)?.pop(),
child: Text(context.l10n.videoStartOverButtonLabel), child: Text(context.l10n.videoStartOverButtonLabel),
), ),
TextButton( TextButton(
onPressed: () => Navigator.pop(context, true), onPressed: () => Navigator.maybeOf(context)?.pop(true),
child: Text(context.l10n.videoResumeButtonLabel), child: Text(context.l10n.videoResumeButtonLabel),
), ),
], ],

View file

@ -120,8 +120,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
if (context != null) { if (context != null) {
final source = _collection.source; final source = _collection.source;
final newUri = newFields['uri'] as String?; final newUri = newFields['uri'] as String?;
Navigator.pushAndRemoveUntil( Navigator.maybeOf(context)?.pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: CollectionPage.routeName), settings: const RouteSettings(name: CollectionPage.routeName),
builder: (context) => CollectionPage( builder: (context) => CollectionPage(
@ -181,8 +180,7 @@ class VideoActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
resumePosition = controller.currentPosition; resumePosition = controller.currentPosition;
await controller.pause(); await controller.pause();
} }
await Navigator.push( await Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: VideoSettingsPage.routeName), settings: const RouteSettings(name: VideoSettingsPage.routeName),
builder: (context) => const VideoSettingsPage(), builder: (context) => const VideoSettingsPage(),

View file

@ -222,8 +222,7 @@ class _WelcomePageState extends State<WelcomePage> {
} }
void _goToPolicyPage() { void _goToPolicyPage() {
Navigator.push( Navigator.maybeOf(context)?.push(
context,
MaterialPageRoute( MaterialPageRoute(
settings: const RouteSettings(name: PolicyPage.routeName), settings: const RouteSettings(name: PolicyPage.routeName),
builder: (context) => const PolicyPage(), builder: (context) => const PolicyPage(),

View file

@ -84,14 +84,14 @@ packages:
name: firebase_crashlytics name: firebase_crashlytics
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.10" version: "3.0.11"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.10" version: "3.3.11"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter

View file

@ -35,7 +35,7 @@ packages:
name: args name: args
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -307,14 +307,14 @@ packages:
name: firebase_crashlytics name: firebase_crashlytics
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.10" version: "3.0.11"
firebase_crashlytics_platform_interface: firebase_crashlytics_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_crashlytics_platform_interface name: firebase_crashlytics_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.3.10" version: "3.3.11"
flex_color_picker: flex_color_picker:
dependency: "direct main" dependency: "direct main"
description: description: