From ead19d2210bc484c4db0a46a81a3e44f7d39c9ac Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 30 May 2022 16:55:52 +0300 Subject: [PATCH] Finish recovery key workflow and pages Co-authored-by: Inex Code --- .../server_installation_cubit.dart | 23 +- .../server_installation_repository.dart | 6 +- .../components/brand_button/brand_button.dart | 2 +- .../brand_button/filled_button.dart | 14 +- .../brand_hero_screen/brand_hero_screen.dart | 8 +- lib/ui/pages/more/more.dart | 8 + lib/ui/pages/recovery_key/recovery_key.dart | 384 ++++++++++++------ .../recovery_key/recovery_key_receiving.dart | 4 +- .../recovering/recover_by_old_token.dart | 4 +- .../recovery_confirm_backblaze.dart | 6 +- .../recovery_confirm_cloudflare.dart | 4 +- .../recovering/recovery_confirm_server.dart | 115 ++++-- pubspec.lock | 16 +- 13 files changed, 404 insertions(+), 190 deletions(-) diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 9aedf6fb..44e06862 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -80,12 +80,7 @@ class ServerInstallationCubit extends Cubit { ); await repository.saveBackblazeKey(backblazeCredential); if (state is ServerInstallationRecovery) { - final mainUser = await repository.getMainUser(); - final updatedState = (state as ServerInstallationRecovery).copyWith( - backblazeCredential: backblazeCredential, - rootUser: mainUser, - ); - emit(updatedState.finish()); + finishRecoveryProcess(backblazeCredential); return; } emit((state as ServerInstallationNotFinished) @@ -458,6 +453,19 @@ class ServerInstallationCubit extends Cubit { )); } + void finishRecoveryProcess(BackblazeCredential backblazeCredential) async { + await repository.saveIsServerStarted(true); + await repository.saveIsServerResetedFirstTime(true); + await repository.saveIsServerResetedSecondTime(true); + await repository.saveHasFinalChecked(true); + final mainUser = await repository.getMainUser(); + final updatedState = (state as ServerInstallationRecovery).copyWith( + backblazeCredential: backblazeCredential, + rootUser: mainUser, + ); + emit(updatedState.finish()); + } + @override void onChange(Change change) { super.onChange(change); @@ -474,6 +482,9 @@ class ServerInstallationCubit extends Cubit { print( 'Recovery Capabilities: ${(change.nextState as ServerInstallationRecovery).recoveryCapabilities}'); } + if (change.nextState is TimerState) { + print('Timer: ${(change.nextState as TimerState).duration}'); + } } void clearAppConfig() { diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 5a1bfb58..a31ef764 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -431,6 +431,7 @@ class ServerInstallationRepository { isWithToken: false, overrideDomain: serverDomain.domainName, ); + final serverIp = await getServerIpFromDomain(serverDomain); final apiResponse = await serverApi.useRecoveryToken( DeviceToken(device: await getDeviceName(), token: recoveryKey)); @@ -443,7 +444,7 @@ class ServerInstallationRepository { ), provider: ServerProvider.unknown, id: 0, - ip4: '', + ip4: serverIp, startTime: null, createTime: null, ); @@ -464,6 +465,7 @@ class ServerInstallationRepository { overrideDomain: serverDomain.domainName, customToken: apiToken, ); + final serverIp = await getServerIpFromDomain(serverDomain); if (recoveryCapabilities == ServerRecoveryCapabilities.legacy) { final apiResponse = await serverApi.servicesPowerCheck(); if (apiResponse.isNotEmpty) { @@ -475,7 +477,7 @@ class ServerInstallationRepository { ), provider: ServerProvider.unknown, id: 0, - ip4: '', + ip4: serverIp, startTime: null, createTime: null, ); diff --git a/lib/ui/components/brand_button/brand_button.dart b/lib/ui/components/brand_button/brand_button.dart index 186056c0..0b5b9c49 100644 --- a/lib/ui/components/brand_button/brand_button.dart +++ b/lib/ui/components/brand_button/brand_button.dart @@ -34,7 +34,7 @@ class BrandButton { }) => ConstrainedBox( constraints: const BoxConstraints( - minHeight: 48, + minHeight: 40, minWidth: double.infinity, ), child: TextButton(onPressed: onPressed, child: Text(title)), diff --git a/lib/ui/components/brand_button/filled_button.dart b/lib/ui/components/brand_button/filled_button.dart index a3230ddf..7bf1b1dd 100644 --- a/lib/ui/components/brand_button/filled_button.dart +++ b/lib/ui/components/brand_button/filled_button.dart @@ -26,10 +26,16 @@ class FilledButton extends StatelessWidget { primary: Theme.of(context).colorScheme.onSurface.withAlpha(98), ).copyWith(elevation: ButtonStyleButton.allOrNull(0.0)); - return ElevatedButton( - onPressed: onPressed, - style: disabled ? _disabledStyle : _enabledStyle, - child: child ?? Text(title ?? ''), + return ConstrainedBox( + constraints: const BoxConstraints( + minHeight: 40, + minWidth: double.infinity, + ), + child: ElevatedButton( + onPressed: onPressed, + style: disabled ? _disabledStyle : _enabledStyle, + child: child ?? Text(title ?? ''), + ), ); } } diff --git a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart index 33ddcfa8..6d00963c 100644 --- a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart +++ b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart @@ -52,13 +52,17 @@ class BrandHeroScreen extends StatelessWidget { if (heroTitle != null) Text( heroTitle!, - style: Theme.of(context).textTheme.headlineMedium, + style: Theme.of(context).textTheme.headlineMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), textAlign: TextAlign.start, ), const SizedBox(height: 8.0), if (heroSubtitle != null) Text(heroSubtitle!, - style: Theme.of(context).textTheme.bodyMedium, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onBackground, + ), textAlign: TextAlign.start), const SizedBox(height: 16.0), ...children, diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 3897c19f..49b5e840 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -8,6 +8,7 @@ import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; +import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart'; import 'package:selfprivacy/ui/pages/setup/initializing.dart'; import 'package:selfprivacy/ui/pages/onboarding/onboarding.dart'; import 'package:selfprivacy/ui/pages/root_route.dart'; @@ -77,6 +78,13 @@ class MorePage extends StatelessWidget { goTo: SshKeysPage( user: context.read().state.rootUser, )), + _NavItem( + isEnabled: context.read().state + is ServerInstallationFinished, + iconData: Icons.password_outlined, + goTo: const RecoveryKey(), + title: 'recovery_key.key_main_header'.tr(), + ) ], ), ) diff --git a/lib/ui/pages/recovery_key/recovery_key.dart b/lib/ui/pages/recovery_key/recovery_key.dart index 30a7cc76..70dbaf8f 100644 --- a/lib/ui/pages/recovery_key/recovery_key.dart +++ b/lib/ui/pages/recovery_key/recovery_key.dart @@ -31,8 +31,8 @@ class _RecoveryKeyState extends State { switch (keyStatus.loadingStatus) { case LoadingStatus.refreshing: widgets = [ - const Icon(Icons.refresh_outlined), - const SizedBox(height: 18), + const Center(child: CircularProgressIndicator()), + const SizedBox(height: 16), BrandText( 'recovery_key.key_synchronizing'.tr(), type: TextType.h1, @@ -48,7 +48,7 @@ class _RecoveryKeyState extends State { case LoadingStatus.error: widgets = [ const Icon(Icons.sentiment_dissatisfied_outlined), - const SizedBox(height: 18), + const SizedBox(height: 16), BrandText( 'recovery_key.key_connection_error'.tr(), type: TextType.h1, @@ -75,78 +75,25 @@ class RecoveryKeyContent extends StatefulWidget { } class _RecoveryKeyContentState extends State { - bool _isAmountToggled = true; - bool _isExpirationToggled = true; bool _isConfigurationVisible = false; - final _amountController = TextEditingController(); - final _expirationController = TextEditingController(); - @override Widget build(BuildContext context) { - var keyStatus = context.read().state; - _isConfigurationVisible = !keyStatus.exists; + final keyStatus = context.watch().state; List widgets = []; if (keyStatus.exists) { - if (keyStatus.isValid) { - widgets = [ - BrandCards.filled( - child: ListTile( - title: Text('recovery_key.key_valid'.tr()), - leading: const Icon(Icons.check_circle_outlined), - tileColor: Colors.lightGreen, - ), - ), - ...widgets - ]; - } else { - widgets = [ - BrandCards.filled( - child: ListTile( - title: Text('recovery_key.key_invalid'.tr()), - leading: const Icon(Icons.cancel_outlined), - tileColor: Colors.redAccent, - ), - ), - ...widgets - ]; - } + widgets = [ + RecoveryKeyStatusCard(isValid: keyStatus.isValid), + RecoveryKeyInformation(state: keyStatus), + ...widgets, + ]; - if (keyStatus.expiresAt != null && !_isConfigurationVisible) { + if (_isConfigurationVisible) { widgets = [ ...widgets, - const SizedBox(height: 18), - Text( - 'recovery_key.key_valid_until'.tr( - args: [keyStatus.expiresAt!.toIso8601String()], - ), - ) - ]; - } - - if (keyStatus.usesLeft != null && !_isConfigurationVisible) { - widgets = [ - ...widgets, - const SizedBox(height: 18), - Text( - 'recovery_key.key_valid_for'.tr( - args: [keyStatus.usesLeft!.toString()], - ), - ) - ]; - } - - if (keyStatus.generatedAt != null && !_isConfigurationVisible) { - widgets = [ - ...widgets, - const SizedBox(height: 18), - Text( - 'recovery_key.key_creation_date'.tr( - args: [keyStatus.generatedAt!.toIso8601String()], - ), - ) + const RecoveryKeyConfiguration(), ]; } @@ -154,87 +101,274 @@ class _RecoveryKeyContentState extends State { if (keyStatus.isValid) { widgets = [ ...widgets, - const SizedBox(height: 18), + const SizedBox(height: 16), BrandButton.text( title: 'recovery_key.key_replace_button'.tr(), - onPressed: () => _isConfigurationVisible = true, + onPressed: () { + setState(() { + _isConfigurationVisible = true; + }); + }, ), ]; } else { widgets = [ ...widgets, - const SizedBox(height: 18), + const SizedBox(height: 16), FilledButton( title: 'recovery_key.key_replace_button'.tr(), - onPressed: () => _isConfigurationVisible = true, + onPressed: () { + setState(() { + _isConfigurationVisible = true; + }); + }, ), ]; } } } - if (_isConfigurationVisible) { + if (!keyStatus.exists) { widgets = [ ...widgets, - const SizedBox(height: 18), - Row( - children: [ - Text('key_amount_toggle'.tr()), - Switch( - value: _isAmountToggled, - onChanged: (bool toogled) => _isAmountToggled = toogled, - ), - ], - ), - const SizedBox(height: 18), - TextField( - enabled: _isAmountToggled, - controller: _amountController, - decoration: InputDecoration( - labelText: 'recovery_key.key_amount_field_title'.tr()), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], // Only numbers can be entered - ), - const SizedBox(height: 18), - Row( - children: [ - Text('key_duedate_toggle'.tr()), - Switch( - value: _isExpirationToggled, - onChanged: (bool toogled) => _isExpirationToggled = toogled, - ), - ], - ), - const SizedBox(height: 18), - TextField( - enabled: _isExpirationToggled, - controller: _expirationController, - decoration: InputDecoration( - labelText: 'recovery_key.key_duedate_field_title'.tr()), - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], // Only numbers can be entered - ), - const SizedBox(height: 18), - FilledButton( - title: 'recovery_key.key_receive_button'.tr(), - disabled: - (_isExpirationToggled && _expirationController.text.isEmpty) || - (_isAmountToggled && _amountController.text.isEmpty), - onPressed: () { - Navigator.of(context).push( - materialRoute( - const RecoveryKeyReceiving(recoveryKey: ''), // TO DO - ), - ); - }, - ), + const RecoveryKeyConfiguration(), ]; } return Column(children: widgets); } } + +class RecoveryKeyStatusCard extends StatelessWidget { + const RecoveryKeyStatusCard({required this.isValid, Key? key}) + : super(key: key); + + final bool isValid; + + @override + Widget build(BuildContext context) { + return BrandCards.filled( + child: ListTile( + title: isValid + ? Text( + 'recovery_key.key_valid'.tr(), + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ) + : Text( + 'recovery_key.key_invalid'.tr(), + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: Theme.of(context).colorScheme.onErrorContainer, + ), + ), + leading: isValid + ? Icon( + Icons.check_circle_outlined, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ) + : Icon( + Icons.cancel_outlined, + color: Theme.of(context).colorScheme.onErrorContainer, + ), + tileColor: isValid + ? Theme.of(context).colorScheme.surfaceVariant + : Theme.of(context).colorScheme.errorContainer, + ), + ); + } +} + +class RecoveryKeyInformation extends StatelessWidget { + const RecoveryKeyInformation({required this.state, Key? key}) + : super(key: key); + + final RecoveryKeyState state; + + @override + Widget build(BuildContext context) { + const padding = EdgeInsets.symmetric(vertical: 8.0); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state.expiresAt != null) + Padding( + padding: padding, + child: Text( + 'recovery_key.key_valid_until'.tr( + args: [state.expiresAt!.toIso8601String()], + ), + ), + ), + if (state.usesLeft != null) + Padding( + padding: padding, + child: Text( + 'recovery_key.key_valid_for'.tr( + args: [state.usesLeft!.toString()], + ), + ), + ), + if (state.generatedAt != null) + Padding( + padding: padding, + child: Text( + 'recovery_key.key_creation_date'.tr( + args: [state.generatedAt!.toIso8601String()], + ), + textAlign: TextAlign.start, + ), + ), + ], + ); + } +} + +class RecoveryKeyConfiguration extends StatefulWidget { + const RecoveryKeyConfiguration({Key? key}) : super(key: key); + + @override + State createState() => _RecoveryKeyConfigurationState(); +} + +class _RecoveryKeyConfigurationState extends State { + bool _isAmountToggled = false; + bool _isExpirationToggled = false; + + bool _isAmountError = false; + bool _isExpirationError = false; + + final TextEditingController _amountController = TextEditingController(); + final TextEditingController _expirationController = TextEditingController(); + + DateTime _selectedDate = DateTime.now(); + bool _isDateSelected = false; + + @override + Widget build(BuildContext context) { + if (_isDateSelected) { + _expirationController.text = _selectedDate.toIso8601String(); + } + + return Column( + children: [ + const SizedBox(height: 16), + Row( + children: [ + Text('recovery_key.key_amount_toggle'.tr()), + Switch( + value: _isAmountToggled, + onChanged: (bool toogled) { + setState( + () { + _isAmountToggled = toogled; + _isExpirationToggled = _isExpirationToggled; + }, + ); + }, + ), + ], + ), + const SizedBox(height: 16), + TextField( + enabled: _isAmountToggled, + controller: _amountController, + decoration: InputDecoration( + errorText: _isAmountError ? ' ' : null, + labelText: 'recovery_key.key_amount_field_title'.tr()), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], // Only numbers can be entered + ), + const SizedBox(height: 16), + Row( + children: [ + Text('recovery_key.key_duedate_toggle'.tr()), + Switch( + value: _isExpirationToggled, + onChanged: (bool toogled) { + setState( + () { + _isAmountToggled = _isAmountToggled; + _isExpirationToggled = toogled; + }, + ); + }, + ), + ], + ), + const SizedBox(height: 16), + TextField( + enabled: _isExpirationToggled, + controller: _expirationController, + onTap: () { + _selectDate(context); + }, + decoration: InputDecoration( + errorText: _isExpirationError ? ' ' : null, + labelText: 'recovery_key.key_duedate_field_title'.tr()), + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + ], // Only numbers can be entered + ), + const SizedBox(height: 16), + FilledButton( + title: 'recovery_key.key_receive_button'.tr(), + onPressed: () { + if (_isExpirationToggled && _expirationController.text.isEmpty) { + setState(() { + _isExpirationError = true; + _isAmountError = false; + _isAmountToggled = _isAmountToggled; + _isExpirationToggled = _isExpirationToggled; + }); + return; + } else if (_isAmountToggled && _amountController.text.isEmpty) { + setState(() { + _isAmountError = true; + _isExpirationError = false; + _isAmountToggled = _isAmountToggled; + _isExpirationToggled = _isExpirationToggled; + }); + return; + } else { + setState(() { + _isAmountError = false; + _isExpirationError = false; + _isAmountToggled = _isAmountToggled; + _isExpirationToggled = _isExpirationToggled; + }); + + Navigator.of(context).push( + materialRoute( + const RecoveryKeyReceiving(recoveryKey: ''), // TO DO + ), + ); + } + }, + ), + ], + ); + } + + Future _selectDate(BuildContext context) async { + final selected = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: DateTime.now(), + lastDate: DateTime(DateTime.now().year + 50)); + + if (selected != null && selected != _selectedDate) { + setState( + () { + _selectedDate = selected; + _isDateSelected = true; + }, + ); + } + + return _selectedDate; + } +} diff --git a/lib/ui/pages/recovery_key/recovery_key_receiving.dart b/lib/ui/pages/recovery_key/recovery_key_receiving.dart index 8605e871..edd3f89e 100644 --- a/lib/ui/pages/recovery_key/recovery_key_receiving.dart +++ b/lib/ui/pages/recovery_key/recovery_key_receiving.dart @@ -20,10 +20,10 @@ class RecoveryKeyReceiving extends StatelessWidget { hasFlashButton: false, children: [ Text(recoveryKey, style: Theme.of(context).textTheme.bodyLarge), - const SizedBox(height: 18), + const SizedBox(height: 16), const Icon(Icons.info_outlined, size: 14), Text('recovery_key.key_receiving_info'.tr()), - const SizedBox(height: 18), + const SizedBox(height: 16), FilledButton( title: 'recovery_key.key_receiving_done'.tr(), onPressed: () { diff --git a/lib/ui/pages/setup/recovering/recover_by_old_token.dart b/lib/ui/pages/setup/recovering/recover_by_old_token.dart index d62221e9..46c1b3b4 100644 --- a/lib/ui/pages/setup/recovering/recover_by_old_token.dart +++ b/lib/ui/pages/setup/recovering/recover_by_old_token.dart @@ -33,7 +33,7 @@ class RecoverByOldTokenInstruction extends StatelessWidget { BrandMarkdown( fileName: instructionFilename, ), - const SizedBox(height: 18), + const SizedBox(height: 16), FilledButton( title: 'recovering.method_device_button'.tr(), onPressed: () => context @@ -79,7 +79,7 @@ class RecoverByOldToken extends StatelessWidget { labelText: 'recovering.method_device_input_placeholder'.tr(), ), ), - const SizedBox(height: 18), + const SizedBox(height: 16), FilledButton( title: 'more.continue'.tr(), onPressed: formCubitState.isSubmitting diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart b/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart index 63e3a019..646984a3 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_backblaze.dart @@ -36,7 +36,7 @@ class RecoveryConfirmBackblaze extends StatelessWidget { hintText: 'KeyID', ), ), - const SizedBox(height: 18), + const SizedBox(height: 16), CubitFormTextField( formFieldCubit: context.read().applicationKey, textAlign: TextAlign.center, @@ -46,14 +46,14 @@ class RecoveryConfirmBackblaze extends StatelessWidget { hintText: 'Master Application Key', ), ), - const SizedBox(height: 18), + const SizedBox(height: 16), BrandButton.rised( onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), text: 'basis.connect'.tr(), ), - const SizedBox(height: 18), + const SizedBox(height: 16), BrandButton.text( onPressed: () => showModalBottomSheet( context: context, diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart b/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart index 19dce048..1006e390 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_cloudflare.dart @@ -38,14 +38,14 @@ class RecoveryConfirmCloudflare extends StatelessWidget { hintText: 'initializing.5'.tr(), ), ), - const SizedBox(height: 18), + const SizedBox(height: 16), BrandButton.rised( onPressed: formCubitState.isSubmitting ? null : () => context.read().trySubmit(), text: 'basis.connect'.tr(), ), - const SizedBox(height: 18), + const SizedBox(height: 16), BrandButton.text( onPressed: () => showModalBottomSheet( context: context, diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart index 96bcb51d..e1b5403a 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart @@ -133,22 +133,54 @@ class _RecoveryConfirmServerState extends State { VoidCallback? onTap}) { return BrandCards.filled( child: ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 16), onTap: onTap, - title: Text(server.name), - leading: const Icon(Icons.dns), + title: Text( + server.name, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + leading: Icon( + Icons.dns, + color: Theme.of(context).colorScheme.onSurface, + ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - Icon(server.isReverseDnsValid ? Icons.check : Icons.close), - Text('rDNS: ${server.reverseDns}'), + Icon( + server.isReverseDnsValid ? Icons.check : Icons.close, + color: Theme.of(context).colorScheme.onSurface, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'rDNS: ${server.reverseDns}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), ], ), Row( children: [ - Icon(server.isIpValid ? Icons.check : Icons.close), - Text('IP: ${server.ip}'), + Icon( + server.isIpValid ? Icons.check : Icons.close, + color: Theme.of(context).colorScheme.onSurface, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'IP: ${server.ip}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ), ], ), ], @@ -186,27 +218,19 @@ class _RecoveryConfirmServerState extends State { style: Theme.of(context).textTheme.titleMedium, textAlign: TextAlign.start, ), - const SizedBox(height: 4), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(server.isReverseDnsValid ? Icons.check : Icons.close), - const SizedBox(width: 8), - Text(server.isReverseDnsValid - ? 'recovering.modal_confirmation_dns_valid'.tr() - : 'recovering.modal_confirmation_dns_invalid'.tr()), - ], + const SizedBox(height: 8), + IsValidStringDisplay( + isValid: server.isReverseDnsValid, + textIfValid: 'recovering.modal_confirmation_dns_valid'.tr(), + textIfInvalid: + 'recovering.modal_confirmation_dns_invalid'.tr(), ), - const SizedBox(height: 4), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon(server.isIpValid ? Icons.check : Icons.close), - const SizedBox(width: 8), - Text(server.isIpValid - ? 'recovering.modal_confirmation_ip_valid'.tr() - : 'recovering.modal_confirmation_ip_invalid'.tr()), - ], + const SizedBox(height: 8), + IsValidStringDisplay( + isValid: server.isIpValid, + textIfValid: 'recovering.modal_confirmation_ip_valid'.tr(), + textIfInvalid: + 'recovering.modal_confirmation_ip_invalid'.tr(), ), ], ), @@ -229,3 +253,42 @@ class _RecoveryConfirmServerState extends State { }, ); } + +class IsValidStringDisplay extends StatelessWidget { + const IsValidStringDisplay({ + Key? key, + required this.isValid, + required this.textIfValid, + required this.textIfInvalid, + }) : super(key: key); + + final bool isValid; + final String textIfValid; + final String textIfInvalid; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + isValid + ? Icon(Icons.check, color: Theme.of(context).colorScheme.onSurface) + : Icon(Icons.close, color: Theme.of(context).colorScheme.error), + const SizedBox(width: 8), + isValid + ? Text( + textIfValid, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSurface, + ), + ) + : Text( + textIfInvalid, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.error, + ), + ) + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index e3faf1b6..965eb666 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -363,13 +363,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.2" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" flutter_localizations: dependency: transitive description: flutter @@ -526,7 +519,7 @@ packages: source: hosted version: "3.1.3" intl: - dependency: "direct main" + dependency: transitive description: name: intl url: "https://pub.dartlang.org" @@ -567,13 +560,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.2.0" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" local_auth: dependency: "direct main" description: