diff --git a/lib/app/cubits/connectivity_info.dart b/lib/app/cubits/connectivity_info.dart index ddd39432..e58f2a06 100644 --- a/lib/app/cubits/connectivity_info.dart +++ b/lib/app/cubits/connectivity_info.dart @@ -3,7 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:ouisync/ouisync.dart'; -import '../utils/log.dart'; +import '../utils/utils.dart' show AppLogger; +import 'cubits.dart' show CubitActions; class ConnectivityInfoState extends Equatable { final String tcpListenerV4; @@ -71,7 +72,8 @@ class ConnectivityInfoState extends Equatable { "externalAddressV6: $externalAddressV6)"; } -class ConnectivityInfo extends Cubit with AppLogger { +class ConnectivityInfo extends Cubit + with AppLogger, CubitActions { final Session _session; final _networkInfo = NetworkInfo(); @@ -89,7 +91,7 @@ class ConnectivityInfo extends Cubit with AppLogger { return; } - emit(state.copyWith( + emitUnlessClosed(state.copyWith( tcpListenerV4: tcpListenerV4 ?? '', tcpListenerV6: tcpListenerV6 ?? '', quicListenerV4: quicListenerV4 ?? '', @@ -104,9 +106,9 @@ class ConnectivityInfo extends Cubit with AppLogger { if (localIPv4 != null) { final port = _extractPort(quicListenerV4 ?? tcpListenerV4 ?? ''); - emit(state.copyWith(localAddressV4: "$localIPv4:$port")); + emitUnlessClosed(state.copyWith(localAddressV4: "$localIPv4:$port")); } else { - emit(state.copyWith(localAddressV4: "")); + emitUnlessClosed(state.copyWith(localAddressV4: "")); } final localIPv6 = await _networkInfo.getWifiIPv6(); @@ -117,9 +119,9 @@ class ConnectivityInfo extends Cubit with AppLogger { if (localIPv6 != null) { final port = _extractPort(quicListenerV6 ?? tcpListenerV6 ?? ''); - emit(state.copyWith(localAddressV6: "[$localIPv6]:$port")); + emitUnlessClosed(state.copyWith(localAddressV6: "[$localIPv6]:$port")); } else { - emit(state.copyWith(localAddressV6: "")); + emitUnlessClosed(state.copyWith(localAddressV6: "")); } final externalAddressV4 = await _session.externalAddressV4 ?? ""; @@ -128,7 +130,7 @@ class ConnectivityInfo extends Cubit with AppLogger { return; } - emit(state.copyWith(externalAddressV4: externalAddressV4)); + emitUnlessClosed(state.copyWith(externalAddressV4: externalAddressV4)); final externalAddressV6 = await _session.externalAddressV6 ?? ""; @@ -136,7 +138,7 @@ class ConnectivityInfo extends Cubit with AppLogger { return; } - emit(state.copyWith(externalAddressV6: externalAddressV6)); + emitUnlessClosed(state.copyWith(externalAddressV6: externalAddressV6)); } } diff --git a/lib/app/cubits/cubits.dart b/lib/app/cubits/cubits.dart index e1a28dfa..7ed4cbae 100644 --- a/lib/app/cubits/cubits.dart +++ b/lib/app/cubits/cubits.dart @@ -17,5 +17,6 @@ export 'repos.dart'; export 'sort_list.dart'; export 'state_monitor.dart'; export 'upgrade_exists.dart'; +export 'utils.dart'; export 'value.dart'; export 'watch.dart'; diff --git a/lib/app/cubits/entry_bottom_sheet.dart b/lib/app/cubits/entry_bottom_sheet.dart index cf40638b..38d4f1cf 100644 --- a/lib/app/cubits/entry_bottom_sheet.dart +++ b/lib/app/cubits/entry_bottom_sheet.dart @@ -2,8 +2,8 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ouisync/bindings.dart'; -import '../utils/log.dart'; -import 'cubits.dart'; +import '../utils/utils.dart' show AppLogger; +import 'cubits.dart' show CubitActions, NavigationCubit, RepoCubit, ReposCubit; enum BottomSheetType { move, upload, gone } @@ -73,7 +73,7 @@ class SaveMediaSheetState extends Equatable implements EntryBottomSheetState { class HideSheetState implements EntryBottomSheetState {} class EntryBottomSheetCubit extends Cubit - with AppLogger { + with AppLogger, CubitActions { EntryBottomSheetCubit() : super(HideSheetState()); void showMoveEntry({ @@ -82,7 +82,7 @@ class EntryBottomSheetCubit extends Cubit required String entryPath, required EntryType entryType, }) => - emit( + emitUnlessClosed( MoveEntrySheetState( repoCubit: repoCubit, navigationCubit: navigationCubit, @@ -93,12 +93,12 @@ class EntryBottomSheetCubit extends Cubit void showSaveMedia( {required ReposCubit reposCubit, required List paths}) => - emit( + emitUnlessClosed( SaveMediaSheetState( reposCubit: reposCubit, sharedMediaPaths: paths, ), ); - void hide() => emit(HideSheetState()); + void hide() => emitUnlessClosed(HideSheetState()); } diff --git a/lib/app/cubits/file_progress.dart b/lib/app/cubits/file_progress.dart index 43a7b1e2..337a25be 100644 --- a/lib/app/cubits/file_progress.dart +++ b/lib/app/cubits/file_progress.dart @@ -3,14 +3,14 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:stream_transform/stream_transform.dart'; -import 'repo.dart'; +import 'cubits.dart' show CubitActions, RepoCubit; /// Cubit representing sync progress of a file. -class FileProgress extends Cubit { +class FileProgress extends Cubit with CubitActions { FileProgress(RepoCubit repo, this.path) : super(null) { _subscription = repo.events.startWith(null).asyncMapSample((_) => _fetch(repo)).listen( - emit, + emitUnlessClosed, onError: (e, st) {}, // these errors are not important - ignore ); } diff --git a/lib/app/cubits/job.dart b/lib/app/cubits/job.dart index 8271ce4d..93f93596 100644 --- a/lib/app/cubits/job.dart +++ b/lib/app/cubits/job.dart @@ -1,5 +1,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'cubits.dart' show CubitActions; + class JobState { int soFar; int total; @@ -14,14 +16,14 @@ class JobState { ); } -class Job extends Cubit { +class Job extends Cubit with CubitActions { Job(int soFar, int total) : super(JobState(soFar: soFar, total: total)); void update(int soFar) { - emit(state.copyWith(soFar: soFar)); + emitUnlessClosed(state.copyWith(soFar: soFar)); } void cancel() { - emit(state.copyWith(cancel: true)); + emitUnlessClosed(state.copyWith(cancel: true)); } } diff --git a/lib/app/cubits/navigation.dart b/lib/app/cubits/navigation.dart index ded940dc..362d29e7 100644 --- a/lib/app/cubits/navigation.dart +++ b/lib/app/cubits/navigation.dart @@ -1,6 +1,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import '../models/repo_location.dart'; -import '../utils/utils.dart'; + +import '../models/models.dart' show RepoLocation; +import '../utils/utils.dart' show AppLogger; +import 'cubits.dart' show CubitActions; class NavigationState { final RepoLocation? repoLocation; @@ -14,7 +16,8 @@ class NavigationState { }); } -class NavigationCubit extends Cubit with AppLogger { +class NavigationCubit extends Cubit + with AppLogger, CubitActions { NavigationCubit() : super(NavigationState( repoLocation: null, @@ -23,7 +26,7 @@ class NavigationCubit extends Cubit with AppLogger { )); void current(RepoLocation repoLocation, String path, bool isFolder) => - emit(NavigationState( + emitUnlessClosed(NavigationState( repoLocation: repoLocation, path: path, isFolder: isFolder, diff --git a/lib/app/cubits/power_control.dart b/lib/app/cubits/power_control.dart index b4b19d90..94bd1c4c 100644 --- a/lib/app/cubits/power_control.dart +++ b/lib/app/cubits/power_control.dart @@ -1,14 +1,15 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ouisync/ouisync.dart' as oui; -import 'package:equatable/equatable.dart'; import '../../generated/l10n.dart'; -import '../utils/utils.dart'; -import 'utils.dart'; +import '../utils/utils.dart' + show AppLogger, LocalInterfaceAddr, LocalInterfaceWatch, AppLoggy, Settings; import '../utils/watch.dart' as watch; +import 'cubits.dart' show CubitActions; const _unspecifiedV4 = "0.0.0.0:0"; const _unspecifiedV6 = "[::]:0"; @@ -105,7 +106,7 @@ class PowerControlState { } class PowerControl extends Cubit - with CubitActions, AppLogger { + with AppLogger, CubitActions { final oui.Session _session; final Settings _settings; final Connectivity _connectivity; @@ -171,7 +172,7 @@ class PowerControl extends Cubit return; } - emit(state.copyWith(userWantsPortForwardingEnabled: value)); + emitUnlessClosed(state.copyWith(userWantsPortForwardingEnabled: value)); await _session.setPortForwardingEnabled(value); } diff --git a/lib/app/cubits/repo_creation.dart b/lib/app/cubits/repo_creation.dart index 0be09ee3..4ecb429e 100644 --- a/lib/app/cubits/repo_creation.dart +++ b/lib/app/cubits/repo_creation.dart @@ -6,15 +6,21 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ouisync/ouisync.dart' show AccessMode, ShareToken; import '../../generated/l10n.dart'; -import '../models/auth_mode.dart'; -import '../models/local_secret.dart'; -import '../models/repo_entry.dart'; -import '../models/repo_location.dart'; -import '../utils/dialogs.dart'; -import '../utils/log.dart'; -import '../utils/strings.dart'; -import 'repos.dart'; -import 'utils.dart'; +import '../models/models.dart' + show + ErrorRepoEntry, + LocalSecretKeyAndSalt, + LocalSecretInput, + LocalSecretManual, + LocalSecretMode, + LocalSecretRandom, + LoadingRepoEntry, + MissingRepoEntry, + OpenRepoEntry, + RepoLocation, + SetLocalSecret; +import '../utils/utils.dart' show AppLogger, Dialogs, Strings; +import 'cubits.dart' show CubitActions, ReposCubit; class RepoCreationState { static const initialLocalSecretMode = LocalSecretMode.randomStored; @@ -159,7 +165,7 @@ class RepoCreationCubit extends Cubit final useCacheServers = await reposCubit.cacheServers.isEnabledForShareToken(token); - emit(state.copyWith( + emitUnlessClosed(state.copyWith( accessMode: accessMode, suggestedName: suggestedName, token: token, @@ -175,7 +181,7 @@ class RepoCreationCubit extends Cubit } void setUseCacheServers(bool value) { - emit(state.copyWith(useCacheServers: value)); + emitUnlessClosed(state.copyWith(useCacheServers: value)); } void setLocalSecret(LocalSecretInput input) { @@ -213,7 +219,8 @@ class RepoCreationCubit extends Cubit ), }; - emit(state.copyWith(substate: substate, localSecretMode: input.mode)); + emitUnlessClosed( + state.copyWith(substate: substate, localSecretMode: input.mode)); } Future save() async { @@ -245,11 +252,11 @@ class RepoCreationCubit extends Cubit switch (repoEntry) { case OpenRepoEntry(): - emit(state.copyWith( + emitUnlessClosed(state.copyWith( substate: RepoCreationSuccess(location: substate.location), )); case ErrorRepoEntry(): - emit(state.copyWith( + emitUnlessClosed(state.copyWith( substate: RepoCreationFailure( location: substate.location, error: repoEntry.error, diff --git a/lib/app/cubits/repo_security.dart b/lib/app/cubits/repo_security.dart index 2854e1bc..0c3a6fa5 100644 --- a/lib/app/cubits/repo_security.dart +++ b/lib/app/cubits/repo_security.dart @@ -2,13 +2,24 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../models/auth_mode.dart'; -import '../models/local_secret.dart'; -import '../utils/local_auth.dart'; -import '../utils/log.dart'; -import '../utils/master_key.dart'; -import '../utils/option.dart'; -import '../utils/password_hasher.dart'; +import '../models/models.dart' + show + AuthMode, + AuthModeBlindOrManual, + AuthModeKeyStoredOnDevice, + AuthModePasswordStoredOnDevice, + LocalSecret, + LocalSecretKeyAndSalt, + LocalSecretInput, + LocalSecretKey, + LocalSecretManual, + LocalSecretMode, + LocalSecretRandom, + LocalPassword, + SecretKeyOrigin, + SecretKeyStore; +import '../utils/utils.dart' + show AppLogger, LocalAuth, MasterKey, None, Option, PasswordHasher, Some; import 'repo.dart'; import 'utils.dart'; @@ -175,19 +186,19 @@ class RepoSecurityCubit extends Cubit } void setOrigin(SecretKeyOrigin value) { - emit(state.copyWith(origin: value)); + emitUnlessClosed(state.copyWith(origin: value)); } void setStore(bool value) { - emit(state.copyWith(userWantsToStoreSecret: value)); + emitUnlessClosed(state.copyWith(userWantsToStoreSecret: value)); } void setSecureWithBiometrics(bool value) { - emit(state.copyWith(secureWithBiometrics: value)); + emitUnlessClosed(state.copyWith(secureWithBiometrics: value)); } void setLocalPassword(String? value) { - emit(state.copyWith( + emitUnlessClosed(state.copyWith( localPassword: value != null ? Some(LocalPassword(value)) : None(), )); } @@ -221,7 +232,7 @@ class RepoSecurityCubit extends Cubit ? Some(newLocalSecretInput.password) : None(); - emit(state.copyWith( + emitUnlessClosed(state.copyWith( oldLocalSecretMode: newAuthMode.localSecretMode, updatedLocalPassword: newLocalPassword, )); @@ -244,7 +255,8 @@ class RepoSecurityCubit extends Cubit oldSecret: state.oldLocalSecret, newSecret: newLocalSecret, ); - emit(state.copyWith(oldLocalSecret: newLocalSecret.toLocalSecret())); + emitUnlessClosed( + state.copyWith(oldLocalSecret: newLocalSecret.toLocalSecret())); loggy.debug('Repo local secret updated'); } catch (e, st) { loggy.error( diff --git a/lib/app/cubits/sort_list.dart b/lib/app/cubits/sort_list.dart index e9c47ef6..540d3371 100644 --- a/lib/app/cubits/sort_list.dart +++ b/lib/app/cubits/sort_list.dart @@ -1,7 +1,8 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../utils/log.dart'; +import '../utils/utils.dart' show AppLogger; +import 'cubits.dart' show CubitActions; class SortListState extends Equatable { final SortBy sortBy; @@ -24,7 +25,7 @@ class SortListState extends Equatable { List get props => [sortBy, direction, listType]; } -class SortListCubit extends Cubit with AppLogger { +class SortListCubit extends Cubit with AppLogger, CubitActions { SortListCubit._(super.state); static SortListCubit create( @@ -37,13 +38,14 @@ class SortListCubit extends Cubit with AppLogger { return SortListCubit._(initialState); } - void sortBy(SortBy sortBy) => emit(state.copyWith(sortBy: sortBy)); + void sortBy(SortBy sortBy) => + emitUnlessClosed(state.copyWith(sortBy: sortBy)); void switchListType(ListType listType) => - emit(state.copyWith(listType: listType)); + emitUnlessClosed(state.copyWith(listType: listType)); void switchSortDirection(SortDirection direction) => - emit(state.copyWith(direction: direction)); + emitUnlessClosed(state.copyWith(direction: direction)); } enum SortBy { name, size, type } diff --git a/lib/app/cubits/value.dart b/lib/app/cubits/value.dart index ca90180d..520d10e6 100644 --- a/lib/app/cubits/value.dart +++ b/lib/app/cubits/value.dart @@ -1,8 +1,10 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'cubits.dart' show CubitActions; // The simplest cubit. We can't use `Cubit` directly because that is abstract. -class Value extends Cubit { +class Value extends Cubit with CubitActions { Value(super.initial); Widget builder(Widget Function(State) func) { @@ -15,7 +17,7 @@ class Value extends Cubit { } void changed() { - emit(state); + emitUnlessClosed(state); } void update(void Function(State) f) { diff --git a/lib/app/cubits/watch.dart b/lib/app/cubits/watch.dart index 6e0bbf0d..6112c6cc 100644 --- a/lib/app/cubits/watch.dart +++ b/lib/app/cubits/watch.dart @@ -1,12 +1,14 @@ import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'cubits.dart' show CubitActions; -class WatchSelf extends Cubit { +class WatchSelf extends Cubit with CubitActions { WatchSelf() : super(Changed(0)); void changed() { - emit(state.next); + emitUnlessClosed(state.next); } void update(void Function(Self) f) { diff --git a/lib/app/mixins/repo_actions_mixin.dart b/lib/app/mixins/repo_actions_mixin.dart index 72d9592e..6d83a07b 100644 --- a/lib/app/mixins/repo_actions_mixin.dart +++ b/lib/app/mixins/repo_actions_mixin.dart @@ -35,7 +35,7 @@ mixin RepositoryActionsMixin on LoggyType { } await Dialogs.executeFutureWithLoadingDialog( - context, + null, reposCubit.renameRepository(repoCubit.location, newName), ); @@ -202,12 +202,13 @@ mixin RepositoryActionsMixin on LoggyType { ), ), actions: [ - Fields.dialogActions(context, buttons: [ + Fields.dialogActions(buttons: [ NegativeButton( - text: S.current.actionCancelCapital, - onPressed: () async => - await Navigator.of(context).maybePop(false), - buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton), + text: S.current.actionCancelCapital, + onPressed: () async => + await Navigator.of(context).maybePop(false), + buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton, + ), PositiveButton( text: S.current.actionDeleteCapital, onPressed: () async => await Navigator.of(context).maybePop(true), diff --git a/lib/app/pages/accept_eq_values_terms_privacy_page.dart b/lib/app/pages/accept_eq_values_terms_privacy_page.dart index da31b26d..9d72ee77 100644 --- a/lib/app/pages/accept_eq_values_terms_privacy_page.dart +++ b/lib/app/pages/accept_eq_values_terms_privacy_page.dart @@ -31,7 +31,6 @@ class _AcceptEqualitieValuesTermsPrivacyPageState child: ContentWithStickyFooterState( content: _buildContent(context), footer: Fields.dialogActions( - context, mainAxisAlignment: MainAxisAlignment.spaceEvenly, buttons: _buildActions(context), ), diff --git a/lib/app/pages/main_page.dart b/lib/app/pages/main_page.dart index 390a41a5..cc7c63b5 100644 --- a/lib/app/pages/main_page.dart +++ b/lib/app/pages/main_page.dart @@ -390,7 +390,7 @@ class _MainPageState extends State focusNode: _fabFocus, heroTag: Constants.heroTagRepoListActions, child: icon, - onPressed: () => _showRepoListActions(context), + onPressed: () => unawaited(_showRepoListActions(context)), ); } } else if (current is OpenRepoEntry) { @@ -403,7 +403,10 @@ class _MainPageState extends State focusNode: _fabFocus, heroTag: Constants.heroTagMainPageActions, child: icon, - onPressed: () => _showDirectoryActions(context, current), + onPressed: () => unawaited(_showDirectoryActions( + context, + current, + )), ), ), ); @@ -578,7 +581,7 @@ class _MainPageState extends State /// using a local HTTP server and the internet navigator previewer. try { final url = await Dialogs.executeFutureWithLoadingDialog( - context, + null, repo.previewFileUrl(entry.path), ); @@ -924,24 +927,21 @@ class _MainPageState extends State ); } - Future _showDirectoryActions( + Future _showDirectoryActions( BuildContext parentContext, OpenRepoEntry repo, - ) => + ) async => showModalBottomSheet( isScrollControlled: true, context: parentContext, shape: Dimensions.borderBottomSheetTop, - builder: (context) { - return DirectoryActions( - parentContext: parentContext, - repoCubit: repo.cubit, - bottomSheetCubit: widget.reposCubit.bottomSheet, - ); - }, + builder: (context) => DirectoryActions( + repoCubit: repo.cubit, + bottomSheetCubit: widget.reposCubit.bottomSheet, + ), ); - Future _showRepoListActions(BuildContext context) => + Future _showRepoListActions(BuildContext context) async => showModalBottomSheet( isScrollControlled: true, context: context, diff --git a/lib/app/utils/dialogs.dart b/lib/app/utils/dialogs.dart index bd0d5985..772d1ea6 100644 --- a/lib/app/utils/dialogs.dart +++ b/lib/app/utils/dialogs.dart @@ -144,7 +144,7 @@ abstract class Dialogs { ]), const SizedBox(height: 20.0), Text(S.current.messageConfirmFileDeletion), - Fields.dialogActions(context, buttons: [ + Fields.dialogActions(buttons: [ NegativeButton( text: S.current.actionCancel, onPressed: () async => @@ -181,7 +181,6 @@ abstract class Dialogs { const SizedBox(height: 20.0), Text(validationMessage), Fields.dialogActions( - context, buttons: [ NegativeButton( text: S.current.actionCancel, diff --git a/lib/app/utils/fields.dart b/lib/app/utils/fields.dart index 74e052ea..4a3857c1 100644 --- a/lib/app/utils/fields.dart +++ b/lib/app/utils/fields.dart @@ -494,16 +494,19 @@ class Fields { )); } - static Widget dialogActions(BuildContext context, - {required List buttons, - EdgeInsetsDirectional padding = Dimensions.paddingActionsSection, - MainAxisAlignment mainAxisAlignment = MainAxisAlignment.center}) => + static Widget dialogActions({ + required List buttons, + EdgeInsetsDirectional padding = Dimensions.paddingActionsSection, + MainAxisAlignment mainAxisAlignment = MainAxisAlignment.center, + }) => Padding( - padding: padding, - child: Row( - mainAxisAlignment: mainAxisAlignment, - mainAxisSize: MainAxisSize.max, - children: buttons)); + padding: padding, + child: Row( + mainAxisAlignment: mainAxisAlignment, + mainAxisSize: MainAxisSize.max, + children: buttons, + ), + ); static Widget placeholderWidget( {required String assetName, @@ -592,7 +595,7 @@ class Fields { } final content = await Dialogs.executeFutureWithLoadingDialog( - context, + null, webView.loadUrl(context, url), ); diff --git a/lib/app/utils/file_io.dart b/lib/app/utils/file_io.dart index 1f70f3e2..eb33dba5 100644 --- a/lib/app/utils/file_io.dart +++ b/lib/app/utils/file_io.dart @@ -3,7 +3,7 @@ import 'dart:io' as io; import 'package:device_info_plus/device_info_plus.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart' - show AlertDialog, Axis, BuildContext, Flex, Navigator, showDialog; + show AlertDialog, Axis, BuildContext, Flex, showDialog; import 'package:ouisync/ouisync.dart'; import 'package:path/path.dart' as p; import 'package:permission_handler/permission_handler.dart'; @@ -26,14 +26,16 @@ import 'utils.dart' enum FileDestination { device, ouisync } class FileIO with AppLogger { - const FileIO({required this.context, required this.repoCubit}); + const FileIO({ + required this.context, + required this.repoCubit, + }); final BuildContext context; final RepoCubit repoCubit; - Future addFileFromDevice({ - required FileType type, - }) async { + Future addFileFromDevice( + {required FileType type, required Future popCallback}) async { final storagePermissionOk = await _maybeRequestPermission(context); if (storagePermissionOk == false) { return; @@ -51,7 +53,7 @@ class FileIO with AppLogger { return 'Adding files $fileNames'; }); - await Navigator.of(context).maybePop(); + await popCallback; for (final srcFile in result.files) { final parentPath = repoCubit.state.currentFolder.path; @@ -67,7 +69,10 @@ class FileIO with AppLogger { ); } - final replaceOrKeepEntry = await _confirmKeepOrReplaceEntry(fileName); + final replaceOrKeepEntry = await _confirmKeepOrReplaceEntry( + context, + fileName: fileName, + ); if (replaceOrKeepEntry == null) { return; @@ -98,7 +103,10 @@ class FileIO with AppLogger { } } - Future _confirmKeepOrReplaceEntry(String fileName) async => + Future _confirmKeepOrReplaceEntry( + BuildContext context, { + required String fileName, + }) async => showDialog( context: context, builder: (BuildContext context) => AlertDialog( diff --git a/lib/app/utils/utils.dart b/lib/app/utils/utils.dart index 5b507046..70cdd835 100644 --- a/lib/app/utils/utils.dart +++ b/lib/app/utils/utils.dart @@ -22,6 +22,7 @@ export 'log.dart'; export 'master_key.dart'; export 'log_reader.dart'; export 'move_entry.dart'; +export 'option.dart'; export 'password_hasher.dart'; export 'native.dart'; export 'permissions.dart'; @@ -32,3 +33,4 @@ export 'settings/settings.dart'; export 'spinning_icon.dart'; export 'static_file_handler.dart'; export 'strings.dart'; +export 'watch.dart'; diff --git a/lib/app/widgets/dialogs/add_peer_dialog.dart b/lib/app/widgets/dialogs/add_peer_dialog.dart index 453afb95..0c2dec62 100644 --- a/lib/app/widgets/dialogs/add_peer_dialog.dart +++ b/lib/app/widgets/dialogs/add_peer_dialog.dart @@ -105,7 +105,7 @@ class _AddPeerDialogState extends State { } Future _cancel(BuildContext context) async => - await Navigator.of(context).maybePop(); + Navigator.of(context).maybePop(); String? get _value { final a = _address; diff --git a/lib/app/widgets/dialogs/modal_actions_bottom_sheet.dart b/lib/app/widgets/dialogs/modal_actions_bottom_sheet.dart index 35883e9b..d3608c6a 100644 --- a/lib/app/widgets/dialogs/modal_actions_bottom_sheet.dart +++ b/lib/app/widgets/dialogs/modal_actions_bottom_sheet.dart @@ -12,17 +12,15 @@ import '../../cubits/cubits.dart' HideSheetState, RepoCubit; import '../../utils/utils.dart' - show AppLogger, Dialogs, Dimensions, Fields, FileIO; + show AppLogger, Dialogs, Dimensions, Fields, FileIO, showSnackBar; import '../widgets.dart' show ActionsDialog, FolderCreation; class DirectoryActions extends StatelessWidget with AppLogger { const DirectoryActions({ - required this.parentContext, required this.repoCubit, required this.bottomSheetCubit, }); - final BuildContext parentContext; final RepoCubit repoCubit; final EntryBottomSheetCubit bottomSheetCubit; @@ -53,9 +51,12 @@ class DirectoryActions extends StatelessWidget with AppLogger { _buildAction( name: S.current.actionNewFolder, icon: Icons.create_new_folder_outlined, - action: () => createFolderDialog(context, repoCubit), + action: () async => await createFolderDialog( + context, + repoCubit, + ), ), - _buildNewFileAction(parentContext), + _buildNewFileAction(cubit: repoCubit), ], ), ), @@ -94,7 +95,7 @@ class DirectoryActions extends StatelessWidget with AppLogger { ); } - Widget _buildNewFileAction(BuildContext parentContext) => + Widget _buildNewFileAction({required RepoCubit cubit}) => BlocBuilder( bloc: bottomSheetCubit, builder: (context, state) { @@ -111,9 +112,9 @@ class DirectoryActions extends StatelessWidget with AppLogger { icon: Icons.upload_file_outlined, action: enable ? () async => await addFile( - parentContext, - repoCubit, - FileType.any, + context, + repoCubit: cubit, + type: FileType.any, ) : null, ), @@ -123,9 +124,9 @@ class DirectoryActions extends StatelessWidget with AppLogger { icon: Icons.photo_library_outlined, action: enable ? () async => await addFile( - parentContext, - repoCubit, - FileType.media, + context, + repoCubit: cubit, + type: FileType.media, ) : () async => await _showNotAvailableAlertDialog(context), ), @@ -141,28 +142,43 @@ class DirectoryActions extends StatelessWidget with AppLogger { message: S.current.messageMovingEntry, ); - void createFolderDialog(context, RepoCubit cubit) async { + Future createFolderDialog(BuildContext context, RepoCubit cubit) async { final parent = cubit.state.currentFolder.path; - var newFolderPath = await showDialog( + + final newFolderPath = await showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) => ActionsDialog( - title: S.current.titleCreateFolder, - body: FolderCreation(cubit: cubit, parent: parent)), + title: S.current.titleCreateFolder, + body: FolderCreation(cubit: cubit, parent: parent), + ), ); if (newFolderPath == null || newFolderPath.isEmpty) { return; } - await Navigator.of(parentContext).maybePop(); + final result = await Dialogs.executeWithLoadingDialog( + null, + () async => await cubit.createFolder(newFolderPath), + ); + + if (!result) { + showSnackBar('Error creating folder $newFolderPath'); + return; + } + + Navigator.of(context).pop(); } Future addFile( - parentContext, - RepoCubit repoCubit, - FileType type, - ) async => - FileIO(context: parentContext, repoCubit: repoCubit) - .addFileFromDevice(type: type); + BuildContext context, { + required RepoCubit repoCubit, + required FileType type, + }) async { + final callback = Navigator.of(context).maybePop(); + + await FileIO(context: context, repoCubit: repoCubit) + .addFileFromDevice(type: type, popCallback: callback); + } } diff --git a/lib/app/widgets/dialogs/modal_file_detail_bottom_sheet.dart b/lib/app/widgets/dialogs/modal_file_detail_bottom_sheet.dart index ef72e0d0..5b0c39b7 100644 --- a/lib/app/widgets/dialogs/modal_file_detail_bottom_sheet.dart +++ b/lib/app/widgets/dialogs/modal_file_detail_bottom_sheet.dart @@ -4,13 +4,13 @@ import 'package:flutter/material.dart'; import 'package:ouisync/native_channels.dart'; import 'package:ouisync/ouisync.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../../../generated/l10n.dart'; import '../../cubits/cubits.dart' show BottomSheetType, RepoCubit; import '../../models/models.dart' show FileEntry; import '../../pages/pages.dart' show PreviewFileCallback; +import '../../utils/repo_path.dart' as repo_path; import '../../utils/utils.dart' show AppThemeExtension, @@ -186,8 +186,8 @@ class _FileDetailState extends State { isDanger: true, dense: true, onTap: () async { - final fileName = p.basename(widget.entry.path); - final parent = p.dirname(widget.entry.path); + final fileName = repo_path.basename(widget.entry.path); + final parent = repo_path.dirname(widget.entry.path); final deletedFileName = await Dialogs.deleteFileAlertDialog( widget.repoCubit, @@ -217,7 +217,7 @@ class _FileDetailState extends State { EntryInfoTable( entryInfo: { S.current.labelName: widget.entry.name, - S.current.labelLocation: p.dirname(widget.entry.path), + S.current.labelLocation: repo_path.dirname(widget.entry.path), S.current.labelSize: formatSize(widget.entry.size ?? 0), }, ) @@ -233,9 +233,9 @@ class _FileDetailState extends State { return ScaffoldMessenger( child: Builder( builder: (context) { - final parent = p.dirname(entry.path); - final oldName = p.basename(entry.path); - final originalExtension = p.extension(entry.path); + final parent = repo_path.dirname(entry.path); + final oldName = repo_path.basename(entry.path); + final originalExtension = repo_path.extension(entry.path); return Scaffold( backgroundColor: Colors.transparent, @@ -260,8 +260,8 @@ class _FileDetailState extends State { (newName) async { if (newName.isNotEmpty) { // The new name provided by the user. - final parent = p.dirname(entry.path); - final newEntryPath = p.join(parent, newName); + final parent = repo_path.dirname(entry.path); + final newEntryPath = repo_path.join(parent, newName); await widget.repoCubit.moveEntry( source: entry.path, diff --git a/lib/app/widgets/dialogs/modal_folder_creation_dialog.dart b/lib/app/widgets/dialogs/modal_folder_creation_dialog.dart index cb4d8a1f..7cfcc75c 100644 --- a/lib/app/widgets/dialogs/modal_folder_creation_dialog.dart +++ b/lib/app/widgets/dialogs/modal_folder_creation_dialog.dart @@ -2,11 +2,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import '../../../generated/l10n.dart'; -import '../../cubits/cubits.dart'; +import '../../cubits/cubits.dart' show RepoCubit; +import '../../utils/platform/platform.dart' show PlatformValues; import '../../utils/repo_path.dart' as repo_path; -import '../../utils/platform/platform.dart'; -import '../../utils/utils.dart'; -import '../widgets.dart'; +import '../../utils/utils.dart' + show + Dimensions, + Fields, + Strings, + TextEditingControllerExtension, + validateNoEmptyMaybeRegExpr; +import '../widgets.dart' show NegativeButton, PositiveButton; class FolderCreation extends HookWidget { FolderCreation({required this.cubit, required this.parent}); @@ -47,15 +53,16 @@ class FolderCreation extends HookWidget { hintText: S.current.messageFolderName, errorText: nameController.text.isEmpty ? '' : errorMessage, - onFieldSubmitted: (newFolderName) async { - final submitted = - await submitField(parent, newFolderName); - if (submitted && PlatformValues.isDesktopDevice) { - final newFolderPath = - repo_path.join(parent, newFolderName!); - await Navigator.of(context).maybePop(newFolderPath); - await _saveAndPop(newFolderPath); + onFieldSubmitted: (String? newFolderName) async { + if (newFolderName == null || newFolderName.isEmpty) { + return; } + + await _onCreateButtonPress( + context, + parent: parent, + newFolderName: newFolderName, + ); }, validator: validateNoEmptyMaybeRegExpr( emptyError: @@ -67,7 +74,7 @@ class FolderCreation extends HookWidget { focusNode: nameTextFieldFocus); }, ), - Fields.dialogActions(context, buttons: _actions(context, parent)), + Fields.dialogActions(buttons: _actions(context, parent: parent)), ])); } @@ -125,44 +132,37 @@ class FolderCreation extends HookWidget { return true; } - List _actions(BuildContext context, String parent) => [ + List _actions( + BuildContext context, { + required String parent, + }) => + [ NegativeButton( text: S.current.actionCancel, onPressed: () async => await Navigator.of(context).maybePop(''), buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton), PositiveButton( text: S.current.actionCreate, - onPressed: () async { - final newFolderName = nameController.text; - final newFolderPath = repo_path.join(parent, newFolderName); - - await _onCreateButtonPress( - parent, - newFolderName, - newFolderPath, - ); - - Navigator.of(context).pop(newFolderPath); - }, + onPressed: () async => await _onCreateButtonPress( + context, + parent: parent, + newFolderName: nameController.text, + ), buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton, focusNode: positiveButtonFocus, ) ]; Future _onCreateButtonPress( - String parent, - String newFolderName, - String newFolderPath, - ) async { + BuildContext context, { + required String parent, + required String newFolderName, + }) async { final submitted = await submitField(parent, newFolderName); + if (submitted) { - return _saveAndPop(newFolderPath); + final newFolderPath = repo_path.join(parent, newFolderName); + await Navigator.of(context).maybePop(newFolderPath); } } - - Future _saveAndPop(String newFolderPath) async => - Dialogs.executeWithLoadingDialog( - null, - () async => await cubit.createFolder(newFolderPath), - ); } diff --git a/lib/app/widgets/dialogs/modal_rename_dialog.dart b/lib/app/widgets/dialogs/modal_rename_dialog.dart index 2aa8d24d..34ae8490 100644 --- a/lib/app/widgets/dialogs/modal_rename_dialog.dart +++ b/lib/app/widgets/dialogs/modal_rename_dialog.dart @@ -120,7 +120,7 @@ class RenameEntry extends HookWidget with AppLogger { ); }, ), - Fields.dialogActions(context, buttons: _actions(context)), + Fields.dialogActions(buttons: _actions(context)), ]); Future _submitField(String parent, String? newName) async { diff --git a/lib/app/widgets/dialogs/modal_rename_repository_dialog.dart b/lib/app/widgets/dialogs/modal_rename_repository_dialog.dart index a1af9934..074c1515 100644 --- a/lib/app/widgets/dialogs/modal_rename_repository_dialog.dart +++ b/lib/app/widgets/dialogs/modal_rename_repository_dialog.dart @@ -71,7 +71,7 @@ class _RenameRepository extends State { focusNode: newNameFocus, autofocus: true, ), - Fields.dialogActions(context, buttons: buildActions(context)), + Fields.dialogActions(buttons: buildActions(context)), ], ), ); diff --git a/lib/app/widgets/dialogs/modal_replace_keep_entry_dialog.dart b/lib/app/widgets/dialogs/modal_replace_keep_entry_dialog.dart index 72300340..3da8ced1 100644 --- a/lib/app/widgets/dialogs/modal_replace_keep_entry_dialog.dart +++ b/lib/app/widgets/dialogs/modal_replace_keep_entry_dialog.dart @@ -69,7 +69,7 @@ class ReplaceKeepEntry extends StatelessWidget { }, ), Dimensions.spacingVertical, - Fields.dialogActions(context, buttons: _actions(context)), + Fields.dialogActions(buttons: _actions(context)), ]); } diff --git a/lib/app/widgets/dialogs/modal_unlock_repository_dialog.dart b/lib/app/widgets/dialogs/modal_unlock_repository_dialog.dart index 82a4eedb..e71cb9ba 100644 --- a/lib/app/widgets/dialogs/modal_unlock_repository_dialog.dart +++ b/lib/app/widgets/dialogs/modal_unlock_repository_dialog.dart @@ -55,7 +55,7 @@ class _UnlockRepositoryState extends State with AppLogger { buildPasswordField(context), buildStoreSwitch(), buildBiometricsSwitch(), - Fields.dialogActions(context, buttons: buildActions(context)), + Fields.dialogActions(buttons: buildActions(context)), ], ), ); @@ -157,7 +157,7 @@ class _UnlockRepositoryState extends State with AppLogger { ); } - await Navigator.of(context).maybePop(UnlockRepositoryResult( + Navigator.of(context).pop(UnlockRepositoryResult( repoLocation: widget.repoCubit.location, password: password, accessMode: accessMode, diff --git a/lib/app/widgets/dialogs/move_entry_bottom_sheet.dart b/lib/app/widgets/dialogs/move_entry_bottom_sheet.dart index 102bdfa1..9e576263 100644 --- a/lib/app/widgets/dialogs/move_entry_bottom_sheet.dart +++ b/lib/app/widgets/dialogs/move_entry_bottom_sheet.dart @@ -110,7 +110,6 @@ class _MoveEntryDialogState extends State { builder: (context, state) { final aspectRatio = _getButtonAspectRatio(widgetSize); return Fields.dialogActions( - context, buttons: _actions(context, isRepoList, aspectRatio), padding: const EdgeInsetsDirectional.only(top: 20.0), mainAxisAlignment: MainAxisAlignment.end, @@ -152,7 +151,7 @@ class _MoveEntryDialogState extends State { onPressed: canMove ? () async { await Dialogs.executeFutureWithLoadingDialog( - context, + null, widget.onMoveEntry(), ).then( (_) { diff --git a/lib/app/widgets/dialogs/save_shared_media.dart b/lib/app/widgets/dialogs/save_shared_media.dart index 6b0401bf..77f8097c 100644 --- a/lib/app/widgets/dialogs/save_shared_media.dart +++ b/lib/app/widgets/dialogs/save_shared_media.dart @@ -132,7 +132,6 @@ class _SaveSharedMediaState extends State { Widget _buildActions() => widget.reposCubit.builder( (reposCubit) => Fields.dialogActions( - context, buttons: _actions(reposCubit), padding: const EdgeInsetsDirectional.only(top: 20.0), mainAxisAlignment: MainAxisAlignment.end, diff --git a/lib/app/widgets/dialogs/unlock_dialog.dart b/lib/app/widgets/dialogs/unlock_dialog.dart index fa38752f..d267cd35 100644 --- a/lib/app/widgets/dialogs/unlock_dialog.dart +++ b/lib/app/widgets/dialogs/unlock_dialog.dart @@ -68,10 +68,7 @@ class _UnlockDialogState extends State with AppLogger { ), autofocus: true, ), - Fields.dialogActions( - context, - buttons: buildActions(context), - ), + Fields.dialogActions(buttons: buildActions(context)), ], ); diff --git a/lib/app/widgets/repo_creation.dart b/lib/app/widgets/repo_creation.dart index fb999d69..7569a3fe 100644 --- a/lib/app/widgets/repo_creation.dart +++ b/lib/app/widgets/repo_creation.dart @@ -61,7 +61,6 @@ class RepoCreation extends StatelessWidget { builder: (context, state) => ContentWithStickyFooterState( content: _buildContent(context, securityCubit, state), footer: Fields.dialogActions( - context, mainAxisAlignment: MainAxisAlignment.spaceEvenly, buttons: _buildActions(context, securityCubit, state), ), diff --git a/lib/app/widgets/settings/about_section.dart b/lib/app/widgets/settings/about_section.dart index 7232e169..baef67ca 100644 --- a/lib/app/widgets/settings/about_section.dart +++ b/lib/app/widgets/settings/about_section.dart @@ -196,7 +196,7 @@ class AboutSection extends SettingsSection with AppLogger { if (PlatformValues.isMobileDevice) { final pageTitle = Text(title); final content = await Dialogs.executeFutureWithLoadingDialog( - context, + null, webView.loadUrl(context, url), ); @@ -310,17 +310,19 @@ class _FeedbackDialogState extends State { }, ), actions: [ - Fields.dialogActions(context, buttons: [ + Fields.dialogActions(buttons: [ NegativeButton( - text: S.current.actionCancel, - onPressed: () async => - await Navigator.of(context).maybePop(null), - buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton), + text: S.current.actionCancel, + onPressed: () async => + await Navigator.of(context).maybePop(null), + buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton, + ), PositiveButton( - text: S.current.actionOK, - onPressed: () async => - await Navigator.of(context).maybePop(attachments), - buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton) + text: S.current.actionOK, + onPressed: () async => + await Navigator.of(context).maybePop(attachments), + buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton, + ) ]) ]); } diff --git a/pubspec.yaml b/pubspec.yaml index 7dca4ff1..733861ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.2+66 +version: 0.8.3+67 environment: sdk: ^3.5.1