diff --git a/app/lib/bloc/app_password_exchange.dart b/app/lib/bloc/app_password_exchange.dart index f8a2b0e2..02d93acf 100644 --- a/app/lib/bloc/app_password_exchange.dart +++ b/app/lib/bloc/app_password_exchange.dart @@ -3,7 +3,8 @@ import 'dart:io'; import 'package:bloc/bloc.dart'; import 'package:logging/logging.dart'; - +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/api/api_util.dart' as api_util; import 'package:nc_photos/exception.dart'; @@ -38,6 +39,13 @@ class AppPasswordExchangeBlocPoll extends AppPasswordExchangeBlocEvent { final api_util.InitiateLoginPollOptions pollOptions; } +class AppPasswordExchangeBlocCancel extends AppPasswordExchangeBlocEvent { + const AppPasswordExchangeBlocCancel(); + + @override + String toString() => "AppPasswordExchangeBlocCancel {}"; +} + class _AppPasswordExchangeBlocAppPwReceived extends AppPasswordExchangeBlocEvent { const _AppPasswordExchangeBlocAppPwReceived(this.appPasswordResponse); @@ -113,6 +121,17 @@ class AppPasswordExchangeBlocFailure extends AppPasswordExchangeBlocState { final dynamic exception; } +class AppPasswordExchangeBlocResult extends AppPasswordExchangeBlocState { + const AppPasswordExchangeBlocResult(this.result); + + @override + String toString() => "AppPasswordExchangeBlocResult {" + "result: $result, " + "}"; + + final Account? result; +} + /// Business Logic Component (BLoC) which handles the App password exchange. /// /// The flow followed by this component is described in the Nextcloud documentation under @@ -136,6 +155,10 @@ class AppPasswordExchangeBloc Future _onEvent(AppPasswordExchangeBlocEvent event, Emitter emit) async { _log.info("[_onEvent] $event"); + if (_isCanceled) { + _log.fine("[_onEvent] canceled = true, ignore event"); + return; + } if (event is AppPasswordExchangeBlocInitiateLogin) { await _onEventInitiateLogin(event, emit); } else if (event is AppPasswordExchangeBlocPoll) { @@ -144,6 +167,8 @@ class AppPasswordExchangeBloc await _onEventAppPasswordReceived(event, emit); } else if (event is _AppPasswordExchangeBlocAppPwFailed) { await _onEventAppPasswordFailure(event, emit); + } else if (event is AppPasswordExchangeBlocCancel) { + await _onEventCancel(event, emit); } } @@ -191,7 +216,9 @@ class AppPasswordExchangeBloc } } catch (e, stacktrace) { _log.shout( - "[_pollAppPasswordStreamListener] Failed while polling for password", e, stacktrace); + "[_pollAppPasswordStreamListener] Failed while polling for password", + e, + stacktrace); add(_AppPasswordExchangeBlocAppPwFailed(e)); } } @@ -200,7 +227,17 @@ class AppPasswordExchangeBloc _AppPasswordExchangeBlocAppPwReceived ev, Emitter emit) async { try { - emit(AppPasswordExchangeBlocAppPwSuccess(ev.appPasswordResponse)); + final response = ev.appPasswordResponse; + final account = Account( + Account.newId(), + response.server.scheme, + response.server.authority, + response.loginName.toCi(), + response.loginName, + response.appPassword, + [""], + ); + emit(AppPasswordExchangeBlocResult(account)); } catch (e, stacktrace) { _log.shout( "[_onEventAppPasswordReceived] Failed while exchanging password", @@ -217,6 +254,13 @@ class AppPasswordExchangeBloc emit(AppPasswordExchangeBlocFailure(ev.exception)); } + Future _onEventCancel(AppPasswordExchangeBlocCancel ev, + Emitter emit) async { + await _pollPasswordSubscription?.cancel(); + _isCanceled = true; + emit(const AppPasswordExchangeBlocResult(null)); + } + @override Future close() { _pollPasswordSubscription?.cancel(); @@ -228,4 +272,5 @@ class AppPasswordExchangeBloc StreamSubscription>? _pollPasswordSubscription; + bool _isCanceled = false; } diff --git a/app/lib/widget/connect.dart b/app/lib/widget/connect.dart index 724657e8..339c898a 100644 --- a/app/lib/widget/connect.dart +++ b/app/lib/widget/connect.dart @@ -92,20 +92,48 @@ class _ConnectState extends State { } Widget _buildContent(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center( + return Center( + child: Container( + constraints: BoxConstraints( + maxWidth: Theme.of(context).widthLimitedContentMaxWidth, + ), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, children: [ - const CloudProgressIndicator(size: 192), - const SizedBox(height: 16), - Text( - L10n.global().connectingToServer2, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge, - ) + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CloudProgressIndicator(size: 192), + const SizedBox(height: 16), + Text( + L10n.global().connectingToServer2, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + TextButton( + onPressed: () { + _bloc.add(const AppPasswordExchangeBlocCancel()); + }, + child: Text( + MaterialLocalizations.of(context).cancelButtonLabel, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ), ], ), ), @@ -119,15 +147,6 @@ class _ConnectState extends State { launch(state.result.login); // and start polling the API for login credentials _bloc.add(AppPasswordExchangeBlocPoll(state.result.poll)); - } else if (state is AppPasswordExchangeBlocAppPwSuccess) { - final newAccount = Account( - Account.newId(), - state.result.server.scheme, - state.result.server.authority, - state.result.loginName.toCi(), - state.result.loginName, - state.result.appPassword, []); - _checkWebDavUrl(context, newAccount); } else if (state is AppPasswordExchangeBlocFailure) { if (features.isSupportSelfSignedCert && state.exception is HandshakeException) { @@ -146,6 +165,13 @@ class _ConnectState extends State { )); Navigator.of(context).pop(null); } + } else if (state is AppPasswordExchangeBlocResult) { + if (state.result == null) { + // user canceled + Navigator.of(context).pop(null); + } else { + _checkWebDavUrl(context, state.result!); + } } }