diff --git a/lib/app/cubits/create_repo.dart b/lib/app/cubits/create_repo.dart new file mode 100644 index 000000000..0d06a277e --- /dev/null +++ b/lib/app/cubits/create_repo.dart @@ -0,0 +1,177 @@ +import 'dart:io' as io; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:ouisync_plugin/ouisync_plugin.dart'; + +import '../models/models.dart'; +import '../utils/utils.dart'; +import 'cubits.dart'; + +class CreateRepositoryState extends Equatable { + final bool isBiometricsAvailable; + final ShareToken? shareToken; + final bool isBlindReplica; + final bool secureWithBiometrics; + final bool addPassword; + + final String suggestedName; + final RepoMetaInfo? repositoryMetaInfo; + + final bool obscurePassword; + final bool obscureRetypePassword; + + final bool showSuggestedName; + final bool showAccessModeMessage; + final bool showSavePasswordWarning; + final bool showRepositoryNameInUseWarning; + + final bool deleteRepositoryBeforePop; + + CreateRepositoryState( + {required this.isBiometricsAvailable, + required this.shareToken, + required this.isBlindReplica, + required this.secureWithBiometrics, + required this.addPassword, + required this.suggestedName, + required this.repositoryMetaInfo, + required this.obscurePassword, + required this.obscureRetypePassword, + required this.showSuggestedName, + required this.showAccessModeMessage, + required this.showSavePasswordWarning, + required this.showRepositoryNameInUseWarning, + required this.deleteRepositoryBeforePop}); + + CreateRepositoryState copyWith( + {bool? isBiometricsAvailable, + ShareToken? shareToken, + bool? isBlindReplica, + bool? secureWithBiometrics, + bool? addPassword, + String? suggestedName, + RepoMetaInfo? repositoryMetaInfo, + bool? obscurePassword, + bool? obscureRetypePassword, + bool? showSuggestedName, + bool? showAccessModeMessage, + bool? showSavePasswordWarning, + bool? showRepositoryNameInUseWarning, + bool? deleteRepositoryBeforePop}) => + CreateRepositoryState( + isBiometricsAvailable: + isBiometricsAvailable ?? this.isBiometricsAvailable, + shareToken: shareToken ?? this.shareToken, + isBlindReplica: isBlindReplica ?? this.isBlindReplica, + secureWithBiometrics: + secureWithBiometrics ?? this.secureWithBiometrics, + addPassword: addPassword ?? this.addPassword, + suggestedName: suggestedName ?? this.suggestedName, + repositoryMetaInfo: repositoryMetaInfo ?? this.repositoryMetaInfo, + obscurePassword: obscurePassword ?? this.obscurePassword, + obscureRetypePassword: + obscureRetypePassword ?? this.obscureRetypePassword, + showSuggestedName: showSuggestedName ?? this.showSuggestedName, + showAccessModeMessage: + showAccessModeMessage ?? this.showAccessModeMessage, + showSavePasswordWarning: + showSavePasswordWarning ?? this.showSavePasswordWarning, + showRepositoryNameInUseWarning: showRepositoryNameInUseWarning ?? + this.showRepositoryNameInUseWarning, + deleteRepositoryBeforePop: + deleteRepositoryBeforePop ?? this.deleteRepositoryBeforePop); + + @override + List get props => [ + isBiometricsAvailable, + shareToken, + isBlindReplica, + secureWithBiometrics, + addPassword, + suggestedName, + repositoryMetaInfo, + obscurePassword, + obscureRetypePassword, + showSuggestedName, + showAccessModeMessage, + showSavePasswordWarning, + showRepositoryNameInUseWarning, + deleteRepositoryBeforePop + ]; +} + +class CreateRepositoryCubit extends Cubit + with AppLogger { + CreateRepositoryCubit._(this._reposCubit, super.state); + + final ReposCubit _reposCubit; + + Future get defaultRepoLocation => + _reposCubit.settings.defaultRepoLocation(); + + static CreateRepositoryCubit create( + {required ReposCubit reposCubit, + required bool isBiometricsAvailable, + required ShareToken? shareToken, + required bool isBlindReplica, + required String? suggestedName, + required bool showSuggestedName, + required bool showAccessModeMessage}) { + var initialState = CreateRepositoryState( + isBiometricsAvailable: isBiometricsAvailable, + shareToken: shareToken, + isBlindReplica: isBlindReplica, + secureWithBiometrics: false, + addPassword: false, + suggestedName: suggestedName ?? '', + repositoryMetaInfo: null, + obscurePassword: true, + obscureRetypePassword: true, + showSuggestedName: showSuggestedName, + showAccessModeMessage: showAccessModeMessage, + showSavePasswordWarning: false, + showRepositoryNameInUseWarning: false, + deleteRepositoryBeforePop: false); + + return CreateRepositoryCubit._(reposCubit, initialState); + } + + Future createRepository( + RepoMetaInfo repositoryMetaInfo, + String password, + ShareToken? shareToken, + AuthMode authenticationMode, + bool setCurrent) async => + _reposCubit.createRepository(repositoryMetaInfo, + password: password, + token: shareToken, + authenticationMode: authenticationMode, + setCurrent: setCurrent); + + void addPassword(bool add) => emit(state.copyWith(addPassword: add)); + + void secureWithBiometrics(bool useBiometrics) => + emit(state.copyWith(secureWithBiometrics: useBiometrics)); + + void showSuggestedName(bool show) => + emit(state.copyWith(showSuggestedName: show)); + + void showRepositoryNameInUseWarning(bool show) => + emit(state.copyWith(showRepositoryNameInUseWarning: show)); + + void showSavePasswordWarning(bool show) => + emit(state.copyWith(showSavePasswordWarning: show)); + + void obscurePassword(bool obscure) => + emit(state.copyWith(obscurePassword: obscure)); + + void obscureRetypePassword(bool obscure) => + emit(state.copyWith(obscureRetypePassword: obscure)); + + void deleteRepositoryBeforePop(bool delete) => + emit(state.copyWith(deleteRepositoryBeforePop: delete)); + + void repositoryMetaInfo(RepoMetaInfo? metaInfo) => + emit(state.copyWith(repositoryMetaInfo: metaInfo)); +} diff --git a/lib/app/widgets/dialogs/modal_repository_creation_dialog.dart b/lib/app/widgets/dialogs/modal_repository_creation_dialog.dart index 38c67d283..01371d5f1 100644 --- a/lib/app/widgets/dialogs/modal_repository_creation_dialog.dart +++ b/lib/app/widgets/dialogs/modal_repository_creation_dialog.dart @@ -3,16 +3,18 @@ import 'dart:io' as io; import 'package:biometric_storage/biometric_storage.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:ouisync_plugin/ouisync_plugin.dart'; import '../../../generated/l10n.dart'; +import '../../cubits/create_repo.dart'; import '../../cubits/cubits.dart'; import '../../models/models.dart'; import '../../utils/utils.dart'; import '../widgets.dart'; -class RepositoryCreation extends StatefulWidget { - const RepositoryCreation( +class RepositoryCreation extends StatelessWidget with AppLogger { + RepositoryCreation( {required this.context, required this.cubit, this.initialTokenValue, @@ -25,13 +27,6 @@ class RepositoryCreation extends StatefulWidget { final String? initialTokenValue; final bool isBiometricsAvailable; - @override - State createState() => _RepositoryCreationState(); -} - -class _RepositoryCreationState extends State - with AppLogger { - ShareToken? _shareToken; final _scrollKey = GlobalKey(); final _repositoryNameInputKey = GlobalKey(); @@ -49,190 +44,190 @@ class _RepositoryCreationState extends State final _passwordFocus = FocusNode(); final _retryPasswordFocus = FocusNode(); - bool _obscurePassword = true; - bool _obscureRetypePassword = true; - - bool _isBlindReplica = false; - - bool _addPassword = false; - - bool _isBiometricsAvailable = false; - bool _secureWithBiometrics = false; - - bool _deleteRepositoryBeforePop = false; - RepoMetaInfo? _repositoryMetaInfo; + late final CreateRepositoryCubit createRepo; final ValueNotifier _accessModeNotifier = ValueNotifier(''); - bool _showAccessModeMessage = false; - - String _suggestedName = ''; - bool _showSuggestedName = false; - - bool _showSavePasswordWarning = false; - - bool _showRepositoryNameInUseWarning = false; TextStyle? _linkStyle; TextStyle? _messageSmall; TextStyle? _labelStyle; @override - void initState() { - unawaited(_init()); + Widget build(BuildContext context) { + _linkStyle = context.theme.appTextStyle.bodySmall + .copyWith(fontWeight: FontWeight.w500); + + _labelStyle = context.theme.appTextStyle.labelMedium + .copyWith(color: Constants.inputLabelForeColor); - _repositoryNameFocus.requestFocus(); - _addListeners(); + _messageSmall = + context.theme.appTextStyle.bodySmall.copyWith(color: Colors.black54); - super.initState(); + return FutureBuilder( + future: initCubit(), + builder: (context, snapshot) { + if (snapshot.hasData) { + createRepo = snapshot.data!; + + _populatePasswordControllers(generatePassword: true); + + _repositoryNameFocus.requestFocus(); + _addListeners(); + + return BlocBuilder( + bloc: createRepo, + builder: (context, state) => WillPopScope( + onWillPop: () async { + if (state.deleteRepositoryBeforePop) { + assert(state.repositoryMetaInfo != null, + '_repositoryMetaInfo is null'); + + if (state.repositoryMetaInfo == null) { + throw ('A repository was created, but saving the password into the ' + 'secure storage failed and it may be lost.\nMost likely this ' + 'repository needs to be deleted.'); + } + + final repoName = state.repositoryMetaInfo!.name; + final authMode = + cubit.settings.getAuthenticationMode(repoName); + + await cubit.deleteRepository( + state.repositoryMetaInfo!, authMode); + } + + return true; + }, + child: Form( + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + SingleChildScrollView( + reverse: true, + child: _newRepositoryWidget(context, state)) + ])))); + } else if (snapshot.hasError) { + return Container( + child: Center( + child: Column(children: [ + const Icon( + Icons.error_outline, + color: Colors.red, + size: 60, + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Text('Error: ${snapshot.error}'), + ) + ]))); + } else { + return Container( + child: Center( + child: Column(children: [ + SizedBox( + width: 60, + height: 60, + child: CircularProgressIndicator(), + ), + Padding( + padding: EdgeInsets.only(top: 16), + child: Text('Awaiting result...'), + ) + ]))); + } + }); } - Future _init() async { - await _validateToken(); + Future initCubit() async { + ShareToken? shareToken; + if (initialTokenValue != null) { + shareToken = await _validateToken(initialTokenValue!); + } - final accessModeFuture = _shareToken?.mode; - final accessMode = - (accessModeFuture != null) ? await accessModeFuture : null; + String suggestedName = ''; + AccessMode? accessMode; - setState(() { - _isBlindReplica = accessMode == AccessMode.blind; - _isBiometricsAvailable = widget.isBiometricsAvailable; - }); + if (shareToken != null) { + suggestedName = await shareToken.suggestedName; + accessMode = await shareToken.mode; + } + + final showSuggestedName = + _nameController.text.isEmpty && initialTokenValue != null; + + _accessModeNotifier.value = accessMode?.name ?? ''; + final showAccessModeMessage = + _accessModeNotifier.value.toString().isNotEmpty; - _populatePasswordControllers(generatePassword: true); + final state = CreateRepositoryCubit.create( + reposCubit: cubit, + isBiometricsAvailable: isBiometricsAvailable, + shareToken: shareToken, + isBlindReplica: accessMode == AccessMode.blind, + suggestedName: suggestedName, + showSuggestedName: showSuggestedName, + showAccessModeMessage: showAccessModeMessage); + + return state; } - Future _validateToken() async { - final token = widget.initialTokenValue; - if (token == null) return; + Future _validateToken(String initialToken) async { + ShareToken? shareToken; try { - _shareToken = await ShareToken.fromString(widget.cubit.session, token); + shareToken = await ShareToken.fromString(cubit.session, initialToken); - if (_shareToken == null) { - throw "Failed to construct the token from \"$token\""; + if (shareToken == null) { + throw "Failed to construct the token from \"$initialToken\""; } } catch (e, st) { loggy.app('Extract repository token exception', e, st); showSnackBar(context, message: S.current.messageErrorTokenInvalid); - - cleanupFormOnEmptyToken(); } - if (_shareToken == null) return; - - _suggestedName = await _shareToken!.suggestedName; - _accessModeNotifier.value = (await _shareToken!.mode).name; - - _updateNameController(_suggestedName); - - setState(() { - _showAccessModeMessage = _accessModeNotifier.value.toString().isNotEmpty; - _showSuggestedName = _nameController.text.isEmpty; - }); - } - - void cleanupFormOnEmptyToken() { - setState(() { - _showSuggestedName = false; - _showAccessModeMessage = false; - }); - - _suggestedName = ''; - - _accessModeNotifier.value = ''; - - _updateNameController(null); - } - - void _updateNameController(String? value) { - _nameController.text = value ?? ''; - _nameController.selection = - TextSelection(baseOffset: 0, extentOffset: _suggestedName.length); - - setState(() => _showSuggestedName = _nameController.text.isEmpty); - - final targetContext = _scrollKey.currentContext; - if (targetContext != null) { - Scrollable.ensureVisible(targetContext, - alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart); - } + return shareToken; } void _addListeners() { _repositoryNameFocus.addListener(() { - if (widget.initialTokenValue != null && _nameController.text.isEmpty) { - setState(() => _showSuggestedName = _nameController.text.isEmpty); + if (initialTokenValue != null && _nameController.text.isEmpty) { + createRepo.showSuggestedName(_nameController.text.isEmpty); } }); _nameController.addListener(() { - if (widget.initialTokenValue != null && _nameController.text.isEmpty) { - setState(() => _showSuggestedName = true); + if (initialTokenValue != null && _nameController.text.isEmpty) { + createRepo.showSuggestedName(true); } - setState(() => _showRepositoryNameInUseWarning = false); + createRepo.showRepositoryNameInUseWarning(false); }); } - @override - Widget build(BuildContext context) { - _linkStyle = context.theme.appTextStyle.bodySmall - .copyWith(fontWeight: FontWeight.w500); - - _labelStyle = context.theme.appTextStyle.labelMedium - .copyWith(color: Constants.inputLabelForeColor); - - _messageSmall = - context.theme.appTextStyle.bodySmall.copyWith(color: Colors.black54); - - return WillPopScope( - onWillPop: () async { - if (_deleteRepositoryBeforePop) { - assert(_repositoryMetaInfo != null, '_repositoryMetaInfo is null'); - - if (_repositoryMetaInfo == null) { - throw ('A repository was created, but saving the password into the ' - 'secure storage failed and it may be lost.\nMost likely this ' - 'repository needs to be deleted.'); - } - - final repoName = _repositoryMetaInfo!.name; - final authMode = - widget.cubit.settings.getAuthenticationMode(repoName); - - await widget.cubit.deleteRepository(_repositoryMetaInfo!, authMode); - } - - return true; - }, - child: Form( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - SingleChildScrollView( - reverse: true, child: _newRepositoryWidget(widget.context)) - ]))); - } - - Widget _newRepositoryWidget(BuildContext context) => Column( + Widget _newRepositoryWidget( + BuildContext context, CreateRepositoryState state) => + Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (widget.initialTokenValue?.isNotEmpty ?? false) - ..._buildTokenLabel(), - ..._repositoryName(), - if (!_isBlindReplica) ..._passwordSection(), - if (_isBiometricsAvailable && !_isBlindReplica && _addPassword) - _useBiometricsSwitch(), + if (initialTokenValue?.isNotEmpty ?? false) + ..._buildTokenLabel(state), + ..._repositoryName(state), + if (!state.isBlindReplica) _passwordInputs(state), + if (state.isBiometricsAvailable && + !state.isBlindReplica && + !state.addPassword) + _useBiometricsSwitch(state), Dimensions.spacingVertical, - _manualPasswordWarning(context), - Fields.dialogActions(context, buttons: _actions(context)), + if (!state.isBlindReplica) _addLocalPassword(state), + _manualPasswordWarning(context, state), + Fields.dialogActions(context, buttons: _actions(context, state)), ]); - List _buildTokenLabel() => [ + List _buildTokenLabel(CreateRepositoryState state) => [ Padding( padding: Dimensions.paddingVertical10, child: Container( @@ -248,15 +243,13 @@ class _RepositoryCreationState extends State Fields.constrainedText(S.current.labelRepositoryLink, flex: 0, style: _labelStyle), Dimensions.spacingVerticalHalf, - Text( - formatShareLinkForDisplay( - widget.initialTokenValue ?? ''), + Text(formatShareLinkForDisplay(initialTokenValue ?? ''), style: _linkStyle) ]))), ValueListenableBuilder( valueListenable: _accessModeNotifier, builder: (context, message, child) => Visibility( - visible: _showAccessModeMessage, + visible: state.showAccessModeMessage, child: Fields.constrainedText( S.current .messageRepositoryAccessMode(message as String? ?? '?'), @@ -264,7 +257,7 @@ class _RepositoryCreationState extends State style: _messageSmall))) ]; - List _repositoryName() => [ + List _repositoryName(CreateRepositoryState state) => [ Dimensions.spacingVertical, Fields.formTextField( key: _repositoryNameInputKey, @@ -277,31 +270,44 @@ class _RepositoryCreationState extends State validateNoEmpty(S.current.messageErrorFormValidatorNameDefault), autovalidateMode: AutovalidateMode.disabled, focusNode: _repositoryNameFocus), - _repositoryNameTakenWarning(), + _repositoryNameTakenWarning(state), Visibility( - visible: _showSuggestedName, + visible: state.showSuggestedName, child: GestureDetector( - onTap: () => _updateNameController(_suggestedName), + onTap: () => _updateNameController(state.suggestedName), child: Row(mainAxisSize: MainAxisSize.min, children: [ Fields.constrainedText( - S.current.messageRepositorySuggestedName(_suggestedName), + S.current + .messageRepositorySuggestedName(state.suggestedName), style: _messageSmall) ]))), Dimensions.spacingVertical ]; - Widget _repositoryNameTakenWarning() => Visibility( - visible: _showRepositoryNameInUseWarning, + void _updateNameController(String value) { + _nameController.text = value; + _nameController.selection = + TextSelection(baseOffset: 0, extentOffset: value.length); + + createRepo.showSuggestedName(value.isEmpty && initialTokenValue != null); + + final targetContext = _scrollKey.currentContext; + if (targetContext != null) { + Scrollable.ensureVisible(targetContext, + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart); + } + } + + Widget _repositoryNameTakenWarning(CreateRepositoryState state) => Visibility( + visible: state.showRepositoryNameInUseWarning, child: Fields.autosizeText(S.current.messageErrorRepositoryNameExist, style: TextStyle(color: Colors.red), maxLines: 10, softWrap: true, textOverflow: TextOverflow.ellipsis)); - List _passwordSection() => [_passwordInputs(), _addLocalPassword()]; - - Widget _passwordInputs() => Visibility( - visible: _addPassword && !_secureWithBiometrics, + Widget _passwordInputs(CreateRepositoryState state) => Visibility( + visible: state.addPassword && !state.secureWithBiometrics, child: Container( child: Column(children: [ Row(children: [ @@ -310,9 +316,9 @@ class _RepositoryCreationState extends State key: _passwordInputKey, context: context, textEditingController: _passwordController, - obscureText: _obscurePassword, + obscureText: state.obscurePassword, label: S.current.labelPassword, - suffixIcon: _passwordActions(), + suffixIcon: _passwordActions(state), hint: S.current.messageRepositoryPassword, onSaved: (_) {}, validator: validateNoEmpty( @@ -326,9 +332,9 @@ class _RepositoryCreationState extends State key: _retypePasswordInputKey, context: context, textEditingController: _retypedPasswordController, - obscureText: _obscureRetypePassword, + obscureText: state.obscureRetypePassword, label: S.current.labelRetypePassword, - suffixIcon: _retypePasswordActions(), + suffixIcon: _retypePasswordActions(state), hint: S.current.messageRepositoryPassword, onSaved: (_) {}, validator: (retypedPassword) => retypedPasswordValidator( @@ -340,11 +346,10 @@ class _RepositoryCreationState extends State ]) ]))); - Widget _passwordActions() => Wrap(children: [ + Widget _passwordActions(CreateRepositoryState state) => Wrap(children: [ IconButton( - onPressed: () => - setState(() => _obscurePassword = !_obscurePassword), - icon: _obscurePassword + onPressed: () => createRepo.obscurePassword(!state.obscurePassword), + icon: state.obscurePassword ? const Icon(Constants.iconVisibilityOff) : const Icon(Constants.iconVisibilityOn), padding: EdgeInsets.zero, @@ -365,11 +370,11 @@ class _RepositoryCreationState extends State color: Colors.black) ]); - Widget _retypePasswordActions() => Wrap(children: [ + Widget _retypePasswordActions(CreateRepositoryState state) => Wrap(children: [ IconButton( - onPressed: () => setState( - () => _obscureRetypePassword = !_obscureRetypePassword), - icon: _obscureRetypePassword + onPressed: () => + createRepo.obscureRetypePassword(!state.obscureRetypePassword), + icon: state.obscureRetypePassword ? const Icon(Constants.iconVisibilityOff) : const Icon(Constants.iconVisibilityOn), padding: EdgeInsets.zero, @@ -399,69 +404,71 @@ class _RepositoryCreationState extends State return null; } - Widget _addLocalPassword() => Visibility( - visible: !_addPassword, - child: Row(children: [ + Widget _addLocalPassword(CreateRepositoryState state) => Visibility( + visible: !state.addPassword, + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( - onPressed: () => _updatePasswordSection(true), + onPressed: state.secureWithBiometrics + ? null + : () => _updatePasswordSection(true), child: Text(S.current.messageAddLocalPassword)) ])); - Widget _useBiometricsSwitch() => Container( + Widget _useBiometricsSwitch(CreateRepositoryState state) => Container( child: SwitchListTile.adaptive( - value: _secureWithBiometrics, + value: state.secureWithBiometrics, title: Text(S.current.messageSecureUsingBiometrics, - textAlign: TextAlign.end, + textAlign: TextAlign.start, style: context.theme.appTextStyle.bodyMedium), onChanged: (enableBiometrics) { - setState(() { - _secureWithBiometrics = enableBiometrics; - - _showSavePasswordWarning = !enableBiometrics; + createRepo.secureWithBiometrics(enableBiometrics); + createRepo.showSavePasswordWarning(!enableBiometrics); - _populatePasswordControllers(generatePassword: enableBiometrics); - }); + _populatePasswordControllers(generatePassword: true); }, contentPadding: EdgeInsets.zero, visualDensity: VisualDensity.compact)); - Widget _manualPasswordWarning(BuildContext context) => Visibility( - visible: _showSavePasswordWarning && _addPassword, - child: Fields.autosizeText(S.current.messageRememberSavePasswordAlert, - style: - context.theme.appTextStyle.bodyMedium.copyWith(color: Colors.red), - maxLines: 10, - softWrap: true, - textOverflow: TextOverflow.ellipsis)); - - List _actions(context) => [ + Widget _manualPasswordWarning( + BuildContext context, CreateRepositoryState state) => + Visibility( + visible: state.showSavePasswordWarning && state.addPassword, + child: Fields.autosizeText(S.current.messageRememberSavePasswordAlert, + style: context.theme.appTextStyle.bodyMedium + .copyWith(color: Colors.red), + maxLines: 10, + softWrap: true, + textOverflow: TextOverflow.ellipsis)); + + List _actions(BuildContext context, CreateRepositoryState state) => [ NegativeButton( - text: _addPassword ? S.current.actionBack : S.current.actionCancel, - onPressed: () => _addPassword + text: state.addPassword + ? S.current.actionBack + : S.current.actionCancel, + onPressed: () => state.addPassword ? _updatePasswordSection(false) : Navigator.of(context).pop(''), buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton), PositiveButton( - text: _shareToken == null + text: state.shareToken == null ? S.current.actionCreate : S.current.actionImport, onPressed: () { final name = _nameController.text; - final password = _isBlindReplica ? '' : _passwordController.text; + final password = + state.isBlindReplica ? '' : _passwordController.text; - _onSaved(widget.cubit, name, password); + _onSaved(name, password, state); }, buttonsAspectRatio: Dimensions.aspectRatioModalDialogButton) ]; void _updatePasswordSection(bool addPassword) { - setState(() { - _addPassword = addPassword; + createRepo.addPassword(addPassword); - // We used make biometrics the default; now we let the user enable it. - _secureWithBiometrics = false; - _showSavePasswordWarning = addPassword; - }); + // Biometrics used to be the default; now we let the user enable it. + createRepo.secureWithBiometrics(false); + createRepo.showSavePasswordWarning(addPassword); _populatePasswordControllers(generatePassword: !addPassword); } @@ -482,7 +489,8 @@ class _RepositoryCreationState extends State } } - void _onSaved(ReposCubit cubit, String name, String password) async { + void _onSaved( + String name, String password, CreateRepositoryState state) async { final isRepoNameOk = _repositoryNameInputKey.currentState?.validate() ?? false; final isPasswordOk = _passwordInputKey.currentState?.validate() ?? false; @@ -492,20 +500,19 @@ class _RepositoryCreationState extends State if (!isRepoNameOk) return; _repositoryNameInputKey.currentState!.save(); - // A blind replica has no password - if (!_isBlindReplica && _addPassword && !_secureWithBiometrics) { + if (state.addPassword) { if (!(isPasswordOk && isRetypePasswordOk)) return; _passwordInputKey.currentState!.save(); _retypePasswordInputKey.currentState!.save(); } - final info = RepoMetaInfo.fromDirAndName( - await cubit.settings.defaultRepoLocation(), name); + final defaultRepoLocation = await createRepo.defaultRepoLocation; + final repoMetaInfo = RepoMetaInfo.fromDirAndName(defaultRepoLocation, name); final exist = await Dialogs.executeFutureWithLoadingDialog(context, - f: io.File(info.path()).exists()); - setState(() => _showRepositoryNameInUseWarning = exist); + f: io.File(repoMetaInfo.path()).exists()); + createRepo.showRepositoryNameInUseWarning(exist); if (exist) return; @@ -518,26 +525,24 @@ class _RepositoryCreationState extends State /// (authenticationRequired=true). /// /// Both cases: Autogenerated and saved to secure storage. - final savePasswordToSecureStorage = _isBlindReplica + final savePasswordToSecureStorage = state.isBlindReplica ? false - : _addPassword - ? _secureWithBiometrics - : true; + : state.secureWithBiometrics + ? true + : !state.addPassword; - final authenticationRequired = _addPassword ? _secureWithBiometrics : false; + final authenticationRequired = + state.secureWithBiometrics ? true : state.addPassword; - final authenticationMode = authenticationRequired - ? AuthMode.version2 - : savePasswordToSecureStorage - ? AuthMode.noLocalPassword - : AuthMode.manual; + final authenticationMode = savePasswordToSecureStorage + ? authenticationRequired + ? AuthMode.version2 + : AuthMode.noLocalPassword + : AuthMode.manual; final repoEntry = await Dialogs.executeFutureWithLoadingDialog(context, - f: cubit.createRepository(info, - password: password, - setCurrent: true, - token: _shareToken, - authenticationMode: authenticationMode)); + f: createRepo.createRepository(repoMetaInfo, password, state.shareToken, + authenticationMode, true)); if (repoEntry is! OpenRepoEntry) { var err = "Unknown"; @@ -547,7 +552,7 @@ class _RepositoryCreationState extends State } await Dialogs.simpleAlertDialog( - context: widget.context, + context: context, title: S.current.messsageFailedCreateRepository(name), message: err); @@ -557,7 +562,7 @@ class _RepositoryCreationState extends State /// MANUAL PASSWORD - NO BIOMETRICS (ALSO: BLIND REPLICAS) /// ==================================================== if (savePasswordToSecureStorage == false) { - Navigator.of(widget.context).pop(name); + Navigator.of(context).pop(name); return; } @@ -595,7 +600,7 @@ class _RepositoryCreationState extends State if ((secureStorageResult.exception as AuthException).code != AuthExceptionCode.userCanceled) { await Dialogs.simpleAlertDialog( - context: widget.context, + context: context, title: S.current.messsageFailedCreateRepository(name), message: S.current.messageErrorAuthenticatingBiometrics); } @@ -606,24 +611,23 @@ class _RepositoryCreationState extends State _setDeleteRepoBeforePop(false, null); - Navigator.of(widget.context).pop(name); + Navigator.of(context).pop(name); } - void _setDeleteRepoBeforePop(bool delete, RepoMetaInfo? repoMetaInfo) => - setState(() { - _deleteRepositoryBeforePop = delete; - _repositoryMetaInfo = repoMetaInfo; - }); + void _setDeleteRepoBeforePop(bool delete, RepoMetaInfo? repoMetaInfo) { + createRepo.deleteRepositoryBeforePop(delete); + createRepo.repositoryMetaInfo(repoMetaInfo); + } - @override - void dispose() { - _nameController.dispose(); - _passwordController.dispose(); - _retypedPasswordController.dispose(); + // @override + // void dispose() { + // _nameController.dispose(); + // _passwordController.dispose(); + // _retypedPasswordController.dispose(); - _repositoryNameFocus.dispose(); - _passwordFocus.dispose(); + // _repositoryNameFocus.dispose(); + // _passwordFocus.dispose(); - super.dispose(); - } + // super.dispose(); + // } }