From c398ff7b08ed5b09b12fe659f3b76704f6177fc9 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 9 Jan 2026 16:21:08 +0800 Subject: [PATCH 1/6] initial commit --- .../high_security/security_icon_big.svg | 21 ++ .../high_security/security_icon_black.svg | 7 + .../step_indicator_active_icon.svg | 3 + .../high_security/step_indicator_icon.svg | 3 + .../components/custom_text_field.dart | 18 +- .../features/components/gradient_text.dart | 26 ++ mobile-app/lib/features/components/steps.dart | 68 +++++ .../components/wallet_action_button.dart | 25 ++ .../main/screens/account_settings_screen.dart | 41 +-- .../main/screens/accounts_screen.dart | 3 + .../guardian_account_info_sheet.dart | 166 +++++++++++ .../high_security_cancel_warning_sheet.dart | 151 ++++++++++ .../high_security_confirmation_sheet.dart | 244 +++++++++++++++ .../high_security_created_sheet.dart | 132 +++++++++ .../high_security_get_started_screen.dart | 64 ++++ .../high_security_guardian_wizard.dart | 181 +++++++++++ ...high_security_safeguard_window_wizard.dart | 170 +++++++++++ .../high_security_summary_wizard.dart | 188 ++++++++++++ .../safeguard_window_picker_sheet.dart | 280 ++++++++++++++++++ .../lib/features/styles/app_colors_theme.dart | 7 + .../lib/features/styles/app_size_theme.dart | 13 + .../high_security_form_provider.dart | 23 ++ quantus_sdk/lib/quantus_sdk.dart | 2 + .../lib/src/constants/app_constants.dart | 4 + .../lib/src/models/high_security_data.dart | 19 ++ .../services/datetime_formatting_service.dart | 14 + .../src/services/high_security_service.dart | 52 ++++ 27 files changed, 1903 insertions(+), 22 deletions(-) create mode 100644 mobile-app/assets/high_security/security_icon_big.svg create mode 100644 mobile-app/assets/high_security/security_icon_black.svg create mode 100644 mobile-app/assets/high_security/step_indicator_active_icon.svg create mode 100644 mobile-app/assets/high_security/step_indicator_icon.svg create mode 100644 mobile-app/lib/features/components/gradient_text.dart create mode 100644 mobile-app/lib/features/components/steps.dart create mode 100644 mobile-app/lib/features/components/wallet_action_button.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart create mode 100644 mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart create mode 100644 mobile-app/lib/providers/high_security_form_provider.dart create mode 100644 quantus_sdk/lib/src/models/high_security_data.dart create mode 100644 quantus_sdk/lib/src/services/high_security_service.dart diff --git a/mobile-app/assets/high_security/security_icon_big.svg b/mobile-app/assets/high_security/security_icon_big.svg new file mode 100644 index 00000000..592ccaae --- /dev/null +++ b/mobile-app/assets/high_security/security_icon_big.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile-app/assets/high_security/security_icon_black.svg b/mobile-app/assets/high_security/security_icon_black.svg new file mode 100644 index 00000000..ee793ee2 --- /dev/null +++ b/mobile-app/assets/high_security/security_icon_black.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/mobile-app/assets/high_security/step_indicator_active_icon.svg b/mobile-app/assets/high_security/step_indicator_active_icon.svg new file mode 100644 index 00000000..1d151160 --- /dev/null +++ b/mobile-app/assets/high_security/step_indicator_active_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile-app/assets/high_security/step_indicator_icon.svg b/mobile-app/assets/high_security/step_indicator_icon.svg new file mode 100644 index 00000000..7ea01eef --- /dev/null +++ b/mobile-app/assets/high_security/step_indicator_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile-app/lib/features/components/custom_text_field.dart b/mobile-app/lib/features/components/custom_text_field.dart index 10a66b94..0fe98e34 100644 --- a/mobile-app/lib/features/components/custom_text_field.dart +++ b/mobile-app/lib/features/components/custom_text_field.dart @@ -4,6 +4,8 @@ import 'package:resonance_network_wallet/features/components/label.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +enum TextFieldVariant { primary, secondary } + class CustomTextField extends StatelessWidget { final String? labelText; final TextStyle? textStyle; @@ -19,6 +21,7 @@ class CustomTextField extends StatelessWidget { final String? errorMsg; final double? leftPadding; final bool? disabled; + final TextFieldVariant variant; const CustomTextField({ super.key, @@ -36,10 +39,20 @@ class CustomTextField extends StatelessWidget { this.leftPadding, this.controller, this.disabled = false, + this.variant = TextFieldVariant.primary, }) : assert(initialValue == null || controller == null, 'Cannot provide both an initialValue and a controller.'); @override Widget build(BuildContext context) { + final effectiveTextStyle = variant == TextFieldVariant.primary + ? context.themeText.smallTitle + : context.themeText.paragraph; + final effectiveHintStyle = variant == TextFieldVariant.primary + ? context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)) + : context.themeText.paragraph?.copyWith( + color: context.themeColors.textPrimary.useOpacity(0.5), + ); + // The main container for the entire widget return SizedBox( width: double.infinity, @@ -60,7 +73,7 @@ class CustomTextField extends StatelessWidget { onChanged: onChanged, obscureText: obscureText, // Styling for the text inside the input field - style: textStyle ?? context.themeText.smallTitle, + style: textStyle ?? effectiveTextStyle, decoration: InputDecoration( fillColor: fillColor, isDense: true, // Reduces vertical padding @@ -79,8 +92,7 @@ class CustomTextField extends StatelessWidget { hintText: hintText, // Style for the hint text when the field is empty hintStyle: - hintStyle ?? - context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)), + hintStyle ?? effectiveHintStyle, ), ), diff --git a/mobile-app/lib/features/components/gradient_text.dart b/mobile-app/lib/features/components/gradient_text.dart new file mode 100644 index 00000000..1c5e976c --- /dev/null +++ b/mobile-app/lib/features/components/gradient_text.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class GradientText extends StatelessWidget { + final String text; + final List colors; + final TextStyle? style; + + const GradientText( + this.text, { + super.key, + required this.colors, + required this.style, + }); + + @override + Widget build(BuildContext context) { + return ShaderMask( + shaderCallback: (bounds) => LinearGradient( + colors: colors, + begin: const Alignment(0.00, -1.00), + end: const Alignment(0, 1), + ).createShader(bounds), + child: Text(text, style: style?.copyWith(color: Colors.white)), + ); + } +} diff --git a/mobile-app/lib/features/components/steps.dart b/mobile-app/lib/features/components/steps.dart new file mode 100644 index 00000000..618e1890 --- /dev/null +++ b/mobile-app/lib/features/components/steps.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; + +class StepsIndicator extends StatelessWidget { + final int currentStep; + final int totalSteps; + final double lineHeight = 2; + final double iconHeight = 6.56; + + const StepsIndicator({ + super.key, + required this.currentStep, + required this.totalSteps, + + }) : assert(currentStep >= 1 && currentStep <= totalSteps ); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox( + height: iconHeight, + child: Row( + children: List.generate(totalSteps, (index) { + return Expanded( + child: Row( + children: [ + if (index < totalSteps) _buildStepLine(context, index), + _buildStepPoint(context, index), + ], + ), + ); + }), + ), + ), + ], + ); + } + + Widget _buildStepPoint(BuildContext context, int index) { + final isCompleted = index < currentStep - 1; + final isCurrent = index == currentStep - 1; + final iconPath = (isCompleted || isCurrent) + ? 'assets/high_security/step_indicator_active_icon.svg' + : 'assets/high_security/step_indicator_icon.svg'; + + return Center( + child: SvgPicture.asset( + iconPath, + width: 4, + height: iconHeight, + ), + ); + } + + Widget _buildStepLine(BuildContext context, int index) { + final isCompleted = index < currentStep - 1; + final isCurrent = index == currentStep - 1; + final lineColor = (isCompleted || isCurrent) + ? context.themeColors.checksum + : const Color(0x66FFFFFF); + + return Expanded( + child: Container(height: lineHeight, color: lineColor), + ); + } +} diff --git a/mobile-app/lib/features/components/wallet_action_button.dart b/mobile-app/lib/features/components/wallet_action_button.dart new file mode 100644 index 00000000..912a1079 --- /dev/null +++ b/mobile-app/lib/features/components/wallet_action_button.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; + +class WalletActionButton extends StatelessWidget { + final String assetPath; + const WalletActionButton({super.key, required this.assetPath}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(4), + decoration: ShapeDecoration( + color: Colors.white.useOpacity(0.15), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + child: SvgPicture.asset( + assetPath, + width: context.themeSize.mainMenuIconSize, + height: context.themeSize.mainMenuIconSize, + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/account_settings_screen.dart b/mobile-app/lib/features/main/screens/account_settings_screen.dart index e9db52c0..7ed4114e 100644 --- a/mobile-app/lib/features/main/screens/account_settings_screen.dart +++ b/mobile-app/lib/features/main/screens/account_settings_screen.dart @@ -16,6 +16,7 @@ import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; import 'package:resonance_network_wallet/features/components/sphere.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; import 'package:resonance_network_wallet/features/main/screens/create_account_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_get_started_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/receive_screen.dart'; import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; @@ -276,25 +277,27 @@ class _AccountSettingsScreenState extends ConsumerState { Widget _buildSecuritySection() { return _buildSettingCard( - child: Padding( - padding: const EdgeInsets.only(top: 12.0, left: 12.0, bottom: 12.0, right: 26.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - SvgPicture.asset('assets/high_security_icon.svg', width: context.isTablet ? 28 : 20), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('High Security', style: context.themeText.largeTag), - Text('COMING SOON', style: context.themeText.detail?.copyWith(color: context.themeColors.checksum)), - ], - ), - ], - ), - ], + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const HighSecurityGetStartedScreen()), + ); + }, + child: Padding( + padding: const EdgeInsets.only(top: 12.0, left: 12.0, bottom: 12.0, right: 26.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + SvgPicture.asset('assets/high_security_icon.svg', width: context.isTablet ? 28 : 20), + const SizedBox(width: 12), + Text('High Security', style: context.themeText.largeTag), + ], + ), + ], + ), ), ), ); diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart index e3a13691..567bc09d 100644 --- a/mobile-app/lib/features/main/screens/accounts_screen.dart +++ b/mobile-app/lib/features/main/screens/accounts_screen.dart @@ -407,6 +407,9 @@ class _AccountsScreenState extends ConsumerState { await Navigator.push( context, MaterialPageRoute( + settings: const RouteSettings( + name: AppConstants.accountSettingsRouteName, + ), builder: (context) => AccountSettingsScreen( account: account, balance: _formattingService.formatBalance(balance, addSymbol: true), diff --git a/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart new file mode 100644 index 00000000..fc0a109a --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart @@ -0,0 +1,166 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +class GuardianAccountInfoSheet extends StatefulWidget { + const GuardianAccountInfoSheet({super.key}); + + @override + State createState() => + _GuardianAccountInfoSheetState(); +} + +class _GuardianAccountInfoSheetState extends State { + void _closeSheet() { + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: false, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16), + decoration: ShapeDecoration( + color: context.themeColors.background, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(7), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: _closeSheet, + child: Icon(Icons.close, size: context.isTablet ? 28 : 24), + ), + ], + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Icon( + Icons.info_outline, + size: context.themeSize.infoSheetTitleIcon, + ), + const SizedBox(width: 22), + Text( + 'What is a Guardian Account', + style: context.themeText.largeTag, + ), + ], + ), + const SizedBox(height: 22), + Text( + 'A Guardian account acts as a secure backstop. It intercepts transactions by diverting funds to itself if the Entrusted account (this account) is compromised. The Guardian account can be owned by you or a trusted 3rd party.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'A Guardian account can:', + style: context.themeText.smallParagraph, + ), + Text( + '• Intercept any transaction', + style: context.themeText.smallParagraph, + ), + Text( + '• Pull all funds from this account', + style: context.themeText.smallParagraph, + ), + Text( + '• Change the recovery address', + style: context.themeText.smallParagraph, + ), + ], + ), + ), + const SizedBox(height: 16), + Text( + 'The Guardian account should not be in the same wallet as the Entrusted account as in the case of theft both would be exposed.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 16), + Text( + 'The harder the Guardian account is to access, the higher the security. An account on a cold storage wallet is the most secure.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 40), + Button( + variant: ButtonVariant.primary, + label: 'Got it!', + onPressed: () { + _closeSheet(); + }, + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ), + ); + } +} + +// Helper function to show the receive sheet +void showGuardianAccountInfoSheet(BuildContext context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, // Ensure full width + ), + builder: (context) => Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + const Color(0xFF312E6E).useOpacity(0.4), + Colors.black, + ], + ), + ), + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + color: Colors.black.useOpacity(0.3), + child: const GuardianAccountInfoSheet(), + ), + ), + ), + ], + ), + ); +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart new file mode 100644 index 00000000..f3d94c99 --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart @@ -0,0 +1,151 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +class HighSecurityCancelWarningSheet extends StatefulWidget { + const HighSecurityCancelWarningSheet({super.key}); + + @override + State createState() => + _HighSecurityCancelWarningSheetState(); +} + +class _HighSecurityCancelWarningSheetState + extends State { + void _returnToAccountSetting() { + if (!mounted) return; + Navigator.of( + context, + ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + } + + void _continueSetup() { + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: false, + child: Container( + height: MediaQuery.of(context).size.height * 0.8, + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16), + decoration: ShapeDecoration( + color: context.themeColors.background, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + ), + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(7), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: _continueSetup, + child: Icon(Icons.close, size: context.isTablet ? 28 : 24), + ), + ], + ), + ), + const SizedBox(height: 69), + Text( + 'Are you sure you want to exit High Security Setup?', + textAlign: TextAlign.center, + style: context.themeText.smallTitle, + ), + const SizedBox(height: 16), + Text( + 'Your preferences will be discarded', + textAlign: TextAlign.center, + style: context.themeText.smallTitle, + ), + const SizedBox(height: 69), + Row( + spacing: context.themeSize.buttonsHorizontalSpacing, + children: [ + Expanded( + child: Button( + variant: ButtonVariant.danger, + label: 'Exit anyway', + textStyle: context.themeText.smallParagraph?.copyWith( + fontWeight: FontWeight.w600, + ), + onPressed: () { + _returnToAccountSetting(); + }, + ), + ), + Expanded( + child: Button( + variant: ButtonVariant.neutral, + label: 'Continue', + textStyle: context.themeText.smallParagraph?.copyWith( + fontWeight: FontWeight.w600, + ), + onPressed: _continueSetup, + ), + ), + ], + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ), + ); + } +} + +void showHighSecurityCancelWarningSheet(BuildContext context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, // Ensure full width + ), + builder: (context) => Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + const Color(0xFF312E6E).useOpacity(0.4), + Colors.black, + ], + ), + ), + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + color: Colors.black.useOpacity(0.3), + child: const HighSecurityCancelWarningSheet(), + ), + ), + ), + ], + ), + ); +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart new file mode 100644 index 00000000..51e11db0 --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart @@ -0,0 +1,244 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_cancel_warning_sheet.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_created_sheet.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/providers/high_security_form_provider.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +class HighSecurityConfirmationSheet extends ConsumerStatefulWidget { + const HighSecurityConfirmationSheet({super.key}); + + @override + ConsumerState createState() => + _HighSecurityConfirmationSheetState(); +} + +class _HighSecurityConfirmationSheetState + extends ConsumerState { + final HighSecurityService _highSecurityService = HighSecurityService(); + final SettingsService _settingsService = SettingsService(); + + BigInt? _networkFee; + bool _isSubmitting = false; + + void _confirmSetup() async { + setState(() { + _isSubmitting = true; + }); + + final formData = ref.read(highSecurityFormProvider); + final activeAccount = (await _settingsService.getActiveAccount())!; + + await _highSecurityService.setupHighSecurityAccount( + activeAccount, + formData, + ); + + setState(() { + _isSubmitting = false; + }); + + if (mounted) { + showHighSecurityCreatedSheet(context); + } + } + + void _cancelSetup() { + showHighSecurityCancelWarningSheet(context); + } + + void _fetchNetworkFee() async { + final activeAccount = (await _settingsService.getActiveAccount())!; + final formData = ref.read(highSecurityFormProvider); + + final fee = await _highSecurityService.getHighSecuritySetupFee( + activeAccount, + formData, + ); + + setState(() { + _networkFee = fee.fee; + }); + } + + @override + void initState() { + super.initState(); + + _fetchNetworkFee(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: false, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16), + decoration: ShapeDecoration( + color: context.themeColors.background, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(7), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: _cancelSetup, + child: Icon(Icons.close, size: context.isTablet ? 28 : 24), + ), + ], + ), + ), + const SizedBox(height: 24), + Text( + 'WARNING:', + style: context.themeText.largeTitle?.copyWith( + color: context.themeColors.buttonDanger, + ), + ), + const SizedBox(height: 11), + Text( + 'These features are designed to help keep your funds safer, but once confirmed this account CANNOT:', + style: context.themeText.largeTag, + ), + const SizedBox(height: 11), + SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '• Turn off High Security', + style: context.themeText.smallParagraph, + ), + Text( + '• Reverse a transaction', + style: context.themeText.smallParagraph, + ), + Text( + '• Change the Guardian account', + style: context.themeText.smallParagraph, + ), + Text( + '• Change the Recovery account', + style: context.themeText.smallParagraph, + ), + Text( + '• Change the Safeguard window', + style: context.themeText.smallParagraph, + ), + Text( + '• Deny a recovery request', + style: context.themeText.smallParagraph, + ), + ], + ), + ), + const SizedBox(height: 11), + Text( + 'You will still be able to send and receive crypto and view requests.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 116), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Network Fee', + style: context.themeText.detail?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + Text( + '${_networkFee ?? 'Fetching...'} ${AppConstants.tokenSymbol}', + style: context.themeText.detail?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: 13), + Button( + isLoading: _isSubmitting, + variant: ButtonVariant.primary, + label: 'Confirm', + onPressed: () { + _confirmSetup(); + }, + ), + const SizedBox(height: 13), + Button( + isDisabled: _isSubmitting, + variant: ButtonVariant.dangerOutline, + label: 'Cancel', + onPressed: () { + _cancelSetup(); + }, + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ), + ); + } +} + +void showHighSecurityConfirmationSheet(BuildContext context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, // Ensure full width + ), + builder: (context) => Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + const Color(0xFF312E6E).useOpacity(0.4), + Colors.black, + ], + ), + ), + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + color: Colors.black.useOpacity(0.3), + child: const HighSecurityConfirmationSheet(), + ), + ), + ), + ], + ), + ); +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart new file mode 100644 index 00000000..e676a23f --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart @@ -0,0 +1,132 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +class HighSecurityCreatedSheet extends StatefulWidget { + const HighSecurityCreatedSheet({super.key}); + + @override + State createState() => + _HighSecurityCreatedSheetState(); +} + +class _HighSecurityCreatedSheetState extends State { + void _returnToAccountSetting() { + if (!mounted) return; + Navigator.of( + context, + ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: false, + child: Container( + height: MediaQuery.of(context).size.height * 0.8, + padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 16), + decoration: ShapeDecoration( + color: context.themeColors.background, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), + ), + child: Column( + children: [ + Container( + width: double.infinity, + padding: const EdgeInsets.all(7), + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(100), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: _returnToAccountSetting, + child: Icon(Icons.close, size: context.isTablet ? 28 : 24), + ), + ], + ), + ), + const SizedBox(height: 69), + SvgPicture.asset('assets/logo/logo.svg', width: 91, height: 85), + const SizedBox(height: 18), + Text('CONFIRMED', style: context.themeText.largeTitle), + const SizedBox(height: 46), + Text( + 'High Security has been successfully setup on this account', + style: context.themeText.largeTag, + textAlign: TextAlign.center, + ), + const SizedBox(height: 46), + Button( + variant: ButtonVariant.neutral, + label: 'Done', + width: 188, + onPressed: _returnToAccountSetting, + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ), + ); + } +} + +void showHighSecurityCreatedSheet(BuildContext context) { + void returnToAccountSetting() { + Navigator.of( + context, + ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + } + + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, // Ensure full width + ), + builder: (context) => Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + const Color(0xFF312E6E).useOpacity(0.4), + Colors.black, + ], + ), + ), + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + color: Colors.black.useOpacity(0.3), + child: const HighSecurityCreatedSheet(), + ), + ), + ), + ], + ), + ).whenComplete(() { + returnToAccountSetting(); + }); +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart new file mode 100644 index 00000000..673f221e --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_guardian_wizard.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/providers/high_security_form_provider.dart'; + +class HighSecurityGetStartedScreen extends ConsumerWidget { + const HighSecurityGetStartedScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formNotifier = ref.read(highSecurityFormProvider.notifier); + + return ScaffoldBase( + appBar: WalletAppBar.simple(title: 'Security Settings'), + child: Column( + children: [ + const SizedBox(height: 73), + SvgPicture.asset( + 'assets/high_security/security_icon_big.svg', + width: 140, + height: 175, + ), + const SizedBox(height: 26), + Text('HIGH SECURITY', style: context.themeText.largeTitle), + const SizedBox(height: 25), + Text( + "Don't risk your funds!\nEnabling High Security is a great way to keep your money safe. But safety comes at the cost of convenience.", + textAlign: TextAlign.center, + style: context.themeText.paragraph, + ), + const SizedBox(height: 13), + Text( + 'Once you enable this feature it cannot be disabled', + textAlign: TextAlign.center, + style: context.themeText.paragraph?.copyWith( + fontWeight: FontWeight.w600, + ), + ), + const Expanded(child: SizedBox()), + Button( + variant: ButtonVariant.neutral, + label: 'Start', + onPressed: () { + formNotifier.resetState(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const HighSecurityGuardianWizard(), + ), + ); + }, + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart new file mode 100644 index 00000000..59c25f99 --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/components/custom_text_field.dart'; +import 'package:resonance_network_wallet/features/components/gradient_text.dart'; +import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/features/components/steps.dart'; +import 'package:resonance_network_wallet/features/components/wallet_action_button.dart'; +import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/guardian_account_info_sheet.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_safeguard_window_wizard.dart'; +import 'package:resonance_network_wallet/features/main/screens/send/qr_scanner_screen.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/providers/high_security_form_provider.dart'; + +class HighSecurityGuardianWizard extends ConsumerStatefulWidget { + const HighSecurityGuardianWizard({super.key}); + + @override + ConsumerState createState() => + _HighSecurityGuardianWizardState(); +} + +class _HighSecurityGuardianWizardState + extends ConsumerState { + Future _scanQRCode() async { + final formNotifier = ref.read(highSecurityFormProvider.notifier); + + final scannedAddress = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const QRScannerScreen(), + fullscreenDialog: true, + ), + ); + + if (scannedAddress != null && mounted) { + formNotifier.updateGuardianAddress(scannedAddress); + } + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final formNotifier = ref.read(highSecurityFormProvider.notifier); + final guardianAddress = ref.watch(highSecurityFormProvider).guardianAddress; + + final bool isDisabled = guardianAddress.isEmpty; + + return ScaffoldBase( + appBar: WalletAppBar.simple(title: 'Theft Deterrence'), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 36), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 204, + child: StepsIndicator( + currentStep: 1, + totalSteps: AppConstants.highSecurityStepsCount, + ), + ), + ], + ), + const SizedBox(height: 32), + GradientText( + 'THEFT DETERRENCE', + colors: context.themeColors.aquaBlue, + style: context.themeText.largeTitle, + ), + const SizedBox(height: 4), + Text( + 'Intercept any transaction or “pull” all funds in the case of theft.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 38), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Guardian Account', style: context.themeText.largeTag), + InkWell( + onTap: () { + showGuardianAccountInfoSheet(context); + }, + child: const Icon(Icons.info_outline), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'Choose an account that keeps your funds safe if your main wallet is compromised.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 13), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () async { + final data = await Clipboard.getData('text/plain'); + if (data != null && data.text != null) { + formNotifier.updateGuardianAddress(data.text!); + } + }, + child: const WalletActionButton( + assetPath: 'assets/paste_icon_1.svg', + ), + ), + const SizedBox(width: 12), + GestureDetector( + onTap: _scanQRCode, + child: const WalletActionButton(assetPath: 'assets/scan_1.svg'), + ), + ], + ), + const SizedBox(height: 13), + CustomTextField( + variant: TextFieldVariant.secondary, + initialValue: guardianAddress, + onChanged: formNotifier.updateGuardianAddress, + hintText: 'Enter address', + ), + const SizedBox(height: 13), + Text( + 'The harder the Guardian account is to access the higher the security. An address on a cold storage wallet is the most secure.', + style: context.themeText.smallParagraph?.copyWith( + color: context.themeColors.textMuted, + ), + ), + const Expanded(child: SizedBox()), + Row( + spacing: 36, + children: [ + Expanded( + child: Button( + label: 'Back', + onPressed: () { + Navigator.pop(context); + }, + ), + ), + Expanded( + child: Button( + isDisabled: isDisabled, + variant: ButtonVariant.neutral, + label: 'Next', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const HighSecuritySafeguardWindowWizard(), + ), + ); + }, + ), + ), + ], + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart new file mode 100644 index 00000000..4453ed5c --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/components/gradient_text.dart'; +import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/features/components/steps.dart'; +import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_summary_wizard.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/safeguard_window_picker_sheet.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/providers/high_security_form_provider.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +class HighSecuritySafeguardWindowWizard extends ConsumerStatefulWidget { + const HighSecuritySafeguardWindowWizard({super.key}); + + @override + ConsumerState createState() => + _HighSecuritySafeguardWindowWizardState(); +} + +class _HighSecuritySafeguardWindowWizardState + extends ConsumerState { + @override + Widget build(BuildContext context) { + final formNotifier = ref.read(highSecurityFormProvider.notifier); + final safeguardTimeSeconds = ref + .watch(highSecurityFormProvider) + .safeguardWindow; + + final int secondsInADay = 86400; + final int secondsInAMonth = + secondsInADay * 30; // 86400 seconds/day * 30 days/month + + /// This is an approximation. + final int safeguardTimeMonths = safeguardTimeSeconds ~/ secondsInAMonth; + final int safeguardTimeDays = + (safeguardTimeSeconds % secondsInAMonth) ~/ secondsInADay; + final int safeguardTimeHours = (safeguardTimeSeconds % secondsInADay) ~/ 3600; + + final bool isDisabled = safeguardTimeSeconds == 0; + + return ScaffoldBase( + appBar: WalletAppBar.simple(title: 'Safeguard Window'), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 36), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 204, + child: StepsIndicator( + currentStep: 2, + totalSteps: AppConstants.highSecurityStepsCount, + ), + ), + ], + ), + const SizedBox(height: 32), + GradientText( + 'SAFEGUARD WINDOW', + colors: context.themeColors.aquaBlue, + style: context.themeText.largeTitle, + ), + const SizedBox(height: 4), + Text( + 'The time window in which the Guardian can deny or intercept a transaction.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 38), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Safeguard Window', style: context.themeText.largeTag), + ], + ), + const SizedBox(height: 4), + Text( + 'Set how long the Guardian account has to act once a transaction is initiated.', + style: context.themeText.smallParagraph, + ), + const SizedBox(height: 13), + GestureDetector( + onTap: () { + showSafeguardWindowPickerSheet( + context, + safeguardTimeMonths: safeguardTimeMonths, + safeguardTimeDays: safeguardTimeDays, + safeguardTimeHours: safeguardTimeHours, + setSafeguardTimeSeconds: formNotifier.updateSafeguardWindow, + ); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: ShapeDecoration( + color: const Color(0xFF313131), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + DatetimeFormattingService.formatSafeguardTime( + safeguardTimeMonths, + safeguardTimeDays, + safeguardTimeHours, + ), + style: context.themeText.smallParagraph, + ), + Icon( + Icons.edit, + color: Colors.white70, + size: context.isTablet ? 22 : 14, + ), + ], + ), + ), + ), + const SizedBox(height: 13), + Text( + 'Allow a reasonable window for your Guardian account to respond in an emergency.', + style: context.themeText.smallParagraph?.copyWith( + color: context.themeColors.textMuted, + ), + ), + const Expanded(child: SizedBox()), + Row( + spacing: 36, + children: [ + Expanded( + child: Button( + label: 'Back', + onPressed: () { + Navigator.pop(context); + }, + ), + ), + Expanded( + child: Button( + isDisabled: isDisabled, + variant: ButtonVariant.neutral, + label: 'Next', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + const HighSecuritySummaryWizard(), + ), + ); + }, + ), + ), + ], + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart new file mode 100644 index 00000000..7b047d7d --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/components/gradient_text.dart'; +import 'package:resonance_network_wallet/features/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/features/components/steps.dart'; +import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; +import 'package:resonance_network_wallet/features/main/screens/high_security/high_security_confirmation_sheet.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; +import 'package:resonance_network_wallet/providers/high_security_form_provider.dart'; + +class HighSecuritySummaryWizard extends ConsumerStatefulWidget { + const HighSecuritySummaryWizard({super.key}); + + @override + ConsumerState createState() => + _HighSecuritySummaryWizardState(); +} + +class _HighSecuritySummaryWizardState + extends ConsumerState { + final HumanReadableChecksumService _humanReadableChecksumService = + HumanReadableChecksumService(); + + @override + Widget build(BuildContext context) { + final formData = ref.read(highSecurityFormProvider); + + final guardianChecksumFuture = _humanReadableChecksumService + .getHumanReadableName(formData.guardianAddress); + + return ScaffoldBase( + appBar: WalletAppBar.simple(title: 'Summary'), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 36), + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 204, + child: StepsIndicator( + currentStep: 3, + totalSteps: AppConstants.highSecurityStepsCount, + ), + ), + ], + ), + const SizedBox(height: 32), + GradientText( + 'SUMMARY', + colors: context.themeColors.aquaBlue, + style: context.themeText.largeTitle, + ), + const SizedBox(height: 19), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, + children: [ + Text('HIGH SECURITY ACCOUNT:', style: context.themeText.detail), + Text('Everyday Account', style: context.themeText.smallTitle), + Text( + 'Grain-Red-Flash-Hyper-Cloud', + style: context.themeText.smallParagraph?.copyWith( + color: context.themeColors.checksumDarker, + ), + ), + SizedBox( + width: 220, + child: Text( + '5FEUm MJ6w5 36upW fhFcK n61jN UniW3 norvT ULjwj MhbfN cs4N', + style: context.themeText.detail?.copyWith( + color: Colors.white.useOpacity(0.6000000238418579), + ), + ), + ), + ], + ), + const SizedBox(height: 19), + SummaryCard( + type: SummaryType.guardian, + checksumFuture: guardianChecksumFuture, + address: AddressFormattingService.splitIntoChunks( + formData.guardianAddress, + ).join(' '), + ), + const Expanded(child: SizedBox()), + Row( + spacing: 36, + children: [ + Expanded( + child: Button( + label: 'Back', + onPressed: () { + Navigator.pop(context); + }, + ), + ), + Expanded( + child: Button( + variant: ButtonVariant.neutral, + label: 'Next', + onPressed: () { + showHighSecurityConfirmationSheet(context); + }, + ), + ), + ], + ), + SizedBox(height: context.themeSize.bottomButtonSpacing), + ], + ), + ); + } +} + +enum SummaryType { guardian, recovery } + +class SummaryCard extends StatelessWidget { + final SummaryType type; + final Future checksumFuture; + final String address; + + const SummaryCard({ + super.key, + required this.type, + required this.checksumFuture, + required this.address, + }); + + @override + Widget build(BuildContext context) { + final String label = type == SummaryType.guardian + ? 'GUARDIAN ACCOUNT:' + : 'RECOVERY ACCOUNT:'; + final Color checksumColor = type == SummaryType.guardian + ? context.themeColors.yellow + : context.themeColors.buttonDanger; + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: ShapeDecoration( + color: const Color(0x99313131), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 2, + children: [ + Text(label, style: context.themeText.detail), + FutureBuilder( + future: checksumFuture, + builder: (context, snapshot) { + String text = 'Loading checksum...'; + + if (snapshot.error != null) { + text = 'Failed loading checksum: ${snapshot.error}'; + } else if (snapshot.hasData) { + text = snapshot.data!; + } + + return Text( + text, + style: context.themeText.smallParagraph?.copyWith( + color: checksumColor, + ), + ); + }, + ), + SizedBox( + width: 220, + child: Text( + address, + style: context.themeText.detail?.copyWith( + color: Colors.white.useOpacity(0.6000000238418579), + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart new file mode 100644 index 00000000..71048039 --- /dev/null +++ b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart @@ -0,0 +1,280 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:resonance_network_wallet/features/components/app_modal_bottom_sheet.dart'; +import 'package:resonance_network_wallet/features/components/button.dart'; +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; + +class SafeguardWindowPickerSheet extends StatelessWidget { + final int safeguardTimeMonths; + final int safeguardTimeDays; + final int safeguardTimeHours; + final Function(int) setSafeguardTimeSeconds; + + const SafeguardWindowPickerSheet({ + super.key, + required this.safeguardTimeMonths, + required this.safeguardTimeDays, + required this.safeguardTimeHours, + required this.setSafeguardTimeSeconds, + }); + + @override + Widget build(BuildContext context) { + var selectedMonths = safeguardTimeMonths; + var selectedDays = safeguardTimeDays; + var selectedHours = safeguardTimeHours; + + return Container( + height: MediaQuery.of(context).size.height * 0.75, + padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 60), + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + children: [ + // Header + Column( + children: [ + SvgPicture.asset('assets/hourglass.svg', width: 29), + const SizedBox(height: 16), + Text( + 'Set Safeguard Window', + textAlign: TextAlign.center, + style: context.themeText.smallTitle?.copyWith( + color: context.themeColors.checksum, + ), + ), + const SizedBox(height: 4), + SizedBox( + width: context.themeSize.timePickerSubtitleWidth, + child: Text( + 'The Guardian can intercept a transaction during this period', + textAlign: TextAlign.center, + style: context.themeText.detail, + ), + ), + ], + ), + + const SizedBox(height: 20), + + // Time pickers + Expanded( + child: Row( + children: [ + // Months + Expanded( + child: Column( + children: [ + Text( + 'Months', + style: context.themeText.largeTag?.copyWith( + color: context.themeColors.textMuted, + ), + ), + const SizedBox(height: 8), + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: selectedMonths, + ), + itemExtent: 40, + onSelectedItemChanged: (index) => + selectedMonths = index, + children: List.generate( + 13, + (index) => Center( + child: Text( + index.toString().padLeft(2, '0'), + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + const Text( + ':', + style: TextStyle( + color: Colors.white, + fontSize: 28, + ), + ), + ], + ), + ), + ], + ), + ), + // Days + Expanded( + child: Column( + children: [ + Text( + 'Days', + style: context.themeText.largeTag?.copyWith( + color: context.themeColors.textMuted, + ), + ), + const SizedBox(height: 8), + Expanded( + child: Row( + children: [ + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: selectedDays, + ), + itemExtent: 40, + onSelectedItemChanged: (index) => + selectedDays = index, + children: List.generate( + 30, + (index) => Center( + child: Text( + index.toString().padLeft(2, '0'), + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + const Text( + ':', + style: TextStyle( + color: Colors.white, + fontSize: 28, + ), + ), + ], + ), + ), + ], + ), + ), + // Hours + Expanded( + child: Column( + children: [ + Text( + 'Hours', + style: context.themeText.largeTag?.copyWith( + color: context.themeColors.textMuted, + ), + ), + const SizedBox(height: 8), + Expanded( + child: CupertinoPicker( + scrollController: FixedExtentScrollController( + initialItem: selectedHours, + ), + itemExtent: 40, + onSelectedItemChanged: (index) => + selectedHours = index, + children: List.generate( + 24, + (index) => Center( + child: Text( + index.toString().padLeft(2, '0'), + style: const TextStyle( + color: Colors.white, + fontSize: 28, + fontFamily: 'Fira Code', + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + + const SizedBox(height: 20), + + // Action buttons + Row( + children: [ + Expanded( + child: Button( + variant: ButtonVariant.neutral, + label: 'Cancel', + textStyle: context.themeText.paragraph?.copyWith( + color: context.themeColors.textSecondary, + fontWeight: FontWeight.w600, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Button( + variant: ButtonVariant.success, + label: 'Set', + textStyle: context.themeText.paragraph?.copyWith( + color: context.themeColors.textSecondary, + fontWeight: FontWeight.w600, + ), + onPressed: () { + final int secondsInAMonth = + 86400 * 30; // 86400 seconds/day * 30 days/month + final newTimeSeconds = + (selectedMonths * secondsInAMonth) + + (selectedDays * 86400) + + (selectedHours * 3600); + + setSafeguardTimeSeconds(newTimeSeconds); + Navigator.pop(context); + }, + ), + ), + ], + ), + const SizedBox(height: 35), + ], + ), + ); + } +} + +void showSafeguardWindowPickerSheet( + BuildContext context, { + required int safeguardTimeMonths, + required int safeguardTimeDays, + required int safeguardTimeHours, + + required Function(int) setSafeguardTimeSeconds, +}) { + showAppModalBottomSheet( + context: context, + builder: (context) => SafeguardWindowPickerSheet( + safeguardTimeMonths: safeguardTimeMonths, + safeguardTimeDays: safeguardTimeDays, + safeguardTimeHours: safeguardTimeHours, + + setSafeguardTimeSeconds: setSafeguardTimeSeconds, + ), + ); +} diff --git a/mobile-app/lib/features/styles/app_colors_theme.dart b/mobile-app/lib/features/styles/app_colors_theme.dart index 637f9dbb..f5dab625 100644 --- a/mobile-app/lib/features/styles/app_colors_theme.dart +++ b/mobile-app/lib/features/styles/app_colors_theme.dart @@ -8,6 +8,7 @@ class AppColorsTheme extends ThemeExtension { final Color secondary; // What we use + final List aquaBlue; final Color purple; final Color pink; final Color yellow; @@ -43,6 +44,7 @@ class AppColorsTheme extends ThemeExtension { required this.primary, required this.secondary, + required this.aquaBlue, required this.purple, required this.pink, required this.yellow, @@ -80,6 +82,7 @@ class AppColorsTheme extends ThemeExtension { primary: const Color(0xFF6B46C1), secondary: const Color(0xFF9F7AEA), + aquaBlue: const [Color(0xFF16CECE), Color(0xFF0000FF)], purple: const Color(0xFFB259F2), pink: const Color(0xFFED4CCE), yellow: const Color(0xFFFFE91F), @@ -117,6 +120,7 @@ class AppColorsTheme extends ThemeExtension { primary: const Color(0xFF6B46C1), secondary: const Color(0xFF9F7AEA), + aquaBlue: const [Color(0xFF16CECE), Color(0xFF0000FF)], purple: const Color(0xFFB259F2), pink: const Color(0xFFED4CCE), yellow: const Color(0xFFFFE91F), @@ -153,6 +157,7 @@ class AppColorsTheme extends ThemeExtension { AppColorsTheme copyWith({ Color? primary, Color? secondary, + List? aquaBlue, Color? purple, Color? pink, Color? yellow, @@ -188,6 +193,7 @@ class AppColorsTheme extends ThemeExtension { return AppColorsTheme( primary: primary ?? this.primary, secondary: secondary ?? this.secondary, + aquaBlue: aquaBlue ?? this.aquaBlue, purple: purple ?? this.purple, pink: pink ?? this.pink, yellow: yellow ?? this.yellow, @@ -227,6 +233,7 @@ class AppColorsTheme extends ThemeExtension { return AppColorsTheme( primary: Color.lerp(primary, other.primary, t) ?? primary, secondary: Color.lerp(secondary, other.secondary, t) ?? secondary, + aquaBlue: other.aquaBlue, purple: Color.lerp(purple, other.purple, t) ?? purple, pink: Color.lerp(pink, other.pink, t) ?? pink, yellow: Color.lerp(yellow, other.yellow, t) ?? yellow, diff --git a/mobile-app/lib/features/styles/app_size_theme.dart b/mobile-app/lib/features/styles/app_size_theme.dart index bbabb172..6cc5c0ef 100644 --- a/mobile-app/lib/features/styles/app_size_theme.dart +++ b/mobile-app/lib/features/styles/app_size_theme.dart @@ -27,6 +27,9 @@ class AppSizeTheme extends ThemeExtension { final double pasteIconSize; final double timePickerSubtitleWidth; final double bottomButtonSpacing; + final double buttonsHorizontalSpacing; + final double infoSheetTitleIcon; + const AppSizeTheme({ required this.logoHeight, @@ -54,6 +57,8 @@ class AppSizeTheme extends ThemeExtension { required this.pasteIconSize, required this.timePickerSubtitleWidth, required this.bottomButtonSpacing, + required this.buttonsHorizontalSpacing, + required this.infoSheetTitleIcon, }); const AppSizeTheme.defaultTheme() @@ -83,6 +88,8 @@ class AppSizeTheme extends ThemeExtension { pasteIconSize: 18.0, timePickerSubtitleWidth: 249, bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 25, ); const AppSizeTheme.iPad() @@ -112,6 +119,8 @@ class AppSizeTheme extends ThemeExtension { pasteIconSize: 24.0, timePickerSubtitleWidth: 400, bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 28, ); @override @@ -170,6 +179,8 @@ class AppSizeTheme extends ThemeExtension { pasteIconSize: pasteIconSize ?? this.pasteIconSize, timePickerSubtitleWidth: timePickerSubtitleWidth ?? this.timePickerSubtitleWidth, bottomButtonSpacing: bottomButtonSpacing ?? this.bottomButtonSpacing, + buttonsHorizontalSpacing: buttonsHorizontalSpacing ?? this.buttonsHorizontalSpacing, + infoSheetTitleIcon: infoSheetTitleIcon ?? this.infoSheetTitleIcon, ); } @@ -206,6 +217,8 @@ class AppSizeTheme extends ThemeExtension { pasteIconSize: pasteIconSize + (other.pasteIconSize - pasteIconSize) * t, timePickerSubtitleWidth: timePickerSubtitleWidth + (other.timePickerSubtitleWidth - timePickerSubtitleWidth) * t, bottomButtonSpacing: bottomButtonSpacing + (other.bottomButtonSpacing - bottomButtonSpacing) * t, + buttonsHorizontalSpacing: buttonsHorizontalSpacing + (other.buttonsHorizontalSpacing - buttonsHorizontalSpacing) * t, + infoSheetTitleIcon: infoSheetTitleIcon + (other.infoSheetTitleIcon - infoSheetTitleIcon) * t, ); } } diff --git a/mobile-app/lib/providers/high_security_form_provider.dart b/mobile-app/lib/providers/high_security_form_provider.dart new file mode 100644 index 00000000..d17162d4 --- /dev/null +++ b/mobile-app/lib/providers/high_security_form_provider.dart @@ -0,0 +1,23 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +class HighSecurityFormNotifier extends StateNotifier { + HighSecurityFormNotifier() : super(const HighSecurityData()); + void updateGuardianAddress(String address) { + state = state.copyWith(guardianAddress: address); + } + + void updateSafeguardWindow(int window) { + state = state.copyWith(safeguardWindow: window); + } + + void resetState() { + state = const HighSecurityData(); + } +} + +// Provider +final highSecurityFormProvider = + StateNotifierProvider((ref) { + return HighSecurityFormNotifier(); + }); diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 132f6652..70c67a1f 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -15,6 +15,7 @@ export 'src/extensions/keypair_extensions.dart'; export 'src/extensions/string_extensions.dart'; // UI-related exports export 'src/models/account.dart'; +export 'src/models/high_security_data.dart'; export 'src/models/account_stats.dart'; export 'src/models/account_associations.dart'; export 'src/models/event_type.dart'; @@ -43,6 +44,7 @@ export 'src/services/chain_history_service.dart'; export 'src/services/connectivity_service.dart'; export 'src/services/datetime_formatting_service.dart'; export 'src/services/hd_wallet_service.dart'; +export 'src/services/high_security_service.dart'; export 'src/services/human_readable_checksum_service.dart'; export 'src/services/migration_service.dart'; export 'src/services/network/redundant_endpoint.dart'; diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index 3119b43a..33af4d94 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -57,4 +57,8 @@ class AppConstants { // This starts the hardware wallet flow using a soft wallet - quite useful for debugging // hardware wallet flow without using a hardware wallet. static const debugHardwareWallet = false; + + static const String accountSettingsRouteName = 'account-settings'; + static const int highSecurityStepsCount = 3; + } diff --git a/quantus_sdk/lib/src/models/high_security_data.dart b/quantus_sdk/lib/src/models/high_security_data.dart new file mode 100644 index 00000000..f73e1f92 --- /dev/null +++ b/quantus_sdk/lib/src/models/high_security_data.dart @@ -0,0 +1,19 @@ +class HighSecurityData { + final String guardianAddress; + final int safeguardWindow; + + const HighSecurityData({ + this.guardianAddress = '', + this.safeguardWindow = 10 * 60 * 60, // 10 hours in seconds + }); + + HighSecurityData copyWith({ + String? guardianAddress, + int? safeguardWindow, + }) { + return HighSecurityData( + guardianAddress: guardianAddress ?? this.guardianAddress, + safeguardWindow: safeguardWindow ?? this.safeguardWindow, + ); + } +} diff --git a/quantus_sdk/lib/src/services/datetime_formatting_service.dart b/quantus_sdk/lib/src/services/datetime_formatting_service.dart index 82b61687..7ba0b156 100644 --- a/quantus_sdk/lib/src/services/datetime_formatting_service.dart +++ b/quantus_sdk/lib/src/services/datetime_formatting_service.dart @@ -125,4 +125,18 @@ class DatetimeFormattingService { static String _pluralize(int count) { return count == 1 ? '' : 's'; } + + static String formatSafeguardTime(int months, int days, int hours) { + if (months > 0) { + return '$months month${months > 1 ? 's' : ''}, ' + '$days day${days != 1 ? 's' : ''}, \n' + '$hours hr${hours != 1 ? 's' : ''}'; + } else if (days > 0) { + return '$days day${days != 1 ? 's' : ''}, ' + '$hours hr${hours != 1 ? 's' : ''}'; + } else { + return '$hours hr${hours != 1 ? 's' : ''}'; + } + } + } diff --git a/quantus_sdk/lib/src/services/high_security_service.dart b/quantus_sdk/lib/src/services/high_security_service.dart new file mode 100644 index 00000000..266c249e --- /dev/null +++ b/quantus_sdk/lib/src/services/high_security_service.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:quantus_sdk/quantus_sdk.dart'; + +class HighSecurityService { + static final HighSecurityService _instance = HighSecurityService._internal(); + factory HighSecurityService() => _instance; + HighSecurityService._internal(); + + // ignore: unused_field + final SubstrateService _substrateService = SubstrateService(); + + Future setupHighSecurityAccount( + Account account, + HighSecurityData formData, + ) async { + try { + await Future.delayed(const Duration(seconds: 2)); + // Submit the extrinsic and return its result + // return await _substrateService.submitExtrinsic(account, runtimeCall); + } catch (e, stackTrace) { + print('Failed to setup: $e'); + print('Failed to setup: $stackTrace'); + throw Exception('Failed to setup: $e'); + } + } + + // TODO replace with actual fee calculation + Future getHighSecuritySetupFee( + Account account, + HighSecurityData formData, + ) async { + try { + await Future.delayed(const Duration(seconds: 2)); + + // Mock fetch + return ExtrinsicFeeData( + fee: BigInt.from(1000000000000000000), // 1.0 + blockHash: '0x0', + blockNumber: 0, + ); + } catch (e, stackTrace) { + print('Failed to get setup fee: $e'); + print('Failed to get setup fee: $stackTrace'); + throw Exception('Failed to get setup fee: $e'); + } + } + + void getHighSecuritySetupCall(HighSecurityData formData) { + throw Exception('No Implementation'); + } +} From 94ba3004e2183be12c385d179acdffb38813a9fe Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 9 Jan 2026 17:24:43 +0800 Subject: [PATCH 2/6] basic high security flow works added back buttons updated telemetrydeck added icons --- .../ios/Runner.xcodeproj/project.pbxproj | 112 +++++++++--------- .../features/components/wallet_app_bar.dart | 2 + .../high_security_get_started_screen.dart | 2 +- .../high_security_guardian_wizard.dart | 2 +- ...high_security_safeguard_window_wizard.dart | 2 +- .../high_security_summary_wizard.dart | 2 +- mobile-app/pubspec.yaml | 5 +- 7 files changed, 66 insertions(+), 61 deletions(-) diff --git a/mobile-app/ios/Runner.xcodeproj/project.pbxproj b/mobile-app/ios/Runner.xcodeproj/project.pbxproj index f3a23933..eb4370d4 100644 --- a/mobile-app/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile-app/ios/Runner.xcodeproj/project.pbxproj @@ -8,14 +8,14 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 193D36486744F9CA896B5858 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 86E110147A80CB996E2CA4AB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97939A145D33F6801E304DF5 /* Pods_Runner.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - A7A4D6A980D319AC470A9637 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */; }; + F2AFF251EE4997924A7D0F78 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -42,30 +42,30 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0E7E3C1546B23A5AFF5942FD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 1F5D52D89FF5F3FC074A093C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 4672F8772DB9DA61003B0FFF /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 4F6FA13E483D97F15F98587C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 823E822652161FDACC420786 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97939A145D33F6801E304DF5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - E3E5F71FCCC8D2959D796E5E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + CDD18E53B3D0BB011AFA8C44 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -73,7 +73,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 193D36486744F9CA896B5858 /* Pods_RunnerTests.framework in Frameworks */, + F2AFF251EE4997924A7D0F78 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -81,7 +81,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - A7A4D6A980D319AC470A9637 /* Pods_Runner.framework in Frameworks */, + 86E110147A80CB996E2CA4AB /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -96,11 +96,11 @@ path = RunnerTests; sourceTree = ""; }; - 58644EB040BB5BEDAFF2CD5F /* Frameworks */ = { + 568D30F7218BAC6CA6F26C90 /* Frameworks */ = { isa = PBXGroup; children = ( - 313CD1C8F586AE6AE7FC4933 /* Pods_Runner.framework */, - ABCD771D31123455D6A0A380 /* Pods_RunnerTests.framework */, + 97939A145D33F6801E304DF5 /* Pods_Runner.framework */, + 0ABF2C48D8CF924735FEFF77 /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -108,12 +108,12 @@ 697901FF368C5DA3A4CA46A0 /* Pods */ = { isa = PBXGroup; children = ( - 0E7E3C1546B23A5AFF5942FD /* Pods-Runner.debug.xcconfig */, - 4F6FA13E483D97F15F98587C /* Pods-Runner.release.xcconfig */, - E3E5F71FCCC8D2959D796E5E /* Pods-Runner.profile.xcconfig */, - BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */, - 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */, - C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */, + 823E822652161FDACC420786 /* Pods-Runner.debug.xcconfig */, + 1F5D52D89FF5F3FC074A093C /* Pods-Runner.release.xcconfig */, + CDD18E53B3D0BB011AFA8C44 /* Pods-Runner.profile.xcconfig */, + DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */, + CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */, + 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -137,7 +137,7 @@ 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, 697901FF368C5DA3A4CA46A0 /* Pods */, - 58644EB040BB5BEDAFF2CD5F /* Frameworks */, + 568D30F7218BAC6CA6F26C90 /* Frameworks */, ); sourceTree = ""; }; @@ -173,7 +173,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 005E706ADC7DC70A01BC32C5 /* [CP] Check Pods Manifest.lock */, + BF3E28A893917F2766F78391 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 83542EEFCC0959E7B844E8CA /* Frameworks */, @@ -192,14 +192,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 48B9C5DA509103240CAD37F8 /* [CP] Check Pods Manifest.lock */, + EC24DA874FC9ADBA74522F88 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - CA35B1F2B3EDD6A977D48ECC /* [CP] Embed Pods Frameworks */, + E9E213D6C6EC5BE831912A8A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -271,45 +271,38 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 005E706ADC7DC70A01BC32C5 /* [CP] Check Pods Manifest.lock */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); + name = "Thin Binary"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); - name = "Thin Binary"; + name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - 48B9C5DA509103240CAD37F8 /* [CP] Check Pods Manifest.lock */ = { + BF3E28A893917F2766F78391 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -324,43 +317,50 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + E9E213D6C6EC5BE831912A8A /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputPaths = ( + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "Run Script"; - outputPaths = ( + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - CA35B1F2B3EDD6A977D48ECC /* [CP] Embed Pods Frameworks */ = { + EC24DA874FC9ADBA74522F88 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -498,7 +498,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BFE1153F1F57BEE110B11642 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = DD6CCDCA2672B6A4F381839C /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -517,7 +517,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 91653C25A16732D6D1699E5A /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = CE44BF236E1BD0C36A568AA1 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -534,7 +534,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C4D362161EB554F367F7E9E7 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 19F520CF0B8598B21F21E35D /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/mobile-app/lib/features/components/wallet_app_bar.dart b/mobile-app/lib/features/components/wallet_app_bar.dart index d877722c..7bef8791 100644 --- a/mobile-app/lib/features/components/wallet_app_bar.dart +++ b/mobile-app/lib/features/components/wallet_app_bar.dart @@ -9,6 +9,8 @@ abstract class WalletAppBar extends StatelessWidget implements PreferredSizeWidg _StandardWalletAppBar(key: key, title: title, onBack: onBack, actions: actions); factory WalletAppBar.simple({Key? key, required String title}) => _SimpleWalletAppBar(key: key, title: title); + + factory WalletAppBar.simpleWithBackButton({Key? key, required String title, VoidCallback? onBack}) => _StandardWalletAppBar(key: key, title: title, onBack: onBack); factory WalletAppBar.custom({Key? key, required Widget titleWidget, Widget? leadingWidget, List? actions}) => _CustomWalletAppBar(key: key, titleWidget: titleWidget, leadingWidget: leadingWidget, actions: actions); diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart index 673f221e..e61927fb 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart @@ -17,7 +17,7 @@ class HighSecurityGetStartedScreen extends ConsumerWidget { final formNotifier = ref.read(highSecurityFormProvider.notifier); return ScaffoldBase( - appBar: WalletAppBar.simple(title: 'Security Settings'), + appBar: WalletAppBar.simpleWithBackButton(title: 'Security Settings'), child: Column( children: [ const SizedBox(height: 73), diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart index 59c25f99..54008555 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart @@ -61,7 +61,7 @@ class _HighSecurityGuardianWizardState final bool isDisabled = guardianAddress.isEmpty; return ScaffoldBase( - appBar: WalletAppBar.simple(title: 'Theft Deterrence'), + appBar: WalletAppBar.simpleWithBackButton(title: 'Theft Deterrence'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart index 4453ed5c..c761f3aa 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart @@ -44,7 +44,7 @@ class _HighSecuritySafeguardWindowWizardState final bool isDisabled = safeguardTimeSeconds == 0; return ScaffoldBase( - appBar: WalletAppBar.simple(title: 'Safeguard Window'), + appBar: WalletAppBar.simpleWithBackButton(title: 'Safeguard Window'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart index 7b047d7d..7512ed1a 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart @@ -33,7 +33,7 @@ class _HighSecuritySummaryWizardState .getHumanReadableName(formData.guardianAddress); return ScaffoldBase( - appBar: WalletAppBar.simple(title: 'Summary'), + appBar: WalletAppBar.simpleWithBackButton(title: 'Summary'), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index a8992a56..14ebec12 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -46,7 +46,7 @@ dependencies: share_plus: ^12.0.1 flutter_riverpod: ^2.6.1 riverpod_annotation: ^2.6.1 - telemetrydecksdk: ^2.5.0 + telemetrydecksdk: ^3.0.0 async: ^2.13.0 app_links: ^6.4.1 flutter_dotenv: ^5.1.0 @@ -94,6 +94,9 @@ flutter: - assets/qq-logo.png - assets/navbar/qcat_navbar_icon.png - assets/notification/notification_top_icon.png + - assets/high_security/security_icon_big.svg + - assets/high_security/step_indicator_active_icon.svg + - assets/high_security/step_indicator_icon.svg fonts: - family: Fira Code From 6b44e63e28b9076f497225828adf3e7f70aa8ca5 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 9 Jan 2026 17:29:49 +0800 Subject: [PATCH 3/6] format --- .../components/custom_text_field.dart | 7 +- .../features/components/gradient_text.dart | 7 +- mobile-app/lib/features/components/steps.dart | 25 ++---- .../features/components/wallet_app_bar.dart | 5 +- .../main/screens/account_settings_screen.dart | 5 +- .../main/screens/accounts_screen.dart | 4 +- .../guardian_account_info_sheet.dart | 50 +++--------- .../high_security_cancel_warning_sheet.dart | 35 ++------ .../high_security_confirmation_sheet.dart | 81 ++++--------------- .../high_security_created_sheet.dart | 35 ++------ .../high_security_get_started_screen.dart | 17 +--- .../high_security_guardian_wizard.dart | 35 ++------ ...high_security_safeguard_window_wizard.dart | 53 +++--------- .../high_security_summary_wizard.dart | 57 +++---------- .../safeguard_window_picker_sheet.dart | 69 ++++------------ .../lib/features/styles/app_size_theme.dart | 4 +- .../high_security_form_provider.dart | 7 +- .../lib/src/constants/app_constants.dart | 1 - .../lib/src/models/high_security_data.dart | 5 +- .../services/datetime_formatting_service.dart | 1 - .../src/services/high_security_service.dart | 12 +-- 21 files changed, 113 insertions(+), 402 deletions(-) diff --git a/mobile-app/lib/features/components/custom_text_field.dart b/mobile-app/lib/features/components/custom_text_field.dart index 0fe98e34..02e360e8 100644 --- a/mobile-app/lib/features/components/custom_text_field.dart +++ b/mobile-app/lib/features/components/custom_text_field.dart @@ -49,9 +49,7 @@ class CustomTextField extends StatelessWidget { : context.themeText.paragraph; final effectiveHintStyle = variant == TextFieldVariant.primary ? context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)) - : context.themeText.paragraph?.copyWith( - color: context.themeColors.textPrimary.useOpacity(0.5), - ); + : context.themeText.paragraph?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)); // The main container for the entire widget return SizedBox( @@ -91,8 +89,7 @@ class CustomTextField extends StatelessWidget { ), // Removes default padding hintText: hintText, // Style for the hint text when the field is empty - hintStyle: - hintStyle ?? effectiveHintStyle, + hintStyle: hintStyle ?? effectiveHintStyle, ), ), diff --git a/mobile-app/lib/features/components/gradient_text.dart b/mobile-app/lib/features/components/gradient_text.dart index 1c5e976c..eead202e 100644 --- a/mobile-app/lib/features/components/gradient_text.dart +++ b/mobile-app/lib/features/components/gradient_text.dart @@ -5,12 +5,7 @@ class GradientText extends StatelessWidget { final List colors; final TextStyle? style; - const GradientText( - this.text, { - super.key, - required this.colors, - required this.style, - }); + const GradientText(this.text, {super.key, required this.colors, required this.style}); @override Widget build(BuildContext context) { diff --git a/mobile-app/lib/features/components/steps.dart b/mobile-app/lib/features/components/steps.dart index 618e1890..1053d4c0 100644 --- a/mobile-app/lib/features/components/steps.dart +++ b/mobile-app/lib/features/components/steps.dart @@ -8,12 +8,8 @@ class StepsIndicator extends StatelessWidget { final double lineHeight = 2; final double iconHeight = 6.56; - const StepsIndicator({ - super.key, - required this.currentStep, - required this.totalSteps, - - }) : assert(currentStep >= 1 && currentStep <= totalSteps ); + const StepsIndicator({super.key, required this.currentStep, required this.totalSteps}) + : assert(currentStep >= 1 && currentStep <= totalSteps); @override Widget build(BuildContext context) { @@ -25,10 +21,7 @@ class StepsIndicator extends StatelessWidget { children: List.generate(totalSteps, (index) { return Expanded( child: Row( - children: [ - if (index < totalSteps) _buildStepLine(context, index), - _buildStepPoint(context, index), - ], + children: [if (index < totalSteps) _buildStepLine(context, index), _buildStepPoint(context, index)], ), ); }), @@ -45,21 +38,13 @@ class StepsIndicator extends StatelessWidget { ? 'assets/high_security/step_indicator_active_icon.svg' : 'assets/high_security/step_indicator_icon.svg'; - return Center( - child: SvgPicture.asset( - iconPath, - width: 4, - height: iconHeight, - ), - ); + return Center(child: SvgPicture.asset(iconPath, width: 4, height: iconHeight)); } Widget _buildStepLine(BuildContext context, int index) { final isCompleted = index < currentStep - 1; final isCurrent = index == currentStep - 1; - final lineColor = (isCompleted || isCurrent) - ? context.themeColors.checksum - : const Color(0x66FFFFFF); + final lineColor = (isCompleted || isCurrent) ? context.themeColors.checksum : const Color(0x66FFFFFF); return Expanded( child: Container(height: lineHeight, color: lineColor), diff --git a/mobile-app/lib/features/components/wallet_app_bar.dart b/mobile-app/lib/features/components/wallet_app_bar.dart index 7bef8791..bfdcc71a 100644 --- a/mobile-app/lib/features/components/wallet_app_bar.dart +++ b/mobile-app/lib/features/components/wallet_app_bar.dart @@ -9,8 +9,9 @@ abstract class WalletAppBar extends StatelessWidget implements PreferredSizeWidg _StandardWalletAppBar(key: key, title: title, onBack: onBack, actions: actions); factory WalletAppBar.simple({Key? key, required String title}) => _SimpleWalletAppBar(key: key, title: title); - - factory WalletAppBar.simpleWithBackButton({Key? key, required String title, VoidCallback? onBack}) => _StandardWalletAppBar(key: key, title: title, onBack: onBack); + + factory WalletAppBar.simpleWithBackButton({Key? key, required String title, VoidCallback? onBack}) => + _StandardWalletAppBar(key: key, title: title, onBack: onBack); factory WalletAppBar.custom({Key? key, required Widget titleWidget, Widget? leadingWidget, List? actions}) => _CustomWalletAppBar(key: key, titleWidget: titleWidget, leadingWidget: leadingWidget, actions: actions); diff --git a/mobile-app/lib/features/main/screens/account_settings_screen.dart b/mobile-app/lib/features/main/screens/account_settings_screen.dart index 7ed4114e..a424dfeb 100644 --- a/mobile-app/lib/features/main/screens/account_settings_screen.dart +++ b/mobile-app/lib/features/main/screens/account_settings_screen.dart @@ -279,10 +279,7 @@ class _AccountSettingsScreenState extends ConsumerState { return _buildSettingCard( child: InkWell( onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const HighSecurityGetStartedScreen()), - ); + Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecurityGetStartedScreen())); }, child: Padding( padding: const EdgeInsets.only(top: 12.0, left: 12.0, bottom: 12.0, right: 26.0), diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart index 567bc09d..a43424f9 100644 --- a/mobile-app/lib/features/main/screens/accounts_screen.dart +++ b/mobile-app/lib/features/main/screens/accounts_screen.dart @@ -407,9 +407,7 @@ class _AccountsScreenState extends ConsumerState { await Navigator.push( context, MaterialPageRoute( - settings: const RouteSettings( - name: AppConstants.accountSettingsRouteName, - ), + settings: const RouteSettings(name: AppConstants.accountSettingsRouteName), builder: (context) => AccountSettingsScreen( account: account, balance: _formattingService.formatBalance(balance, addSymbol: true), diff --git a/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart index fc0a109a..ab41e26b 100644 --- a/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart +++ b/mobile-app/lib/features/main/screens/high_security/guardian_account_info_sheet.dart @@ -12,8 +12,7 @@ class GuardianAccountInfoSheet extends StatefulWidget { const GuardianAccountInfoSheet({super.key}); @override - State createState() => - _GuardianAccountInfoSheetState(); + State createState() => _GuardianAccountInfoSheetState(); } class _GuardianAccountInfoSheetState extends State { @@ -37,11 +36,7 @@ class _GuardianAccountInfoSheetState extends State { Container( width: double.infinity, padding: const EdgeInsets.all(7), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), - ), - ), + decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -55,15 +50,9 @@ class _GuardianAccountInfoSheetState extends State { const SizedBox(height: 24), Row( children: [ - Icon( - Icons.info_outline, - size: context.themeSize.infoSheetTitleIcon, - ), + Icon(Icons.info_outline, size: context.themeSize.infoSheetTitleIcon), const SizedBox(width: 22), - Text( - 'What is a Guardian Account', - style: context.themeText.largeTag, - ), + Text('What is a Guardian Account', style: context.themeText.largeTag), ], ), const SizedBox(height: 22), @@ -77,22 +66,10 @@ class _GuardianAccountInfoSheetState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'A Guardian account can:', - style: context.themeText.smallParagraph, - ), - Text( - '• Intercept any transaction', - style: context.themeText.smallParagraph, - ), - Text( - '• Pull all funds from this account', - style: context.themeText.smallParagraph, - ), - Text( - '• Change the recovery address', - style: context.themeText.smallParagraph, - ), + Text('A Guardian account can:', style: context.themeText.smallParagraph), + Text('• Intercept any transaction', style: context.themeText.smallParagraph), + Text('• Pull all funds from this account', style: context.themeText.smallParagraph), + Text('• Change the recovery address', style: context.themeText.smallParagraph), ], ), ), @@ -139,11 +116,7 @@ void showGuardianAccountInfoSheet(BuildContext context) { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.black, - const Color(0xFF312E6E).useOpacity(0.4), - Colors.black, - ], + colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black], ), ), ), @@ -154,10 +127,7 @@ void showGuardianAccountInfoSheet(BuildContext context) { right: 0, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), - child: Container( - color: Colors.black.useOpacity(0.3), - child: const GuardianAccountInfoSheet(), - ), + child: Container(color: Colors.black.useOpacity(0.3), child: const GuardianAccountInfoSheet()), ), ), ], diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart index f3d94c99..454c150a 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_cancel_warning_sheet.dart @@ -12,17 +12,13 @@ class HighSecurityCancelWarningSheet extends StatefulWidget { const HighSecurityCancelWarningSheet({super.key}); @override - State createState() => - _HighSecurityCancelWarningSheetState(); + State createState() => _HighSecurityCancelWarningSheetState(); } -class _HighSecurityCancelWarningSheetState - extends State { +class _HighSecurityCancelWarningSheetState extends State { void _returnToAccountSetting() { if (!mounted) return; - Navigator.of( - context, - ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); } void _continueSetup() { @@ -45,11 +41,7 @@ class _HighSecurityCancelWarningSheetState Container( width: double.infinity, padding: const EdgeInsets.all(7), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), - ), - ), + decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -80,9 +72,7 @@ class _HighSecurityCancelWarningSheetState child: Button( variant: ButtonVariant.danger, label: 'Exit anyway', - textStyle: context.themeText.smallParagraph?.copyWith( - fontWeight: FontWeight.w600, - ), + textStyle: context.themeText.smallParagraph?.copyWith(fontWeight: FontWeight.w600), onPressed: () { _returnToAccountSetting(); }, @@ -92,9 +82,7 @@ class _HighSecurityCancelWarningSheetState child: Button( variant: ButtonVariant.neutral, label: 'Continue', - textStyle: context.themeText.smallParagraph?.copyWith( - fontWeight: FontWeight.w600, - ), + textStyle: context.themeText.smallParagraph?.copyWith(fontWeight: FontWeight.w600), onPressed: _continueSetup, ), ), @@ -124,11 +112,7 @@ void showHighSecurityCancelWarningSheet(BuildContext context) { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.black, - const Color(0xFF312E6E).useOpacity(0.4), - Colors.black, - ], + colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black], ), ), ), @@ -139,10 +123,7 @@ void showHighSecurityCancelWarningSheet(BuildContext context) { right: 0, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), - child: Container( - color: Colors.black.useOpacity(0.3), - child: const HighSecurityCancelWarningSheet(), - ), + child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityCancelWarningSheet()), ), ), ], diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart index 51e11db0..f4ffceda 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_confirmation_sheet.dart @@ -16,12 +16,10 @@ class HighSecurityConfirmationSheet extends ConsumerStatefulWidget { const HighSecurityConfirmationSheet({super.key}); @override - ConsumerState createState() => - _HighSecurityConfirmationSheetState(); + ConsumerState createState() => _HighSecurityConfirmationSheetState(); } -class _HighSecurityConfirmationSheetState - extends ConsumerState { +class _HighSecurityConfirmationSheetState extends ConsumerState { final HighSecurityService _highSecurityService = HighSecurityService(); final SettingsService _settingsService = SettingsService(); @@ -36,10 +34,7 @@ class _HighSecurityConfirmationSheetState final formData = ref.read(highSecurityFormProvider); final activeAccount = (await _settingsService.getActiveAccount())!; - await _highSecurityService.setupHighSecurityAccount( - activeAccount, - formData, - ); + await _highSecurityService.setupHighSecurityAccount(activeAccount, formData); setState(() { _isSubmitting = false; @@ -58,10 +53,7 @@ class _HighSecurityConfirmationSheetState final activeAccount = (await _settingsService.getActiveAccount())!; final formData = ref.read(highSecurityFormProvider); - final fee = await _highSecurityService.getHighSecuritySetupFee( - activeAccount, - formData, - ); + final fee = await _highSecurityService.getHighSecuritySetupFee(activeAccount, formData); setState(() { _networkFee = fee.fee; @@ -92,11 +84,7 @@ class _HighSecurityConfirmationSheetState Container( width: double.infinity, padding: const EdgeInsets.all(7), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), - ), - ), + decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -108,12 +96,7 @@ class _HighSecurityConfirmationSheetState ), ), const SizedBox(height: 24), - Text( - 'WARNING:', - style: context.themeText.largeTitle?.copyWith( - color: context.themeColors.buttonDanger, - ), - ), + Text('WARNING:', style: context.themeText.largeTitle?.copyWith(color: context.themeColors.buttonDanger)), const SizedBox(height: 11), Text( 'These features are designed to help keep your funds safer, but once confirmed this account CANNOT:', @@ -125,30 +108,12 @@ class _HighSecurityConfirmationSheetState child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - '• Turn off High Security', - style: context.themeText.smallParagraph, - ), - Text( - '• Reverse a transaction', - style: context.themeText.smallParagraph, - ), - Text( - '• Change the Guardian account', - style: context.themeText.smallParagraph, - ), - Text( - '• Change the Recovery account', - style: context.themeText.smallParagraph, - ), - Text( - '• Change the Safeguard window', - style: context.themeText.smallParagraph, - ), - Text( - '• Deny a recovery request', - style: context.themeText.smallParagraph, - ), + Text('• Turn off High Security', style: context.themeText.smallParagraph), + Text('• Reverse a transaction', style: context.themeText.smallParagraph), + Text('• Change the Guardian account', style: context.themeText.smallParagraph), + Text('• Change the Recovery account', style: context.themeText.smallParagraph), + Text('• Change the Safeguard window', style: context.themeText.smallParagraph), + Text('• Deny a recovery request', style: context.themeText.smallParagraph), ], ), ), @@ -161,17 +126,10 @@ class _HighSecurityConfirmationSheetState Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'Network Fee', - style: context.themeText.detail?.copyWith( - fontWeight: FontWeight.w600, - ), - ), + Text('Network Fee', style: context.themeText.detail?.copyWith(fontWeight: FontWeight.w600)), Text( '${_networkFee ?? 'Fetching...'} ${AppConstants.tokenSymbol}', - style: context.themeText.detail?.copyWith( - fontWeight: FontWeight.w600, - ), + style: context.themeText.detail?.copyWith(fontWeight: FontWeight.w600), ), ], ), @@ -217,11 +175,7 @@ void showHighSecurityConfirmationSheet(BuildContext context) { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.black, - const Color(0xFF312E6E).useOpacity(0.4), - Colors.black, - ], + colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black], ), ), ), @@ -232,10 +186,7 @@ void showHighSecurityConfirmationSheet(BuildContext context) { right: 0, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), - child: Container( - color: Colors.black.useOpacity(0.3), - child: const HighSecurityConfirmationSheet(), - ), + child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityConfirmationSheet()), ), ), ], diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart index e676a23f..ea9fad0f 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_created_sheet.dart @@ -13,16 +13,13 @@ class HighSecurityCreatedSheet extends StatefulWidget { const HighSecurityCreatedSheet({super.key}); @override - State createState() => - _HighSecurityCreatedSheetState(); + State createState() => _HighSecurityCreatedSheetState(); } class _HighSecurityCreatedSheetState extends State { void _returnToAccountSetting() { if (!mounted) return; - Navigator.of( - context, - ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); } @override @@ -41,11 +38,7 @@ class _HighSecurityCreatedSheetState extends State { Container( width: double.infinity, padding: const EdgeInsets.all(7), - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(100), - ), - ), + decoration: ShapeDecoration(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100))), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -67,12 +60,7 @@ class _HighSecurityCreatedSheetState extends State { textAlign: TextAlign.center, ), const SizedBox(height: 46), - Button( - variant: ButtonVariant.neutral, - label: 'Done', - width: 188, - onPressed: _returnToAccountSetting, - ), + Button(variant: ButtonVariant.neutral, label: 'Done', width: 188, onPressed: _returnToAccountSetting), SizedBox(height: context.themeSize.bottomButtonSpacing), ], ), @@ -83,9 +71,7 @@ class _HighSecurityCreatedSheetState extends State { void showHighSecurityCreatedSheet(BuildContext context) { void returnToAccountSetting() { - Navigator.of( - context, - ).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); + Navigator.of(context).popUntil(ModalRoute.withName(AppConstants.accountSettingsRouteName)); } showModalBottomSheet( @@ -103,11 +89,7 @@ void showHighSecurityCreatedSheet(BuildContext context) { gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [ - Colors.black, - const Color(0xFF312E6E).useOpacity(0.4), - Colors.black, - ], + colors: [Colors.black, const Color(0xFF312E6E).useOpacity(0.4), Colors.black], ), ), ), @@ -118,10 +100,7 @@ void showHighSecurityCreatedSheet(BuildContext context) { right: 0, child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), - child: Container( - color: Colors.black.useOpacity(0.3), - child: const HighSecurityCreatedSheet(), - ), + child: Container(color: Colors.black.useOpacity(0.3), child: const HighSecurityCreatedSheet()), ), ), ], diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart index e61927fb..6d7cffdc 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_get_started_screen.dart @@ -21,11 +21,7 @@ class HighSecurityGetStartedScreen extends ConsumerWidget { child: Column( children: [ const SizedBox(height: 73), - SvgPicture.asset( - 'assets/high_security/security_icon_big.svg', - width: 140, - height: 175, - ), + SvgPicture.asset('assets/high_security/security_icon_big.svg', width: 140, height: 175), const SizedBox(height: 26), Text('HIGH SECURITY', style: context.themeText.largeTitle), const SizedBox(height: 25), @@ -38,9 +34,7 @@ class HighSecurityGetStartedScreen extends ConsumerWidget { Text( 'Once you enable this feature it cannot be disabled', textAlign: TextAlign.center, - style: context.themeText.paragraph?.copyWith( - fontWeight: FontWeight.w600, - ), + style: context.themeText.paragraph?.copyWith(fontWeight: FontWeight.w600), ), const Expanded(child: SizedBox()), Button( @@ -48,12 +42,7 @@ class HighSecurityGetStartedScreen extends ConsumerWidget { label: 'Start', onPressed: () { formNotifier.resetState(); - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const HighSecurityGuardianWizard(), - ), - ); + Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecurityGuardianWizard())); }, ), SizedBox(height: context.themeSize.bottomButtonSpacing), diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart index 54008555..bf71b6b5 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_guardian_wizard.dart @@ -21,21 +21,16 @@ class HighSecurityGuardianWizard extends ConsumerStatefulWidget { const HighSecurityGuardianWizard({super.key}); @override - ConsumerState createState() => - _HighSecurityGuardianWizardState(); + ConsumerState createState() => _HighSecurityGuardianWizardState(); } -class _HighSecurityGuardianWizardState - extends ConsumerState { +class _HighSecurityGuardianWizardState extends ConsumerState { Future _scanQRCode() async { final formNotifier = ref.read(highSecurityFormProvider.notifier); final scannedAddress = await Navigator.push( context, - MaterialPageRoute( - builder: (context) => const QRScannerScreen(), - fullscreenDialog: true, - ), + MaterialPageRoute(builder: (context) => const QRScannerScreen(), fullscreenDialog: true), ); if (scannedAddress != null && mounted) { @@ -71,19 +66,12 @@ class _HighSecurityGuardianWizardState children: [ SizedBox( width: 204, - child: StepsIndicator( - currentStep: 1, - totalSteps: AppConstants.highSecurityStepsCount, - ), + child: StepsIndicator(currentStep: 1, totalSteps: AppConstants.highSecurityStepsCount), ), ], ), const SizedBox(height: 32), - GradientText( - 'THEFT DETERRENCE', - colors: context.themeColors.aquaBlue, - style: context.themeText.largeTitle, - ), + GradientText('THEFT DETERRENCE', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle), const SizedBox(height: 4), Text( 'Intercept any transaction or “pull” all funds in the case of theft.', @@ -118,9 +106,7 @@ class _HighSecurityGuardianWizardState formNotifier.updateGuardianAddress(data.text!); } }, - child: const WalletActionButton( - assetPath: 'assets/paste_icon_1.svg', - ), + child: const WalletActionButton(assetPath: 'assets/paste_icon_1.svg'), ), const SizedBox(width: 12), GestureDetector( @@ -139,9 +125,7 @@ class _HighSecurityGuardianWizardState const SizedBox(height: 13), Text( 'The harder the Guardian account is to access the higher the security. An address on a cold storage wallet is the most secure.', - style: context.themeText.smallParagraph?.copyWith( - color: context.themeColors.textMuted, - ), + style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.textMuted), ), const Expanded(child: SizedBox()), Row( @@ -163,10 +147,7 @@ class _HighSecurityGuardianWizardState onPressed: () { Navigator.push( context, - MaterialPageRoute( - builder: (context) => - const HighSecuritySafeguardWindowWizard(), - ), + MaterialPageRoute(builder: (context) => const HighSecuritySafeguardWindowWizard()), ); }, ), diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart index c761f3aa..03a41798 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_safeguard_window_wizard.dart @@ -18,27 +18,21 @@ class HighSecuritySafeguardWindowWizard extends ConsumerStatefulWidget { const HighSecuritySafeguardWindowWizard({super.key}); @override - ConsumerState createState() => - _HighSecuritySafeguardWindowWizardState(); + ConsumerState createState() => _HighSecuritySafeguardWindowWizardState(); } -class _HighSecuritySafeguardWindowWizardState - extends ConsumerState { +class _HighSecuritySafeguardWindowWizardState extends ConsumerState { @override Widget build(BuildContext context) { final formNotifier = ref.read(highSecurityFormProvider.notifier); - final safeguardTimeSeconds = ref - .watch(highSecurityFormProvider) - .safeguardWindow; + final safeguardTimeSeconds = ref.watch(highSecurityFormProvider).safeguardWindow; final int secondsInADay = 86400; - final int secondsInAMonth = - secondsInADay * 30; // 86400 seconds/day * 30 days/month + final int secondsInAMonth = secondsInADay * 30; // 86400 seconds/day * 30 days/month /// This is an approximation. final int safeguardTimeMonths = safeguardTimeSeconds ~/ secondsInAMonth; - final int safeguardTimeDays = - (safeguardTimeSeconds % secondsInAMonth) ~/ secondsInADay; + final int safeguardTimeDays = (safeguardTimeSeconds % secondsInAMonth) ~/ secondsInADay; final int safeguardTimeHours = (safeguardTimeSeconds % secondsInADay) ~/ 3600; final bool isDisabled = safeguardTimeSeconds == 0; @@ -54,19 +48,12 @@ class _HighSecuritySafeguardWindowWizardState children: [ SizedBox( width: 204, - child: StepsIndicator( - currentStep: 2, - totalSteps: AppConstants.highSecurityStepsCount, - ), + child: StepsIndicator(currentStep: 2, totalSteps: AppConstants.highSecurityStepsCount), ), ], ), const SizedBox(height: 32), - GradientText( - 'SAFEGUARD WINDOW', - colors: context.themeColors.aquaBlue, - style: context.themeText.largeTitle, - ), + GradientText('SAFEGUARD WINDOW', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle), const SizedBox(height: 4), Text( 'The time window in which the Guardian can deny or intercept a transaction.', @@ -75,9 +62,7 @@ class _HighSecuritySafeguardWindowWizardState const SizedBox(height: 38), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Safeguard Window', style: context.themeText.largeTag), - ], + children: [Text('Safeguard Window', style: context.themeText.largeTag)], ), const SizedBox(height: 4), Text( @@ -100,9 +85,7 @@ class _HighSecuritySafeguardWindowWizardState padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), decoration: ShapeDecoration( color: const Color(0xFF313131), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -116,11 +99,7 @@ class _HighSecuritySafeguardWindowWizardState ), style: context.themeText.smallParagraph, ), - Icon( - Icons.edit, - color: Colors.white70, - size: context.isTablet ? 22 : 14, - ), + Icon(Icons.edit, color: Colors.white70, size: context.isTablet ? 22 : 14), ], ), ), @@ -128,9 +107,7 @@ class _HighSecuritySafeguardWindowWizardState const SizedBox(height: 13), Text( 'Allow a reasonable window for your Guardian account to respond in an emergency.', - style: context.themeText.smallParagraph?.copyWith( - color: context.themeColors.textMuted, - ), + style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.textMuted), ), const Expanded(child: SizedBox()), Row( @@ -150,13 +127,7 @@ class _HighSecuritySafeguardWindowWizardState variant: ButtonVariant.neutral, label: 'Next', onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - const HighSecuritySummaryWizard(), - ), - ); + Navigator.push(context, MaterialPageRoute(builder: (context) => const HighSecuritySummaryWizard())); }, ), ), diff --git a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart index 7512ed1a..d7d9508b 100644 --- a/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart +++ b/mobile-app/lib/features/main/screens/high_security/high_security_summary_wizard.dart @@ -16,21 +16,17 @@ class HighSecuritySummaryWizard extends ConsumerStatefulWidget { const HighSecuritySummaryWizard({super.key}); @override - ConsumerState createState() => - _HighSecuritySummaryWizardState(); + ConsumerState createState() => _HighSecuritySummaryWizardState(); } -class _HighSecuritySummaryWizardState - extends ConsumerState { - final HumanReadableChecksumService _humanReadableChecksumService = - HumanReadableChecksumService(); +class _HighSecuritySummaryWizardState extends ConsumerState { + final HumanReadableChecksumService _humanReadableChecksumService = HumanReadableChecksumService(); @override Widget build(BuildContext context) { final formData = ref.read(highSecurityFormProvider); - final guardianChecksumFuture = _humanReadableChecksumService - .getHumanReadableName(formData.guardianAddress); + final guardianChecksumFuture = _humanReadableChecksumService.getHumanReadableName(formData.guardianAddress); return ScaffoldBase( appBar: WalletAppBar.simpleWithBackButton(title: 'Summary'), @@ -43,19 +39,12 @@ class _HighSecuritySummaryWizardState children: [ SizedBox( width: 204, - child: StepsIndicator( - currentStep: 3, - totalSteps: AppConstants.highSecurityStepsCount, - ), + child: StepsIndicator(currentStep: 3, totalSteps: AppConstants.highSecurityStepsCount), ), ], ), const SizedBox(height: 32), - GradientText( - 'SUMMARY', - colors: context.themeColors.aquaBlue, - style: context.themeText.largeTitle, - ), + GradientText('SUMMARY', colors: context.themeColors.aquaBlue, style: context.themeText.largeTitle), const SizedBox(height: 19), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -65,17 +54,13 @@ class _HighSecuritySummaryWizardState Text('Everyday Account', style: context.themeText.smallTitle), Text( 'Grain-Red-Flash-Hyper-Cloud', - style: context.themeText.smallParagraph?.copyWith( - color: context.themeColors.checksumDarker, - ), + style: context.themeText.smallParagraph?.copyWith(color: context.themeColors.checksumDarker), ), SizedBox( width: 220, child: Text( '5FEUm MJ6w5 36upW fhFcK n61jN UniW3 norvT ULjwj MhbfN cs4N', - style: context.themeText.detail?.copyWith( - color: Colors.white.useOpacity(0.6000000238418579), - ), + style: context.themeText.detail?.copyWith(color: Colors.white.useOpacity(0.6000000238418579)), ), ), ], @@ -84,9 +69,7 @@ class _HighSecuritySummaryWizardState SummaryCard( type: SummaryType.guardian, checksumFuture: guardianChecksumFuture, - address: AddressFormattingService.splitIntoChunks( - formData.guardianAddress, - ).join(' '), + address: AddressFormattingService.splitIntoChunks(formData.guardianAddress).join(' '), ), const Expanded(child: SizedBox()), Row( @@ -125,18 +108,11 @@ class SummaryCard extends StatelessWidget { final Future checksumFuture; final String address; - const SummaryCard({ - super.key, - required this.type, - required this.checksumFuture, - required this.address, - }); + const SummaryCard({super.key, required this.type, required this.checksumFuture, required this.address}); @override Widget build(BuildContext context) { - final String label = type == SummaryType.guardian - ? 'GUARDIAN ACCOUNT:' - : 'RECOVERY ACCOUNT:'; + final String label = type == SummaryType.guardian ? 'GUARDIAN ACCOUNT:' : 'RECOVERY ACCOUNT:'; final Color checksumColor = type == SummaryType.guardian ? context.themeColors.yellow : context.themeColors.buttonDanger; @@ -164,21 +140,14 @@ class SummaryCard extends StatelessWidget { text = snapshot.data!; } - return Text( - text, - style: context.themeText.smallParagraph?.copyWith( - color: checksumColor, - ), - ); + return Text(text, style: context.themeText.smallParagraph?.copyWith(color: checksumColor)); }, ), SizedBox( width: 220, child: Text( address, - style: context.themeText.detail?.copyWith( - color: Colors.white.useOpacity(0.6000000238418579), - ), + style: context.themeText.detail?.copyWith(color: Colors.white.useOpacity(0.6000000238418579)), ), ), ], diff --git a/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart index 71048039..4b8c6bf8 100644 --- a/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart +++ b/mobile-app/lib/features/main/screens/high_security/safeguard_window_picker_sheet.dart @@ -44,9 +44,7 @@ class SafeguardWindowPickerSheet extends StatelessWidget { Text( 'Set Safeguard Window', textAlign: TextAlign.center, - style: context.themeText.smallTitle?.copyWith( - color: context.themeColors.checksum, - ), + style: context.themeText.smallTitle?.copyWith(color: context.themeColors.checksum), ), const SizedBox(height: 4), SizedBox( @@ -70,24 +68,16 @@ class SafeguardWindowPickerSheet extends StatelessWidget { Expanded( child: Column( children: [ - Text( - 'Months', - style: context.themeText.largeTag?.copyWith( - color: context.themeColors.textMuted, - ), - ), + Text('Months', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)), const SizedBox(height: 8), Expanded( child: Row( children: [ Expanded( child: CupertinoPicker( - scrollController: FixedExtentScrollController( - initialItem: selectedMonths, - ), + scrollController: FixedExtentScrollController(initialItem: selectedMonths), itemExtent: 40, - onSelectedItemChanged: (index) => - selectedMonths = index, + onSelectedItemChanged: (index) => selectedMonths = index, children: List.generate( 13, (index) => Center( @@ -104,13 +94,7 @@ class SafeguardWindowPickerSheet extends StatelessWidget { ), ), ), - const Text( - ':', - style: TextStyle( - color: Colors.white, - fontSize: 28, - ), - ), + const Text(':', style: TextStyle(color: Colors.white, fontSize: 28)), ], ), ), @@ -121,24 +105,16 @@ class SafeguardWindowPickerSheet extends StatelessWidget { Expanded( child: Column( children: [ - Text( - 'Days', - style: context.themeText.largeTag?.copyWith( - color: context.themeColors.textMuted, - ), - ), + Text('Days', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)), const SizedBox(height: 8), Expanded( child: Row( children: [ Expanded( child: CupertinoPicker( - scrollController: FixedExtentScrollController( - initialItem: selectedDays, - ), + scrollController: FixedExtentScrollController(initialItem: selectedDays), itemExtent: 40, - onSelectedItemChanged: (index) => - selectedDays = index, + onSelectedItemChanged: (index) => selectedDays = index, children: List.generate( 30, (index) => Center( @@ -155,13 +131,7 @@ class SafeguardWindowPickerSheet extends StatelessWidget { ), ), ), - const Text( - ':', - style: TextStyle( - color: Colors.white, - fontSize: 28, - ), - ), + const Text(':', style: TextStyle(color: Colors.white, fontSize: 28)), ], ), ), @@ -172,21 +142,13 @@ class SafeguardWindowPickerSheet extends StatelessWidget { Expanded( child: Column( children: [ - Text( - 'Hours', - style: context.themeText.largeTag?.copyWith( - color: context.themeColors.textMuted, - ), - ), + Text('Hours', style: context.themeText.largeTag?.copyWith(color: context.themeColors.textMuted)), const SizedBox(height: 8), Expanded( child: CupertinoPicker( - scrollController: FixedExtentScrollController( - initialItem: selectedHours, - ), + scrollController: FixedExtentScrollController(initialItem: selectedHours), itemExtent: 40, - onSelectedItemChanged: (index) => - selectedHours = index, + onSelectedItemChanged: (index) => selectedHours = index, children: List.generate( 24, (index) => Center( @@ -238,12 +200,9 @@ class SafeguardWindowPickerSheet extends StatelessWidget { fontWeight: FontWeight.w600, ), onPressed: () { - final int secondsInAMonth = - 86400 * 30; // 86400 seconds/day * 30 days/month + final int secondsInAMonth = 86400 * 30; // 86400 seconds/day * 30 days/month final newTimeSeconds = - (selectedMonths * secondsInAMonth) + - (selectedDays * 86400) + - (selectedHours * 3600); + (selectedMonths * secondsInAMonth) + (selectedDays * 86400) + (selectedHours * 3600); setSafeguardTimeSeconds(newTimeSeconds); Navigator.pop(context); diff --git a/mobile-app/lib/features/styles/app_size_theme.dart b/mobile-app/lib/features/styles/app_size_theme.dart index 6cc5c0ef..9da58c2e 100644 --- a/mobile-app/lib/features/styles/app_size_theme.dart +++ b/mobile-app/lib/features/styles/app_size_theme.dart @@ -30,7 +30,6 @@ class AppSizeTheme extends ThemeExtension { final double buttonsHorizontalSpacing; final double infoSheetTitleIcon; - const AppSizeTheme({ required this.logoHeight, required this.mainMenuHeight, @@ -217,7 +216,8 @@ class AppSizeTheme extends ThemeExtension { pasteIconSize: pasteIconSize + (other.pasteIconSize - pasteIconSize) * t, timePickerSubtitleWidth: timePickerSubtitleWidth + (other.timePickerSubtitleWidth - timePickerSubtitleWidth) * t, bottomButtonSpacing: bottomButtonSpacing + (other.bottomButtonSpacing - bottomButtonSpacing) * t, - buttonsHorizontalSpacing: buttonsHorizontalSpacing + (other.buttonsHorizontalSpacing - buttonsHorizontalSpacing) * t, + buttonsHorizontalSpacing: + buttonsHorizontalSpacing + (other.buttonsHorizontalSpacing - buttonsHorizontalSpacing) * t, infoSheetTitleIcon: infoSheetTitleIcon + (other.infoSheetTitleIcon - infoSheetTitleIcon) * t, ); } diff --git a/mobile-app/lib/providers/high_security_form_provider.dart b/mobile-app/lib/providers/high_security_form_provider.dart index d17162d4..c0b44207 100644 --- a/mobile-app/lib/providers/high_security_form_provider.dart +++ b/mobile-app/lib/providers/high_security_form_provider.dart @@ -17,7 +17,6 @@ class HighSecurityFormNotifier extends StateNotifier { } // Provider -final highSecurityFormProvider = - StateNotifierProvider((ref) { - return HighSecurityFormNotifier(); - }); +final highSecurityFormProvider = StateNotifierProvider((ref) { + return HighSecurityFormNotifier(); +}); diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index 33af4d94..a6014055 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -60,5 +60,4 @@ class AppConstants { static const String accountSettingsRouteName = 'account-settings'; static const int highSecurityStepsCount = 3; - } diff --git a/quantus_sdk/lib/src/models/high_security_data.dart b/quantus_sdk/lib/src/models/high_security_data.dart index f73e1f92..16020882 100644 --- a/quantus_sdk/lib/src/models/high_security_data.dart +++ b/quantus_sdk/lib/src/models/high_security_data.dart @@ -7,10 +7,7 @@ class HighSecurityData { this.safeguardWindow = 10 * 60 * 60, // 10 hours in seconds }); - HighSecurityData copyWith({ - String? guardianAddress, - int? safeguardWindow, - }) { + HighSecurityData copyWith({String? guardianAddress, int? safeguardWindow}) { return HighSecurityData( guardianAddress: guardianAddress ?? this.guardianAddress, safeguardWindow: safeguardWindow ?? this.safeguardWindow, diff --git a/quantus_sdk/lib/src/services/datetime_formatting_service.dart b/quantus_sdk/lib/src/services/datetime_formatting_service.dart index 7ba0b156..24c44168 100644 --- a/quantus_sdk/lib/src/services/datetime_formatting_service.dart +++ b/quantus_sdk/lib/src/services/datetime_formatting_service.dart @@ -138,5 +138,4 @@ class DatetimeFormattingService { return '$hours hr${hours != 1 ? 's' : ''}'; } } - } diff --git a/quantus_sdk/lib/src/services/high_security_service.dart b/quantus_sdk/lib/src/services/high_security_service.dart index 266c249e..c8caa1ea 100644 --- a/quantus_sdk/lib/src/services/high_security_service.dart +++ b/quantus_sdk/lib/src/services/high_security_service.dart @@ -10,10 +10,7 @@ class HighSecurityService { // ignore: unused_field final SubstrateService _substrateService = SubstrateService(); - Future setupHighSecurityAccount( - Account account, - HighSecurityData formData, - ) async { + Future setupHighSecurityAccount(Account account, HighSecurityData formData) async { try { await Future.delayed(const Duration(seconds: 2)); // Submit the extrinsic and return its result @@ -26,13 +23,10 @@ class HighSecurityService { } // TODO replace with actual fee calculation - Future getHighSecuritySetupFee( - Account account, - HighSecurityData formData, - ) async { + Future getHighSecuritySetupFee(Account account, HighSecurityData formData) async { try { await Future.delayed(const Duration(seconds: 2)); - + // Mock fetch return ExtrinsicFeeData( fee: BigInt.from(1000000000000000000), // 1.0 From f0a8222d89f5225ebb763ddb269a86470cc8fc85 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sun, 11 Jan 2026 20:28:17 +0800 Subject: [PATCH 4/6] add some guardian UX code --- .../lib/features/components/tree_list.dart | 431 ++++++++++++++++++ .../main/screens/accounts_screen.dart | 365 ++++++++++----- .../providers/entrusted_account_provider.dart | 30 ++ 3 files changed, 703 insertions(+), 123 deletions(-) create mode 100644 mobile-app/lib/features/components/tree_list.dart create mode 100644 mobile-app/lib/providers/entrusted_account_provider.dart diff --git a/mobile-app/lib/features/components/tree_list.dart b/mobile-app/lib/features/components/tree_list.dart new file mode 100644 index 00000000..2609d59b --- /dev/null +++ b/mobile-app/lib/features/components/tree_list.dart @@ -0,0 +1,431 @@ +import 'package:flutter/material.dart'; + +// Data model for tree nodes +class TreeNode { + final T data; + final List> children; + bool isExpanded; + + TreeNode({ + required this.data, + this.children = const [], + this.isExpanded = true, + }); + + bool get hasChildren => children.isNotEmpty; + bool get isLeaf => children.isEmpty; +} + +// Tree structure list widget +class TreeListView extends StatefulWidget { + final List> nodes; + final Widget Function(BuildContext context, TreeNode node, int depth) + nodeBuilder; + + final bool showExpandCollapse; + final EdgeInsetsGeometry? padding; + final ScrollPhysics? physics; + final bool shrinkWrap; + + const TreeListView({ + super.key, + required this.nodes, + required this.nodeBuilder, + this.showExpandCollapse = true, + this.padding, + this.physics, + this.shrinkWrap = false, + }); + + @override + State> createState() => _TreeListViewState(); +} + +class _TreeListViewState extends State> { + final Color lineColor = const Color(0x66FFFFFF); + final double lineWidth = 1.0; + final double indentWidth = 24.0; + + @override + Widget build(BuildContext context) { + return ListView( + padding: widget.padding, + physics: widget.physics, + shrinkWrap: widget.shrinkWrap, + children: _buildTreeNodes(widget.nodes, 0, []), + ); + } + + List _buildTreeNodes( + List> nodes, + int depth, + List parentLines, + ) { + List widgets = []; + + for (int i = 0; i < nodes.length; i++) { + final node = nodes[i]; + final isLast = i == nodes.length - 1; + final currentParentLines = List.from(parentLines)..add(!isLast); + + widgets.add( + _TreeNodeWidget( + node: node, + depth: depth, + isLast: isLast, + parentLines: parentLines, + indentWidth: indentWidth, + lineColor: lineColor, + lineWidth: lineWidth, + showExpandCollapse: widget.showExpandCollapse, + nodeBuilder: widget.nodeBuilder, + onToggleExpanded: () { + setState(() { + node.isExpanded = !node.isExpanded; + }); + }, + ), + ); + + if (node.hasChildren && node.isExpanded) { + widgets.addAll( + _buildTreeNodes(node.children, depth + 1, currentParentLines), + ); + } + } + + return widgets; + } +} + +class _TreeNodeWidget extends StatelessWidget { + final TreeNode node; + final int depth; + final bool isLast; + final List parentLines; + final double indentWidth; + final Color lineColor; + final double lineWidth; + final bool showExpandCollapse; + final Widget Function(BuildContext context, TreeNode node, int depth) + nodeBuilder; + final VoidCallback onToggleExpanded; + + const _TreeNodeWidget({ + required this.node, + required this.depth, + required this.isLast, + required this.parentLines, + required this.indentWidth, + required this.lineColor, + required this.lineWidth, + required this.showExpandCollapse, + required this.nodeBuilder, + required this.onToggleExpanded, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + // Tree lines + SizedBox( + width: (depth + 1) * indentWidth, + height: 56, + child: CustomPaint( + painter: TreeLinePainter( + depth: depth, + isLast: isLast, + parentLines: parentLines, + lineColor: lineColor, + lineWidth: lineWidth, + indentWidth: indentWidth, + ), + ), + ), + // Expand/collapse button + if (showExpandCollapse && node.hasChildren) + GestureDetector( + onTap: onToggleExpanded, + child: Container( + width: 20, + height: 20, + margin: const EdgeInsets.only(right: 4), + decoration: BoxDecoration( + border: Border.all(color: lineColor), + color: Colors.white, + ), + child: Icon( + node.isExpanded ? Icons.remove : Icons.add, + size: 12, + color: lineColor, + ), + ), + ) + else if (showExpandCollapse) + const SizedBox(width: 24), + // Node content + Expanded(child: nodeBuilder(context, node, depth)), + ], + ); + } +} + +class TreeLinePainter extends CustomPainter { + final int depth; + final bool isLast; + final List parentLines; + final Color lineColor; + final double lineWidth; + final double indentWidth; + + TreeLinePainter({ + required this.depth, + required this.isLast, + required this.parentLines, + required this.lineColor, + required this.lineWidth, + required this.indentWidth, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = lineColor + ..strokeWidth = lineWidth + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round; + + // Draw vertical lines for parent levels + for (int i = 0; i < parentLines.length; i++) { + if (parentLines[i]) { + final x = (i + 1) * indentWidth - indentWidth / 2; + canvas.drawLine( + Offset(x, 0), + Offset(x, size.height), + paint, + ); + } + } + + if (depth >= 0) { + final x = (depth + 1) * indentWidth - indentWidth / 2; + final centerY = size.height / 2; + + // Draw vertical line (up to center or full height) + if (!isLast) { + canvas.drawLine( + Offset(x, 0), + Offset(x, size.height), + paint, + ); + } else { + canvas.drawLine( + Offset(x, 0), + Offset(x, centerY), + paint, + ); + } + + // Draw horizontal line to the node (shorter to make room for arrow) + final horizontalEndX = x + indentWidth / 2 - 6; // Leave space for arrow + canvas.drawLine( + Offset(x, centerY), + Offset(horizontalEndX, centerY), + paint, + ); + + // Draw arrow at the end of horizontal line + _drawArrow(canvas, paint, Offset(horizontalEndX, centerY)); + } + } + + void _drawArrow(Canvas canvas, Paint paint, Offset position) { + final arrowSize = 5.0; + + // Draw horizontal line for arrow shaft + canvas.drawLine( + Offset(position.dx, position.dy), + Offset(position.dx + arrowSize, position.dy), + paint, + ); + + // Draw arrow head (two diagonal lines forming >) + canvas.drawLine( + Offset(position.dx + arrowSize, position.dy), + Offset(position.dx + arrowSize - 2, position.dy - 2), + paint, + ); + canvas.drawLine( + Offset(position.dx + arrowSize, position.dy), + Offset(position.dx + arrowSize - 2, position.dy + 2), + paint, + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +// Simple tree builder utility functions +List> buildTreeFromMap( + Map data, + T Function(String key, dynamic value) converter, +) { + List> nodes = []; + + data.forEach((key, value) { + if (value is Map) { + nodes.add( + TreeNode( + data: converter(key, value), + children: buildTreeFromMap(value, converter), + ), + ); + } else if (value is List) { + nodes.add( + TreeNode( + data: converter(key, value), + children: value + .map>( + (item) => TreeNode(data: converter(item.toString(), item)), + ) + .toList(), + ), + ); + } else { + nodes.add(TreeNode(data: converter(key, value))); + } + }); + + return nodes; +} + +// File system item for demo +class FileSystemItem { + final String name; + final bool isDirectory; + final String? extension; + + FileSystemItem({ + required this.name, + required this.isDirectory, + this.extension, + }); + + IconData get icon { + if (isDirectory) return Icons.folder; + switch (extension?.toLowerCase()) { + case 'dart': + return Icons.code; + case 'md': + return Icons.description; + case 'json': + return Icons.data_object; + case 'yaml': + case 'yml': + return Icons.settings; + default: + return Icons.insert_drive_file; + } + } + + Color get iconColor { + if (isDirectory) return Colors.blue; + switch (extension?.toLowerCase()) { + case 'dart': + return Colors.blue; + case 'md': + return Colors.orange; + case 'json': + return Colors.green; + case 'yaml': + case 'yml': + return Colors.purple; + default: + return Colors.grey; + } + } +} + +// Demo widget +class TreeListViewDemo extends StatelessWidget { + const TreeListViewDemo({super.key}); + + @override + Widget build(BuildContext context) { + final fileSystem = [ + TreeNode( + data: FileSystemItem(name: 'lib', isDirectory: true), + ), + TreeNode( + data: FileSystemItem(name: 'assets', isDirectory: true), + ), + TreeNode( + data: FileSystemItem( + name: 'pubspec.yaml', + isDirectory: false, + extension: 'yaml', + ), + ), + TreeNode( + data: FileSystemItem( + name: 'README.md', + isDirectory: false, + extension: 'md', + ), + ), + ]; + + return Scaffold( + appBar: AppBar( + title: const Text('Tree Structure List'), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'File System Tree:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 16), + Expanded( + child: TreeListView( + showExpandCollapse: false, + nodes: fileSystem, + nodeBuilder: (context, node, depth) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: Row( + children: [ + Icon( + node.data.icon, + size: 18, + color: node.data.iconColor, + ), + const SizedBox(width: 8), + Text( + node.data.name, + style: TextStyle( + fontSize: 14, + fontWeight: node.data.isDirectory + ? FontWeight.w500 + : FontWeight.normal, + ), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart index a43424f9..13bd5492 100644 --- a/mobile-app/lib/features/main/screens/accounts_screen.dart +++ b/mobile-app/lib/features/main/screens/accounts_screen.dart @@ -8,6 +8,7 @@ import 'package:resonance_network_wallet/features/components/scaffold_base.dart' import 'package:resonance_network_wallet/features/components/select.dart'; import 'package:resonance_network_wallet/features/components/select_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/sphere.dart'; +import 'package:resonance_network_wallet/features/components/tree_list.dart'; import 'package:resonance_network_wallet/features/components/wallet_app_bar.dart'; import 'package:resonance_network_wallet/features/main/screens/account_settings_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/add_hardware_account_screen.dart'; @@ -18,9 +19,11 @@ import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/providers/entrusted_account_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; import 'package:resonance_network_wallet/utils/feature_flags.dart'; +import 'dart:math'; enum _WalletMoreAction { createWallet, importWallet, addHardwareWallet } @@ -267,6 +270,15 @@ class _AccountsScreenState extends ConsumerState { } Widget _buildAccountListItem(Account account, bool isActive, int index) { + final entrustedAccountsAsync = ref.watch(entrustedAccountsProvider(account)); + final entrustedAccountsData = entrustedAccountsAsync.value ?? []; + + final entrustedNodes = entrustedAccountsData + .map((entrusted) => TreeNode(data: entrusted)) + .toList(); + + final double constraintMaxHeight = min(entrustedNodes.length * 52, 104); + return InkWell( onTap: () async { await ref.read(activeAccountProvider.notifier).setActiveAccount(account); @@ -275,154 +287,235 @@ class _AccountsScreenState extends ConsumerState { child: Stack( clipBehavior: Clip.hardEdge, children: [ - Row( + Column( children: [ - Expanded( - child: Container( - padding: EdgeInsets.symmetric(horizontal: context.isTablet ? 20 : 8, vertical: 8), - decoration: ShapeDecoration( - color: isActive ? context.themeColors.surfaceActive : context.themeColors.surface, - shape: RoundedRectangleBorder( - side: BorderSide(width: 1, color: context.themeColors.borderLight), - borderRadius: BorderRadius.circular(5), - ), - ), - child: Row( - children: [ - const SizedBox(width: 24), - Expanded( - child: Consumer( - builder: (context, ref, child) { - final balanceAsync = ref.watch(balanceProviderFamily(account.accountId)); - - return FutureBuilder( - future: _checksumService.getHumanReadableName(account.accountId), - builder: (context, checksumSnapshot) { - final humanChecksum = checksumSnapshot.data ?? ''; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - account.name, - style: context.themeText.paragraph?.copyWith( - color: isActive ? Colors.black : Colors.white, - ), - ), - Text( - humanChecksum, - style: context.themeText.detail?.copyWith( - color: isActive - ? context.themeColors.checksum - : context.themeColors.checksumDarker, - ), - ), - Row( + Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.symmetric(horizontal: context.isTablet ? 20 : 8, vertical: 8), + height: context.themeSize.accountListItemHeight, + decoration: ShapeDecoration( + color: isActive ? context.themeColors.surfaceActive : context.themeColors.surface, + shape: RoundedRectangleBorder( + side: BorderSide(width: 1, color: context.themeColors.borderLight), + borderRadius: BorderRadius.circular(5), + ), + ), + child: Row( + children: [ + const SizedBox(width: 24), + Expanded( + child: Consumer( + builder: (context, ref, child) { + final balanceAsync = ref.watch(balanceProviderFamily(account.accountId)); + + return FutureBuilder( + future: _checksumService.getHumanReadableName(account.accountId), + builder: (context, checksumSnapshot) { + final humanChecksum = checksumSnapshot.data ?? ''; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + account.name, + style: context.themeText.smallParagraph?.copyWith( + color: isActive ? Colors.black : Colors.white, + ), + ), + if (entrustedNodes.isNotEmpty) + const AccountTag('Guardian', color: Color(0xFF9747FF)), + ], + ), Text( - context.isTablet - ? account.accountId - // ignore: lines_longer_than_80_chars - : AddressFormattingService.formatAddress(account.accountId), + humanChecksum, style: context.themeText.detail?.copyWith( color: isActive - ? context.themeColors.darkGray - : context.themeColors.textMuted, + ? context.themeColors.checksumDarker + : context.themeColors.checksum, ), ), - ], - ), - const SizedBox(height: 2), - balanceAsync.when( - loading: () => Text( - 'loading balance...', - style: context.themeText.detail?.copyWith( - color: isActive ? context.themeColors.darkGray : context.themeColors.light, - ), - ), - error: (error, _) => Text( - 'error loading', - style: context.themeText.detail?.copyWith( - color: isActive - ? context.themeColors.darkGray - : context.themeColors.textPrimary, - ), - ), - data: (balance) => Text.rich( - TextSpan( + Row( children: [ - TextSpan( - text: _formattingService.formatBalance(balance), - style: context.themeText.smallParagraph?.copyWith( + Text( + context.isTablet + ? account.accountId + // ignore: lines_longer_than_80_chars + : AddressFormattingService.formatAddress(account.accountId), + style: context.themeText.tiny?.copyWith( color: isActive ? context.themeColors.darkGray - : context.themeColors.textPrimary, + : context.themeColors.textMuted, ), ), + ], + ), + const SizedBox(height: 2), + balanceAsync.when( + loading: () => Text( + 'loading balance...', + style: context.themeText.detail?.copyWith( + color: isActive ? context.themeColors.darkGray : context.themeColors.light, + ), + ), + error: (error, _) => Text( + 'error loading', + style: context.themeText.detail?.copyWith( + color: isActive + ? context.themeColors.darkGray + : context.themeColors.textPrimary, + ), + ), + data: (balance) => Text.rich( TextSpan( - text: ' ${AppConstants.tokenSymbol}', - style: context.themeText.detail?.copyWith( - color: isActive - ? context.themeColors.darkGray - : context.themeColors.textPrimary, - ), + children: [ + TextSpan( + text: _formattingService.formatBalance(balance), + style: context.themeText.detail?.copyWith( + color: isActive + ? context.themeColors.darkGray + : context.themeColors.textPrimary, + ), + ), + TextSpan( + text: ' ${AppConstants.tokenSymbol}', + style: context.themeText.tiny?.copyWith( + color: isActive + ? context.themeColors.darkGray + : context.themeColors.textPrimary, + ), + ), + ], ), - ], + ), ), - ), - ), - ], + ], + ); + }, ); }, - ); - }, - ), + ), + ), + ], ), - ], + ), ), - ), - ), - IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - icon: SvgPicture.asset( - 'assets/settings_icon_off.svg', - width: context.isTablet ? 28 : 21, - colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), - ), - onPressed: () async { - // Get current data from providers - final balanceAsync = ref.read(balanceProviderFamily(account.accountId)); - final checksumName = await _checksumService.getHumanReadableName(account.accountId); - - balanceAsync.when( - loading: () { - // Show loading or handle appropriately - }, - error: (error, _) { - // Handle error + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: SvgPicture.asset( + 'assets/settings_icon_off.svg', + width: context.isTablet ? 28 : 21, + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + onPressed: () async { + // Get current data from providers + final balanceAsync = ref.read(balanceProviderFamily(account.accountId)); + final checksumName = await _checksumService.getHumanReadableName(account.accountId); + + balanceAsync.when( + loading: () { + // Show loading or handle appropriately + }, + error: (error, _) { + // Handle error + }, + data: (balance) async { + if (!mounted) return; + await Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: AppConstants.accountSettingsRouteName), + builder: (context) => AccountSettingsScreen( + account: account, + balance: _formattingService.formatBalance(balance, addSymbol: true), + checksumName: checksumName, + ), + ), + ); + // Providers will automatically refresh if needed + }, + ); }, - data: (balance) async { - if (!mounted) return; - await Navigator.push( - context, - MaterialPageRoute( - settings: const RouteSettings(name: AppConstants.accountSettingsRouteName), - builder: (context) => AccountSettingsScreen( - account: account, - balance: _formattingService.formatBalance(balance, addSymbol: true), - checksumName: checksumName, + ), + ], + ), + if (entrustedNodes.isNotEmpty) + ConstrainedBox( + constraints: BoxConstraints(maxHeight: constraintMaxHeight), + child: TreeListView( + showExpandCollapse: false, + nodes: entrustedNodes, + nodeBuilder: (context, node, depth) { + final entrusted = node.data; + return Row( + children: [ + Expanded( + child: Container( + decoration: ShapeDecoration( + color: context.themeColors.darkGray, + shape: RoundedRectangleBorder( + side: const BorderSide(color: Color(0x26FFFFFF)), + borderRadius: BorderRadius.circular(5), + ), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + entrusted.name, + style: context.themeText.smallParagraph, + ), + const AccountTag('Entrusted'), + ], + ), + ), ), - ), + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + icon: SvgPicture.asset( + 'assets/settings_icon_off.svg', + width: context.isTablet ? 28 : 21, + colorFilter: const ColorFilter.mode(Colors.white, BlendMode.srcIn), + ), + onPressed: () async { + // Get current data from providers + final balanceAsync = ref.read(balanceProviderFamily(entrusted.accountId)); + final checksumName = await _checksumService.getHumanReadableName(entrusted.accountId); + + balanceAsync.when( + loading: () {}, + error: (error, _) {}, + data: (balance) async { + if (!mounted) return; + await Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: AppConstants.accountSettingsRouteName), + builder: (context) => AccountSettingsScreen( + account: entrusted, + balance: _formattingService.formatBalance(balance, addSymbol: true), + checksumName: checksumName, + ), + ), + ); + }, + ); + }, + ), + ], ); - // Providers will automatically refresh if needed }, - ); - }, - ), + ), + ), ], ), - Positioned( // calculating the middle point top: (context.themeSize.accountListItemHeight / 2) - (context.themeSize.accountListItemLogoWidth / 2), @@ -438,3 +531,29 @@ class _AccountsScreenState extends ConsumerState { ); } } + +class AccountTag extends StatelessWidget { + final String label; + final Color? color; + + const AccountTag(this.label, {super.key, this.color}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: color ?? const Color(0xFFFFD541), // Default to Entrusted color (Yellow-ish) + borderRadius: BorderRadius.circular(4), + ), + child: Text( + label, + style: context.themeText.tiny?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 10 + ), + ), + ); + } +} diff --git a/mobile-app/lib/providers/entrusted_account_provider.dart b/mobile-app/lib/providers/entrusted_account_provider.dart new file mode 100644 index 00000000..5078aef4 --- /dev/null +++ b/mobile-app/lib/providers/entrusted_account_provider.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +final entrustedAccountsProvider = FutureProvider.family, Account>((ref, account) async { + // TODO: Implement actual fetching of entrusted accounts from SDK/API + // For now we simulate the delay and return empty list or dummy data + + await Future.delayed(const Duration(milliseconds: 500)); + + // Dummy data logic for demonstration/development + // If you want to see the UI, you can uncomment this or use a specific account ID + if (account.name.startsWith('G')) { // arbitrary condition for testing + return [ + const Account( + walletIndex: 0, + index: 0, + name: 'Entrusted Account 1', + accountId: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', + ), + const Account( + walletIndex: 0, + index: 0, + name: 'Zander Sky', + accountId: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', + ), + ]; + } + + return []; +}); From dc494bf3bee2dc6d38a734bfd5e7c960fc27d34f Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sun, 11 Jan 2026 20:32:48 +0800 Subject: [PATCH 5/6] feature flag for high security --- .../main/screens/account_settings_screen.dart | 13 +++++++++---- .../lib/features/main/screens/accounts_screen.dart | 2 +- mobile-app/lib/utils/feature_flags.dart | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/mobile-app/lib/features/main/screens/account_settings_screen.dart b/mobile-app/lib/features/main/screens/account_settings_screen.dart index a424dfeb..c4cd71b6 100644 --- a/mobile-app/lib/features/main/screens/account_settings_screen.dart +++ b/mobile-app/lib/features/main/screens/account_settings_screen.dart @@ -22,6 +22,7 @@ import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; +import 'package:resonance_network_wallet/utils/feature_flags.dart'; class AccountSettingsScreen extends ConsumerStatefulWidget { final Account account; @@ -166,10 +167,14 @@ class _AccountSettingsScreenState extends ConsumerState { _buildShareSection(), const SizedBox(height: 20), _buildAddressSection(), - const SizedBox(height: 20), - _buildSecuritySection(), - const SizedBox(height: 20), - if (widget.account.accountType == AccountType.keystone) _buildDisconnectWalletButton(), + if (FeatureFlags.enableHighSecurity) ...[ + const SizedBox(height: 20), + _buildSecuritySection(), + ], + if (widget.account.accountType == AccountType.keystone) ...[ + const SizedBox(height: 20), + _buildDisconnectWalletButton(), + ], const SizedBox(height: 30), ], ), diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart index 13bd5492..38efb590 100644 --- a/mobile-app/lib/features/main/screens/accounts_screen.dart +++ b/mobile-app/lib/features/main/screens/accounts_screen.dart @@ -112,7 +112,7 @@ class _AccountsScreenState extends ConsumerState { Item(value: _WalletMoreAction.importWallet, label: 'Import wallet'), ]; - if (FeatureFlags.showKeystoneHardwareWallet) { + if (FeatureFlags.enableKeystoneHardwareWallet) { items.add(Item(value: _WalletMoreAction.addHardwareWallet, label: 'Add hardware wallet')); } diff --git a/mobile-app/lib/utils/feature_flags.dart b/mobile-app/lib/utils/feature_flags.dart index 0f93d5f5..07623f6d 100644 --- a/mobile-app/lib/utils/feature_flags.dart +++ b/mobile-app/lib/utils/feature_flags.dart @@ -1,5 +1,6 @@ // Simple feature flags for things we want in the code but not yet in the production app class FeatureFlags { static const bool enableTestButtons = false; // Only show in debug mode - static const bool showKeystoneHardwareWallet = false; // turn keystone hw wallet on and off + static const bool enableKeystoneHardwareWallet = false; // turn keystone hw wallet on and off + static const bool enableHighSecurity = false; // turn keystone hw wallet on and off } From bbc9d34833228fd0a23da0efd025202e9b58b1be Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sun, 11 Jan 2026 20:33:11 +0800 Subject: [PATCH 6/6] format --- .../lib/features/components/tree_list.dart | 123 ++++-------------- .../main/screens/account_settings_screen.dart | 5 +- .../main/screens/accounts_screen.dart | 21 +-- .../providers/entrusted_account_provider.dart | 19 +-- 4 files changed, 43 insertions(+), 125 deletions(-) diff --git a/mobile-app/lib/features/components/tree_list.dart b/mobile-app/lib/features/components/tree_list.dart index 2609d59b..535111fe 100644 --- a/mobile-app/lib/features/components/tree_list.dart +++ b/mobile-app/lib/features/components/tree_list.dart @@ -6,11 +6,7 @@ class TreeNode { final List> children; bool isExpanded; - TreeNode({ - required this.data, - this.children = const [], - this.isExpanded = true, - }); + TreeNode({required this.data, this.children = const [], this.isExpanded = true}); bool get hasChildren => children.isNotEmpty; bool get isLeaf => children.isEmpty; @@ -19,9 +15,8 @@ class TreeNode { // Tree structure list widget class TreeListView extends StatefulWidget { final List> nodes; - final Widget Function(BuildContext context, TreeNode node, int depth) - nodeBuilder; - + final Widget Function(BuildContext context, TreeNode node, int depth) nodeBuilder; + final bool showExpandCollapse; final EdgeInsetsGeometry? padding; final ScrollPhysics? physics; @@ -56,11 +51,7 @@ class _TreeListViewState extends State> { ); } - List _buildTreeNodes( - List> nodes, - int depth, - List parentLines, - ) { + List _buildTreeNodes(List> nodes, int depth, List parentLines) { List widgets = []; for (int i = 0; i < nodes.length; i++) { @@ -88,9 +79,7 @@ class _TreeListViewState extends State> { ); if (node.hasChildren && node.isExpanded) { - widgets.addAll( - _buildTreeNodes(node.children, depth + 1, currentParentLines), - ); + widgets.addAll(_buildTreeNodes(node.children, depth + 1, currentParentLines)); } } @@ -107,8 +96,7 @@ class _TreeNodeWidget extends StatelessWidget { final Color lineColor; final double lineWidth; final bool showExpandCollapse; - final Widget Function(BuildContext context, TreeNode node, int depth) - nodeBuilder; + final Widget Function(BuildContext context, TreeNode node, int depth) nodeBuilder; final VoidCallback onToggleExpanded; const _TreeNodeWidget({ @@ -155,11 +143,7 @@ class _TreeNodeWidget extends StatelessWidget { border: Border.all(color: lineColor), color: Colors.white, ), - child: Icon( - node.isExpanded ? Icons.remove : Icons.add, - size: 12, - color: lineColor, - ), + child: Icon(node.isExpanded ? Icons.remove : Icons.add, size: 12, color: lineColor), ), ) else if (showExpandCollapse) @@ -200,11 +184,7 @@ class TreeLinePainter extends CustomPainter { for (int i = 0; i < parentLines.length; i++) { if (parentLines[i]) { final x = (i + 1) * indentWidth - indentWidth / 2; - canvas.drawLine( - Offset(x, 0), - Offset(x, size.height), - paint, - ); + canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); } } @@ -214,26 +194,14 @@ class TreeLinePainter extends CustomPainter { // Draw vertical line (up to center or full height) if (!isLast) { - canvas.drawLine( - Offset(x, 0), - Offset(x, size.height), - paint, - ); + canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint); } else { - canvas.drawLine( - Offset(x, 0), - Offset(x, centerY), - paint, - ); + canvas.drawLine(Offset(x, 0), Offset(x, centerY), paint); } // Draw horizontal line to the node (shorter to make room for arrow) final horizontalEndX = x + indentWidth / 2 - 6; // Leave space for arrow - canvas.drawLine( - Offset(x, centerY), - Offset(horizontalEndX, centerY), - paint, - ); + canvas.drawLine(Offset(x, centerY), Offset(horizontalEndX, centerY), paint); // Draw arrow at the end of horizontal line _drawArrow(canvas, paint, Offset(horizontalEndX, centerY)); @@ -242,14 +210,10 @@ class TreeLinePainter extends CustomPainter { void _drawArrow(Canvas canvas, Paint paint, Offset position) { final arrowSize = 5.0; - + // Draw horizontal line for arrow shaft - canvas.drawLine( - Offset(position.dx, position.dy), - Offset(position.dx + arrowSize, position.dy), - paint, - ); - + canvas.drawLine(Offset(position.dx, position.dy), Offset(position.dx + arrowSize, position.dy), paint); + // Draw arrow head (two diagonal lines forming >) canvas.drawLine( Offset(position.dx + arrowSize, position.dy), @@ -268,29 +232,17 @@ class TreeLinePainter extends CustomPainter { } // Simple tree builder utility functions -List> buildTreeFromMap( - Map data, - T Function(String key, dynamic value) converter, -) { +List> buildTreeFromMap(Map data, T Function(String key, dynamic value) converter) { List> nodes = []; data.forEach((key, value) { if (value is Map) { - nodes.add( - TreeNode( - data: converter(key, value), - children: buildTreeFromMap(value, converter), - ), - ); + nodes.add(TreeNode(data: converter(key, value), children: buildTreeFromMap(value, converter))); } else if (value is List) { nodes.add( TreeNode( data: converter(key, value), - children: value - .map>( - (item) => TreeNode(data: converter(item.toString(), item)), - ) - .toList(), + children: value.map>((item) => TreeNode(data: converter(item.toString(), item))).toList(), ), ); } else { @@ -307,11 +259,7 @@ class FileSystemItem { final bool isDirectory; final String? extension; - FileSystemItem({ - required this.name, - required this.isDirectory, - this.extension, - }); + FileSystemItem({required this.name, required this.isDirectory, this.extension}); IconData get icon { if (isDirectory) return Icons.folder; @@ -355,25 +303,13 @@ class TreeListViewDemo extends StatelessWidget { @override Widget build(BuildContext context) { final fileSystem = [ - TreeNode( - data: FileSystemItem(name: 'lib', isDirectory: true), - ), - TreeNode( - data: FileSystemItem(name: 'assets', isDirectory: true), - ), + TreeNode(data: FileSystemItem(name: 'lib', isDirectory: true)), + TreeNode(data: FileSystemItem(name: 'assets', isDirectory: true)), TreeNode( - data: FileSystemItem( - name: 'pubspec.yaml', - isDirectory: false, - extension: 'yaml', - ), + data: FileSystemItem(name: 'pubspec.yaml', isDirectory: false, extension: 'yaml'), ), TreeNode( - data: FileSystemItem( - name: 'README.md', - isDirectory: false, - extension: 'md', - ), + data: FileSystemItem(name: 'README.md', isDirectory: false, extension: 'md'), ), ]; @@ -388,10 +324,7 @@ class TreeListViewDemo extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'File System Tree:', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), + const Text('File System Tree:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Expanded( child: TreeListView( @@ -402,19 +335,13 @@ class TreeListViewDemo extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ - Icon( - node.data.icon, - size: 18, - color: node.data.iconColor, - ), + Icon(node.data.icon, size: 18, color: node.data.iconColor), const SizedBox(width: 8), Text( node.data.name, style: TextStyle( fontSize: 14, - fontWeight: node.data.isDirectory - ? FontWeight.w500 - : FontWeight.normal, + fontWeight: node.data.isDirectory ? FontWeight.w500 : FontWeight.normal, ), ), ], diff --git a/mobile-app/lib/features/main/screens/account_settings_screen.dart b/mobile-app/lib/features/main/screens/account_settings_screen.dart index c4cd71b6..14f3fa69 100644 --- a/mobile-app/lib/features/main/screens/account_settings_screen.dart +++ b/mobile-app/lib/features/main/screens/account_settings_screen.dart @@ -167,10 +167,7 @@ class _AccountSettingsScreenState extends ConsumerState { _buildShareSection(), const SizedBox(height: 20), _buildAddressSection(), - if (FeatureFlags.enableHighSecurity) ...[ - const SizedBox(height: 20), - _buildSecuritySection(), - ], + if (FeatureFlags.enableHighSecurity) ...[const SizedBox(height: 20), _buildSecuritySection()], if (widget.account.accountType == AccountType.keystone) ...[ const SizedBox(height: 20), _buildDisconnectWalletButton(), diff --git a/mobile-app/lib/features/main/screens/accounts_screen.dart b/mobile-app/lib/features/main/screens/accounts_screen.dart index 38efb590..26a8356c 100644 --- a/mobile-app/lib/features/main/screens/accounts_screen.dart +++ b/mobile-app/lib/features/main/screens/accounts_screen.dart @@ -273,9 +273,7 @@ class _AccountsScreenState extends ConsumerState { final entrustedAccountsAsync = ref.watch(entrustedAccountsProvider(account)); final entrustedAccountsData = entrustedAccountsAsync.value ?? []; - final entrustedNodes = entrustedAccountsData - .map((entrusted) => TreeNode(data: entrusted)) - .toList(); + final entrustedNodes = entrustedAccountsData.map((entrusted) => TreeNode(data: entrusted)).toList(); final double constraintMaxHeight = min(entrustedNodes.length * 52, 104); @@ -328,7 +326,7 @@ class _AccountsScreenState extends ConsumerState { ), ), if (entrustedNodes.isNotEmpty) - const AccountTag('Guardian', color: Color(0xFF9747FF)), + const AccountTag('Guardian', color: Color(0xFF9747FF)), ], ), Text( @@ -359,7 +357,9 @@ class _AccountsScreenState extends ConsumerState { loading: () => Text( 'loading balance...', style: context.themeText.detail?.copyWith( - color: isActive ? context.themeColors.darkGray : context.themeColors.light, + color: isActive + ? context.themeColors.darkGray + : context.themeColors.light, ), ), error: (error, _) => Text( @@ -467,10 +467,7 @@ class _AccountsScreenState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - entrusted.name, - style: context.themeText.smallParagraph, - ), + Text(entrusted.name, style: context.themeText.smallParagraph), const AccountTag('Entrusted'), ], ), @@ -548,11 +545,7 @@ class AccountTag extends StatelessWidget { ), child: Text( label, - style: context.themeText.tiny?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 10 - ), + style: context.themeText.tiny?.copyWith(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 10), ), ); } diff --git a/mobile-app/lib/providers/entrusted_account_provider.dart b/mobile-app/lib/providers/entrusted_account_provider.dart index 5078aef4..cf9f4651 100644 --- a/mobile-app/lib/providers/entrusted_account_provider.dart +++ b/mobile-app/lib/providers/entrusted_account_provider.dart @@ -4,23 +4,24 @@ import 'package:quantus_sdk/quantus_sdk.dart'; final entrustedAccountsProvider = FutureProvider.family, Account>((ref, account) async { // TODO: Implement actual fetching of entrusted accounts from SDK/API // For now we simulate the delay and return empty list or dummy data - + await Future.delayed(const Duration(milliseconds: 500)); // Dummy data logic for demonstration/development // If you want to see the UI, you can uncomment this or use a specific account ID - if (account.name.startsWith('G')) { // arbitrary condition for testing + if (account.name.startsWith('G')) { + // arbitrary condition for testing return [ - const Account( - walletIndex: 0, - index: 0, - name: 'Entrusted Account 1', + const Account( + walletIndex: 0, + index: 0, + name: 'Entrusted Account 1', accountId: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', ), const Account( - walletIndex: 0, - index: 0, - name: 'Zander Sky', + walletIndex: 0, + index: 0, + name: 'Zander Sky', accountId: '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty', ), ];