Kate
1 year ago
committed by
GitHub
25 changed files with 914 additions and 399 deletions
@ -1,120 +0,0 @@ |
|||||||
import 'dart:async'; |
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:meta/meta.dart'; |
|
||||||
import 'package:neon/src/bloc/bloc.dart'; |
|
||||||
import 'package:neon/src/models/account.dart'; |
|
||||||
import 'package:nextcloud/nextcloud.dart'; |
|
||||||
import 'package:package_info_plus/package_info_plus.dart'; |
|
||||||
import 'package:rxdart/rxdart.dart'; |
|
||||||
|
|
||||||
abstract class LoginBlocEvents { |
|
||||||
void setServerURL(final String? url); |
|
||||||
} |
|
||||||
|
|
||||||
abstract class LoginBlocStates { |
|
||||||
BehaviorSubject<String?> get serverURL; |
|
||||||
|
|
||||||
BehaviorSubject<ServerConnectionState?> get serverConnectionState; |
|
||||||
|
|
||||||
BehaviorSubject<CoreLoginFlowInit?> get loginFlowInit; |
|
||||||
|
|
||||||
BehaviorSubject<CoreLoginFlowResult?> get loginFlowResult; |
|
||||||
} |
|
||||||
|
|
||||||
@internal |
|
||||||
class LoginBloc extends InteractiveBloc implements LoginBlocEvents, LoginBlocStates { |
|
||||||
LoginBloc(this._packageInfo); |
|
||||||
|
|
||||||
final PackageInfo _packageInfo; |
|
||||||
|
|
||||||
Timer? _pollTimer; |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_cancelPollTimer(); |
|
||||||
unawaited(serverURL.close()); |
|
||||||
unawaited(serverConnectionState.close()); |
|
||||||
unawaited(loginFlowInit.close()); |
|
||||||
unawaited(loginFlowResult.close()); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<CoreLoginFlowInit?> loginFlowInit = BehaviorSubject<CoreLoginFlowInit?>.seeded(null); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<CoreLoginFlowResult?> loginFlowResult = BehaviorSubject<CoreLoginFlowResult?>.seeded(null); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<ServerConnectionState?> serverConnectionState = BehaviorSubject<ServerConnectionState?>.seeded(null); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<String?> serverURL = BehaviorSubject<String?>.seeded(null); |
|
||||||
|
|
||||||
@override |
|
||||||
Future refresh() async { |
|
||||||
await _setServerURL(serverURL.valueOrNull); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void setServerURL(final String? url) { |
|
||||||
unawaited(_setServerURL(url)); |
|
||||||
} |
|
||||||
|
|
||||||
Future _setServerURL(final String? url) async { |
|
||||||
serverURL.add(url); |
|
||||||
loginFlowInit.add(null); |
|
||||||
loginFlowResult.add(null); |
|
||||||
serverConnectionState.add(url != null ? ServerConnectionState.loading : null); |
|
||||||
|
|
||||||
if (url != null) { |
|
||||||
try { |
|
||||||
final client = NextcloudClient( |
|
||||||
url, |
|
||||||
userAgentOverride: userAgent(_packageInfo), |
|
||||||
); |
|
||||||
|
|
||||||
final status = await client.core.getStatus(); |
|
||||||
if (status.maintenance) { |
|
||||||
serverConnectionState.add(ServerConnectionState.maintenanceMode); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
serverConnectionState.add(ServerConnectionState.success); |
|
||||||
|
|
||||||
final init = await client.core.initLoginFlow(); |
|
||||||
loginFlowInit.add(init); |
|
||||||
|
|
||||||
_cancelPollTimer(); |
|
||||||
_pollTimer = Timer.periodic(const Duration(seconds: 2), (final _) async { |
|
||||||
try { |
|
||||||
final result = await client.core.getLoginFlowResult(token: init.poll.token); |
|
||||||
_cancelPollTimer(); |
|
||||||
loginFlowResult.add(result); |
|
||||||
} catch (e, s) { |
|
||||||
debugPrint(e.toString()); |
|
||||||
debugPrint(s.toString()); |
|
||||||
} |
|
||||||
}); |
|
||||||
} catch (e, s) { |
|
||||||
debugPrint(e.toString()); |
|
||||||
debugPrint(s.toString()); |
|
||||||
serverConnectionState.add(ServerConnectionState.unreachable); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void _cancelPollTimer() { |
|
||||||
if (_pollTimer != null) { |
|
||||||
_pollTimer!.cancel(); |
|
||||||
_pollTimer = null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
enum ServerConnectionState { |
|
||||||
loading, |
|
||||||
unreachable, |
|
||||||
maintenanceMode, |
|
||||||
success, |
|
||||||
} |
|
@ -0,0 +1,69 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:neon/neon.dart'; |
||||||
|
import 'package:neon/src/bloc/bloc.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:neon/src/models/account.dart'; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
import 'package:rxdart/rxdart.dart'; |
||||||
|
|
||||||
|
abstract interface class LoginCheckAccountBlocEvents {} |
||||||
|
|
||||||
|
abstract interface class LoginCheckAccountBlocStates { |
||||||
|
/// Contains the account for the user |
||||||
|
BehaviorSubject<Result<Account>> get state; |
||||||
|
} |
||||||
|
|
||||||
|
class LoginCheckAccountBloc extends InteractiveBloc |
||||||
|
implements LoginCheckAccountBlocEvents, LoginCheckAccountBlocStates { |
||||||
|
LoginCheckAccountBloc( |
||||||
|
this.serverURL, |
||||||
|
this.loginName, |
||||||
|
this.password, |
||||||
|
) { |
||||||
|
unawaited(refresh()); |
||||||
|
} |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
final String loginName; |
||||||
|
final String password; |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
unawaited(state.close()); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Result<Account>> state = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
Future refresh() async { |
||||||
|
state.add(Result.loading()); |
||||||
|
|
||||||
|
try { |
||||||
|
final client = NextcloudClient( |
||||||
|
serverURL, |
||||||
|
loginName: loginName, |
||||||
|
password: password, |
||||||
|
userAgentOverride: neonUserAgent, |
||||||
|
); |
||||||
|
|
||||||
|
final response = await client.provisioningApi.getCurrentUser(); |
||||||
|
|
||||||
|
final account = Account( |
||||||
|
serverURL: serverURL, |
||||||
|
loginName: loginName, |
||||||
|
username: response.ocs.data.id, |
||||||
|
password: password, |
||||||
|
userAgent: neonUserAgent, |
||||||
|
); |
||||||
|
|
||||||
|
state.add(Result.success(account)); |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
state.add(Result.error(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:neon/neon.dart'; |
||||||
|
import 'package:neon/src/bloc/bloc.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
import 'package:rxdart/rxdart.dart'; |
||||||
|
|
||||||
|
abstract interface class LoginCheckServerStatusBlocEvents {} |
||||||
|
|
||||||
|
abstract interface class LoginCheckServerStatusBlocStates { |
||||||
|
/// Contains the current server connection state |
||||||
|
BehaviorSubject<Result<CoreServerStatus>> get state; |
||||||
|
} |
||||||
|
|
||||||
|
class LoginCheckServerStatusBloc extends InteractiveBloc |
||||||
|
implements LoginCheckServerStatusBlocEvents, LoginCheckServerStatusBlocStates { |
||||||
|
LoginCheckServerStatusBloc(this.serverURL) { |
||||||
|
unawaited(refresh()); |
||||||
|
} |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
unawaited(state.close()); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Result<CoreServerStatus>> state = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
Future refresh() async { |
||||||
|
state.add(Result.loading()); |
||||||
|
|
||||||
|
try { |
||||||
|
final client = NextcloudClient( |
||||||
|
serverURL, |
||||||
|
userAgentOverride: neonUserAgent, |
||||||
|
); |
||||||
|
|
||||||
|
final status = await client.core.getStatus(); |
||||||
|
state.add(Result.success(status)); |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
state.add(Result.error(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,77 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:neon/neon.dart'; |
||||||
|
import 'package:neon/src/bloc/bloc.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
import 'package:rxdart/rxdart.dart'; |
||||||
|
|
||||||
|
abstract class LoginFlowBlocEvents {} |
||||||
|
|
||||||
|
abstract class LoginFlowBlocStates { |
||||||
|
BehaviorSubject<Result<CoreLoginFlowInit>> get init; |
||||||
|
|
||||||
|
Stream<CoreLoginFlowResult> get result; |
||||||
|
} |
||||||
|
|
||||||
|
class LoginFlowBloc extends InteractiveBloc implements LoginFlowBlocEvents, LoginFlowBlocStates { |
||||||
|
LoginFlowBloc(this.serverURL) { |
||||||
|
unawaited(refresh()); |
||||||
|
} |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
late final _client = NextcloudClient( |
||||||
|
serverURL, |
||||||
|
userAgentOverride: neonUserAgent, |
||||||
|
); |
||||||
|
final _resultController = StreamController<CoreLoginFlowResult>(); |
||||||
|
|
||||||
|
Timer? _pollTimer; |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
_cancelPollTimer(); |
||||||
|
unawaited(init.close()); |
||||||
|
unawaited(_resultController.close()); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Result<CoreLoginFlowInit>> init = BehaviorSubject<Result<CoreLoginFlowInit>>(); |
||||||
|
|
||||||
|
@override |
||||||
|
late Stream<CoreLoginFlowResult> result = _resultController.stream.asBroadcastStream(); |
||||||
|
|
||||||
|
@override |
||||||
|
Future refresh() async { |
||||||
|
try { |
||||||
|
init.add(Result.loading()); |
||||||
|
|
||||||
|
final initResponse = await _client.core.initLoginFlow(); |
||||||
|
init.add(Result.success(initResponse)); |
||||||
|
|
||||||
|
_cancelPollTimer(); |
||||||
|
_pollTimer = Timer.periodic(const Duration(seconds: 1), (final _) async { |
||||||
|
try { |
||||||
|
final resultResponse = await _client.core.getLoginFlowResult(token: initResponse.poll.token); |
||||||
|
_cancelPollTimer(); |
||||||
|
_resultController.add(resultResponse); |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
} |
||||||
|
}); |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
init.add(Result.error(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void _cancelPollTimer() { |
||||||
|
if (_pollTimer != null) { |
||||||
|
_pollTimer!.cancel(); |
||||||
|
_pollTimer = null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,109 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:neon/l10n/localizations.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:neon/src/bloc/result_builder.dart'; |
||||||
|
import 'package:neon/src/blocs/accounts.dart'; |
||||||
|
import 'package:neon/src/blocs/login_check_account.dart'; |
||||||
|
import 'package:neon/src/models/account.dart'; |
||||||
|
import 'package:neon/src/router.dart'; |
||||||
|
import 'package:neon/src/utils/theme.dart'; |
||||||
|
import 'package:neon/src/widgets/account_tile.dart'; |
||||||
|
import 'package:neon/src/widgets/exception.dart'; |
||||||
|
import 'package:neon/src/widgets/linear_progress_indicator.dart'; |
||||||
|
import 'package:neon/src/widgets/validation_tile.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
|
||||||
|
class LoginCheckAccountPage extends StatefulWidget { |
||||||
|
const LoginCheckAccountPage({ |
||||||
|
required this.serverURL, |
||||||
|
required this.loginName, |
||||||
|
required this.password, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
final String loginName; |
||||||
|
final String password; |
||||||
|
|
||||||
|
@override |
||||||
|
State<LoginCheckAccountPage> createState() => _LoginCheckAccountPageState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _LoginCheckAccountPageState extends State<LoginCheckAccountPage> { |
||||||
|
late final LoginCheckAccountBloc bloc; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
bloc = LoginCheckAccountBloc( |
||||||
|
widget.serverURL, |
||||||
|
widget.loginName, |
||||||
|
widget.password, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
bloc.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => Scaffold( |
||||||
|
appBar: AppBar(), |
||||||
|
body: Center( |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(10), |
||||||
|
child: ConstrainedBox( |
||||||
|
constraints: Theme.of(context).extension<NeonTheme>()?.tabletLayout ?? const BoxConstraints(), |
||||||
|
child: ResultBuilder.behaviorSubject( |
||||||
|
stream: bloc.state, |
||||||
|
builder: (final context, final state) => Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
NeonLinearProgressIndicator( |
||||||
|
visible: state.isLoading, |
||||||
|
), |
||||||
|
NeonException( |
||||||
|
state.error, |
||||||
|
onRetry: bloc.refresh, |
||||||
|
), |
||||||
|
_buildAccountTile(state), |
||||||
|
Align( |
||||||
|
alignment: Alignment.bottomRight, |
||||||
|
child: ElevatedButton( |
||||||
|
onPressed: state.hasData |
||||||
|
? () { |
||||||
|
Provider.of<AccountsBloc>(context, listen: false) |
||||||
|
..updateAccount(state.requireData) |
||||||
|
..setActiveAccount(state.requireData); |
||||||
|
|
||||||
|
const HomeRoute().go(context); |
||||||
|
} |
||||||
|
: null, |
||||||
|
child: Text(AppLocalizations.of(context).actionContinue), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
Widget _buildAccountTile(final Result<Account> result) { |
||||||
|
if (result.hasData) { |
||||||
|
return NeonAccountTile( |
||||||
|
account: result.requireData, |
||||||
|
showStatus: false, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginCheckingAccount, |
||||||
|
state: ValidationState.loading, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:neon/l10n/localizations.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:neon/src/bloc/result_builder.dart'; |
||||||
|
import 'package:neon/src/blocs/login_check_server_status.dart'; |
||||||
|
import 'package:neon/src/utils/theme.dart'; |
||||||
|
import 'package:neon/src/widgets/exception.dart'; |
||||||
|
import 'package:neon/src/widgets/linear_progress_indicator.dart'; |
||||||
|
import 'package:neon/src/widgets/validation_tile.dart'; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
|
||||||
|
class LoginCheckServerStatusPage extends StatefulWidget { |
||||||
|
const LoginCheckServerStatusPage({ |
||||||
|
required this.serverURL, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
|
||||||
|
@override |
||||||
|
State<LoginCheckServerStatusPage> createState() => _LoginCheckServerStatusPageState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage> { |
||||||
|
late final LoginCheckServerStatusBloc bloc; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
bloc = LoginCheckServerStatusBloc(widget.serverURL); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
bloc.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => Scaffold( |
||||||
|
appBar: AppBar(), |
||||||
|
body: Center( |
||||||
|
child: Padding( |
||||||
|
padding: const EdgeInsets.all(10), |
||||||
|
child: ConstrainedBox( |
||||||
|
constraints: Theme.of(context).extension<NeonTheme>()?.tabletLayout ?? const BoxConstraints(), |
||||||
|
child: ResultBuilder.behaviorSubject( |
||||||
|
stream: bloc.state, |
||||||
|
builder: (final context, final state) => Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
NeonLinearProgressIndicator( |
||||||
|
visible: state.isLoading, |
||||||
|
), |
||||||
|
NeonException( |
||||||
|
state.error, |
||||||
|
onRetry: bloc.refresh, |
||||||
|
), |
||||||
|
_buildServerVersionTile(state), |
||||||
|
_buildMaintenanceModeTile(state), |
||||||
|
Align( |
||||||
|
alignment: Alignment.bottomRight, |
||||||
|
child: ElevatedButton( |
||||||
|
onPressed: state.hasData && state.requireData.isSupported && !state.requireData.maintenance |
||||||
|
? () => Navigator.of(context).pop(true) |
||||||
|
: null, |
||||||
|
child: Text(AppLocalizations.of(context).actionContinue), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
Widget _buildServerVersionTile(final Result<CoreServerStatus> result) { |
||||||
|
if (!result.hasData) { |
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginCheckingServerVersion, |
||||||
|
state: ValidationState.loading, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (result.requireData.isSupported) { |
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginSupportedServerVersion(result.requireData.versionstring), |
||||||
|
state: ValidationState.success, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginUnsupportedServerVersion(result.requireData.versionstring), |
||||||
|
state: ValidationState.failure, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
Widget _buildMaintenanceModeTile(final Result<CoreServerStatus> result) { |
||||||
|
if (!result.hasData) { |
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginCheckingMaintenanceMode, |
||||||
|
state: ValidationState.loading, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (result.requireData.maintenance) { |
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginMaintenanceModeEnabled, |
||||||
|
state: ValidationState.failure, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return NeonValidationTile( |
||||||
|
title: AppLocalizations.of(context).loginMaintenanceModeDisabled, |
||||||
|
state: ValidationState.success, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:neon/l10n/localizations.dart'; |
||||||
|
import 'package:neon/src/bloc/result_builder.dart'; |
||||||
|
import 'package:neon/src/blocs/login_flow.dart'; |
||||||
|
import 'package:neon/src/router.dart'; |
||||||
|
import 'package:neon/src/widgets/exception.dart'; |
||||||
|
import 'package:neon/src/widgets/linear_progress_indicator.dart'; |
||||||
|
import 'package:url_launcher/url_launcher_string.dart'; |
||||||
|
|
||||||
|
class LoginFlowPage extends StatefulWidget { |
||||||
|
const LoginFlowPage({ |
||||||
|
required this.serverURL, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final String serverURL; |
||||||
|
|
||||||
|
@override |
||||||
|
State<LoginFlowPage> createState() => _LoginFlowPageState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _LoginFlowPageState extends State<LoginFlowPage> { |
||||||
|
late final LoginFlowBloc bloc; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
|
||||||
|
bloc = LoginFlowBloc(widget.serverURL); |
||||||
|
|
||||||
|
bloc.init.listen((final result) async { |
||||||
|
if (result.hasData) { |
||||||
|
await launchUrlString( |
||||||
|
result.requireData.login, |
||||||
|
mode: LaunchMode.externalApplication, |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
bloc.result.listen((final result) { |
||||||
|
LoginCheckAccountRoute( |
||||||
|
serverURL: result.server, |
||||||
|
loginName: result.loginName, |
||||||
|
password: result.appPassword, |
||||||
|
).pushReplacement(context); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
bloc.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => Scaffold( |
||||||
|
appBar: AppBar(), |
||||||
|
body: Center( |
||||||
|
child: ResultBuilder.behaviorSubject( |
||||||
|
stream: bloc.init, |
||||||
|
builder: (final context, final init) => Column( |
||||||
|
mainAxisAlignment: MainAxisAlignment.center, |
||||||
|
children: [ |
||||||
|
NeonLinearProgressIndicator( |
||||||
|
visible: init.isLoading, |
||||||
|
), |
||||||
|
NeonException( |
||||||
|
init.error, |
||||||
|
onRetry: bloc.refresh, |
||||||
|
), |
||||||
|
if (init.hasData) ...[ |
||||||
|
Text(AppLocalizations.of(context).loginSwitchToBrowserWindow), |
||||||
|
const SizedBox( |
||||||
|
height: 10, |
||||||
|
), |
||||||
|
ElevatedButton( |
||||||
|
onPressed: bloc.refresh, |
||||||
|
child: Text(AppLocalizations.of(context).loginOpenAgain), |
||||||
|
), |
||||||
|
], |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
class NeonValidationTile extends StatelessWidget { |
||||||
|
const NeonValidationTile({ |
||||||
|
required this.title, |
||||||
|
required this.state, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final String title; |
||||||
|
final ValidationState state; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) { |
||||||
|
const size = 32.0; |
||||||
|
|
||||||
|
final leading = switch (state) { |
||||||
|
ValidationState.loading => const SizedBox( |
||||||
|
width: size, |
||||||
|
height: size, |
||||||
|
child: CircularProgressIndicator( |
||||||
|
strokeWidth: 3, |
||||||
|
), |
||||||
|
), |
||||||
|
ValidationState.failure => Icon( |
||||||
|
Icons.error_outline, |
||||||
|
color: Theme.of(context).colorScheme.error, |
||||||
|
size: size, |
||||||
|
), |
||||||
|
ValidationState.success => Icon( |
||||||
|
Icons.check_circle, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
size: size, |
||||||
|
), |
||||||
|
}; |
||||||
|
return ListTile( |
||||||
|
leading: leading, |
||||||
|
title: Text(title), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
enum ValidationState { |
||||||
|
loading, |
||||||
|
failure, |
||||||
|
success, |
||||||
|
} |
Loading…
Reference in new issue