mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-01-11 10:29:39 +00:00
Finish recovery key screen
This commit is contained in:
parent
1db8e9556e
commit
8ec3b8c3e3
|
@ -291,7 +291,7 @@
|
|||
"method_select_other_device": "I have access on another device",
|
||||
"method_select_recovery_key": "I have a recovery key",
|
||||
"method_select_nothing": "I don't have any of that",
|
||||
"method_device_description": "Open the application on another device, then go to the device page. Press \"Add device\" to receive your token.",
|
||||
"method_device_description": "Open the application on another device, then go to the devices page. Press \"Add device\" to receive your token.",
|
||||
"method_device_button": "I have received my token",
|
||||
"method_device_input_description": "Enter your authorization token",
|
||||
"method_device_input_placeholder": "Token",
|
||||
|
@ -342,7 +342,8 @@
|
|||
"key_replace_button": "Generate new key",
|
||||
"key_receiving_description": "Write down this key and put to a safe place. It is used to restore full access to your server:",
|
||||
"key_receiving_info": "The key will never ever be shown again, but you will be able to replace it with another one.",
|
||||
"key_receiving_done": "Done!"
|
||||
"key_receiving_done": "Done!",
|
||||
"generation_error": "Couldn't generate a recovery key. {}"
|
||||
},
|
||||
"modals": {
|
||||
"_comment": "messages in modals",
|
||||
|
|
|
@ -323,13 +323,25 @@
|
|||
"confirm_backblaze_description": "Введите токен Backblaze, который имеет права на хранилище резервных копий:"
|
||||
},
|
||||
"recovery_key": {
|
||||
"key_connection_error": "Не удалось соединиться с сервером",
|
||||
"key_synchronizing": "Синхронизация...",
|
||||
"key_main_header": "Ключ восстановления",
|
||||
"key_main_description": "Требуется для авторизации SelfPrivacy, когда авторизованные устройства недоступны.",
|
||||
"key_amount_toggle": "Ограничить использования",
|
||||
"key_amount_field_title": "Макс. кол-во использований",
|
||||
"key_duedate_toggle": "Ограничить срок использования",
|
||||
"key_duedate_field_title": "Дата окончания срока",
|
||||
"key_receive_button": "Получить ключ"
|
||||
"key_receive_button": "Получить ключ",
|
||||
"key_valid": "Ваш ключ действителен",
|
||||
"key_invalid": "Ваш ключ больше не действителен",
|
||||
"key_valid_until": "Действителен до {}",
|
||||
"key_valid_for": "Можно использовать ещё {} раз",
|
||||
"key_creation_date": "Создан {}",
|
||||
"key_replace_button": "Сгенерировать новый ключ",
|
||||
"key_receiving_description": "Запишите этот ключ в безопасном месте. Он предоставляет полный доступ к вашему серверу:",
|
||||
"key_receiving_info": "Этот ключ больше не будет показан, но вы сможете заменить его новым.",
|
||||
"key_receiving_done": "Готово!",
|
||||
"generation_error": "Не удалось сгенерировать ключ. {}"
|
||||
},
|
||||
"modals": {
|
||||
"_comment": "messages in modals",
|
||||
|
|
|
@ -109,6 +109,9 @@ class BNames {
|
|||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isServerResetedSecondTime = 'isServerResetedSecondTime';
|
||||
|
||||
/// A boolean field of [serverInstallationBox] box.
|
||||
static String isRecoveringServer = 'isRecoveringServer';
|
||||
|
||||
/// Deprecated users box as it is unencrypted
|
||||
static String usersDeprecated = 'users';
|
||||
|
||||
|
|
|
@ -664,7 +664,8 @@ class ServerApi extends ApiMap {
|
|||
var client = await getClient();
|
||||
var data = {};
|
||||
if (expiration != null) {
|
||||
data['expiration'] = expiration.toIso8601String();
|
||||
data['expiration'] = '${expiration.toIso8601String()}Z';
|
||||
print(data['expiration']);
|
||||
}
|
||||
if (uses != null) {
|
||||
data['uses'] = uses;
|
||||
|
|
|
@ -14,7 +14,16 @@ class RecoveryDeviceFormCubit extends FormCubit {
|
|||
|
||||
@override
|
||||
FutureOr<void> onSubmit() async {
|
||||
installationCubit.tryToRecover(tokenField.state.value, recoveryMethod);
|
||||
late final String token;
|
||||
// Trim spaces and make lowercase
|
||||
if (recoveryMethod == ServerRecoveryMethods.recoveryKey ||
|
||||
recoveryMethod == ServerRecoveryMethods.newDeviceKey) {
|
||||
token = tokenField.state.value.trim().toLowerCase();
|
||||
} else {
|
||||
token = tokenField.state.value.trim();
|
||||
}
|
||||
|
||||
installationCubit.tryToRecover(token, recoveryMethod);
|
||||
}
|
||||
|
||||
final ServerInstallationCubit installationCubit;
|
||||
|
|
|
@ -287,6 +287,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
await repository.getRecoveryCapabilities(serverDomain);
|
||||
|
||||
await repository.saveDomain(serverDomain);
|
||||
await repository.saveIsRecoveringServer(true);
|
||||
|
||||
emit(ServerInstallationRecovery(
|
||||
serverDomain: serverDomain,
|
||||
|
@ -458,6 +459,7 @@ class ServerInstallationCubit extends Cubit<ServerInstallationState> {
|
|||
await repository.saveIsServerResetedFirstTime(true);
|
||||
await repository.saveIsServerResetedSecondTime(true);
|
||||
await repository.saveHasFinalChecked(true);
|
||||
await repository.saveIsRecoveringServer(false);
|
||||
final mainUser = await repository.getMainUser();
|
||||
final updatedState = (state as ServerInstallationRecovery).copyWith(
|
||||
backblazeCredential: backblazeCredential,
|
||||
|
|
|
@ -62,7 +62,8 @@ class ServerInstallationRepository {
|
|||
);
|
||||
}
|
||||
|
||||
if (serverDomain != null && serverDomain.provider == DnsProvider.unknown) {
|
||||
if (box.get(BNames.isRecoveringServer, defaultValue: false) &&
|
||||
serverDomain != null) {
|
||||
return ServerInstallationRecovery(
|
||||
hetznerKey: hetznerToken,
|
||||
cloudFlareKey: cloudflareToken,
|
||||
|
@ -601,6 +602,10 @@ class ServerInstallationRepository {
|
|||
await box.put(BNames.rootUser, rootUser);
|
||||
}
|
||||
|
||||
Future<void> saveIsRecoveringServer(bool value) async {
|
||||
await box.put(BNames.isRecoveringServer, value);
|
||||
}
|
||||
|
||||
Future<void> saveHasFinalChecked(bool value) async {
|
||||
await box.put(BNames.hasFinalChecked, value);
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@ class FilledButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ButtonStyle _enabledStyle = ElevatedButton.styleFrom(
|
||||
final ButtonStyle enabledStyle = ElevatedButton.styleFrom(
|
||||
onPrimary: Theme.of(context).colorScheme.onPrimary,
|
||||
primary: Theme.of(context).colorScheme.primary,
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0));
|
||||
|
||||
final ButtonStyle _disabledStyle = ElevatedButton.styleFrom(
|
||||
final ButtonStyle disabledStyle = ElevatedButton.styleFrom(
|
||||
onPrimary: Theme.of(context).colorScheme.onSurface.withAlpha(30),
|
||||
primary: Theme.of(context).colorScheme.onSurface.withAlpha(98),
|
||||
).copyWith(elevation: ButtonStyleButton.allOrNull(0.0));
|
||||
|
@ -33,7 +33,7 @@ class FilledButton extends StatelessWidget {
|
|||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: onPressed,
|
||||
style: disabled ? _disabledStyle : _enabledStyle,
|
||||
style: disabled ? disabledStyle : enabledStyle,
|
||||
child: child ?? Text(title ?? ''),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
|
||||
class BrandCards {
|
||||
static Widget big({required Widget child}) => _BrandCard(
|
||||
|
@ -23,7 +22,9 @@ class BrandCards {
|
|||
static Widget outlined({required Widget child}) => _OutlinedCard(
|
||||
child: child,
|
||||
);
|
||||
static Widget filled({required Widget child}) => _FilledCard(
|
||||
static Widget filled({required Widget child, bool tertiary = false}) =>
|
||||
_FilledCard(
|
||||
tertiary: tertiary,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
@ -80,12 +81,11 @@ class _OutlinedCard extends StatelessWidget {
|
|||
}
|
||||
|
||||
class _FilledCard extends StatelessWidget {
|
||||
const _FilledCard({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
const _FilledCard({Key? key, required this.child, required this.tertiary})
|
||||
: super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final bool tertiary;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
|
@ -94,7 +94,9 @@ class _FilledCard extends StatelessWidget {
|
|||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
color: tertiary
|
||||
? Theme.of(context).colorScheme.tertiaryContainer
|
||||
: Theme.of(context).colorScheme.surfaceVariant,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:ionicons/ionicons.dart';
|
||||
import 'package:selfprivacy/config/brand_colors.dart';
|
||||
import 'package:selfprivacy/config/brand_theme.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_cards/brand_cards.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';
|
||||
|
@ -26,6 +24,9 @@ class MorePage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(52),
|
||||
|
@ -39,52 +40,53 @@ class MorePage extends StatelessWidget {
|
|||
padding: paddingH15V0,
|
||||
child: Column(
|
||||
children: [
|
||||
const BrandDivider(),
|
||||
_NavItem(
|
||||
title: 'more.configuration_wizard'.tr(),
|
||||
iconData: BrandIcons.triangle,
|
||||
goTo: const InitializingPage(),
|
||||
),
|
||||
_NavItem(
|
||||
if (!isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more.configuration_wizard'.tr(),
|
||||
iconData: Icons.change_history_outlined,
|
||||
goTo: const InitializingPage(),
|
||||
subtitle: 'not_ready_card.in_menu'.tr(),
|
||||
accent: true,
|
||||
),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more.create_ssh_key'.tr(),
|
||||
iconData: Ionicons.key_outline,
|
||||
goTo: SshKeysPage(
|
||||
user: context.read<UsersCubit>().state.rootUser,
|
||||
)),
|
||||
if (isReady)
|
||||
_MoreMenuItem(
|
||||
iconData: Icons.password_outlined,
|
||||
goTo: const RecoveryKey(),
|
||||
title: 'recovery_key.key_main_header'.tr(),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more.settings.title'.tr(),
|
||||
iconData: BrandIcons.settings,
|
||||
iconData: Icons.settings_outlined,
|
||||
goTo: const AppSettingsPage(),
|
||||
),
|
||||
_NavItem(
|
||||
_MoreMenuItem(
|
||||
title: 'more.about_project'.tr(),
|
||||
iconData: BrandIcons.engineer,
|
||||
goTo: const AboutPage(),
|
||||
),
|
||||
_NavItem(
|
||||
_MoreMenuItem(
|
||||
title: 'more.about_app'.tr(),
|
||||
iconData: BrandIcons.fire,
|
||||
goTo: const InfoPage(),
|
||||
),
|
||||
_NavItem(
|
||||
title: 'more.onboarding'.tr(),
|
||||
iconData: BrandIcons.start,
|
||||
goTo: const OnboardingPage(nextPage: RootPage()),
|
||||
),
|
||||
_NavItem(
|
||||
if (!isReady)
|
||||
_MoreMenuItem(
|
||||
title: 'more.onboarding'.tr(),
|
||||
iconData: BrandIcons.start,
|
||||
goTo: const OnboardingPage(nextPage: RootPage()),
|
||||
),
|
||||
_MoreMenuItem(
|
||||
title: 'more.console'.tr(),
|
||||
iconData: BrandIcons.terminal,
|
||||
goTo: const Console(),
|
||||
),
|
||||
_NavItem(
|
||||
isEnabled: context.read<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished,
|
||||
title: 'more.create_ssh_key'.tr(),
|
||||
iconData: Ionicons.key_outline,
|
||||
goTo: SshKeysPage(
|
||||
user: context.read<UsersCubit>().state.rootUser,
|
||||
)),
|
||||
_NavItem(
|
||||
isEnabled: context.read<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished,
|
||||
iconData: Icons.password_outlined,
|
||||
goTo: const RecoveryKey(),
|
||||
title: 'recovery_key.key_main_header'.tr(),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -94,77 +96,53 @@ class MorePage extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _NavItem extends StatelessWidget {
|
||||
const _NavItem({
|
||||
Key? key,
|
||||
this.isEnabled = true,
|
||||
required this.iconData,
|
||||
required this.goTo,
|
||||
required this.title,
|
||||
}) : super(key: key);
|
||||
|
||||
final IconData iconData;
|
||||
final Widget goTo;
|
||||
final String title;
|
||||
final bool isEnabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: isEnabled
|
||||
? () => Navigator.of(context).push(materialRoute(goTo))
|
||||
: null,
|
||||
child: _MoreMenuItem(
|
||||
iconData: iconData,
|
||||
title: title,
|
||||
isActive: isEnabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MoreMenuItem extends StatelessWidget {
|
||||
const _MoreMenuItem({
|
||||
Key? key,
|
||||
required this.iconData,
|
||||
required this.title,
|
||||
required this.isActive,
|
||||
this.subtitle,
|
||||
this.goTo,
|
||||
this.accent = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final IconData iconData;
|
||||
final String title;
|
||||
final bool isActive;
|
||||
final Widget? goTo;
|
||||
final String? subtitle;
|
||||
final bool accent;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 24),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 1.0,
|
||||
color: BrandColors.dividerColor,
|
||||
),
|
||||
final color = accent
|
||||
? Theme.of(context).colorScheme.onTertiaryContainer
|
||||
: Theme.of(context).colorScheme.onSurface;
|
||||
return BrandCards.filled(
|
||||
tertiary: accent,
|
||||
child: ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
onTap: goTo != null
|
||||
? () => Navigator.of(context).push(materialRoute(goTo!))
|
||||
: null,
|
||||
leading: Icon(
|
||||
iconData,
|
||||
size: 24,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
BrandText.body1(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: isActive ? null : Colors.grey,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 56,
|
||||
child: Icon(
|
||||
iconData,
|
||||
size: 20,
|
||||
color: isActive ? null : Colors.grey,
|
||||
),
|
||||
),
|
||||
],
|
||||
title: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
subtitle: subtitle != null
|
||||
? Text(
|
||||
subtitle!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: color,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cubit_form/cubit_form.dart';
|
|||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:selfprivacy/config/get_it_config.dart';
|
||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||
import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
|
@ -88,7 +89,7 @@ class _RecoveryKeyContentState extends State<RecoveryKeyContent> {
|
|||
if (keyStatus.exists && !_isConfigurationVisible)
|
||||
RecoveryKeyInformation(state: keyStatus),
|
||||
if (_isConfigurationVisible || !keyStatus.exists)
|
||||
RecoveryKeyConfiguration(),
|
||||
const RecoveryKeyConfiguration(),
|
||||
const SizedBox(height: 16),
|
||||
if (!_isConfigurationVisible && keyStatus.isValid)
|
||||
BrandButton.text(
|
||||
|
@ -161,39 +162,42 @@ class RecoveryKeyInformation extends StatelessWidget {
|
|||
|
||||
@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()],
|
||||
const padding = EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0);
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (state.expiresAt != null)
|
||||
Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
'recovery_key.key_valid_until'.tr(
|
||||
args: [DateFormat.yMMMMd().format(state.expiresAt!)],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (state.usesLeft != null)
|
||||
Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
'recovery_key.key_valid_for'.tr(
|
||||
args: [state.usesLeft!.toString()],
|
||||
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()],
|
||||
if (state.generatedAt != null)
|
||||
Padding(
|
||||
padding: padding,
|
||||
child: Text(
|
||||
'recovery_key.key_creation_date'.tr(
|
||||
args: [DateFormat.yMMMMd().format(state.generatedAt!)],
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -218,6 +222,38 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
DateTime _selectedDate = DateTime.now();
|
||||
bool _isDateSelected = false;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
Future<void> _generateRecoveryToken() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
try {
|
||||
final token = await context.read<RecoveryKeyCubit>().generateRecoveryKey(
|
||||
numberOfUses:
|
||||
_isAmountToggled ? int.tryParse(_amountController.text) : null,
|
||||
expirationDate: _isExpirationToggled ? _selectedDate : null,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
RecoveryKeyReceiving(recoveryKey: token), // TO DO
|
||||
),
|
||||
);
|
||||
} on GenerationError catch (e) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
getIt<NavigationService>().showSnackBar(
|
||||
'recovery_key.generation_error'.tr(args: [e.message]),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _updateErrorStatuses() {
|
||||
final amount = _amountController.text;
|
||||
final expiration = _expirationController.text;
|
||||
|
@ -241,8 +277,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
} else if (expiration.isEmpty) {
|
||||
_isExpirationError = true;
|
||||
} else {
|
||||
_isExpirationError =
|
||||
_selectedDate == null || _selectedDate.isBefore(DateTime.now());
|
||||
_isExpirationError = _selectedDate.isBefore(DateTime.now());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -266,10 +301,10 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
value: _isAmountToggled,
|
||||
title: Text('recovery_key.key_amount_toggle'.tr()),
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: (bool toogled) {
|
||||
onChanged: (bool toggled) {
|
||||
setState(
|
||||
() {
|
||||
_isAmountToggled = toogled;
|
||||
_isAmountToggled = toggled;
|
||||
},
|
||||
);
|
||||
_updateErrorStatuses();
|
||||
|
@ -287,7 +322,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
enabled: _isAmountToggled,
|
||||
controller: _amountController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isAmountError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_amount_field_title'.tr()),
|
||||
keyboardType: TextInputType.number,
|
||||
|
@ -304,10 +339,10 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
value: _isExpirationToggled,
|
||||
title: Text('recovery_key.key_duedate_toggle'.tr()),
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
onChanged: (bool toogled) {
|
||||
onChanged: (bool toggled) {
|
||||
setState(
|
||||
() {
|
||||
_isExpirationToggled = toogled;
|
||||
_isExpirationToggled = toggled;
|
||||
},
|
||||
);
|
||||
_updateErrorStatuses();
|
||||
|
@ -329,7 +364,7 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
},
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _isExpirationError ? ' ' : null,
|
||||
labelText: 'recovery_key.key_duedate_field_title'.tr()),
|
||||
keyboardType: TextInputType.number,
|
||||
|
@ -344,15 +379,9 @@ class _RecoveryKeyConfigurationState extends State<RecoveryKeyConfiguration> {
|
|||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'recovery_key.key_receive_button'.tr(),
|
||||
disabled: _isAmountError || _isExpirationError,
|
||||
disabled: _isAmountError || _isExpirationError || _isLoading,
|
||||
onPressed: !_isAmountError && !_isExpirationError
|
||||
? () {
|
||||
Navigator.of(context).push(
|
||||
materialRoute(
|
||||
const RecoveryKeyReceiving(recoveryKey: ''), // TO DO
|
||||
),
|
||||
);
|
||||
}
|
||||
? _generateRecoveryToken
|
||||
: null,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -15,14 +15,31 @@ class RecoveryKeyReceiving extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return BrandHeroScreen(
|
||||
heroTitle: 'recovery_key.key_main_header'.tr(),
|
||||
heroSubtitle: 'recovering.method_select_description'.tr(),
|
||||
heroSubtitle: 'recovery_key.key_receiving_description'.tr(),
|
||||
hasBackButton: true,
|
||||
hasFlashButton: false,
|
||||
children: [
|
||||
Text(recoveryKey, style: Theme.of(context).textTheme.bodyLarge),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
const Icon(Icons.info_outlined, size: 14),
|
||||
Text('recovery_key.key_receiving_info'.tr()),
|
||||
Text(
|
||||
recoveryKey,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontSize: 24,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.info_outlined, size: 24),
|
||||
const SizedBox(height: 16),
|
||||
Text('recovery_key.key_receiving_info'.tr()),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
title: 'recovery_key.key_receiving_done'.tr(),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:selfprivacy/logic/api_maps/server.dart';
|
||||
import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart';
|
||||
import 'package:selfprivacy/ui/components/brand_tab_bar/brand_tab_bar.dart';
|
||||
import 'package:selfprivacy/ui/pages/more/more.dart';
|
||||
import 'package:selfprivacy/ui/pages/providers/providers.dart';
|
||||
|
@ -48,10 +47,11 @@ class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
var selfprivacyServer = ServerApi();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var isReady = context.watch<ServerInstallationCubit>().state
|
||||
is ServerInstallationFinished;
|
||||
|
||||
return SafeArea(
|
||||
child: Provider<ChangeTab>(
|
||||
create: (_) => ChangeTab(tabController.animateTo),
|
||||
|
@ -68,36 +68,23 @@ class _RootPageState extends State<RootPage> with TickerProviderStateMixin {
|
|||
bottomNavigationBar: BrandTabBar(
|
||||
controller: tabController,
|
||||
),
|
||||
floatingActionButton: SizedBox(
|
||||
height: 104 + 16,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ScaleTransition(
|
||||
scale: _animation,
|
||||
child: FloatingActionButton.small(
|
||||
heroTag: 'new_user_fab',
|
||||
onPressed: () {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: NewUser());
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Icon(Icons.person_add_outlined),
|
||||
floatingActionButton: isReady
|
||||
? SizedBox(
|
||||
height: 104 + 16,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
ScaleTransition(
|
||||
scale: _animation,
|
||||
child: const AddUserFab(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const BrandFab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const BrandFab(),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -29,21 +29,17 @@ class RecoveryConfirmBackblaze extends StatelessWidget {
|
|||
children: [
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<BackblazeFormCubit>().keyId,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'KeyID',
|
||||
labelText: 'KeyID',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<BackblazeFormCubit>().applicationKey,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'Master Application Key',
|
||||
labelText: 'Master Application Key',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
|
@ -31,11 +31,9 @@ class RecoveryConfirmCloudflare extends StatelessWidget {
|
|||
children: [
|
||||
CubitFormTextField(
|
||||
formFieldCubit: context.read<CloudFlareFormCubit>().apiKey,
|
||||
textAlign: TextAlign.center,
|
||||
scrollPadding: const EdgeInsets.only(bottom: 70),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'initializing.5'.tr(),
|
||||
labelText: 'initializing.5'.tr(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
|
@ -143,7 +143,7 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
|||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.dns,
|
||||
Icons.dns_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
subtitle: Column(
|
||||
|
@ -199,10 +199,11 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.warning_amber_outlined),
|
||||
const SizedBox(height: 8),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'recovering.modal_confirmation_title'.tr(),
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -212,7 +213,9 @@ class _RecoveryConfirmServerState extends State<RecoveryConfirmServer> {
|
|||
children: <Widget>[
|
||||
Text('recovering.modal_confirmation_description'.tr(),
|
||||
style: Theme.of(context).textTheme.bodyMedium),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
server.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
|
@ -275,19 +278,20 @@ class IsValidStringDisplay extends StatelessWidget {
|
|||
? 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,
|
||||
),
|
||||
)
|
||||
Expanded(
|
||||
child: 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,
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
25
lib/ui/pages/users/add_user_fab.dart
Normal file
25
lib/ui/pages/users/add_user_fab.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
part of 'users.dart';
|
||||
|
||||
class AddUserFab extends StatelessWidget {
|
||||
const AddUserFab({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton.small(
|
||||
heroTag: 'new_user_fab',
|
||||
onPressed: () {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: const NewUser());
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Icon(Icons.person_add_outlined),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
part of 'users.dart';
|
||||
|
||||
class NewUser extends StatelessWidget {
|
||||
const NewUser({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var config = context.watch<ServerInstallationCubit>().state;
|
||||
|
|
|
@ -30,6 +30,7 @@ part 'empty.dart';
|
|||
part 'new_user.dart';
|
||||
part 'user.dart';
|
||||
part 'user_details.dart';
|
||||
part 'add_user_fab.dart';
|
||||
|
||||
class UsersPage extends StatelessWidget {
|
||||
const UsersPage({Key? key}) : super(key: key);
|
||||
|
|
Loading…
Reference in a new issue