From d75fd6a30d61f60b437ae3dacc0f844cef5602c8 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 15:25:34 +0800 Subject: [PATCH 01/49] themes initial version --- mobile-app/assets/fonts/Inter-Bold.ttf | 1449 +++++++++++++++++ mobile-app/assets/fonts/Inter-Light.ttf | 1449 +++++++++++++++++ mobile-app/assets/fonts/Inter-Medium.ttf | 1449 +++++++++++++++++ mobile-app/assets/fonts/Inter-Regular.ttf | 1449 +++++++++++++++++ mobile-app/assets/fonts/Inter-SemiBold.ttf | 1449 +++++++++++++++++ mobile-app/lib/features/main/screens/app.dart | 4 +- mobile-app/lib/v2/theme/app_colors.dart | 285 ++++ mobile-app/lib/v2/theme/app_spacing.dart | 282 ++++ mobile-app/lib/v2/theme/app_text_styles.dart | 212 +++ mobile-app/lib/v2/theme/app_theme.dart | 71 + mobile-app/pubspec.yaml | 12 + 11 files changed, 8109 insertions(+), 2 deletions(-) create mode 100644 mobile-app/assets/fonts/Inter-Bold.ttf create mode 100644 mobile-app/assets/fonts/Inter-Light.ttf create mode 100644 mobile-app/assets/fonts/Inter-Medium.ttf create mode 100644 mobile-app/assets/fonts/Inter-Regular.ttf create mode 100644 mobile-app/assets/fonts/Inter-SemiBold.ttf create mode 100644 mobile-app/lib/v2/theme/app_colors.dart create mode 100644 mobile-app/lib/v2/theme/app_spacing.dart create mode 100644 mobile-app/lib/v2/theme/app_text_styles.dart create mode 100644 mobile-app/lib/v2/theme/app_theme.dart diff --git a/mobile-app/assets/fonts/Inter-Bold.ttf b/mobile-app/assets/fonts/Inter-Bold.ttf new file mode 100644 index 00000000..a695b64c --- /dev/null +++ b/mobile-app/assets/fonts/Inter-Bold.ttf @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/mobile-app/assets/fonts/Inter-Light.ttf b/mobile-app/assets/fonts/Inter-Light.ttf new file mode 100644 index 00000000..d1c93a76 --- /dev/null +++ b/mobile-app/assets/fonts/Inter-Light.ttf @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/mobile-app/assets/fonts/Inter-Medium.ttf b/mobile-app/assets/fonts/Inter-Medium.ttf new file mode 100644 index 00000000..17eeef56 --- /dev/null +++ b/mobile-app/assets/fonts/Inter-Medium.ttf @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/mobile-app/assets/fonts/Inter-Regular.ttf b/mobile-app/assets/fonts/Inter-Regular.ttf new file mode 100644 index 00000000..1e76fef2 --- /dev/null +++ b/mobile-app/assets/fonts/Inter-Regular.ttf @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/mobile-app/assets/fonts/Inter-SemiBold.ttf b/mobile-app/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 00000000..39a9000a --- /dev/null +++ b/mobile-app/assets/fonts/Inter-SemiBold.ttf @@ -0,0 +1,1449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page not found · GitHub · GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ Skip to content + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+ + + + + +
+ + + + + + + + + +
+
+ + + +
+
+ +
+
+ 404 “This is not the web page you are looking for” + + + + + + + + + + + + +
+
+ +
+
+ +
+ + +
+
+ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/mobile-app/lib/features/main/screens/app.dart b/mobile-app/lib/features/main/screens/app.dart index 408ec839..8ce59d32 100644 --- a/mobile-app/lib/features/main/screens/app.dart +++ b/mobile-app/lib/features/main/screens/app.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:resonance_network_wallet/features/main/screens/authentication_wrapper.dart'; import 'package:resonance_network_wallet/features/main/screens/wallet_initializer.dart'; -import 'package:resonance_network_wallet/features/styles/app_theme.dart'; +import 'package:resonance_network_wallet/v2/theme/app_theme.dart'; import 'package:resonance_network_wallet/services/local_notifications_service.dart'; import 'package:resonance_network_wallet/services/notification_integration_service.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; @@ -55,7 +55,7 @@ class _ResonanceWalletAppState extends ConsumerState { '/account': (context) => const WalletInitializer(), '/transactions': (context) => const WalletInitializer(), }, - theme: AppTheme.lightTheme(context), + theme: AppTheme.darkTheme(context), darkTheme: AppTheme.darkTheme(context), themeMode: ThemeMode.dark, builder: (context, child) { diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/mobile-app/lib/v2/theme/app_colors.dart new file mode 100644 index 00000000..1df33112 --- /dev/null +++ b/mobile-app/lib/v2/theme/app_colors.dart @@ -0,0 +1,285 @@ +import 'package:flutter/material.dart'; + +@immutable +class AppColorsTheme extends ThemeExtension { + final Color primary; + final Color secondary; + + final List aquaBlue; + final Color purple; + final Color pink; + final Color yellow; + final Color background; + final Color background2; + final Color surface; + final Color surfaceActive; + final Color error; + final Color textPrimary; + final Color textSecondary; + final Color textError; + final Color textMuted; + final Color inputLabel; + final Color light; + final Color circularLoader; + final Color authButtonBg; + final Color border; + final Color borderLight; + final Color buttonDisabled; + final Color navbarBg; + final Color checksum; + final Color checksumDarker; + final Color darkGray; + final Color settingCard; + final Color buttonGlass; + final Color buttonDanger; + final Color buttonSuccess; + final Color buttonNeutral; + final List buttonPrimary; + final Color skeletonBase; + final Color skeletonHighlight; + final Color accountTagGuardian; + final Color accountTagEntrusted; + final Color accountTagHighSecurity; + + final Color textTertiary; + final Color surfaceGlass; + final Color surfaceCard; + final Color accentGreen; + final Color separator; + + const AppColorsTheme({ + required this.primary, + required this.secondary, + required this.aquaBlue, + required this.purple, + required this.pink, + required this.yellow, + required this.background, + required this.background2, + required this.surface, + required this.surfaceActive, + required this.error, + required this.textPrimary, + required this.textSecondary, + required this.textError, + required this.inputLabel, + required this.light, + required this.circularLoader, + required this.authButtonBg, + required this.textMuted, + required this.border, + required this.borderLight, + required this.buttonDisabled, + required this.navbarBg, + required this.checksum, + required this.checksumDarker, + required this.darkGray, + required this.settingCard, + required this.buttonGlass, + required this.buttonDanger, + required this.buttonSuccess, + required this.buttonNeutral, + required this.buttonPrimary, + required this.skeletonBase, + required this.skeletonHighlight, + required this.accountTagGuardian, + required this.accountTagEntrusted, + required this.accountTagHighSecurity, + required this.textTertiary, + required this.surfaceGlass, + required this.surfaceCard, + required this.accentGreen, + required this.separator, + }); + + const AppColorsTheme.dark() + : this( + 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), + background: const Color(0xFF141414), + background2: const Color(0xFF1F1F1F), + surface: const Color(0xFF292929), + surfaceActive: const Color(0xFFF4F6F9), + error: const Color(0xFFFF2D54), + textError: const Color(0xFFFF5252), + textPrimary: const Color(0xFFFFFFFF), + textSecondary: const Color(0x80FFFFFF), + textTertiary: const Color(0x52FFFFFF), + inputLabel: const Color(0xFFD4D3E0), + light: const Color(0xFFE6E6E6), + circularLoader: Colors.white, + authButtonBg: const Color(0xFF16CECE), + textMuted: const Color(0xFFD4D3E0), + border: const Color(0x33FFFFFF), + borderLight: const Color(0x0FFFFFFF), + buttonDisabled: const Color(0xFF3D3C44), + navbarBg: Colors.black, + checksum: const Color(0xFF4CEDE7), + checksumDarker: const Color(0xFF16CECE), + darkGray: const Color(0xFF3D3C44), + settingCard: const Color(0x0FF4F6F9), + buttonGlass: const Color(0x1AFFFFFF), + buttonDanger: const Color(0xFFFF1F45), + buttonSuccess: const Color(0xFF1FFFA7), + buttonNeutral: const Color(0xFFF4F6F9), + buttonPrimary: const [Color(0xFF0000FF), Color(0xFFED4CCE)], + skeletonBase: const Color(0xFF3D3C44), + skeletonHighlight: const Color(0xFF5A5A5A), + accountTagGuardian: const Color(0xFF9747FF), + accountTagEntrusted: const Color(0xFFFFD541), + accountTagHighSecurity: const Color(0xFF4CEDE7), + surfaceGlass: const Color(0x1AFFFFFF), + surfaceCard: const Color(0x0FFFFFFF), + accentGreen: const Color(0xFF34C759), + separator: const Color(0x1AFFFFFF), + ); + + @override + AppColorsTheme copyWith({ + Color? primary, + Color? secondary, + List? aquaBlue, + Color? purple, + Color? pink, + Color? yellow, + Color? background, + Color? background2, + Color? surface, + Color? surfaceActive, + Color? error, + Color? textPrimary, + Color? textSecondary, + Color? textError, + Color? inputLabel, + Color? light, + Color? circularLoader, + Color? authButtonBg, + Color? textMuted, + Color? border, + Color? borderLight, + Color? buttonDisabled, + Color? navbarBg, + Color? checksum, + Color? checksumDarker, + Color? darkGray, + Color? settingCard, + Color? buttonGlass, + Color? buttonDanger, + Color? buttonSuccess, + Color? buttonNeutral, + List? buttonPrimary, + Color? skeletonBase, + Color? skeletonHighlight, + Color? accountTagGuardian, + Color? accountTagEntrusted, + Color? accountTagHighSecurity, + Color? textTertiary, + Color? surfaceGlass, + Color? surfaceCard, + Color? accentGreen, + Color? separator, + }) { + 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, + background: background ?? this.background, + background2: background2 ?? this.background2, + surface: surface ?? this.surface, + surfaceActive: surfaceActive ?? this.surfaceActive, + error: error ?? this.error, + textPrimary: textPrimary ?? this.textPrimary, + textSecondary: textSecondary ?? this.textSecondary, + textError: textError ?? this.textError, + inputLabel: inputLabel ?? this.inputLabel, + light: light ?? this.light, + circularLoader: circularLoader ?? this.circularLoader, + authButtonBg: authButtonBg ?? this.authButtonBg, + textMuted: textMuted ?? this.textMuted, + border: border ?? this.border, + borderLight: borderLight ?? this.borderLight, + buttonDisabled: buttonDisabled ?? this.buttonDisabled, + navbarBg: navbarBg ?? this.navbarBg, + checksum: checksum ?? this.checksum, + checksumDarker: checksumDarker ?? this.checksumDarker, + darkGray: darkGray ?? this.darkGray, + settingCard: settingCard ?? this.settingCard, + buttonGlass: buttonGlass ?? this.buttonGlass, + buttonDanger: buttonDanger ?? this.buttonDanger, + buttonSuccess: buttonSuccess ?? this.buttonSuccess, + buttonNeutral: buttonNeutral ?? this.buttonNeutral, + buttonPrimary: buttonPrimary ?? this.buttonPrimary, + skeletonBase: skeletonBase ?? this.skeletonBase, + skeletonHighlight: skeletonHighlight ?? this.skeletonHighlight, + accountTagGuardian: accountTagGuardian ?? this.accountTagGuardian, + accountTagEntrusted: accountTagEntrusted ?? this.accountTagEntrusted, + accountTagHighSecurity: accountTagHighSecurity ?? this.accountTagHighSecurity, + textTertiary: textTertiary ?? this.textTertiary, + surfaceGlass: surfaceGlass ?? this.surfaceGlass, + surfaceCard: surfaceCard ?? this.surfaceCard, + accentGreen: accentGreen ?? this.accentGreen, + separator: separator ?? this.separator, + ); + } + + @override + AppColorsTheme lerp(AppColorsTheme? other, double t) { + if (other is! AppColorsTheme) return this; + 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, + background: Color.lerp(background, other.background, t) ?? background, + background2: Color.lerp(background2, other.background2, t) ?? background2, + surface: Color.lerp(surface, other.surface, t) ?? surface, + surfaceActive: Color.lerp(surfaceActive, other.surfaceActive, t) ?? surfaceActive, + error: Color.lerp(error, other.error, t) ?? error, + textPrimary: Color.lerp(textPrimary, other.textPrimary, t) ?? textPrimary, + textSecondary: Color.lerp(textSecondary, other.textSecondary, t) ?? textSecondary, + textError: Color.lerp(textError, other.textError, t) ?? textError, + inputLabel: Color.lerp(inputLabel, other.inputLabel, t) ?? inputLabel, + light: Color.lerp(light, other.light, t) ?? light, + circularLoader: Color.lerp(circularLoader, other.circularLoader, t) ?? circularLoader, + authButtonBg: Color.lerp(authButtonBg, other.authButtonBg, t) ?? authButtonBg, + textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted, + border: Color.lerp(border, other.border, t) ?? border, + borderLight: Color.lerp(borderLight, other.borderLight, t) ?? borderLight, + buttonDisabled: Color.lerp(buttonDisabled, other.buttonDisabled, t) ?? buttonDisabled, + navbarBg: Color.lerp(navbarBg, other.navbarBg, t) ?? navbarBg, + checksum: Color.lerp(checksum, other.checksum, t) ?? checksum, + checksumDarker: Color.lerp(checksumDarker, other.checksumDarker, t) ?? checksumDarker, + darkGray: Color.lerp(darkGray, other.darkGray, t) ?? darkGray, + settingCard: Color.lerp(settingCard, other.settingCard, t) ?? settingCard, + buttonGlass: Color.lerp(buttonGlass, other.buttonGlass, t) ?? buttonGlass, + buttonDanger: Color.lerp(buttonDanger, other.buttonDanger, t) ?? buttonDanger, + buttonSuccess: Color.lerp(buttonSuccess, other.buttonSuccess, t) ?? buttonSuccess, + buttonNeutral: Color.lerp(buttonNeutral, other.buttonNeutral, t) ?? buttonNeutral, + buttonPrimary: other.buttonPrimary, + skeletonBase: Color.lerp(skeletonBase, other.skeletonBase, t) ?? skeletonBase, + skeletonHighlight: Color.lerp(skeletonHighlight, other.skeletonHighlight, t) ?? skeletonHighlight, + accountTagGuardian: Color.lerp(accountTagGuardian, other.accountTagGuardian, t) ?? accountTagGuardian, + accountTagEntrusted: Color.lerp(accountTagEntrusted, other.accountTagEntrusted, t) ?? accountTagEntrusted, + accountTagHighSecurity: + Color.lerp(accountTagHighSecurity, other.accountTagHighSecurity, t) ?? accountTagHighSecurity, + textTertiary: Color.lerp(textTertiary, other.textTertiary, t) ?? textTertiary, + surfaceGlass: Color.lerp(surfaceGlass, other.surfaceGlass, t) ?? surfaceGlass, + surfaceCard: Color.lerp(surfaceCard, other.surfaceCard, t) ?? surfaceCard, + accentGreen: Color.lerp(accentGreen, other.accentGreen, t) ?? accentGreen, + separator: Color.lerp(separator, other.separator, t) ?? separator, + ); + } +} + +extension AppColorsThemeExtension on BuildContext { + AppColorsTheme get themeColors => Theme.of(this).extension()!; +} diff --git a/mobile-app/lib/v2/theme/app_spacing.dart b/mobile-app/lib/v2/theme/app_spacing.dart new file mode 100644 index 00000000..bacc8d44 --- /dev/null +++ b/mobile-app/lib/v2/theme/app_spacing.dart @@ -0,0 +1,282 @@ +import 'package:flutter/material.dart'; + +@immutable +class AppSizeTheme extends ThemeExtension { + final double logoHeight; + final double mainMenuHeight; + final double mainMenuWidth; + final double mainMenuIconSize; + final double navbarHeight; + final double navbarItemHeight; + final double navbarItemWidth; + final double navbarIconWidth; + final double floatingBtnHeight; + final double floatingBtnWidth; + final double settingMenuIconSize; + final double settingMenuShareIconSize; + final double accountListItemHeight; + final double accountListItemLogoWidth; + final double appbarIconSize; + final double sendOverlayContainerWidth; + final double overlayCloseIconSize; + final double mnemonicCellDesiredHeight; + final double txListItemIconWidth; + final double txDetailsIconHeight; + final double txDetailsIconWidth; + final double copyIconSize; + final double pasteIconSize; + final double timePickerSubtitleWidth; + final double bottomButtonSpacing; + final double buttonsHorizontalSpacing; + final double infoSheetTitleIcon; + + final double screenPadding; + final double cardPadding; + final double sectionGap; + final double itemGap; + final double sectionHeaderToContent; + + final double radiusFull; + final double radiusCard; + final double radiusSmall; + + const AppSizeTheme({ + required this.logoHeight, + required this.mainMenuHeight, + required this.mainMenuWidth, + required this.mainMenuIconSize, + required this.navbarHeight, + required this.navbarItemHeight, + required this.navbarItemWidth, + required this.navbarIconWidth, + required this.floatingBtnHeight, + required this.floatingBtnWidth, + required this.settingMenuIconSize, + required this.settingMenuShareIconSize, + required this.accountListItemHeight, + required this.accountListItemLogoWidth, + required this.appbarIconSize, + required this.sendOverlayContainerWidth, + required this.overlayCloseIconSize, + required this.mnemonicCellDesiredHeight, + required this.txListItemIconWidth, + required this.txDetailsIconHeight, + required this.txDetailsIconWidth, + required this.copyIconSize, + required this.pasteIconSize, + required this.timePickerSubtitleWidth, + required this.bottomButtonSpacing, + required this.buttonsHorizontalSpacing, + required this.infoSheetTitleIcon, + required this.screenPadding, + required this.cardPadding, + required this.sectionGap, + required this.itemGap, + required this.sectionHeaderToContent, + required this.radiusFull, + required this.radiusCard, + required this.radiusSmall, + }); + + const AppSizeTheme.defaultTheme() + : this( + logoHeight: 158.0, + mainMenuHeight: 20, + mainMenuWidth: 20, + mainMenuIconSize: 21.0, + navbarHeight: 67.0, + navbarItemHeight: 32, + navbarItemWidth: 40, + navbarIconWidth: 23, + floatingBtnHeight: 49.0, + floatingBtnWidth: 52.0, + settingMenuIconSize: 11.0, + settingMenuShareIconSize: 20.0, + accountListItemHeight: 110.0, + accountListItemLogoWidth: 36.0, + appbarIconSize: 18.0, + sendOverlayContainerWidth: double.infinity, + overlayCloseIconSize: 24.0, + mnemonicCellDesiredHeight: 31.0, + txListItemIconWidth: 21.0, + txDetailsIconHeight: 43.0, + txDetailsIconWidth: 51.0, + copyIconSize: 20.0, + pasteIconSize: 18.0, + timePickerSubtitleWidth: 249, + bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 25, + screenPadding: 24.0, + cardPadding: 20.0, + sectionGap: 40.0, + itemGap: 12.0, + sectionHeaderToContent: 36.0, + radiusFull: 30.0, + radiusCard: 14.0, + radiusSmall: 6.0, + ); + + const AppSizeTheme.iPad() + : this( + logoHeight: 180.0, + mainMenuHeight: 30, + mainMenuWidth: 30, + mainMenuIconSize: 29.0, + navbarHeight: 87.0, + navbarItemHeight: 40, + navbarItemWidth: 48, + navbarIconWidth: 32, + floatingBtnHeight: 74.0, + floatingBtnWidth: 77.0, + settingMenuIconSize: 16.0, + settingMenuShareIconSize: 24.0, + accountListItemHeight: 130.0, + accountListItemLogoWidth: 48.0, + appbarIconSize: 20.0, + sendOverlayContainerWidth: 510.0, + overlayCloseIconSize: 28.0, + mnemonicCellDesiredHeight: 80.0, + txListItemIconWidth: 32.0, + txDetailsIconHeight: 82.0, + txDetailsIconWidth: 91.0, + copyIconSize: 28.0, + pasteIconSize: 24.0, + timePickerSubtitleWidth: 400, + bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 28, + screenPadding: 32.0, + cardPadding: 24.0, + sectionGap: 48.0, + itemGap: 16.0, + sectionHeaderToContent: 40.0, + radiusFull: 30.0, + radiusCard: 14.0, + radiusSmall: 6.0, + ); + + @override + AppSizeTheme copyWith({ + double? logoHeight, + double? mainMenuHeight, + double? mainMenuWidth, + double? mainMenuIconSize, + double? navbarHeight, + double? navbarItemHeight, + double? navbarItemWidth, + double? navbarIconWidth, + double? floatingBtnHeight, + double? floatingBtnWidth, + double? settingMenuIconSize, + double? settingMenuShareIconSize, + double? accountListItemHeight, + double? accountListItemLogoWidth, + double? appbarIconSize, + double? sendOverlayContainerWidth, + double? overlayCloseIconSize, + double? mnemonicCellDesiredHeight, + double? txListItemIconWidth, + double? txDetailsIconHeight, + double? txDetailsIconWidth, + double? copyIconSize, + double? pasteIconSize, + double? timePickerSubtitleWidth, + double? bottomButtonSpacing, + double? buttonsHorizontalSpacing, + double? infoSheetTitleIcon, + double? screenPadding, + double? cardPadding, + double? sectionGap, + double? itemGap, + double? sectionHeaderToContent, + double? radiusFull, + double? radiusCard, + double? radiusSmall, + }) { + return AppSizeTheme( + logoHeight: logoHeight ?? this.logoHeight, + mainMenuHeight: mainMenuHeight ?? this.mainMenuHeight, + mainMenuWidth: mainMenuWidth ?? this.mainMenuWidth, + mainMenuIconSize: mainMenuIconSize ?? this.mainMenuIconSize, + navbarHeight: navbarHeight ?? this.navbarHeight, + navbarItemHeight: navbarItemHeight ?? this.navbarItemHeight, + navbarItemWidth: navbarItemWidth ?? this.navbarItemWidth, + navbarIconWidth: navbarIconWidth ?? this.navbarIconWidth, + floatingBtnHeight: floatingBtnHeight ?? this.floatingBtnHeight, + floatingBtnWidth: floatingBtnWidth ?? this.floatingBtnWidth, + settingMenuIconSize: settingMenuIconSize ?? this.settingMenuIconSize, + settingMenuShareIconSize: settingMenuShareIconSize ?? this.settingMenuShareIconSize, + accountListItemHeight: accountListItemHeight ?? this.accountListItemHeight, + accountListItemLogoWidth: accountListItemLogoWidth ?? this.accountListItemLogoWidth, + appbarIconSize: appbarIconSize ?? this.appbarIconSize, + sendOverlayContainerWidth: sendOverlayContainerWidth ?? this.sendOverlayContainerWidth, + overlayCloseIconSize: overlayCloseIconSize ?? this.overlayCloseIconSize, + mnemonicCellDesiredHeight: mnemonicCellDesiredHeight ?? this.mnemonicCellDesiredHeight, + txListItemIconWidth: txListItemIconWidth ?? this.txListItemIconWidth, + txDetailsIconHeight: txDetailsIconHeight ?? this.txDetailsIconHeight, + txDetailsIconWidth: txDetailsIconWidth ?? this.txDetailsIconWidth, + copyIconSize: copyIconSize ?? this.copyIconSize, + pasteIconSize: pasteIconSize ?? this.pasteIconSize, + timePickerSubtitleWidth: timePickerSubtitleWidth ?? this.timePickerSubtitleWidth, + bottomButtonSpacing: bottomButtonSpacing ?? this.bottomButtonSpacing, + buttonsHorizontalSpacing: buttonsHorizontalSpacing ?? this.buttonsHorizontalSpacing, + infoSheetTitleIcon: infoSheetTitleIcon ?? this.infoSheetTitleIcon, + screenPadding: screenPadding ?? this.screenPadding, + cardPadding: cardPadding ?? this.cardPadding, + sectionGap: sectionGap ?? this.sectionGap, + itemGap: itemGap ?? this.itemGap, + sectionHeaderToContent: sectionHeaderToContent ?? this.sectionHeaderToContent, + radiusFull: radiusFull ?? this.radiusFull, + radiusCard: radiusCard ?? this.radiusCard, + radiusSmall: radiusSmall ?? this.radiusSmall, + ); + } + + @override + AppSizeTheme lerp(AppSizeTheme? other, double t) { + if (other is! AppSizeTheme) return this; + double l(double a, double b) => a + (b - a) * t; + return AppSizeTheme( + logoHeight: l(logoHeight, other.logoHeight), + mainMenuHeight: l(mainMenuHeight, other.mainMenuHeight), + mainMenuWidth: l(mainMenuWidth, other.mainMenuWidth), + mainMenuIconSize: l(mainMenuIconSize, other.mainMenuIconSize), + navbarHeight: l(navbarHeight, other.navbarHeight), + navbarItemHeight: l(navbarItemHeight, other.navbarItemHeight), + navbarItemWidth: l(navbarItemWidth, other.navbarItemWidth), + navbarIconWidth: l(navbarIconWidth, other.navbarIconWidth), + floatingBtnHeight: l(floatingBtnHeight, other.floatingBtnHeight), + floatingBtnWidth: l(floatingBtnWidth, other.floatingBtnWidth), + settingMenuIconSize: l(settingMenuIconSize, other.settingMenuIconSize), + settingMenuShareIconSize: l(settingMenuShareIconSize, other.settingMenuShareIconSize), + accountListItemHeight: l(accountListItemHeight, other.accountListItemHeight), + accountListItemLogoWidth: l(accountListItemLogoWidth, other.accountListItemLogoWidth), + appbarIconSize: l(appbarIconSize, other.appbarIconSize), + sendOverlayContainerWidth: l(sendOverlayContainerWidth, other.sendOverlayContainerWidth), + overlayCloseIconSize: l(overlayCloseIconSize, other.overlayCloseIconSize), + mnemonicCellDesiredHeight: l(mnemonicCellDesiredHeight, other.mnemonicCellDesiredHeight), + txListItemIconWidth: l(txListItemIconWidth, other.txListItemIconWidth), + txDetailsIconHeight: l(txDetailsIconHeight, other.txDetailsIconHeight), + txDetailsIconWidth: l(txDetailsIconWidth, other.txDetailsIconWidth), + copyIconSize: l(copyIconSize, other.copyIconSize), + pasteIconSize: l(pasteIconSize, other.pasteIconSize), + timePickerSubtitleWidth: l(timePickerSubtitleWidth, other.timePickerSubtitleWidth), + bottomButtonSpacing: l(bottomButtonSpacing, other.bottomButtonSpacing), + buttonsHorizontalSpacing: l(buttonsHorizontalSpacing, other.buttonsHorizontalSpacing), + infoSheetTitleIcon: l(infoSheetTitleIcon, other.infoSheetTitleIcon), + screenPadding: l(screenPadding, other.screenPadding), + cardPadding: l(cardPadding, other.cardPadding), + sectionGap: l(sectionGap, other.sectionGap), + itemGap: l(itemGap, other.itemGap), + sectionHeaderToContent: l(sectionHeaderToContent, other.sectionHeaderToContent), + radiusFull: l(radiusFull, other.radiusFull), + radiusCard: l(radiusCard, other.radiusCard), + radiusSmall: l(radiusSmall, other.radiusSmall), + ); + } +} + +extension AppSizeThemeExtension on BuildContext { + AppSizeTheme get themeSize => Theme.of(this).extension()!; +} diff --git a/mobile-app/lib/v2/theme/app_text_styles.dart b/mobile-app/lib/v2/theme/app_text_styles.dart new file mode 100644 index 00000000..5d1c65e5 --- /dev/null +++ b/mobile-app/lib/v2/theme/app_text_styles.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; + +const _fontFamily = 'Inter'; + +@immutable +class AppTextTheme extends ThemeExtension { + final TextStyle? lockTitle; + final TextStyle? extraLargeTitle; + final TextStyle? largeTitle; + final TextStyle? mediumTitle; + final TextStyle? smallTitle; + final TextStyle? paragraph; + final TextStyle? smallParagraph; + final TextStyle? largeTag; + final TextStyle? tag; + final TextStyle? timer; + final TextStyle? detail; + final TextStyle? tiny; + + const AppTextTheme({ + this.lockTitle, + this.extraLargeTitle, + this.largeTitle, + this.mediumTitle, + this.smallTitle, + this.paragraph, + this.smallParagraph, + this.largeTag, + this.tag, + this.timer, + this.detail, + this.tiny, + }); + + const AppTextTheme.defaultTheme() + : this( + lockTitle: const TextStyle(fontSize: 24, fontFamily: _fontFamily), + extraLargeTitle: const TextStyle( + fontSize: 40, + fontFamily: _fontFamily, + fontWeight: FontWeight.w600, + ), + largeTitle: const TextStyle( + fontSize: 30, + fontFamily: _fontFamily, + fontWeight: FontWeight.w300, + ), + mediumTitle: const TextStyle( + fontSize: 24, + fontFamily: _fontFamily, + fontWeight: FontWeight.w500, + ), + smallTitle: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + fontFamily: _fontFamily, + ), + paragraph: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + smallParagraph: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + largeTag: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + tag: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w300, + fontFamily: _fontFamily, + ), + timer: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.w600, + fontFamily: _fontFamily, + ), + detail: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + tiny: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + ); + + const AppTextTheme.iPad() + : this( + lockTitle: const TextStyle( + color: Colors.white, + fontSize: 28, + fontFamily: _fontFamily, + ), + extraLargeTitle: const TextStyle( + fontSize: 52, + fontFamily: _fontFamily, + fontWeight: FontWeight.w600, + ), + largeTitle: const TextStyle( + fontSize: 36, + fontFamily: _fontFamily, + fontWeight: FontWeight.w300, + ), + mediumTitle: const TextStyle( + fontSize: 28, + fontFamily: _fontFamily, + fontWeight: FontWeight.w400, + ), + smallTitle: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w500, + fontFamily: _fontFamily, + ), + paragraph: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + smallParagraph: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + largeTag: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + tag: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w300, + fontFamily: _fontFamily, + ), + timer: const TextStyle( + fontSize: 36, + fontWeight: FontWeight.w600, + fontFamily: _fontFamily, + ), + detail: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + tiny: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w400, + fontFamily: _fontFamily, + ), + ); + + @override + AppTextTheme copyWith({ + TextStyle? lockTitle, + TextStyle? extraLargeTitle, + TextStyle? largeTitle, + TextStyle? mediumTitle, + TextStyle? smallTitle, + TextStyle? paragraph, + TextStyle? smallParagraph, + TextStyle? largeTag, + TextStyle? tag, + TextStyle? timer, + TextStyle? detail, + TextStyle? tiny, + }) { + return AppTextTheme( + lockTitle: lockTitle ?? this.lockTitle, + extraLargeTitle: extraLargeTitle ?? this.extraLargeTitle, + largeTitle: largeTitle ?? this.largeTitle, + mediumTitle: mediumTitle ?? this.mediumTitle, + smallTitle: smallTitle ?? this.smallTitle, + paragraph: paragraph ?? this.paragraph, + smallParagraph: smallParagraph ?? this.smallParagraph, + largeTag: largeTag ?? this.largeTag, + tag: tag ?? this.tag, + timer: timer ?? this.timer, + detail: detail ?? this.detail, + tiny: tiny ?? this.tiny, + ); + } + + @override + AppTextTheme lerp(AppTextTheme? other, double t) { + if (other is! AppTextTheme) return this; + return AppTextTheme( + lockTitle: TextStyle.lerp(lockTitle, other.lockTitle, t), + extraLargeTitle: TextStyle.lerp(extraLargeTitle, other.extraLargeTitle, t), + largeTitle: TextStyle.lerp(largeTitle, other.largeTitle, t), + mediumTitle: TextStyle.lerp(mediumTitle, other.mediumTitle, t), + smallTitle: TextStyle.lerp(smallTitle, other.smallTitle, t), + paragraph: TextStyle.lerp(paragraph, other.paragraph, t), + smallParagraph: TextStyle.lerp(smallParagraph, other.smallParagraph, t), + largeTag: TextStyle.lerp(largeTag, other.largeTag, t), + tag: TextStyle.lerp(tag, other.tag, t), + timer: TextStyle.lerp(timer, other.timer, t), + detail: TextStyle.lerp(detail, other.detail, t), + tiny: TextStyle.lerp(tiny, other.tiny, t), + ); + } +} + +extension AppTextThemeExtension on BuildContext { + AppTextTheme get themeText => Theme.of(this).extension()!; +} diff --git a/mobile-app/lib/v2/theme/app_theme.dart b/mobile-app/lib/v2/theme/app_theme.dart new file mode 100644 index 00000000..243a51b9 --- /dev/null +++ b/mobile-app/lib/v2/theme/app_theme.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; + +import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart' as v1_colors; +import 'package:resonance_network_wallet/features/styles/app_text_theme.dart' as v1_text; +import 'package:resonance_network_wallet/features/styles/app_size_theme.dart' as v1_size; + +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_spacing.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class AppTheme { + static ThemeData darkTheme(BuildContext context) { + final isTablet = context.isTablet; + final appColors = const AppColorsTheme.dark(); + + return ThemeData( + primaryColor: appColors.primary, + scaffoldBackgroundColor: appColors.background, + cardColor: appColors.surface, + colorScheme: ColorScheme.dark( + primary: appColors.primary, + secondary: appColors.secondary, + surface: appColors.surface, + error: appColors.error, + ), + appBarTheme: AppBarTheme( + backgroundColor: appColors.surface, + elevation: 0, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: appColors.primary, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: appColors.secondary, + side: BorderSide(color: appColors.secondary), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: appColors.textPrimary, + ), + ), + inputDecorationTheme: InputDecorationTheme( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + filled: true, + fillColor: appColors.surface, + ), + extensions: [ + isTablet ? const AppTextTheme.iPad() : const AppTextTheme.defaultTheme(), + isTablet ? const AppSizeTheme.iPad() : const AppSizeTheme.defaultTheme(), + appColors, + isTablet ? const v1_text.AppTextTheme.iPad() : const v1_text.AppTextTheme.defaultTheme(), + isTablet ? const v1_size.AppSizeTheme.iPad() : const v1_size.AppSizeTheme.defaultTheme(), + const v1_colors.AppColorsTheme.dark(), + ], + ); + } +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index e42d4218..81e5c55d 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -115,6 +115,18 @@ flutter: weight: 600 - asset: assets/fonts/FiraCode-Bold.ttf weight: 700 + - family: Inter + fonts: + - asset: assets/fonts/Inter-Light.ttf + weight: 300 + - asset: assets/fonts/Inter-Regular.ttf + weight: 400 + - asset: assets/fonts/Inter-Medium.ttf + weight: 500 + - asset: assets/fonts/Inter-SemiBold.ttf + weight: 600 + - asset: assets/fonts/Inter-Bold.ttf + weight: 700 # Add flutter_native_splash configuration section flutter_native_splash: From 15167046a3f634ae453e7474d958f11af36b81f1 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 16:17:49 +0800 Subject: [PATCH 02/49] cleanup --- mobile-app/lib/v2/theme/app_colors.dart | 322 +++++++++--------------- mobile-app/lib/v2/theme/app_theme.dart | 27 +- 2 files changed, 123 insertions(+), 226 deletions(-) diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/mobile-app/lib/v2/theme/app_colors.dart index 1df33112..d2abce1f 100644 --- a/mobile-app/lib/v2/theme/app_colors.dart +++ b/mobile-app/lib/v2/theme/app_colors.dart @@ -1,285 +1,187 @@ import 'package:flutter/material.dart'; @immutable -class AppColorsTheme extends ThemeExtension { - final Color primary; - final Color secondary; - - final List aquaBlue; - final Color purple; - final Color pink; - final Color yellow; +class AppColorsV2 extends ThemeExtension { + // Backgrounds final Color background; - final Color background2; + final Color backgroundAlt; + + // Surfaces final Color surface; - final Color surfaceActive; - final Color error; + final Color surfaceGlass; + final Color surfaceCard; + + // Text final Color textPrimary; final Color textSecondary; + final Color textTertiary; final Color textError; - final Color textMuted; - final Color inputLabel; - final Color light; - final Color circularLoader; - final Color authButtonBg; + + // Accents + final Color accentGreen; + final Color accentPink; + final Color checksum; + + // Semantic + final Color error; + final Color danger; + final Color success; + + // UI elements + final Color separator; final Color border; - final Color borderLight; final Color buttonDisabled; - final Color navbarBg; - final Color checksum; - final Color checksumDarker; - final Color darkGray; - final Color settingCard; - final Color buttonGlass; - final Color buttonDanger; - final Color buttonSuccess; - final Color buttonNeutral; - final List buttonPrimary; + final List buttonPrimaryGradient; final Color skeletonBase; final Color skeletonHighlight; - final Color accountTagGuardian; - final Color accountTagEntrusted; - final Color accountTagHighSecurity; - final Color textTertiary; - final Color surfaceGlass; - final Color surfaceCard; - final Color accentGreen; - final Color separator; + // Account tags + final Color tagGuardian; + final Color tagEntrusted; + final Color tagHighSecurity; - const AppColorsTheme({ - required this.primary, - required this.secondary, - required this.aquaBlue, - required this.purple, - required this.pink, - required this.yellow, + const AppColorsV2({ required this.background, - required this.background2, + required this.backgroundAlt, required this.surface, - required this.surfaceActive, - required this.error, + required this.surfaceGlass, + required this.surfaceCard, required this.textPrimary, required this.textSecondary, + required this.textTertiary, required this.textError, - required this.inputLabel, - required this.light, - required this.circularLoader, - required this.authButtonBg, - required this.textMuted, + required this.accentGreen, + required this.accentPink, + required this.checksum, + required this.error, + required this.danger, + required this.success, + required this.separator, required this.border, - required this.borderLight, required this.buttonDisabled, - required this.navbarBg, - required this.checksum, - required this.checksumDarker, - required this.darkGray, - required this.settingCard, - required this.buttonGlass, - required this.buttonDanger, - required this.buttonSuccess, - required this.buttonNeutral, - required this.buttonPrimary, + required this.buttonPrimaryGradient, required this.skeletonBase, required this.skeletonHighlight, - required this.accountTagGuardian, - required this.accountTagEntrusted, - required this.accountTagHighSecurity, - required this.textTertiary, - required this.surfaceGlass, - required this.surfaceCard, - required this.accentGreen, - required this.separator, + required this.tagGuardian, + required this.tagEntrusted, + required this.tagHighSecurity, }); - const AppColorsTheme.dark() + const AppColorsV2.dark() : this( - 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), background: const Color(0xFF141414), - background2: const Color(0xFF1F1F1F), + backgroundAlt: const Color(0xFF1F1F1F), surface: const Color(0xFF292929), - surfaceActive: const Color(0xFFF4F6F9), - error: const Color(0xFFFF2D54), - textError: const Color(0xFFFF5252), + surfaceGlass: const Color(0x1AFFFFFF), + surfaceCard: const Color(0x0FFFFFFF), textPrimary: const Color(0xFFFFFFFF), textSecondary: const Color(0x80FFFFFF), textTertiary: const Color(0x52FFFFFF), - inputLabel: const Color(0xFFD4D3E0), - light: const Color(0xFFE6E6E6), - circularLoader: Colors.white, - authButtonBg: const Color(0xFF16CECE), - textMuted: const Color(0xFFD4D3E0), + textError: const Color(0xFFFF5252), + accentGreen: const Color(0xFF34C759), + accentPink: const Color(0xFFED4CCE), + checksum: const Color(0xFF4CEDE7), + error: const Color(0xFFFF2D54), + danger: const Color(0xFFFF1F45), + success: const Color(0xFF1FFFA7), + separator: const Color(0x1AFFFFFF), border: const Color(0x33FFFFFF), - borderLight: const Color(0x0FFFFFFF), buttonDisabled: const Color(0xFF3D3C44), - navbarBg: Colors.black, - checksum: const Color(0xFF4CEDE7), - checksumDarker: const Color(0xFF16CECE), - darkGray: const Color(0xFF3D3C44), - settingCard: const Color(0x0FF4F6F9), - buttonGlass: const Color(0x1AFFFFFF), - buttonDanger: const Color(0xFFFF1F45), - buttonSuccess: const Color(0xFF1FFFA7), - buttonNeutral: const Color(0xFFF4F6F9), - buttonPrimary: const [Color(0xFF0000FF), Color(0xFFED4CCE)], + buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], skeletonBase: const Color(0xFF3D3C44), skeletonHighlight: const Color(0xFF5A5A5A), - accountTagGuardian: const Color(0xFF9747FF), - accountTagEntrusted: const Color(0xFFFFD541), - accountTagHighSecurity: const Color(0xFF4CEDE7), - surfaceGlass: const Color(0x1AFFFFFF), - surfaceCard: const Color(0x0FFFFFFF), - accentGreen: const Color(0xFF34C759), - separator: const Color(0x1AFFFFFF), + tagGuardian: const Color(0xFF9747FF), + tagEntrusted: const Color(0xFFFFD541), + tagHighSecurity: const Color(0xFF4CEDE7), ); @override - AppColorsTheme copyWith({ - Color? primary, - Color? secondary, - List? aquaBlue, - Color? purple, - Color? pink, - Color? yellow, + AppColorsV2 copyWith({ Color? background, - Color? background2, + Color? backgroundAlt, Color? surface, - Color? surfaceActive, - Color? error, + Color? surfaceGlass, + Color? surfaceCard, Color? textPrimary, Color? textSecondary, + Color? textTertiary, Color? textError, - Color? inputLabel, - Color? light, - Color? circularLoader, - Color? authButtonBg, - Color? textMuted, + Color? accentGreen, + Color? accentPink, + Color? checksum, + Color? error, + Color? danger, + Color? success, + Color? separator, Color? border, - Color? borderLight, Color? buttonDisabled, - Color? navbarBg, - Color? checksum, - Color? checksumDarker, - Color? darkGray, - Color? settingCard, - Color? buttonGlass, - Color? buttonDanger, - Color? buttonSuccess, - Color? buttonNeutral, - List? buttonPrimary, + List? buttonPrimaryGradient, Color? skeletonBase, Color? skeletonHighlight, - Color? accountTagGuardian, - Color? accountTagEntrusted, - Color? accountTagHighSecurity, - Color? textTertiary, - Color? surfaceGlass, - Color? surfaceCard, - Color? accentGreen, - Color? separator, + Color? tagGuardian, + Color? tagEntrusted, + Color? tagHighSecurity, }) { - 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, + return AppColorsV2( background: background ?? this.background, - background2: background2 ?? this.background2, + backgroundAlt: backgroundAlt ?? this.backgroundAlt, surface: surface ?? this.surface, - surfaceActive: surfaceActive ?? this.surfaceActive, - error: error ?? this.error, + surfaceGlass: surfaceGlass ?? this.surfaceGlass, + surfaceCard: surfaceCard ?? this.surfaceCard, textPrimary: textPrimary ?? this.textPrimary, textSecondary: textSecondary ?? this.textSecondary, + textTertiary: textTertiary ?? this.textTertiary, textError: textError ?? this.textError, - inputLabel: inputLabel ?? this.inputLabel, - light: light ?? this.light, - circularLoader: circularLoader ?? this.circularLoader, - authButtonBg: authButtonBg ?? this.authButtonBg, - textMuted: textMuted ?? this.textMuted, + accentGreen: accentGreen ?? this.accentGreen, + accentPink: accentPink ?? this.accentPink, + checksum: checksum ?? this.checksum, + error: error ?? this.error, + danger: danger ?? this.danger, + success: success ?? this.success, + separator: separator ?? this.separator, border: border ?? this.border, - borderLight: borderLight ?? this.borderLight, buttonDisabled: buttonDisabled ?? this.buttonDisabled, - navbarBg: navbarBg ?? this.navbarBg, - checksum: checksum ?? this.checksum, - checksumDarker: checksumDarker ?? this.checksumDarker, - darkGray: darkGray ?? this.darkGray, - settingCard: settingCard ?? this.settingCard, - buttonGlass: buttonGlass ?? this.buttonGlass, - buttonDanger: buttonDanger ?? this.buttonDanger, - buttonSuccess: buttonSuccess ?? this.buttonSuccess, - buttonNeutral: buttonNeutral ?? this.buttonNeutral, - buttonPrimary: buttonPrimary ?? this.buttonPrimary, + buttonPrimaryGradient: buttonPrimaryGradient ?? this.buttonPrimaryGradient, skeletonBase: skeletonBase ?? this.skeletonBase, skeletonHighlight: skeletonHighlight ?? this.skeletonHighlight, - accountTagGuardian: accountTagGuardian ?? this.accountTagGuardian, - accountTagEntrusted: accountTagEntrusted ?? this.accountTagEntrusted, - accountTagHighSecurity: accountTagHighSecurity ?? this.accountTagHighSecurity, - textTertiary: textTertiary ?? this.textTertiary, - surfaceGlass: surfaceGlass ?? this.surfaceGlass, - surfaceCard: surfaceCard ?? this.surfaceCard, - accentGreen: accentGreen ?? this.accentGreen, - separator: separator ?? this.separator, + tagGuardian: tagGuardian ?? this.tagGuardian, + tagEntrusted: tagEntrusted ?? this.tagEntrusted, + tagHighSecurity: tagHighSecurity ?? this.tagHighSecurity, ); } @override - AppColorsTheme lerp(AppColorsTheme? other, double t) { - if (other is! AppColorsTheme) return this; - 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, + AppColorsV2 lerp(AppColorsV2? other, double t) { + if (other is! AppColorsV2) return this; + return AppColorsV2( background: Color.lerp(background, other.background, t) ?? background, - background2: Color.lerp(background2, other.background2, t) ?? background2, + backgroundAlt: Color.lerp(backgroundAlt, other.backgroundAlt, t) ?? backgroundAlt, surface: Color.lerp(surface, other.surface, t) ?? surface, - surfaceActive: Color.lerp(surfaceActive, other.surfaceActive, t) ?? surfaceActive, - error: Color.lerp(error, other.error, t) ?? error, + surfaceGlass: Color.lerp(surfaceGlass, other.surfaceGlass, t) ?? surfaceGlass, + surfaceCard: Color.lerp(surfaceCard, other.surfaceCard, t) ?? surfaceCard, textPrimary: Color.lerp(textPrimary, other.textPrimary, t) ?? textPrimary, textSecondary: Color.lerp(textSecondary, other.textSecondary, t) ?? textSecondary, + textTertiary: Color.lerp(textTertiary, other.textTertiary, t) ?? textTertiary, textError: Color.lerp(textError, other.textError, t) ?? textError, - inputLabel: Color.lerp(inputLabel, other.inputLabel, t) ?? inputLabel, - light: Color.lerp(light, other.light, t) ?? light, - circularLoader: Color.lerp(circularLoader, other.circularLoader, t) ?? circularLoader, - authButtonBg: Color.lerp(authButtonBg, other.authButtonBg, t) ?? authButtonBg, - textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted, + accentGreen: Color.lerp(accentGreen, other.accentGreen, t) ?? accentGreen, + accentPink: Color.lerp(accentPink, other.accentPink, t) ?? accentPink, + checksum: Color.lerp(checksum, other.checksum, t) ?? checksum, + error: Color.lerp(error, other.error, t) ?? error, + danger: Color.lerp(danger, other.danger, t) ?? danger, + success: Color.lerp(success, other.success, t) ?? success, + separator: Color.lerp(separator, other.separator, t) ?? separator, border: Color.lerp(border, other.border, t) ?? border, - borderLight: Color.lerp(borderLight, other.borderLight, t) ?? borderLight, buttonDisabled: Color.lerp(buttonDisabled, other.buttonDisabled, t) ?? buttonDisabled, - navbarBg: Color.lerp(navbarBg, other.navbarBg, t) ?? navbarBg, - checksum: Color.lerp(checksum, other.checksum, t) ?? checksum, - checksumDarker: Color.lerp(checksumDarker, other.checksumDarker, t) ?? checksumDarker, - darkGray: Color.lerp(darkGray, other.darkGray, t) ?? darkGray, - settingCard: Color.lerp(settingCard, other.settingCard, t) ?? settingCard, - buttonGlass: Color.lerp(buttonGlass, other.buttonGlass, t) ?? buttonGlass, - buttonDanger: Color.lerp(buttonDanger, other.buttonDanger, t) ?? buttonDanger, - buttonSuccess: Color.lerp(buttonSuccess, other.buttonSuccess, t) ?? buttonSuccess, - buttonNeutral: Color.lerp(buttonNeutral, other.buttonNeutral, t) ?? buttonNeutral, - buttonPrimary: other.buttonPrimary, + buttonPrimaryGradient: other.buttonPrimaryGradient, skeletonBase: Color.lerp(skeletonBase, other.skeletonBase, t) ?? skeletonBase, skeletonHighlight: Color.lerp(skeletonHighlight, other.skeletonHighlight, t) ?? skeletonHighlight, - accountTagGuardian: Color.lerp(accountTagGuardian, other.accountTagGuardian, t) ?? accountTagGuardian, - accountTagEntrusted: Color.lerp(accountTagEntrusted, other.accountTagEntrusted, t) ?? accountTagEntrusted, - accountTagHighSecurity: - Color.lerp(accountTagHighSecurity, other.accountTagHighSecurity, t) ?? accountTagHighSecurity, - textTertiary: Color.lerp(textTertiary, other.textTertiary, t) ?? textTertiary, - surfaceGlass: Color.lerp(surfaceGlass, other.surfaceGlass, t) ?? surfaceGlass, - surfaceCard: Color.lerp(surfaceCard, other.surfaceCard, t) ?? surfaceCard, - accentGreen: Color.lerp(accentGreen, other.accentGreen, t) ?? accentGreen, - separator: Color.lerp(separator, other.separator, t) ?? separator, + tagGuardian: Color.lerp(tagGuardian, other.tagGuardian, t) ?? tagGuardian, + tagEntrusted: Color.lerp(tagEntrusted, other.tagEntrusted, t) ?? tagEntrusted, + tagHighSecurity: Color.lerp(tagHighSecurity, other.tagHighSecurity, t) ?? tagHighSecurity, ); } } -extension AppColorsThemeExtension on BuildContext { - AppColorsTheme get themeColors => Theme.of(this).extension()!; +extension AppColorsV2Extension on BuildContext { + AppColorsV2 get colors => Theme.of(this).extension()!; } diff --git a/mobile-app/lib/v2/theme/app_theme.dart b/mobile-app/lib/v2/theme/app_theme.dart index 243a51b9..6f5aa73c 100644 --- a/mobile-app/lib/v2/theme/app_theme.dart +++ b/mobile-app/lib/v2/theme/app_theme.dart @@ -12,25 +12,21 @@ import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AppTheme { static ThemeData darkTheme(BuildContext context) { final isTablet = context.isTablet; - final appColors = const AppColorsTheme.dark(); + final colors = const AppColorsV2.dark(); return ThemeData( - primaryColor: appColors.primary, - scaffoldBackgroundColor: appColors.background, - cardColor: appColors.surface, + scaffoldBackgroundColor: colors.background, + cardColor: colors.surface, colorScheme: ColorScheme.dark( - primary: appColors.primary, - secondary: appColors.secondary, - surface: appColors.surface, - error: appColors.error, + surface: colors.surface, + error: colors.error, ), appBarTheme: AppBarTheme( - backgroundColor: appColors.surface, + backgroundColor: colors.surface, elevation: 0, ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - backgroundColor: appColors.primary, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), @@ -39,8 +35,6 @@ class AppTheme { ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( - foregroundColor: appColors.secondary, - side: BorderSide(color: appColors.secondary), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), @@ -48,7 +42,7 @@ class AppTheme { ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( - foregroundColor: appColors.textPrimary, + foregroundColor: colors.textPrimary, ), ), inputDecorationTheme: InputDecorationTheme( @@ -56,15 +50,16 @@ class AppTheme { enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, filled: true, - fillColor: appColors.surface, + fillColor: colors.surface, ), extensions: [ + colors, isTablet ? const AppTextTheme.iPad() : const AppTextTheme.defaultTheme(), isTablet ? const AppSizeTheme.iPad() : const AppSizeTheme.defaultTheme(), - appColors, + // v1 compat: keeps existing screens working until migrated + const v1_colors.AppColorsTheme.dark(), isTablet ? const v1_text.AppTextTheme.iPad() : const v1_text.AppTextTheme.defaultTheme(), isTablet ? const v1_size.AppSizeTheme.iPad() : const v1_size.AppSizeTheme.defaultTheme(), - const v1_colors.AppColorsTheme.dark(), ], ); } From 14f363fa18c7859d5c1d1f3b5e791128abeff918 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 16:41:44 +0800 Subject: [PATCH 03/49] add home screen --- .../lib/features/main/screens/navbar.dart | 4 +- .../main/screens/wallet_initializer.dart | 12 +- .../lib/v2/screens/home/activity_section.dart | 188 ++++++++++++ .../lib/v2/screens/home/home_screen.dart | 278 ++++++++++++++++++ 4 files changed, 470 insertions(+), 12 deletions(-) create mode 100644 mobile-app/lib/v2/screens/home/activity_section.dart create mode 100644 mobile-app/lib/v2/screens/home/home_screen.dart diff --git a/mobile-app/lib/features/main/screens/navbar.dart b/mobile-app/lib/features/main/screens/navbar.dart index 1dc0ce2e..4e65250d 100644 --- a/mobile-app/lib/features/main/screens/navbar.dart +++ b/mobile-app/lib/features/main/screens/navbar.dart @@ -8,7 +8,7 @@ import 'package:resonance_network_wallet/features/components/referral_action_she import 'package:resonance_network_wallet/features/main/screens/quests/quests_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/settings_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/transactions_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/wallet_main/wallet_main.dart'; +import 'package:resonance_network_wallet/v2/screens/home/home_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/services/referral_service.dart'; @@ -214,7 +214,7 @@ class _NavbarState extends ConsumerState { return IndexedStack( index: _selectedIndex, children: [ - const WalletMain(), + const HomeScreen(), const TransactionsScreen(), const SettingsScreen(), QuestsScreen(key: _questsScreenKey), diff --git a/mobile-app/lib/features/main/screens/wallet_initializer.dart b/mobile-app/lib/features/main/screens/wallet_initializer.dart index 77d18ff7..6f92358a 100644 --- a/mobile-app/lib/features/main/screens/wallet_initializer.dart +++ b/mobile-app/lib/features/main/screens/wallet_initializer.dart @@ -2,10 +2,9 @@ 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/migration_dialog.dart'; -import 'package:resonance_network_wallet/features/main/screens/navbar.dart'; import 'package:resonance_network_wallet/features/main/screens/welcome_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; -import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; @@ -160,23 +159,16 @@ class WalletInitializerState extends ConsumerState { @override Widget build(BuildContext context) { - final hasTxIntent = ref.read(transactionIntentProvider) != null; - // If we have value of tx that means we got arguments from notification tap, - // so we wanted to display the transactions history screen instead which is index 1. - final initialIndex = hasTxIntent ? 1 : 0; - if (_loading) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } - // If migration is needed, render a neutral background (no spinner) while - // the bottom sheet is presented, to avoid a loading indicator behind it. if (_needsMigration) { return const Scaffold(body: SizedBox.shrink()); } if (_walletExists) { - return Navbar(initialIndex: initialIndex); + return const HomeScreen(); } else { return const WelcomeScreen(); } diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart new file mode 100644 index 00000000..c57bc9c9 --- /dev/null +++ b/mobile-app/lib/v2/screens/home/activity_section.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/skeleton.dart'; +import 'package:resonance_network_wallet/features/main/screens/transactions_screen.dart'; +import 'package:resonance_network_wallet/models/combined_transactions_list.dart'; +import 'package:resonance_network_wallet/services/transaction_service.dart'; +import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class ActivitySection extends ConsumerWidget { + final AsyncValue txAsync; + final BaseAccount activeAccount; + final Future Function()? onRetry; + + const ActivitySection({super.key, required this.txAsync, required this.activeAccount, this.onRetry}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colors = context.colors; + final text = context.themeText; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: txAsync.when( + data: (data) { + final txService = ref.read(transactionServiceProvider); + final all = txService.combineAndDeduplicateTransactions( + pendingCancellationIds: data.pendingCancellationIds, + pendingTransactions: data.pendingTransactions, + reversibleTransfers: data.reversibleTransfers, + otherTransfers: data.otherTransfers, + ); + + if (all.isEmpty) return const SizedBox.shrink(); + + return Column( + children: [ + const SizedBox(height: 40), + _header(colors, text, context), + const SizedBox(height: 24), + ...all.take(5).map((tx) => _txItem(tx, colors, text)), + ], + ); + }, + loading: () => Padding( + padding: const EdgeInsets.only(top: 40), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _header(colors, text, context), + const SizedBox(height: 24), + for (var i = 0; i < 3; i++) ...[ + const Skeleton(width: double.infinity, height: 32), + if (i < 2) Divider(color: colors.separator, height: 24), + ], + ], + ), + ), + error: (e, _) => Padding( + padding: const EdgeInsets.only(top: 40), + child: Column( + children: [ + Text('Error loading transactions', style: text.detail?.copyWith(color: colors.textError)), + const SizedBox(height: 12), + GestureDetector( + onTap: () => onRetry?.call(), + child: Text('Retry', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + ), + ], + ), + ), + ), + ); + } + + Widget _header(AppColorsV2 colors, AppTextTheme text, BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Activity', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => TransactionsScreen(fixedAccountId: activeAccount.accountId, showAccountFilter: false), + ), + ), + child: Text( + 'View All', + style: text.paragraph?.copyWith(color: colors.textSecondary, decoration: TextDecoration.underline), + ), + ), + ], + ); + } + + Widget _txItem(TransactionEvent tx, AppColorsV2 colors, AppTextTheme text) { + final accountId = activeAccount.accountId; + final isSend = tx.from == accountId; + final isScheduled = tx.isReversibleScheduled; + + final label = isScheduled + ? (isSend ? 'Pending' : 'Receiving') + : isSend + ? 'Sent' + : 'Received'; + + final timeLabel = isScheduled ? _formatDuration(tx.timeRemaining) : _timeAgo(tx.timestamp); + + final iconBg = isScheduled && !isSend + ? const Color(0x2927F027) + : isScheduled && isSend + ? const Color(0x29FFBC42) + : colors.surface; + final iconColor = isScheduled && !isSend + ? const Color(0xFF27F027) + : isScheduled && isSend + ? const Color(0xFFFFBC42) + : colors.textSecondary; + + final fmt = NumberFormattingService(); + final amount = fmt.formatBalance(tx.amount); + final addr = _shortenAddress(isSend ? tx.to : tx.from); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(6)), + child: Transform.rotate( + angle: isSend ? 3.14159 : 0, + child: Icon(Icons.arrow_downward_rounded, size: 16, color: iconColor), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 2), + Text(timeLabel, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text('$amount ${AppConstants.tokenSymbol}', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 2), + Text('${isSend ? "To" : "From"}: $addr', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ], + ), + ), + Divider(color: colors.separator, height: 1), + ], + ); + } + + String _shortenAddress(String addr) { + if (addr.length <= 10) return addr; + return '${addr.substring(0, 5)}...${addr.substring(addr.length - 3)}'; + } + + String _formatDuration(Duration d) { + final days = d.inDays; + final hours = d.inHours % 24; + final mins = d.inMinutes % 60; + return '${days.toString().padLeft(2, '0')}d:${hours.toString().padLeft(2, '0')}h:${mins.toString().padLeft(2, '0')}m'; + } + + String _timeAgo(DateTime timestamp) { + final diff = DateTime.now().difference(timestamp); + if (diff.inMinutes < 1) return 'now'; + if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; + if (diff.inHours < 24) return '${diff.inHours}h ago'; + return '${diff.inDays}d ago'; + } +} diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart new file mode 100644 index 00000000..5bff7d68 --- /dev/null +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -0,0 +1,278 @@ +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/account_gradient_image.dart'; +import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; +import 'package:resonance_network_wallet/features/components/skeleton.dart'; +import 'package:resonance_network_wallet/features/main/screens/accounts_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/receive_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/send/send_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/settings_screen.dart'; +import 'package:resonance_network_wallet/providers/account_id_list_cache.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; +import 'package:resonance_network_wallet/providers/filtered_all_transactions_provider.dart'; +import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:resonance_network_wallet/v2/screens/home/activity_section.dart'; + +class HomeScreen extends ConsumerStatefulWidget { + const HomeScreen({super.key}); + + @override + ConsumerState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends ConsumerState { + final NumberFormattingService _fmt = NumberFormattingService(); + bool _balanceHidden = false; + + Future _refresh() async { + final active = ref.read(activeAccountProvider).value; + if (active != null) { + ref.invalidate(balanceProviderFamily); + await ref + .read( + filteredPaginationControllerProviderFamily( + AccountIdListCache.get([active.account.accountId]), + ).notifier, + ) + .loadingRefresh(); + } + ref.invalidate(balanceProviderRaw); + ref.invalidate(activeAccountTransactionsProvider); + } + + void _processIntentIfAvailable() { + final shared = ref.read(sharedAccountIntentProvider); + if (shared != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(sharedAccountIntentProvider.notifier).state = null; + showSharedAddressActionSheet(context, shared); + }); + } + } + + @override + Widget build(BuildContext context) { + _processIntentIfAvailable(); + + final accountAsync = ref.watch(activeAccountProvider); + final balanceAsync = ref.watch(balanceProvider); + final txAsync = ref.watch(activeAccountTransactionsProvider); + final colors = context.colors; + final text = context.themeText; + + return accountAsync.when( + loading: () => Scaffold( + backgroundColor: colors.background, + body: Center(child: CircularProgressIndicator(color: colors.textPrimary)), + ), + error: (e, _) => Scaffold( + backgroundColor: colors.background, + body: Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + ), + data: (active) { + if (active == null) { + return Scaffold( + backgroundColor: colors.background, + body: const Center(child: Text('No active account')), + ); + } + return Scaffold( + backgroundColor: colors.background, + body: RefreshIndicator( + color: colors.textPrimary, + backgroundColor: colors.surface, + onRefresh: _refresh, + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter(child: _buildContent(active, balanceAsync, colors, text)), + SliverToBoxAdapter( + child: ActivitySection( + txAsync: txAsync, + activeAccount: active.account, + onRetry: _refresh, + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 120)), + ], + ), + ), + ); + }, + ); + } + + Widget _buildContent(DisplayAccount active, AsyncValue balanceAsync, AppColorsV2 colors, AppTextTheme text) { + return Container( + decoration: BoxDecoration( + gradient: RadialGradient( + center: Alignment.topCenter, + radius: 1.2, + colors: [colors.backgroundAlt, colors.background], + ), + ), + child: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + _buildTopBar(active, colors), + const SizedBox(height: 64), + _buildBalance(balanceAsync, colors, text), + const SizedBox(height: 64), + if (active is RegularAccount) _buildActionButtons(colors, text), + ], + ), + ), + ), + ); + } + + Widget _buildTopBar(DisplayAccount active, AppColorsV2 colors) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AccountsScreen())), + child: AccountGradientImage(accountId: active.account.accountId, width: 40.0, height: 40.0), + ), + Row( + children: [ + _glassCircleButton( + icon: _balanceHidden ? Icons.visibility_off_outlined : Icons.visibility_outlined, + colors: colors, + onTap: () => setState(() => _balanceHidden = !_balanceHidden), + ), + const SizedBox(width: 12), + _glassCircleButton( + icon: Icons.settings_outlined, + colors: colors, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), + ), + ], + ), + ], + ); + } + + Widget _glassCircleButton({required IconData icon, required AppColorsV2 colors, required VoidCallback onTap}) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration(shape: BoxShape.circle, color: colors.surfaceGlass), + child: Icon(icon, color: colors.textPrimary, size: 20), + ), + ); + } + + Widget _buildBalance(AsyncValue balanceAsync, AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + balanceAsync.when( + data: (balance) { + final formatted = _balanceHidden ? '-----' : _fmt.formatBalance(balance); + return Stack( + alignment: Alignment.center, + children: [ + ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Text( + '$formatted ${AppConstants.tokenSymbol}', + style: text.extraLargeTitle?.copyWith(color: colors.textSecondary), + ), + ), + Text( + '$formatted ${AppConstants.tokenSymbol}', + style: text.extraLargeTitle?.copyWith(color: colors.textPrimary), + ), + ], + ); + }, + loading: () => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Skeleton(width: 200, height: 36), + Text(' ${AppConstants.tokenSymbol}', style: text.smallTitle?.copyWith(color: colors.textPrimary)), + ], + ), + error: (_, _) => Text( + 'Error loading balance', + style: text.detail?.copyWith(color: colors.textError), + ), + ), + if (!_balanceHidden) ...[ + const SizedBox(height: 6), + Text('≈ \$0.00', style: text.paragraph?.copyWith(color: colors.textSecondary)), + ], + ], + ); + } + + Widget _buildActionButtons(AppColorsV2 colors, AppTextTheme text) { + return Row( + children: [ + _actionCard( + icon: Icons.arrow_downward_rounded, + label: 'Receive', + colors: colors, + text: text, + onTap: () => showReceiveSheet(context), + ), + const SizedBox(width: 15), + _actionCard( + icon: Icons.arrow_upward_rounded, + label: 'Send', + colors: colors, + text: text, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SendScreen())), + ), + const SizedBox(width: 15), + _actionCard( + icon: Icons.swap_horiz_rounded, + label: 'Swap', + colors: colors, + text: text, + onTap: () {}, + ), + ], + ); + } + + Widget _actionCard({ + required IconData icon, + required String label, + required AppColorsV2 colors, + required AppTextTheme text, + required VoidCallback onTap, + }) { + return Expanded( + child: GestureDetector( + onTap: onTap, + child: Container( + height: 80, + decoration: BoxDecoration( + color: colors.surfaceGlass, + borderRadius: BorderRadius.circular(14), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: colors.textPrimary, size: 24), + const SizedBox(height: 8), + Text(label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + ], + ), + ), + ), + ); + } +} From 83ebd254255848238577e6659e10257875d03328 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 18:21:04 +0800 Subject: [PATCH 04/49] send flow 1 --- .../lib/v2/screens/home/home_screen.dart | 8 +- .../lib/v2/screens/receive/receive_sheet.dart | 268 +++++++++ .../v2/screens/send/address_picker_sheet.dart | 181 ++++++ .../lib/v2/screens/send/send_sheet.dart | 536 ++++++++++++++++++ 4 files changed, 989 insertions(+), 4 deletions(-) create mode 100644 mobile-app/lib/v2/screens/receive/receive_sheet.dart create mode 100644 mobile-app/lib/v2/screens/send/address_picker_sheet.dart create mode 100644 mobile-app/lib/v2/screens/send/send_sheet.dart diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index 5bff7d68..8d76898d 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -6,8 +6,8 @@ import 'package:resonance_network_wallet/features/components/account_gradient_im import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/skeleton.dart'; import 'package:resonance_network_wallet/features/main/screens/accounts_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/receive_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/send/send_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/receive/receive_sheet.dart'; +import 'package:resonance_network_wallet/v2/screens/send/send_sheet.dart'; import 'package:resonance_network_wallet/features/main/screens/settings_screen.dart'; import 'package:resonance_network_wallet/providers/account_id_list_cache.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; @@ -225,7 +225,7 @@ class _HomeScreenState extends ConsumerState { label: 'Receive', colors: colors, text: text, - onTap: () => showReceiveSheet(context), + onTap: () => showReceiveSheetV2(context), ), const SizedBox(width: 15), _actionCard( @@ -233,7 +233,7 @@ class _HomeScreenState extends ConsumerState { label: 'Send', colors: colors, text: text, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SendScreen())), + onTap: () => showSendSheetV2(context), ), const SizedBox(width: 15), _actionCard( diff --git a/mobile-app/lib/v2/screens/receive/receive_sheet.dart b/mobile-app/lib/v2/screens/receive/receive_sheet.dart new file mode 100644 index 00000000..3dabdc4f --- /dev/null +++ b/mobile-app/lib/v2/screens/receive/receive_sheet.dart @@ -0,0 +1,268 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:share_plus/share_plus.dart'; + +class ReceiveSheet extends StatefulWidget { + const ReceiveSheet({super.key}); + + @override + State createState() => _ReceiveSheetState(); +} + +class _ReceiveSheetState extends State { + String? _accountId; + String? _checksum; + Future? _checksumFuture; + + final HumanReadableChecksumService _checksumService = HumanReadableChecksumService(); + final SettingsService _settingsService = SettingsService(); + + @override + void initState() { + super.initState(); + _loadAccountData(); + } + + Future _loadAccountData() async { + try { + final account = (await _settingsService.getActiveAccount())!; + setState(() { + _accountId = account.account.accountId; + _checksumFuture = _checksumService.getHumanReadableName(account.account.accountId); + }); + } catch (e) { + debugPrint('Error loading account data: $e'); + } + } + + void _copyAddress() { + if (_accountId != null) { + ClipboardExtensions.copyTextWithSnackbar(context, _accountId!); + } + } + + void _copyChecksum() { + if (_checksum != null) { + ClipboardExtensions.copyTextWithSnackbar(context, _checksum!, message: 'Checkphrase copied'); + } + } + + void _share() { + if (_accountId != null) { + final text = + 'Hey! These are my Quantus account details:\n\nAddress:\n$_accountId' + '${_checksum != null ? '\n\nCheckphrase: $_checksum' : ''}' + '\n\nTo open in the app or download:\n${AppConstants.websiteBaseUrl}/account?id=$_accountId'; + SharePlus.instance.share( + ShareParams(text: text, subject: 'Shared Address', title: 'Shared Address', sharePositionOrigin: context.sharePositionRect()), + ); + } + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Container( + padding: const EdgeInsets.fromLTRB(24, 40, 24, 40), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Receive', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 32), + if (_accountId == null) + Padding( + padding: const EdgeInsets.symmetric(vertical: 80), + child: CircularProgressIndicator(color: colors.textPrimary), + ) + else ...[ + _buildQrCode(colors), + const SizedBox(height: 20), + _buildAddress(colors, text), + const SizedBox(height: 9), + _buildChecksum(colors, text), + const SizedBox(height: 32), + _buildButtons(colors, text), + ], + ], + ), + ), + ); + } + + Widget _buildQrCode(AppColorsV2 colors) { + return SizedBox( + width: 267, + height: 267, + child: Stack( + alignment: Alignment.center, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(16), + child: QrImageView( + data: _accountId!, + version: QrVersions.auto, + size: 267, + padding: const EdgeInsets.all(16), + backgroundColor: Colors.white, + eyeStyle: const QrEyeStyle(eyeShape: QrEyeShape.square, color: Colors.black), + dataModuleStyle: const QrDataModuleStyle(dataModuleShape: QrDataModuleShape.square, color: Colors.black), + ), + ), + // Container( + // width: 40, + // height: 40, + // decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.white), + // child: ClipOval(child: AccountGradientImage(accountId: _accountId, width: 36.0, height: 36.0)), + // ), + ], + ), + ); + } + + Widget _buildAddress(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: _copyAddress, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + _accountId!, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + ), + const SizedBox(width: 6), + _copyButton(colors), + ], + ), + ); + } + + Widget _buildChecksum(AppColorsV2 colors, AppTextTheme text) { + return FutureBuilder( + future: _checksumFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return SizedBox(height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textSecondary)); + } + if (!snapshot.hasData || snapshot.data == null || snapshot.data!.isEmpty) return const SizedBox.shrink(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_checksum != snapshot.data && mounted) setState(() => _checksum = snapshot.data!); + }); + + return GestureDetector( + onTap: _copyChecksum, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + snapshot.data!, + style: text.detail?.copyWith(color: colors.accentPink), + textAlign: TextAlign.center, + ), + ), + const SizedBox(width: 6), + _copyButton(colors), + ], + ), + ); + }, + ); + } + + Widget _copyButton(AppColorsV2 colors) { + return Container( + width: 20, + height: 20, + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(4)), + child: Icon(Icons.copy, size: 12, color: colors.textPrimary), + ); + } + + Widget _buildButtons(AppColorsV2 colors, AppTextTheme text) { + return Row( + children: [ + Expanded( + child: GestureDetector( + onTap: _copyAddress, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.copy, size: 20, color: colors.textPrimary), + const SizedBox(width: 8), + Text('Copy', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ), + ), + const SizedBox(width: 32), + Expanded( + child: GestureDetector( + onTap: _share, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: colors.surfaceGlass, + borderRadius: BorderRadius.circular(14), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.share, size: 20, color: colors.textPrimary), + const SizedBox(width: 8), + Text('Share', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ), + ), + ], + ); + } +} + +void showReceiveSheetV2(BuildContext context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + builder: (_) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: const ReceiveSheet(), + ), + ); +} diff --git a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart new file mode 100644 index 00000000..56973f0c --- /dev/null +++ b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart @@ -0,0 +1,181 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/account_gradient_image.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class AddressPickerSheet extends StatefulWidget { + const AddressPickerSheet({super.key}); + + @override + State createState() => _AddressPickerSheetState(); +} + +class _AddressPickerSheetState extends State { + final _searchController = TextEditingController(); + final _checksumService = HumanReadableChecksumService(); + List _addresses = []; + List _filtered = []; + final Map _checksums = {}; + + @override + void initState() { + super.initState(); + _loadAddresses(); + _searchController.addListener(_filter); + } + + @override + void dispose() { + _searchController.dispose(); + super.dispose(); + } + + Future _loadAddresses() async { + final addresses = await RecentAddressesService().getAddresses(); + if (!mounted) return; + setState(() { + _addresses = addresses; + _filtered = addresses; + }); + for (final addr in addresses) { + _checksumService.getHumanReadableName(addr).then((name) { + if (mounted) setState(() => _checksums[addr] = name); + }); + } + } + + void _filter() { + final query = _searchController.text.toLowerCase(); + setState(() { + _filtered = query.isEmpty + ? _addresses + : _addresses.where((a) { + final checksum = _checksums[a]?.toLowerCase() ?? ''; + return a.toLowerCase().contains(query) || checksum.contains(query); + }).toList(); + }); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Container( + padding: const EdgeInsets.fromLTRB(24, 40, 24, 40), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: SizedBox( + height: 530, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + ), + Text('Send To', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 40), + Container( + height: 48, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + child: Row( + children: [ + Icon(Icons.search, color: colors.textTertiary, size: 16), + const SizedBox(width: 8), + Expanded( + child: TextField( + controller: _searchController, + style: text.smallParagraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: 'Search', + hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 40), + Align( + alignment: Alignment.centerLeft, + child: Text('Recents', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 24), + Expanded( + child: _filtered.isEmpty + ? Center(child: Text('No recent addresses', style: text.detail?.copyWith(color: colors.textTertiary))) + : ListView.separated( + padding: EdgeInsets.zero, + itemCount: _filtered.length, + separatorBuilder: (_, _) => const SizedBox(height: 24), + itemBuilder: (context, i) => _addressItem(_filtered[i], colors, text), + ), + ), + ], + ), + ), + ), + ); + } + + Widget _addressItem(String address, AppColorsV2 colors, AppTextTheme text) { + final checksum = _checksums[address]; + return GestureDetector( + onTap: () => Navigator.pop(context, address), + child: Row( + children: [ + AccountGradientImage(accountId: address, width: 40.0, height: 40.0), + const SizedBox(width: 17), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (checksum != null) + Text(checksum, style: text.smallParagraph?.copyWith(color: colors.accentPink)), + const SizedBox(height: 4), + Text( + AddressFormattingService.formatAddress(address), + style: text.smallParagraph?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ); + } +} + +Future showAddressPickerSheet(BuildContext context) { + return showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + builder: (_) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), + child: const AddressPickerSheet(), + ), + ); +} diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart new file mode 100644 index 00000000..eddcf3af --- /dev/null +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -0,0 +1,536 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/main/screens/send/send_providers.dart'; +import 'package:resonance_network_wallet/features/main/screens/send/send_screen_logic.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:resonance_network_wallet/v2/screens/send/address_picker_sheet.dart'; + +enum _Step { form, confirm, sending, complete } + +class SendSheet extends ConsumerStatefulWidget { + final String? initialAddress; + const SendSheet({super.key, this.initialAddress}); + + @override + ConsumerState createState() => _SendSheetState(); +} + +class _SendSheetState extends ConsumerState { + final _recipientController = TextEditingController(); + final _amountController = TextEditingController(); + final _fmt = NumberFormattingService(); + final _checksumService = HumanReadableChecksumService(); + + _Step _step = _Step.form; + String? _recipientChecksum; + bool _hasAddressError = true; + BigInt _amount = BigInt.zero; + BigInt _networkFee = BigInt.zero; + int _blockHeight = 0; + bool _isFetchingFee = false; + String? _errorMessage; + + @override + void initState() { + super.initState(); + _recipientController.addListener(_onRecipientChanged); + _amountController.addListener(_onAmountChanged); + if (widget.initialAddress != null) { + _recipientController.text = widget.initialAddress!; + } + } + + @override + void dispose() { + _recipientController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + void _onRecipientChanged() { + final text = _recipientController.text.trim(); + if (text.isEmpty) { + setState(() { + _hasAddressError = true; + _recipientChecksum = null; + }); + return; + } + _lookupAddress(text); + } + + Future _lookupAddress(String address) async { + final substrate = ref.read(substrateServiceProvider); + final isValid = substrate.isValidSS58Address(address); + final checksum = isValid ? await _checksumService.getHumanReadableName(address) : null; + if (!mounted) return; + setState(() { + _hasAddressError = !isValid; + _recipientChecksum = checksum; + }); + if (isValid && _amount > BigInt.zero) _fetchFee(); + } + + void _onAmountChanged() { + final parsed = _fmt.parseAmount(_amountController.text); + setState(() => _amount = parsed ?? BigInt.zero); + if (!_hasAddressError && _amount > BigInt.zero) _fetchFee(); + } + + Future _fetchFee() async { + if (_isFetchingFee) return; + setState(() => _isFetchingFee = true); + try { + final displayAccount = ref.read(activeAccountProvider).value; + if (displayAccount is! RegularAccount) return; + final recipient = _recipientController.text.trim(); + final balancesService = ref.read(balancesServiceProvider); + final feeData = await balancesService.getBalanceTransferFee(displayAccount.account, recipient, _amount); + if (!mounted) return; + setState(() { + _networkFee = feeData.fee; + _blockHeight = feeData.blockNumber; + }); + } catch (e) { + debugPrint('Fee fetch error: $e'); + } finally { + if (mounted) setState(() => _isFetchingFee = false); + } + } + + void _setMax() { + final balance = ref.read(effectiveMaxBalanceProvider).value ?? BigInt.zero; + final max = SendScreenLogic.calculateMaxSendableAmount(balance: balance, networkFee: _networkFee); + _amountController.text = _fmt.formatBalance(max, addThousandsSeparators: false); + } + + Future _scanQr() async { + final address = await Navigator.push(context, MaterialPageRoute(fullscreenDialog: true, builder: (_) => const _QrScanPage())); + if (address != null && mounted) { + _recipientController.text = address; + } + } + + Future _pickRecent() async { + final address = await showAddressPickerSheet(context); + if (address != null && mounted) { + _recipientController.text = address; + } + } + + void _review() => setState(() => _step = _Step.confirm); + void _backToForm() => setState(() => _step = _Step.form); + + Future _confirmSend() async { + setState(() { + _step = _Step.sending; + _errorMessage = null; + }); + try { + final settings = SettingsService(); + final account = (await settings.getActiveRegularAccount())!; + final submissionService = ref.read(transactionSubmissionServiceProvider); + await submissionService.balanceTransfer(account, _recipientController.text.trim(), _amount, _networkFee, _blockHeight); + RecentAddressesService().addAddress(_recipientController.text.trim()); + if (mounted) setState(() => _step = _Step.complete); + } catch (e) { + if (mounted) { + setState(() { + _step = _Step.confirm; + _errorMessage = 'Transfer failed: $e'; + }); + } + } + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final balance = ref.watch(effectiveMaxBalanceProvider); + + return Container( + padding: const EdgeInsets.fromLTRB(24, 40, 24, 40), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + child: switch (_step) { + _Step.form => _buildForm(colors, text, balance), + _Step.confirm => _buildConfirm(colors, text), + _Step.sending => _buildSending(colors, text), + _Step.complete => _buildComplete(colors, text), + }, + ), + ), + ); + } + + Widget _header(AppColorsV2 colors, AppTextTheme text, {VoidCallback? onBack}) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (onBack != null) + GestureDetector(onTap: onBack, child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24)) + else + Text('Send', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ); + } + + Widget _buildForm(AppColorsV2 colors, AppTextTheme text, AsyncValue balance) { + final recipient = _recipientController.text.trim(); + final activeId = ref.watch(activeAccountProvider).value?.account.accountId ?? ''; + final amountStatus = SendScreenLogic.getAmountStatus(_amount, balance.value ?? BigInt.zero, _networkFee); + final btnDisabled = SendScreenLogic.isButtonDisabled( + hasAddressError: _hasAddressError, + amountStatus: amountStatus, + recipientText: recipient, + activeAccountId: activeId, + isFetchingFee: _isFetchingFee, + ); + final btnText = SendScreenLogic.getButtonText( + hasAddressError: _hasAddressError, + amountStatus: amountStatus, + recipientText: recipient, + amount: _amount, + activeAccountId: activeId, + formattingService: _fmt, + ); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _header(colors, text), + const SizedBox(height: 40), + Text('Send To', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 12), + _addressInput(colors, text), + const SizedBox(height: 12), + Row( + children: [ + _iconButton(Icons.qr_code_scanner, colors, _scanQr), + const SizedBox(width: 8), + _iconButton(Icons.history, colors, _pickRecent), + ], + ), + const SizedBox(height: 40), + _amountCard(colors, text, balance), + const SizedBox(height: 12), + _feeRow(colors, text), + const SizedBox(height: 8), + _actionButton( + label: btnText, + colors: colors, + text: text, + disabled: btnDisabled, + onTap: btnDisabled ? null : _review, + ), + ], + ); + } + + Widget _addressInput(AppColorsV2 colors, AppTextTheme text) { + final hasRecipient = _recipientController.text.trim().isNotEmpty && !_hasAddressError; + if (hasRecipient) { + return GestureDetector( + onTap: () => _recipientController.clear(), + child: Container( + width: double.infinity, + padding: const EdgeInsets.fromLTRB(12, 14, 8, 14), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AddressFormattingService.formatAddress(_recipientController.text.trim(), prefix: 15, ellipses: '.......', postFix: 14), + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (_recipientChecksum != null) ...[ + const SizedBox(height: 4), + Text(_recipientChecksum!, style: text.smallParagraph?.copyWith(color: colors.accentPink)), + ], + ], + ), + ), + ); + } + return TextField( + controller: _recipientController, + style: text.smallParagraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration( + filled: true, + fillColor: colors.surfaceGlass, + contentPadding: const EdgeInsets.fromLTRB(12, 14, 8, 14), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), + enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), + focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), + hintText: 'Qu Address', + hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), + ), + ); + } + + Widget _amountCard(AppColorsV2 colors, AppTextTheme text, AsyncValue balance) { + return SizedBox( + height: 120, + child: Stack( + children: [ + Container( + width: double.infinity, + height: 120, + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + ), + Positioned( + left: 20, + right: 20, + top: 20, + child: TextField( + controller: _amountController, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + inputFormatters: [DecimalInputFilter()], + style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontSize: 32), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + hintText: '0 ${AppConstants.tokenSymbol}', + hintStyle: text.mediumTitle?.copyWith(color: colors.textTertiary, fontSize: 32), + ), + ), + ), + Positioned( + left: 20, + right: 20, + bottom: 20, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Available: ${balance.when(data: (b) => _fmt.formatBalance(b), loading: () => '...', error: (_, _) => '0')} ${AppConstants.tokenSymbol}', + style: text.detail?.copyWith(color: colors.textSecondary), + ), + GestureDetector( + onTap: _hasAddressError ? null : _setMax, + child: Text('Max', style: text.detail?.copyWith(color: colors.textSecondary)), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _feeRow(AppColorsV2 colors, AppTextTheme text) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Network Fee:', style: text.detail?.copyWith(color: colors.textSecondary)), + Text( + _isFetchingFee ? '...' : '${_fmt.formatBalance(_networkFee)} ${AppConstants.tokenSymbol}', + style: text.detail?.copyWith(color: colors.textSecondary), + ), + ], + ); + } + + Widget _buildConfirm(AppColorsV2 colors, AppTextTheme text) { + final recipient = _recipientController.text.trim(); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _header(colors, text, onBack: _backToForm), + const SizedBox(height: 72), + Text('${_fmt.formatBalance(_amount)} ${AppConstants.tokenSymbol}', style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontSize: 32)), + const SizedBox(height: 64), + Text('To:', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + const SizedBox(height: 12), + Text( + AddressFormattingService.formatAddress(recipient, prefix: 15, ellipses: '.......', postFix: 14), + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), + if (_recipientChecksum != null) ...[ + const SizedBox(height: 4), + Text(_recipientChecksum!, style: text.smallParagraph?.copyWith(color: colors.accentPink)), + ], + if (_errorMessage != null) ...[ + const SizedBox(height: 16), + Text(_errorMessage!, style: text.detail?.copyWith(color: colors.textError)), + ], + const SizedBox(height: 64), + _feeRow(colors, text), + const SizedBox(height: 8), + _actionButton(label: 'Confirm', colors: colors, text: text, onTap: _confirmSend), + ], + ); + } + + Widget _buildSending(AppColorsV2 colors, AppTextTheme text) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _header(colors, text), + const SizedBox(height: 80), + CircularProgressIndicator(color: colors.textPrimary), + const SizedBox(height: 24), + Text('Sending...', style: text.smallTitle?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 80), + ], + ); + } + + Widget _buildComplete(AppColorsV2 colors, AppTextTheme text) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _header(colors, text), + const SizedBox(height: 80), + Icon(Icons.check_circle_outline, color: colors.accentGreen, size: 64), + const SizedBox(height: 24), + Text('Sent!', style: text.smallTitle?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 8), + Text( + '${_fmt.formatBalance(_amount)} ${AppConstants.tokenSymbol}', + style: text.paragraph?.copyWith(color: colors.textSecondary), + ), + const SizedBox(height: 80), + _actionButton(label: 'Done', colors: colors, text: text, onTap: () => Navigator.pop(context)), + ], + ); + } + + Widget _iconButton(IconData icon, AppColorsV2 colors, VoidCallback onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 40, + height: 40, + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + child: Icon(icon, color: colors.textPrimary, size: 20), + ), + ); + } + + Widget _actionButton({ + required String label, + required AppColorsV2 colors, + required AppTextTheme text, + bool disabled = false, + VoidCallback? onTap, + }) { + return GestureDetector( + onTap: disabled ? null : onTap, + child: Opacity( + opacity: disabled ? 0.2 : 1.0, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: colors.surfaceGlass, + border: Border.all(color: Colors.white.withValues(alpha: 0.44), width: 0.889), + borderRadius: BorderRadius.circular(14), + ), + child: Text(label, textAlign: TextAlign.center, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + ); + } +} + +void showSendSheetV2(BuildContext context, {String? address}) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + builder: (_) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: SendSheet(initialAddress: address), + ), + ), + ); +} + +class _QrScanPage extends StatefulWidget { + const _QrScanPage(); + + @override + State<_QrScanPage> createState() => _QrScanPageState(); +} + +class _QrScanPageState extends State<_QrScanPage> { + final _controller = MobileScannerController(); + bool _scanned = false; + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + MobileScanner( + controller: _controller, + onDetect: (capture) { + if (_scanned) return; + for (final barcode in capture.barcodes) { + final v = barcode.rawValue; + if (v != null && v.isNotEmpty) { + _scanned = true; + Navigator.pop(context, v); + return; + } + } + }, + ), + Positioned( + bottom: 60, + left: 0, + right: 0, + child: Center( + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + borderRadius: BorderRadius.circular(14), + ), + child: const Text('Cancel', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)), + ), + ), + ), + ), + ], + ), + ); + } +} From fb037346e40e5bf0e3b184baacf5d6b2a57998b0 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 18:23:52 +0800 Subject: [PATCH 05/49] minor updates --- mobile-app/lib/v2/screens/send/address_picker_sheet.dart | 5 ++++- mobile-app/lib/v2/screens/send/send_sheet.dart | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart index 56973f0c..46407fad 100644 --- a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart @@ -33,7 +33,10 @@ class _AddressPickerSheetState extends State { } Future _loadAddresses() async { - final addresses = await RecentAddressesService().getAddresses(); + final allAddresses = await RecentAddressesService().getAddresses(); + final active = await SettingsService().getActiveAccount(); + final currentId = active?.account.accountId; + final addresses = allAddresses.where((a) => a != currentId).toList(); if (!mounted) return; setState(() { _addresses = addresses; diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index eddcf3af..2f01e180 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -311,8 +311,11 @@ class _SendSheetState extends ConsumerState { style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontSize: 32), decoration: InputDecoration( isDense: true, + filled: false, contentPadding: EdgeInsets.zero, border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, hintText: '0 ${AppConstants.tokenSymbol}', hintStyle: text.mediumTitle?.copyWith(color: colors.textTertiary, fontSize: 32), ), From 36382aa68f74c5b62bea4cccbb28ead7bfd291bb Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 19:22:18 +0800 Subject: [PATCH 06/49] all transactions --- .../v2/screens/activity/activity_screen.dart | 116 ++++++++ .../activity/transaction_detail_sheet.dart | 249 ++++++++++++++++++ .../lib/v2/screens/activity/tx_item.dart | 131 +++++++++ .../lib/v2/screens/home/activity_section.dart | 109 +------- 4 files changed, 506 insertions(+), 99 deletions(-) create mode 100644 mobile-app/lib/v2/screens/activity/activity_screen.dart create mode 100644 mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart create mode 100644 mobile-app/lib/v2/screens/activity/tx_item.dart diff --git a/mobile-app/lib/v2/screens/activity/activity_screen.dart b/mobile-app/lib/v2/screens/activity/activity_screen.dart new file mode 100644 index 00000000..fb41d1ab --- /dev/null +++ b/mobile-app/lib/v2/screens/activity/activity_screen.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; +import 'package:resonance_network_wallet/services/transaction_service.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:resonance_network_wallet/v2/screens/activity/tx_item.dart'; +import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; + +class ActivityScreen extends ConsumerWidget { + const ActivityScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colors = context.colors; + final text = context.themeText; + final accountAsync = ref.watch(activeAccountProvider); + final txAsync = ref.watch(activeAccountTransactionsProvider); + + return Scaffold( + backgroundColor: colors.background, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + ), + Text('Activity', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Icon(Icons.info_outline, color: colors.textPrimary, size: 24), + ], + ), + const SizedBox(height: 48), + Expanded( + child: accountAsync.when( + loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), + error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + data: (active) { + if (active == null) return const Center(child: Text('No account')); + return txAsync.when( + loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), + error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + data: (data) { + final txService = ref.read(transactionServiceProvider); + final all = txService.combineAndDeduplicateTransactions( + pendingCancellationIds: data.pendingCancellationIds, + pendingTransactions: data.pendingTransactions, + reversibleTransfers: data.reversibleTransfers, + otherTransfers: data.otherTransfers, + ); + if (all.isEmpty) { + return Center(child: Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary))); + } + final grouped = _groupByDate(all); + return ListView.builder( + padding: EdgeInsets.zero, + itemCount: grouped.length, + itemBuilder: (context, i) { + final group = grouped[i]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (i > 0) const SizedBox(height: 40), + Text(group.label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + const SizedBox(height: 20), + ...group.transactions.map((tx) { + final itemData = TxItemData.from(tx, active.account.accountId); + return buildTxItem(tx, itemData, colors, text, onTap: () { + showTransactionDetailSheet(context, tx, active.account.accountId); + }); + }), + ], + ); + }, + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } + + List<_DateGroup> _groupByDate(List transactions) { + final Map> groups = {}; + final Map labelMap = {}; + + for (final tx in transactions) { + final day = DateTime(tx.timestamp.year, tx.timestamp.month, tx.timestamp.day); + final key = '${day.year}-${day.month}-${day.day}'; + groups.putIfAbsent(key, () => []); + groups[key]!.add(tx); + labelMap.putIfAbsent(key, () => dateGroupLabel(tx.timestamp)); + } + + return groups.entries.map((e) => _DateGroup(label: labelMap[e.key]!, transactions: e.value)).toList(); + } +} + +class _DateGroup { + final String label; + final List transactions; + const _DateGroup({required this.label, required this.transactions}); +} diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart new file mode 100644 index 00000000..83c67812 --- /dev/null +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -0,0 +1,249 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void showTransactionDetailSheet(BuildContext context, TransactionEvent tx, String activeAccountId) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) => _TransactionDetailSheet(tx: tx, activeAccountId: activeAccountId), + ); +} + +class _TransactionDetailSheet extends StatefulWidget { + final TransactionEvent tx; + final String activeAccountId; + const _TransactionDetailSheet({required this.tx, required this.activeAccountId}); + + @override + State<_TransactionDetailSheet> createState() => _TransactionDetailSheetState(); +} + +class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { + final _checksumService = HumanReadableChecksumService(); + String? _checkphrase; + + bool get _isSend => widget.tx.from == widget.activeAccountId; + String get _counterparty => _isSend ? widget.tx.to : widget.tx.from; + + @override + void initState() { + super.initState(); + _checksumService.getHumanReadableName(_counterparty).then((name) { + if (mounted) setState(() => _checkphrase = name); + }); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: Container( + color: Colors.black54, + child: Center( + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 14), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: BorderRadius.circular(24), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _headerRow(colors, text), + const SizedBox(height: 72), + _amountCard(colors, text), + const SizedBox(height: 56), + _addressSection(colors, text), + const SizedBox(height: 56), + _feeRow(colors, text), + const SizedBox(height: 32), + _explorerButton(colors, text), + ], + ), + ), + ), + ), + ); + } + + Widget _headerRow(AppColorsV2 colors, AppTextTheme text) { + final label = widget.tx.isReversibleScheduled + ? (_isSend ? 'Pending' : 'Receiving') + : _isSend + ? 'Sent' + : 'Received'; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ); + } + + Widget _amountCard(AppColorsV2 colors, AppTextTheme text) { + final fmt = NumberFormattingService(); + final amount = fmt.formatBalance(widget.tx.amount); + final date = DateFormat('MMM d, yyyy').format(widget.tx.timestamp); + final time = DateFormat('h:mm a').format(widget.tx.timestamp); + + return Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(14), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('$amount ${AppConstants.tokenSymbol}', + style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 32, fontWeight: FontWeight.w600)), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(date, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + const SizedBox(height: 8), + Text('At $time', style: text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5))), + ], + ), + ], + ), + ); + } + + Widget _addressSection(AppColorsV2 colors, AppTextTheme text) { + final direction = _isSend ? 'To:' : 'From:'; + final address = AddressFormattingService.formatAddress(_counterparty, prefix: 15, ellipses: '.......', postFix: 14); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(direction, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Text(address, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis), + ), + const SizedBox(width: 8), + _copyButton(colors, _counterparty), + ], + ), + if (_checkphrase != null) ...[ + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: Text(_checkphrase!, style: text.smallParagraph?.copyWith(color: const Color(0xFFED4CCE))), + ), + const SizedBox(width: 8), + _copyButton(colors, _checkphrase!), + ], + ), + ], + ], + ); + } + + Widget _copyButton(AppColorsV2 colors, String value) { + return GestureDetector( + onTap: () => Clipboard.setData(ClipboardData(text: value)), + child: Container( + width: 20, + height: 20, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: const Icon(Icons.copy, size: 12, color: Colors.white), + ), + ); + } + + Widget _feeRow(AppColorsV2 colors, AppTextTheme text) { + BigInt? fee; + if (widget.tx is TransferEvent) { + fee = (widget.tx as TransferEvent).fee; + } else if (widget.tx is PendingTransactionEvent) { + fee = (widget.tx as PendingTransactionEvent).fee; + } + final fmt = NumberFormattingService(); + final feeStr = fee != null ? '${fmt.formatBalance(fee)} ${AppConstants.tokenSymbol}' : '--'; + final style = text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5)); + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Network Fee:', style: style), + Text(feeStr, style: style), + ], + ); + } + + Widget _explorerButton(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: _openExplorer, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('View in Explorer', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + const SizedBox(width: 8), + Icon(Icons.open_in_new, size: 16, color: colors.textPrimary), + ], + ), + ), + ); + } + + void _openExplorer() { + final tx = widget.tx; + final isMinerReward = tx.isMinerReward; + final transactionType = isMinerReward + ? 'miner-rewards' + : (tx.isReversibleScheduled || tx.isReversibleExecuted || tx.isReversibleCancelled) + ? 'reversible-transactions' + : 'immediate-transactions'; + + String? path; + if (tx.extrinsicHash != null) { + path = '$transactionType/${tx.extrinsicHash}'; + } else if (isMinerReward && tx.blockHash != null) { + path = '$transactionType/${tx.blockHash}'; + } + if (path != null) { + launchUrl(Uri.parse('${AppConstants.explorerEndpoint}/$path')); + } + } +} diff --git a/mobile-app/lib/v2/screens/activity/tx_item.dart b/mobile-app/lib/v2/screens/activity/tx_item.dart new file mode 100644 index 00000000..ddfe4713 --- /dev/null +++ b/mobile-app/lib/v2/screens/activity/tx_item.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class TxItemData { + final String label; + final String timeLabel; + final Color iconBg; + final Color iconColor; + final bool isSend; + final String amount; + final String counterpartyAddr; + + const TxItemData({ + required this.label, + required this.timeLabel, + required this.iconBg, + required this.iconColor, + required this.isSend, + required this.amount, + required this.counterpartyAddr, + }); + + factory TxItemData.from(TransactionEvent tx, String accountId) { + final isSend = tx.from == accountId; + final isScheduled = tx.isReversibleScheduled; + final fmt = NumberFormattingService(); + + return TxItemData( + label: isScheduled + ? (isSend ? 'Pending' : 'Receiving') + : isSend + ? 'Sent' + : 'Received', + timeLabel: isScheduled ? _formatDuration(tx.timeRemaining) : _timeAgo(tx.timestamp), + iconBg: isScheduled && !isSend + ? const Color(0x2927F027) + : isScheduled && isSend + ? const Color(0x29FFBC42) + : const Color(0xFF292929), + iconColor: isScheduled && !isSend + ? const Color(0xFF27F027) + : isScheduled && isSend + ? const Color(0xFFFFBC42) + : const Color(0x80FFFFFF), + isSend: isSend, + amount: '${fmt.formatBalance(tx.amount)} ${AppConstants.tokenSymbol}', + counterpartyAddr: _shortenAddress(isSend ? tx.to : tx.from), + ); + } +} + +Widget buildTxItem(TransactionEvent tx, TxItemData data, AppColorsV2 colors, AppTextTheme text, {VoidCallback? onTap}) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration(color: data.iconBg, borderRadius: BorderRadius.circular(6)), + child: Transform.rotate( + angle: data.isSend ? 3.14159 : 0, + child: Icon(Icons.arrow_downward_rounded, size: 16, color: data.iconColor), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(data.label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 2), + Text(data.timeLabel, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(data.amount, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 2), + Text('${data.isSend ? "To" : "From"}: ${data.counterpartyAddr}', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ], + ), + ), + Divider(color: colors.separator, height: 1), + ], + ), + ); +} + +String _shortenAddress(String addr) { + if (addr.length <= 10) return addr; + return '${addr.substring(0, 5)}...${addr.substring(addr.length - 3)}'; +} + +String _formatDuration(Duration d) { + final days = d.inDays; + final hours = d.inHours % 24; + final mins = d.inMinutes % 60; + return '${days.toString().padLeft(2, '0')}d:${hours.toString().padLeft(2, '0')}h:${mins.toString().padLeft(2, '0')}m'; +} + +String _timeAgo(DateTime timestamp) { + final diff = DateTime.now().difference(timestamp); + if (diff.inMinutes < 1) return 'now'; + if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; + if (diff.inHours < 24) return '${diff.inHours}h ago'; + return '${diff.inDays}d ago'; +} + +String dateGroupLabel(DateTime date) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final txDay = DateTime(date.year, date.month, date.day); + final diff = today.difference(txDay).inDays; + if (diff == 0) return 'Today'; + if (diff == 1) return 'Yesterday'; + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + return '${months[date.month - 1]} ${date.day}, ${date.year}'; +} diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index c57bc9c9..51eb418f 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -2,10 +2,11 @@ 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/skeleton.dart'; -import 'package:resonance_network_wallet/features/main/screens/transactions_screen.dart'; import 'package:resonance_network_wallet/models/combined_transactions_list.dart'; import 'package:resonance_network_wallet/services/transaction_service.dart'; -import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/v2/screens/activity/activity_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; +import 'package:resonance_network_wallet/v2/screens/activity/tx_item.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -40,7 +41,12 @@ class ActivitySection extends ConsumerWidget { const SizedBox(height: 40), _header(colors, text, context), const SizedBox(height: 24), - ...all.take(5).map((tx) => _txItem(tx, colors, text)), + ...all.take(5).map((tx) { + final data = TxItemData.from(tx, activeAccount.accountId); + return buildTxItem(tx, data, colors, text, onTap: () { + showTransactionDetailSheet(context, tx, activeAccount.accountId); + }); + }), ], ); }, @@ -81,12 +87,7 @@ class ActivitySection extends ConsumerWidget { children: [ Text('Activity', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => TransactionsScreen(fixedAccountId: activeAccount.accountId, showAccountFilter: false), - ), - ), + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ActivityScreen())), child: Text( 'View All', style: text.paragraph?.copyWith(color: colors.textSecondary, decoration: TextDecoration.underline), @@ -95,94 +96,4 @@ class ActivitySection extends ConsumerWidget { ], ); } - - Widget _txItem(TransactionEvent tx, AppColorsV2 colors, AppTextTheme text) { - final accountId = activeAccount.accountId; - final isSend = tx.from == accountId; - final isScheduled = tx.isReversibleScheduled; - - final label = isScheduled - ? (isSend ? 'Pending' : 'Receiving') - : isSend - ? 'Sent' - : 'Received'; - - final timeLabel = isScheduled ? _formatDuration(tx.timeRemaining) : _timeAgo(tx.timestamp); - - final iconBg = isScheduled && !isSend - ? const Color(0x2927F027) - : isScheduled && isSend - ? const Color(0x29FFBC42) - : colors.surface; - final iconColor = isScheduled && !isSend - ? const Color(0xFF27F027) - : isScheduled && isSend - ? const Color(0xFFFFBC42) - : colors.textSecondary; - - final fmt = NumberFormattingService(); - final amount = fmt.formatBalance(tx.amount); - final addr = _shortenAddress(isSend ? tx.to : tx.from); - - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - children: [ - Container( - width: 32, - height: 32, - decoration: BoxDecoration(color: iconBg, borderRadius: BorderRadius.circular(6)), - child: Transform.rotate( - angle: isSend ? 3.14159 : 0, - child: Icon(Icons.arrow_downward_rounded, size: 16, color: iconColor), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - const SizedBox(height: 2), - Text(timeLabel, style: text.detail?.copyWith(color: colors.textTertiary)), - ], - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text('$amount ${AppConstants.tokenSymbol}', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - const SizedBox(height: 2), - Text('${isSend ? "To" : "From"}: $addr', style: text.detail?.copyWith(color: colors.textTertiary)), - ], - ), - ], - ), - ), - Divider(color: colors.separator, height: 1), - ], - ); - } - - String _shortenAddress(String addr) { - if (addr.length <= 10) return addr; - return '${addr.substring(0, 5)}...${addr.substring(addr.length - 3)}'; - } - - String _formatDuration(Duration d) { - final days = d.inDays; - final hours = d.inHours % 24; - final mins = d.inMinutes % 60; - return '${days.toString().padLeft(2, '0')}d:${hours.toString().padLeft(2, '0')}h:${mins.toString().padLeft(2, '0')}m'; - } - - String _timeAgo(DateTime timestamp) { - final diff = DateTime.now().difference(timestamp); - if (diff.inMinutes < 1) return 'now'; - if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; - if (diff.inHours < 24) return '${diff.inHours}h ago'; - return '${diff.inDays}d ago'; - } } From b37056d9b109114b84ae663eaec0623bc6e08e66 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 19:30:05 +0800 Subject: [PATCH 07/49] remove network fee if unknown --- .../lib/v2/screens/activity/transaction_detail_sheet.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index 83c67812..bd566f65 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -192,8 +192,9 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { } else if (widget.tx is PendingTransactionEvent) { fee = (widget.tx as PendingTransactionEvent).fee; } + if (fee == null || fee == BigInt.zero) return const SizedBox.shrink(); final fmt = NumberFormattingService(); - final feeStr = fee != null ? '${fmt.formatBalance(fee)} ${AppConstants.tokenSymbol}' : '--'; + final feeStr = '${fmt.formatBalance(fee)} ${AppConstants.tokenSymbol}'; final style = text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5)); return Row( From 1cfa26a2ef7482791f9435b904e7543333d37f8b Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 20:17:21 +0800 Subject: [PATCH 08/49] pin code screen added --- mobile-app/assets/v2/green_checkmark.png | Bin 0 -> 11994 bytes .../assets/v2/pin_number_background.png | Bin 0 -> 4226 bytes .../assets/v2/pin_number_background.svg | 8 + .../lib/v2/components/success_check.dart | 11 + .../lib/v2/screens/home/home_screen.dart | 4 +- .../lib/v2/screens/send/send_sheet.dart | 3 +- .../screens/settings/change_pin_screen.dart | 244 ++++++++++++ .../v2/screens/settings/settings_screen.dart | 363 ++++++++++++++++++ mobile-app/pubspec.yaml | 2 + .../lib/src/services/settings_service.dart | 29 +- 10 files changed, 660 insertions(+), 4 deletions(-) create mode 100644 mobile-app/assets/v2/green_checkmark.png create mode 100644 mobile-app/assets/v2/pin_number_background.png create mode 100644 mobile-app/assets/v2/pin_number_background.svg create mode 100644 mobile-app/lib/v2/components/success_check.dart create mode 100644 mobile-app/lib/v2/screens/settings/change_pin_screen.dart create mode 100644 mobile-app/lib/v2/screens/settings/settings_screen.dart diff --git a/mobile-app/assets/v2/green_checkmark.png b/mobile-app/assets/v2/green_checkmark.png new file mode 100644 index 0000000000000000000000000000000000000000..1b1cb2e653b167817ae48c70b7ab7b96f2926eb3 GIT binary patch literal 11994 zcmXAPcRZW#_kI#VNDyk53gun1cBxTPd(_^0s~xo|A~rQkQPe1{y|)^%OR2q8P_;*^ zM$Kv=u*bMABQeV=o#D^^QGiHwwy6aWB_sVK|q002P9{~ibtzU1SX^Bw+0 zf>Jj20svUW|MvhD-U(Xa3xQrbO3wjRV@x~v53nQh1rh+LO@dxq69NE_Jyhh8dj7zJ z+@K2Tx!f&HeKGo8(S{8}*L%7blpB3UZ+&N_%cDNT$J@yfT3lv zHjQ%<_0Qv${_F9ogQ0M@OgjS?)!bsP4;5GEC|m|KZzgFa*+)J)H9xOG&`Hbe|YtKb%y4$Sh@s`j$B`h zc2`o00l#oPdRnIN3+#VC3?R?IQ+d_kANeG;1U?6mt?tu3Ai8n#fefkiT^2?z; zMc#0k>Nc`-!e*R-W5^R-+oEZCv@&w4E2|3Sb>;LxohdpJt?^7!k_T}7dyu6$L-W}` z`EDRBsDgN(I35whkq;s*>z)jL)-^Lh2JK2`z15ftkJR2wx=J!kGGyS=rbJMU|1I6o z`5j1!>>Fi*ZAfVuJ&z`aKQ?dcdv<*+SNr~kLZ|US;%UF~q5^unMDB>G>{#(eMC`8lc+3Km4j93$_s8$|L_%$V)K(l; z_icz!G(DWogBnl>a+;Ys9jMi1kJaC*$z69j?>b)$(pQ{h14Y<$y#BK(l*A!5djE*r zh!jNuh&-VitFLz)t2^2H4wdZ=l!YNpNVMo#Wa8ALp39t)#`yMP?#fx+v81ks2aP5P zDQU5Q0@*D8n{FV5T%+$ynloa7fA{v2%lzs_bcD?Pp3ykQyRPAn4DHUscKY%(KsHQbj)cw{bI7A4I*1F< zGlJ10{HWvn1Aw4{u!!^KFg%Uig>^(rE8yT}tiLNf`1R7Qm+)5E8){W>3>-x7Z@ctW zGK3I-Zai*Zz0ts16@tg`{3R7BBa(ZUpFGfbhLWzSip&4C!0<{269=G_4G};jqFZ`A zLa$7H|NK;;v}}3(@0F$tP=|!*Z9y`jRiinj!^}nRV^BYe5idwbsHc=Y8Y z5lyfT9n$h1q%Fc+j3MoHwnoY%r}o-NsCyOA+pOp?8Hhl)oB!R-`9kcyNAvARojl8= zVCJhCo)SVv=`ma zZGWX&eyY5q}U%$D6Gtbx&g@fGoK zVM>*KPpwEwitXip09v6p(G469 zFEKqmtYiZ0f)1=nlNZginS=k0jpdA<_KCK-an&#yg&nwx&{Yz(P(@R)&>9fvWJy#I z>0*p%U}_7AgaBDuqD&_-u9ZHB1(KqsJK1>B}E`cxajjMLAbRn zduX(o%RDY}poe@~6ft|eTV5UKNllL-D{Wv@2gpt4dg@8u3HZEz{A4Nk*LK!&3sdXG zz|CXAC@TUOic7#Vp2^LLjj1pv+L`&APTu!22ma=~;oj@aEDw?)9g&q^VLTbtkc{=s z3`^SL$;?}~al0C)FsukC9M;2wb2O-jo_$(OG5K^v{cdMA%wO)~#C$@K8Ujutp08)p zMx9J5{*w>}4&RRZz3}2sOW)&n+BDA(+>#S>pGJ97zoobG*IwhLA>w zT%9e+RP<<&3vUPZbXE_aB)q2%_DvT(ud>dNX?dM&ua)H@TF708dnOZV8v^}>k@DV# zU!7Km^8J^XkXkH20^^oP#5^9YT27m3X!@h~GS^-?%lQQxy;Bge!`KG=HVlo@a?Yvyj-x?KLwVQF)2R{^a z4A-O2D9lSOQ&8RwRzwjSpYsTqq4;EC7ThQf(8cT-OHCP-W5F4bW>=Syd(trOCm1tYOF zjY5apYg)E>mquI?bp!3E!nq&z%E~s+SeYR8Qjd@(dfnd_Unro|&vdAo#6BbZ^%)UK~0rU$2{WH$!YcQ(#M@+Z}mWDI$*7ZzS8ytydCo(U^$QcptL|9iwiL<$2)pss6`kwNU#}Y~6(sHTPj7tf6 z_+l77k7Vi@&?3J|M2p}AT!VBB2#|QuYm2rqQ-_iH6JGmbiRG*L1zSmv<(A)unZkcE zx~_}qO|h2oZvXYLZU?N}sX|~5s^{mETg#i1H^n+P&u_MPIf{uO-PR!?Aym43Kyv!@ zr($ArKOPi4pOhDur^FlJAlU~V)z*4j?g&Nqx$qJePjYMTb<3RDX6|op$=tPOP65xq zx>}~OqLe`}WBv0+iZ*EAyR-p6s$=QAGUiHs;~rHhqOdoFM3&Z=3`2_m1$CNeuASmk zOmB@PQ7%g0tIhG9YLu~F?qI}sw$VMv?+?V2L$2)PkU0x=#|KXXE&o!Lxf`x-ilG01Uz6o8W!TX~FLtL9BUV2+J$R@Xtjl~@wkzqIFjYrif(H5z^{ zFCc?g9USK5X8Q<0pq|9WuyjK4hya#!=B#enID`^jfDm4t7Z|LDKjlCfpxIvh`K{0) zN5v4&zLu{Fj=EOcIxnA^(ISU<21)%oh;RXptgZO19<)S9T9$>U8ix~vg;C3gg&2vp zG7bZQbL)$z{mx%)2NPqj9G+`qQUf9h!mJz8#{DY$XS7IT43N{b-#AG2)|Okn*oqnY zbp*)B%Opg@!iv-+%*cw-FHK%h%h!ti@DJ}e8IwHtY8j>CsI8rIV10hymmJf(&2{Ma zpzZLp6IX7eD#QQ3y$2bY(7-B%@ruZ*mT)H&B85ulANKATNB&7!6~O9H?7PBTk{WS} zjSeXm9j6!V|U;g=_VqfbMw#U(x0Bj{t&4YVfN#gi3H zRhkN_g4NC{-lOeZWxoXO>JOeS^VKkFQE%n}9Fo2D(n}>CA2w1lDVL7eNIv*+fr{+c zbCsnw{vGgUOy1<4mvsIw(zd`)uF{)-fY_tgfA3pr~fIVy|%P4YE$ckfAod6qVSGI0KnF92@ z=;$?Ngpv_&@WC^`yxLUdu8NGyx68>S!iE*el`ZPyHM9|0*XOom5aQ5Z9oq@ssEbt5TQPkWp?-Z`kRzZv*;M$9`QHTKbrK~cS9?#1W>c6dd zAis~bIgkW=&TagkcD2osrpP9?=fFZIF)_=IK>coH--Bh8pZ9>5vTN3ng zb(Iab=448tEw9CDZ%Mn?bQ15?-+iJ0CXN{4rY|MgLyigWv=hySarNKCnL%|7(jt8~ zjDMWv8^|MMzu=<^z7yyPTVpI>v!|yAN6?l`GZz^15JTWpG~+yMUaVTY-ndRnD;oyD z+U93li+(x3z1p8FR#Wpkekb)?di0$O5{0l=2wN(f*$|JHX2uPzq5V&D4Fz?52(e!o zn-SOdCVb8E<^nh@&j0J5Tr+_)DF>t~do)>kn^3+s-At*L(RtvTSb&C~x3>7cfNc%r zyv=kG>f;a!&6Qv65YbcDaeS1}CiUcEFl)m$hipPiHeddpJ0T#9G3M-RC5NxtdHWz$ zD{T=k_mFpd;E&8k^@s;eK|zgkKNg)2mKNY-;JyMsa(#c<$*`cXD;IJyjV608&Cn=H zd>!rN{c{|q*-ou1TnZ?-k6j9O>dkX6sR1%VAm2})gq|%pvU!C~Wb0t#3kxSi5>I7w zb47%9H|75BzM4&aRB2d}a!l055Dr`m*kB30t_6VOiZEru{ySvz{5O_?AX2XSIwPgL z^ELyc%0@K`8$O<%4M2?;MOo}NG-jvy(;yuql%s`?j^E;o@zc_pqP9o-R4{caCgs-g zR3Kg5MM3lL@!#2QwUhMs_p(Q~J&^PJPAzmX(pJu`-E)kerUERi9z{q9$4+yVUq#Y< z>#eDHM>x?|+qBo3ZOn2?2u_lD^Y9RLdmZ3=b9p)&+iOZ8cfNy>pQaZurYO?Z%gC4z z4%+@#&v1Lnr`yxC-{~3i0iF5mNkRaNYDrM{MLc3^A>Ji^aR)paC`I9KfnOBUGn8lT zJJv8#U{fWeYH}lekf-BphXgYBO6;TfELy*31v=!?bH*$^2z5j~{wgzhKze_kS4arAa=S z>LuUuR5RyBrHC_CMo*2_;ls5|$r+#8${P+eE_7fj^Uc|MG#$YT{8829Zo@5qqWp0D z7gp1ZH1*>%eqyV|Z<b+Gejv=gJWRBwB$H1rSaBL9e(EY|3}H_fs$>qRd6yZ=X!zMb@<-1xW09w5RoK$ESKL^eNAD1G|Bcn@j3f5c{L z60~Y^_Lk)?qC@nCww$@LHZiwR3w{FaWkPnl* zy|2(gVHe>V9QM*$zt}>lP!}vRJAfYH&|BEjh0^^6vCbPZ-%V$cz2=O-ThxURRoT*9 z4GVX9uc}pWx0TNd#YdO-nGcS3o@B=#xz)ynnl53)k9UFJq<73ZUmNRb22$$8ndoG* zHoyH-X@NTSxEiZpwQl=)%DJreJe~+MLqbexNiRSV?^j<`BcuFUsbL-7(#=bYzcJo=ucW_CAjFm3t~V0YKNmI=SBD16=XpIA>SkPQC3Y&g znxI;T9~8-lH4>zY0j+C=mPx6VteF%qpo0A3Dzl0q)L?QgvvW4(!Gj+%aYYK_ZVVUA zWO1e!(M;adbbjB=MvcbjqhWeY9}It9`ob_C)Iil1x|QG2QpAsXODuy4RjM&Pz{`HD zWla%HgE^EOBmdJW_5TteZ<&rI{e!2;e;{y-hE%$|9p4; znb(tR|Dx7>F(x`*o#^Ns4epxt{Z9_C-u*X>3X}!_uT$KDN_q7(c6B5<6$lMWIeD3C ziV6``5NH*$HtzU9;NrG$mES~e?Bb#&^Ro_MJ#ZtC;je6**Zj|hkLwoT8#V}2U>bvX zgGd82Z0GNP*N|9Q`w{c^=1=#maslA32BE_MApY`pk)B~Y$56|c;RGt~eOh3pBS?`L z`E2ykua_knN0s%)#w9$w+fEFjxX{tPsxXWEqeDXc(2k6h{xMaL&Qt0yGc5h{ytEor zaGNMD2dFHs3B2kYtXfVPPEVcBG_z00#jr_E&a3=L)bRtv7&=i_;$5QwDg?_~-4dHK zyy0!7YrDqsnzFYAZ`PpvZwJ2%-!+~FKHL2xaR+55iFt1AsU8K0*lV>1Khj2U$l$cS z`7t85B?*FiC`jGovPqb7Swwt_uwt#O%DW~z80h?<&(`xOtV7u|ZX(<0G9x%Oh?S;^ z)t-hNX~l4UMM6MqL>6>A_h2*cbKQvoL!Z6YRI*^q?70c!s?Z^IKxW;tt4QQ&zHoSo zqrwiSHc@X@iFh&u8r`6qC1JOy8U02s=4jfsm@PWI1_6K+NwCfw5*CZqKUy(J;ugk8#!k9p}OEFsyJI2|KoNFX=b}1wH+vMg2?QxR; z^de-nuB~h8=qvtODy8NLx6*v&`F6gf4b`q`C0uLeKm#LnBpCU#ZX8sU6xp_ax2*|q z8*{9rK8~_C-HVfg*OEUb0;L_``rz81zelC+3xp>s1+j|53>R-y4JexVAR`V3^x$_E zQ_GHoM8?quA3`UFDu)uUJvra(Hl$$D@nIZa6;(H9)QbO{h!Ki|qxVbAdB5J#2x`N2 z5q5F9#X~eC)geBF#tj01$k2bkyWmVh|5?I%YJY=f<&xW9A_I2+cKjj`L9Phk9iymH zOG)y@&YP=7b6EBuYef8n>tCj#bVsPwSL*dBmFIZZEmgHH_cA;!F(>}&m1|ERF6dCsQr7tK#!`Rv#+mr`O`pK|14Tbz(DV`SJp zKU8#vTF9e@s;|#sjlsI=KKt-@1AbG z@$EYHsD6ez00}0%b<(~R0C$5HPQ1)fBu;r2T_*N5&by7_?r+zf)AqCfLDW)-3f)J;{M39(OJfWe%X)v3CNvpc1AT(P^2HD~ zCz?QZ{F482Lh&_|yV3R|@A#saT-}j~bRZOC2D=er!1w#7z0Eb20y5|O6hUib-bU}q z2LXLUWkK*gVzd{%#mSH#_W#)&A*2|HvlTw;T)A>y?k_0^$eSgQTB-lfXwamSad%)c zIx{D;BEF<($qWqMPKh!WjuQgn)&I3C3<`5Y6Ckh~6v9T$csG!Szik6PU3$v>LK)8V zhlA!Yk_FymKUb>(uqkLoG2ZtvffxNyC7=QmpQ@IvZog*nr{poHuo{UQ$sGJ5)-Q3r z#pmw^wHeRnOMSpWLV^$boaxQ15X-Md_$su_Dhm7B6d0-JVL}XbeW__*Qqf>67FL=l zQ-b10w9q#H2;8$Nd&}3(5RNMydtavah^x)}IR=-n8Gz!qyG9zpo2DU`gD#tBB)u*l^J|rb(UB2)rHMETJX6 zbI;=kIE!u!j%&;$sN;2y`I{;KBK0T+9Uy*4LpHG;Xx!5ZvsRrJhIfw4X?sg(RJObR zp>h^Xn3@>h=@UCuPrd3nKyR6TIjzzwle0I=zEk}**}|{JX$iSUwW;x=ma09w6>jXJ?YK6MKV#<+hdtyb! z^jFO%=kfjv|Bq*-hd$_@#YUF}!_Gf6zJptbB{$pEAq^JpJePhyY5R~NH4lXu%6_>N zWh%b@nkWUMLh9y$qwVaS55jYPJvb!9q}5?`${x;-zkE_sIB7-bBNi2lgIflWXgrUR zS@vCWdj(UR>8cC|N!=NzdqO}FS#ePOHu@tZD$W&o(K|n%3DNpSL`=(4BHW>H#y1ZT6`cHEfp)aIvzZ%}Lf3pS?D-1v1FPIDIu&`~# z=K&T7D5Sa#5-Q8?aQq?TYrT&J?|u1XnC`XlXrfkSf_gR;Azt)Qw<0>^xePv1Y#g#e znKAAE=mf);1I0NhiV{7VF#lTt8UdLGV|R?{Z)0R|DN|NuS1tI2LyKs zD*D`D_DkTMOdXxeY=v<}aGh~uP#y=3Hk}LI-nsa>>sm6WC}V=GCq&SOVbt3L}ry0$VrZ;g{4OXAwLz)>u19$a|;=t?b81YkAq zRy7y?=k&D)o^nfi{*k}WRP-}Fl0~veRINC*B(%o0e9G2it9;>y{aVD=jbnA89T!JuztY>S6SvXln z`^~R`+rIqI&Rf5eoMX;U=q{P3=->%7WU#xc5iAos9zRa4dq6~T!!S!g+d>!Ge0tPl z3U73-JQVb}s}a*I*Lfp9S=li4-cw{~r5nd_O1tW8q`Ny>ZqAm9gS3MVyH3X} z8qOVi8>4k%inLQn!c>^Epb)us!NB_Tj*>c7M)H_}!lc5bI0Q7(4&&X>E3(vd*YTTa z<1qlz$7=NUfYJ9_OLp49dB{QUYaC+JTw9`!eD1l@412^PaX0Xg&>`{tyo9zb9*YZg z3Ydkyksq%0?nZ9;$a{w443>k>R|<)G>N{?biBK>kd*Lc`aQ9DwGW#Sdiz+ih;MKiZ zf;u{ghQ{k48y0B3EE?yon5!gmR>MlCz-F&)?8k?JJbYp;ZS^YiFxH8f3l(dZLenpR zj8jPd!>(%hj^ z*{vt=CE=s<7gCZ^;#@S&$%I`k=AB#k_Lg}!TIQl4uUz@}fRWmnPs>CF)$?(7R~G`3 zdD%}5H|exE#FOhPN)E&hd|94U#?EnmzP;X(;SYa1gGVZFxS<8(y&^XT5h`piQ!vVp znAE-aP_SiE!1JOf#x>KQEDL}@9!%sL1=u|wcO9x>ZYB)V(w-a8~AYpA`vnmM%4-lgG^3efio?swAzY)gvo|whi`o%7Tn3r*~AG zhQQ)QJ0?RxZunf;O`-Fo?*R#|ZY^-4tZKPehYA}*Jy)E(71N$XE)HH$y1i*po2BbK zCZx>o%ZxvgzUp9%=i(6i!dbV@-X;H@ACuOihekeP>aiH9@wu_EZQ``!t68qQe)s-h zL3KS`6%dy4;lt(u=@lynGFT@hUGFNEfOrL~)5ny(P;!A)yWyerW!XE z@%{5FOMO&~$q|A@d+!)`N!)Mt)))Ju`C!5ADwD_w2o@QdQ67$XXs=X;7cO;PMb3Q& zks~+A;g}SNYSm4T32bk6-9*`mK%T0Kh-)b|SA&>dua|YV7J0@(MBF8yMjcncG?*fW z%N<|)pBQ+0WCpPpRzyI0>s7ui<0KA~Ab%(-7;%LqW}!}MvOoH_=W%;VJ;hmxM&q21 zqZyHDQECogt(msmwRlzaC`{m(gjYeuwL>7=$nK( z>XozdppL8MmVw37_n)}J3AOv5dwySiz31_t@pILNQwjUeKTQrrzPqzO$>*s`4GF#H zk?2bLv(Q4xOaLsclzc;MOuDjn97E*{>kIsuqJ(qcWR9Vck8PpXiBX#W(4jKMEU4}z z@~t8CCftO>vs!nnRiEc@mENvPd@1~pg_b-M@T(yD0hK3lH*0B2xFsH*c`ns+y8NQ| zOs&^Wv_M!s-95jkKzxqzuek0Sx|JiPR#sIvO~=$#SI3jMi;5V`{;Bk&Vmdm?k(o3= z-%P-cfgJ807_RzN=|PBmn%=|iu7fVuY-=vgT7-Qw5h0(Cp9v^DEAFZz0@#}${E-M5 zUc~~2W!3uc*AC8ioUe?K8d%RYIX1tjOG{%!b%t46r5CCqGtx{BDJt4z6-Q9G$E%7!%CILL`M*Y ze?@htozi#S^z(;k!+O@=^?e)Oi`Mw0($nV|GW+#TRJie z$DvJ#VM|0z$+bqcHiSa7*-h???*mUgchUHa9xmBmm4h9R)G5$#&F4@xrkBuDD>#13G(V9C~M$CHSv) z(=GPY)paIQ8-;$er!qE|Q#bcl%gxw^Rl!PGNy1i1B2k+pDr7Ib2gor^A#*Kozum23 ztXU@AK%zeH&AzrZE7D5YK&7a_huM~qNsB9BX?N*O1c9CFcT!Tkk88p4@85d}e$*5r z2?h)S?qkc+YwxEP6S_ygk{l(@M-RA4EAE9u-B@&@5 zfM>z1lhrV}cis59<|)5QHJ%xMtx4&mW!T4&6LJNIV|Ze}t2!d}a!&eU#c-W8HqL?W zqBzg~N}k;>=I)ob^{dr&FdjPT&op0h9L({v_1ADRFX7OpM0_juZ3kK{OwwZQ(~}mD z{+09P+^<%4GMi<@Peb?je_=-s{GaWj1lEo;3+cHJTMLOef5)sp0wc69z_PecPo(Hu z9XI)S9@W#n+5fzJ%C?o1!;HVjep*^>Pvn0Z@!saphZL{B_N*Yv>X8p7z@(tK+|2`A znEk%2|BhPWRT|#>UQwU2mGR#7eW*3LuvS2wySL*#?;QTJ8GyAEVI6gehtW}mqu)v} z9dGP%3-!uyBQtqM_+8|ZF^G{S3kh9>a$X5{9Ok&rQC4& zZba}aYwgpaTX48*j@VG^MmLaww1{7DoYV?m%dd5~eFitDdpN(_k={&Q40m0bf85phO9d>9ZduY^b07SHbSK8jCSWYomgytB zIRq>~o6BXUq={=a_om|erkSYLHL$8GcmjkQ%@R4vt zf43j201OUfL$@KVO$J9E)~vTt0SMMfVgvy&Bds~KPYpeN7;7>F&dM%}tr-0RG%&(2 z()j)74g_ceCNY-{2@Xo2>kJ?|qgkk(EzqLW^_fhUL%p)@k+- zZ=U^CDq@(BJ_#Vdo8*unAFYJOb2~tC-joO%m&vC6M+<1KBSoY{{31OO? ztvp8sVGD)d56?%Yx+eAX{=C$C4^GZ%1e=0vruv3H^L&g06ev@;y;e4%P7k??XyjHm z(2IY>O>Y-Rr6r4|)K7#+fnff95=Kxq{jXo>=DC}ay5W{!NdIDuXZV!esLGGbyfiIMoRhb6S+sa77JNcFt5)pnQN!LBpp$gKA?wUP9Lm zta1zdN_s9k(0$MA{}CqTp9k*CJbN-GbMPCZzfA%qA^n#rBBBRY`6!DggQ5X*K;b7? zZdnwiRrf;+0_qwzxFIfYh5AnYjLz88!G@<5-ll-(l_nWJs6HmIBgDQKX=1v2(q+G9 zsVDc4456F>ELA1}dxsbiQo;N?|IDV-^KzVR9GCpo#1WDk)KTl;r`|sOPIsX;`-y6M zp2L&!`EQ3#=E}+<*`ple-6w~(*E(6xnGN#US&GXMl>|lr0LX>}jS0m{>=PhR=xL~L z!LWIKxcfxz<=QOEEIpSfB?2}z*ZI3qsF|1hIy)d$=3Ri2mtFLUDSU-iQ6^qa->RYg z@VAM|e~npm`xClGjbhW1}D70Owr z^yvP#U*sfSH-;y26-?xE?9hDQCQb;MRTKkXBesb!W_DZW)h+Ha&4W+*86k+zF|6`RxvcZ9L;ZC8{gDh znM8HevIn5tC*UbMjUR)vA2iWkTj9!Lw`fD%`JY8|AevhDMa;l+zI(5OD#v-J@7AL4 gv}}u(w^xBoODiKKapp{TDmp+#K|{XkxfSOB02eM*u>b%7 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/pin_number_background.png b/mobile-app/assets/v2/pin_number_background.png new file mode 100644 index 0000000000000000000000000000000000000000..66b87a8c028f4650bb0016954c46da25748a0a43 GIT binary patch literal 4226 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 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/pin_number_background.svg b/mobile-app/assets/v2/pin_number_background.svg new file mode 100644 index 00000000..9b01077e --- /dev/null +++ b/mobile-app/assets/v2/pin_number_background.svg @@ -0,0 +1,8 @@ + +
+ + + + + +
diff --git a/mobile-app/lib/v2/components/success_check.dart b/mobile-app/lib/v2/components/success_check.dart new file mode 100644 index 00000000..08a1db1f --- /dev/null +++ b/mobile-app/lib/v2/components/success_check.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +class SuccessCheck extends StatelessWidget { + final double size; + const SuccessCheck({super.key, this.size = 88}); + + @override + Widget build(BuildContext context) { + return Image.asset('assets/v2/green_checkmark.png', width: size, height: size); + } +} diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index 8d76898d..e6a3b9e4 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -8,7 +8,7 @@ import 'package:resonance_network_wallet/features/components/skeleton.dart'; import 'package:resonance_network_wallet/features/main/screens/accounts_screen.dart'; import 'package:resonance_network_wallet/v2/screens/receive/receive_sheet.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_sheet.dart'; -import 'package:resonance_network_wallet/features/main/screens/settings_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/settings_screen.dart'; import 'package:resonance_network_wallet/providers/account_id_list_cache.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; @@ -154,7 +154,7 @@ class _HomeScreenState extends ConsumerState { _glassCircleButton( icon: Icons.settings_outlined, colors: colors, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreenV2())), ), ], ), diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index 2f01e180..f9a8e8b7 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -8,6 +8,7 @@ import 'package:resonance_network_wallet/features/main/screens/send/send_screen_ import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; +import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:resonance_network_wallet/v2/screens/send/address_picker_sheet.dart'; @@ -409,7 +410,7 @@ class _SendSheetState extends ConsumerState { children: [ _header(colors, text), const SizedBox(height: 80), - Icon(Icons.check_circle_outline, color: colors.accentGreen, size: 64), + const SuccessCheck(size: 64), const SizedBox(height: 24), Text('Sent!', style: text.smallTitle?.copyWith(color: colors.textPrimary)), const SizedBox(height: 8), diff --git a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart new file mode 100644 index 00000000..68618459 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart @@ -0,0 +1,244 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/success_check.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +enum _Step { enterCurrent, enterNew, confirmNew, success } + +class ChangePinScreen extends StatefulWidget { + const ChangePinScreen({super.key}); + + @override + State createState() => _ChangePinScreenState(); +} + +class _ChangePinScreenState extends State { + final _settingsService = SettingsService(); + final _focusNode = FocusNode(); + final _controller = TextEditingController(); + var _step = _Step.enterCurrent; + String _newPin = ''; + String? _error; + + @override + void initState() { + super.initState(); + _checkExistingPin(); + _controller.addListener(() { + setState(() => _error = null); + if (_controller.text.length == 6) _onContinue(); + }); + } + + @override + void dispose() { + _focusNode.dispose(); + _controller.dispose(); + super.dispose(); + } + + Future _checkExistingPin() async { + final has = await _settingsService.hasPin(); + if (!mounted) return; + setState(() => _step = has ? _Step.enterCurrent : _Step.enterNew); + WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus()); + } + + Future _onContinue() async { + final entry = _controller.text; + if (entry.length != 6) return; + + switch (_step) { + case _Step.enterCurrent: + final ok = await _settingsService.verifyPin(entry); + if (!ok) { + setState(() => _error = 'Incorrect PIN'); + HapticFeedback.heavyImpact(); + return; + } + _controller.clear(); + setState(() => _step = _Step.enterNew); + _focusNode.requestFocus(); + case _Step.enterNew: + _newPin = entry; + _controller.clear(); + setState(() => _step = _Step.confirmNew); + _focusNode.requestFocus(); + case _Step.confirmNew: + if (entry != _newPin) { + setState(() => _error = 'PINs do not match'); + HapticFeedback.heavyImpact(); + return; + } + await _settingsService.setPin(_newPin); + _focusNode.unfocus(); + setState(() => _step = _Step.success); + HapticFeedback.mediumImpact(); + case _Step.success: + break; + } + } + + String get _stepTitle { + switch (_step) { + case _Step.enterCurrent: + return 'Enter Current PIN'; + case _Step.enterNew: + return 'Enter New PIN'; + case _Step.confirmNew: + return 'Confirm New PIN'; + case _Step.success: + return ''; + } + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Stack( + children: [ + // Hidden text field to trigger native keyboard + Opacity( + opacity: 0, + child: SizedBox( + height: 0, + child: TextField( + controller: _controller, + focusNode: _focusNode, + keyboardType: TextInputType.number, + keyboardAppearance: Brightness.dark, + maxLength: 6, + inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)], + decoration: const InputDecoration(counterText: ''), + ), + ), + ), + Column( + children: [ + const SizedBox(height: 16), + _header(colors, text), + Expanded( + child: _step == _Step.success ? _successBody(colors, text) : _pinBody(colors, text), + ), + if (_step == _Step.success) ...[ + _doneButton(colors, text), + const SizedBox(height: 24), + ], + ], + ), + ], + ), + ), + ); + } + + Widget _header(AppColorsV2 colors, AppTextTheme text) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + ), + Text('Change PIN', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), + ); + } + + Widget _pinBody(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: () => _focusNode.requestFocus(), + behavior: HitTestBehavior.opaque, + child: Column( + children: [ + const SizedBox(height: 80), + Text(_stepTitle, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 24, fontWeight: FontWeight.w400)), + const SizedBox(height: 32), + _pinDots(colors), + if (_error != null) ...[ + const SizedBox(height: 16), + Text(_error!, style: text.detail?.copyWith(color: colors.error)), + ], + const Spacer(), + ], + ), + ); + } + + Widget _pinDots(AppColorsV2 colors) { + final entry = _controller.text; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(6, (i) { + final filled = i < entry.length; + return Container( + width: 40, + height: 48, + margin: EdgeInsets.only(left: i == 0 ? 0 : 8), + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset('assets/v2/pin_number_background.png', width: 40, height: 48), + if (filled) + Container( + width: 8, + height: 8, + decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle), + ), + ], + ), + ); + }), + ); + } + + Widget _successBody(AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + const Spacer(flex: 2), + const SuccessCheck(), + const SizedBox(height: 64), + Text('PIN Changed', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Text('Your PIN has been updated successfully', + style: text.paragraph?.copyWith(color: colors.textSecondary), textAlign: TextAlign.center), + ), + const Spacer(flex: 3), + ], + ); + } + + Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Center( + child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart new file mode 100644 index 00000000..2b576667 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -0,0 +1,363 @@ +import 'package:flutter/cupertino.dart'; +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/reset_confirmation_bottom_sheet.dart'; +import 'package:resonance_network_wallet/features/components/snackbar_helper.dart'; +import 'package:resonance_network_wallet/features/main/screens/authentication_settings_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/select_wallet_for_recovery_phrase_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/show_recovery_phrase_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/welcome_screen.dart'; +import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/providers/notification_config_provider.dart'; +import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart'; +import 'package:resonance_network_wallet/services/local_auth_service.dart'; +import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/change_pin_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SettingsScreenV2 extends ConsumerStatefulWidget { + const SettingsScreenV2({super.key}); + + @override + ConsumerState createState() => _SettingsScreenV2State(); +} + +class _SettingsScreenV2State extends ConsumerState { + final _authService = LocalAuthService(); + final _settingsService = SettingsService(); + bool _biometricEnabled = false; + String _biometricDesc = 'Face ID Disabled'; + int _autoLockMinutes = 5; + bool _reversibleEnabled = false; + int _reversibleTimeSeconds = 600; + bool _hasPinSet = false; + + @override + void initState() { + super.initState(); + _loadSettings(); + _loadPinState(); + } + + Future _loadPinState() async { + final has = await _settingsService.hasPin(); + if (mounted) setState(() => _hasPinSet = has); + } + + Future _loadSettings() async { + final bioEnabled = _authService.isLocalAuthEnabled(); + final bioDesc = await _authService.getBiometricDescription(); + final timeout = _authService.getAuthTimeoutMinutes(); + final revTime = await _settingsService.getReversibleTimeSeconds() ?? 600; + final revEnabled = _settingsService.isReversibleEnabled(); + + if (!mounted) return; + setState(() { + _biometricEnabled = bioEnabled; + _biometricDesc = bioEnabled ? bioDesc : 'Face ID Disabled'; + _autoLockMinutes = timeout; + _reversibleTimeSeconds = revTime; + _reversibleEnabled = revEnabled; + }); + } + + Future _toggleBiometric(bool enable) async { + if (enable) { + final available = await _authService.isBiometricAvailable(); + if (!available) { + if (mounted) showTopSnackBar(context, title: 'Error', message: 'Biometric not available on this device'); + return; + } + } + final ok = await _authService.authenticate( + localizedReason: 'Authenticate to ${enable ? 'enable' : 'disable'} biometric', + biometricOnly: false, + forSetup: true, + ); + if (ok) { + _authService.setLocalAuthEnabled(enable); + _loadSettings(); + } + } + + void _toggleReversible(bool enable) { + _settingsService.setReversibleEnabled(enable); + setState(() => _reversibleEnabled = enable); + } + + void _toggleNotifications(bool enable) { + final current = ref.read(notificationConfigProvider); + ref.read(notificationConfigProvider.notifier).updateConfig(current.copyWith(enabled: enable)); + } + + void _navigateToRecoveryPhrase() { + final accountsAsync = ref.read(accountsProvider); + accountsAsync.whenData((accounts) { + final walletIndices = getNonHardwareWalletIndices(accounts); + if (walletIndices.isEmpty) return; + if (walletIndices.length == 1) { + Navigator.push(context, MaterialPageRoute(builder: (_) => ShowRecoveryPhraseScreen(walletIndex: walletIndices.first))); + } else { + Navigator.push(context, MaterialPageRoute(builder: (_) => const SelectWalletForRecoveryPhraseScreen())); + } + }); + } + + void _resetAndClearData() { + _settingsService.clearAll(); + SubstrateService().logout(); + ref.read(pendingTransactionsProvider.notifier).clear(); + ref.read(accountsProvider.notifier).reset(); + ref.read(activeAccountProvider.notifier).reset(); + ref.read(accountAssociationsProvider.notifier).reset(); + if (mounted) { + Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const WelcomeScreen()), (r) => false); + } + } + + void _showResetConfirmation() { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (_) => ResetConfirmationBottomSheet(onReset: _resetAndClearData), + ); + } + + String _autoLockLabel() { + if (_autoLockMinutes == 0) return 'Immediately'; + if (_autoLockMinutes == 60) return '1 hour'; + return '$_autoLockMinutes mins'; + } + + String _timeLimitLabel() { + if (_reversibleTimeSeconds <= 0) return 'Disabled'; + final mins = _reversibleTimeSeconds ~/ 60; + if (mins < 60) return '$mins minutes'; + final hours = mins ~/ 60; + final remMins = mins % 60; + return remMins > 0 ? '${hours}h ${remMins}m' : '$hours hours'; + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final notifConfig = ref.watch(notificationConfigProvider); + + return Scaffold( + backgroundColor: colors.background, + body: SafeArea( + child: Column( + children: [ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + ), + Text('Settings', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), + ), + const SizedBox(height: 48), + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 24), + children: [ + _section('Security', colors, text, [ + _toggleItem('Biometric Lock', _biometricDesc, _biometricEnabled, _toggleBiometric, colors, text), + _divider(colors), + _chevronItem('PIN Code', _hasPinSet ? '6-digit code' : 'Not set', colors, text, + onTap: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const ChangePinScreen())); + _loadPinState(); + }), + _divider(colors), + _chevronItem('Auto-Lock', _autoLockLabel(), colors, text, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AuthenticationSettingsScreen()))), + ]), + const SizedBox(height: 40), + _section('Wallet', colors, text, [ + _chevronItem('Recovery Phase', 'View Backup', colors, text, onTap: _navigateToRecoveryPhrase), + ]), + const SizedBox(height: 40), + _section('Reversible Transactions', colors, text, [ + _toggleItem('Reversible Transactions', _reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, _toggleReversible, colors, text), + _divider(colors), + _chevronItem('Time Limit', _timeLimitLabel(), colors, text, onTap: () {}), + _divider(colors), + _chevronItem('Amount Limit', 'No Limit', colors, text, onTap: () {}), + ]), + const SizedBox(height: 40), + _section('Account Type', colors, text, [ + _comingSoonItem('High Security Account', 'Guardian Approval', colors, text), + _divider(colors), + _comingSoonItem('Multi-Signature', 'Multiple Accounts', colors, text), + _divider(colors), + _comingSoonItem('Hardware Wallet', 'Pair Device', colors, text), + ]), + const SizedBox(height: 40), + _section('Preferences', colors, text, [ + _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), + _divider(colors), + _toggleItem('Notifications', notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', notifConfig.enabled, _toggleNotifications, colors, text), + ]), + const SizedBox(height: 40), + _section('About & Support', colors, text, [ + _externalItem('Help & Support', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl))), + _divider(colors), + _chevronItem('About Quantus', 'Version 1.0.1', colors, text, onTap: () {}), + _divider(colors), + _externalItem('Terms of Service', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + _divider(colors), + _externalItem('Privacy Policy', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + ]), + const SizedBox(height: 40), + _resetButton(colors, text), + const SizedBox(height: 48), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _section(String title, AppColorsV2 colors, AppTextTheme text, List children) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: colors.surfaceCard, + borderRadius: BorderRadius.circular(14), + ), + child: Column(children: children), + ), + ], + ); + } + + Widget _toggleItem(String title, String subtitle, bool value, ValueChanged onChanged, AppColorsV2 colors, AppTextTheme text) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 4), + Text(subtitle, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + CupertinoSwitch( + value: value, + onChanged: onChanged, + activeTrackColor: colors.accentGreen, + ), + ], + ); + } + + Widget _chevronItem(String title, String subtitle, AppColorsV2 colors, AppTextTheme text, {required VoidCallback onTap}) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 4), + Text(subtitle, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + Icon(Icons.chevron_right, color: colors.textSecondary, size: 20), + ], + ), + ); + } + + Widget _externalItem(String title, String? subtitle, AppColorsV2 colors, AppTextTheme text, {required VoidCallback onTap}) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Row( + children: [ + Expanded( + child: subtitle != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 4), + Text(subtitle, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ) + : Text(title, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + ), + Icon(Icons.north_east, color: colors.textSecondary, size: 20), + ], + ), + ); + } + + Widget _comingSoonItem(String title, String subtitle, AppColorsV2 colors, AppTextTheme text) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 4), + Text(subtitle, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + Text('Coming Soon', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ); + } + + Widget _divider(AppColorsV2 colors) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider(color: colors.separator, height: 1), + ); + } + + Widget _resetButton(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: _showResetConfirmation, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: colors.danger), + ), + child: Center( + child: Text('Reset Quantus', style: text.paragraph?.copyWith(color: colors.danger, fontWeight: FontWeight.w500)), + ), + ), + ); + } +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 81e5c55d..60b5890a 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -101,6 +101,8 @@ flutter: - assets/high_security/big_red_button_icon.png - assets/high_security/intercept_icon.svg - assets/quests/quests_top_logo.png + - assets/v2/green_checkmark.png + - assets/v2/pin_number_background.png fonts: - family: Fira Code diff --git a/quantus_sdk/lib/src/services/settings_service.dart b/quantus_sdk/lib/src/services/settings_service.dart index 8fa58ce4..d456d316 100644 --- a/quantus_sdk/lib/src/services/settings_service.dart +++ b/quantus_sdk/lib/src/services/settings_service.dart @@ -264,7 +264,34 @@ class SettingsService { return await _secureStorage.read(key: getMnemonicKey(walletIndex)); } - // Reversible Time Settings + // PIN Code Settings + Future setPin(String pin) async { + await _secureStorage.write(key: 'wallet_pin', value: pin); + } + + Future getPin() async { + return await _secureStorage.read(key: 'wallet_pin'); + } + + Future hasPin() async { + final pin = await _secureStorage.read(key: 'wallet_pin'); + return pin != null && pin.isNotEmpty; + } + + Future verifyPin(String pin) async { + final stored = await _secureStorage.read(key: 'wallet_pin'); + return stored == pin; + } + + // Reversible Transaction Settings + Future setReversibleEnabled(bool enabled) async { + await _prefs.setBool('reversible_enabled', enabled); + } + + bool isReversibleEnabled() { + return _prefs.getBool('reversible_enabled') ?? false; + } + Future setReversibleTimeSeconds(int seconds) async { await _prefs.setInt('reversible_time_seconds', seconds); } From 61ead71d85a04bc0b2d496490969be5342117793 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 21:18:53 +0800 Subject: [PATCH 09/49] background gradients and ellipse shine --- mobile-app/assets/v2/caret_left.svg | 3 + .../v2/components/gradient_background.dart | 75 +++++++++ .../lib/v2/screens/home/home_screen.dart | 10 +- .../screens/settings/change_pin_screen.dart | 62 +++---- .../v2/screens/settings/settings_screen.dart | 155 +++++++++--------- mobile-app/lib/v2/theme/app_colors.dart | 20 ++- mobile-app/pubspec.yaml | 1 + 7 files changed, 206 insertions(+), 120 deletions(-) create mode 100644 mobile-app/assets/v2/caret_left.svg create mode 100644 mobile-app/lib/v2/components/gradient_background.dart diff --git a/mobile-app/assets/v2/caret_left.svg b/mobile-app/assets/v2/caret_left.svg new file mode 100644 index 00000000..2f427be9 --- /dev/null +++ b/mobile-app/assets/v2/caret_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/mobile-app/lib/v2/components/gradient_background.dart b/mobile-app/lib/v2/components/gradient_background.dart new file mode 100644 index 00000000..9670b58d --- /dev/null +++ b/mobile-app/lib/v2/components/gradient_background.dart @@ -0,0 +1,75 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; + +class GradientBackground extends StatelessWidget { + final Widget child; + + const GradientBackground({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + return Stack( + children: [ + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: RadialGradient( + center: const Alignment(0.0, -0.487), + radius: 1.6, + colors: [colors.backgroundAlt, colors.background], + ), + ), + ), + ), + Positioned.fill( + child: CustomPaint(painter: _EllipseGlowPainter(glowColor: colors.backgroundGlow.useOpacity(0.2))), + ), + child, + ], + ); + } +} + +class _EllipseGlowPainter extends CustomPainter { + static const xOffset = -110.0; + static const yOffset = -60.0; + + final Color glowColor; + + _EllipseGlowPainter({required this.glowColor}); + + @override + void paint(Canvas canvas, Size size) { + final sx = size.width / 390.0; + final sy = size.height / 844.0; + final ox = xOffset * sx; + final oy = yOffset * sy; + final paint = Paint() + ..color = glowColor + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 85); + + canvas.save(); + canvas.translate(176.56 * sx + ox, 77.88 * sy + oy); + canvas.rotate(30 * pi / 180); + canvas.drawOval( + Rect.fromCenter(center: Offset.zero, width: 66.46 * sx, height: 406.13 * sy), + paint, + ); + canvas.restore(); + + canvas.save(); + canvas.translate(367.38 * sx + ox, 41.54 * sy + oy); + canvas.rotate(30 * pi / 180); + canvas.drawOval( + Rect.fromCenter(center: Offset.zero, width: 33.41 * sx, height: 446.53 * sy), + paint, + ); + canvas.restore(); + } + + @override + bool shouldRepaint(_EllipseGlowPainter oldDelegate) => glowColor != oldDelegate.glowColor; +} diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index e6a3b9e4..51ee4fed 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -15,6 +15,7 @@ import 'package:resonance_network_wallet/providers/active_account_transactions_p import 'package:resonance_network_wallet/providers/filtered_all_transactions_provider.dart'; import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:resonance_network_wallet/v2/screens/home/activity_section.dart'; @@ -108,14 +109,7 @@ class _HomeScreenState extends ConsumerState { } Widget _buildContent(DisplayAccount active, AsyncValue balanceAsync, AppColorsV2 colors, AppTextTheme text) { - return Container( - decoration: BoxDecoration( - gradient: RadialGradient( - center: Alignment.topCenter, - radius: 1.2, - colors: [colors.backgroundAlt, colors.background], - ), - ), + return GradientBackground( child: SafeArea( bottom: false, child: Padding( diff --git a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart index 68618459..822815db 100644 --- a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart +++ b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -102,39 +103,40 @@ class _ChangePinScreenState extends State { return Scaffold( backgroundColor: colors.background, resizeToAvoidBottomInset: false, - body: SafeArea( - child: Stack( - children: [ - // Hidden text field to trigger native keyboard - Opacity( - opacity: 0, - child: SizedBox( - height: 0, - child: TextField( - controller: _controller, - focusNode: _focusNode, - keyboardType: TextInputType.number, - keyboardAppearance: Brightness.dark, - maxLength: 6, - inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)], - decoration: const InputDecoration(counterText: ''), + body: GradientBackground( + child: SafeArea( + child: Stack( + children: [ + Opacity( + opacity: 0, + child: SizedBox( + height: 0, + child: TextField( + controller: _controller, + focusNode: _focusNode, + keyboardType: TextInputType.number, + keyboardAppearance: Brightness.dark, + maxLength: 6, + inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)], + decoration: const InputDecoration(counterText: ''), + ), ), ), - ), - Column( - children: [ - const SizedBox(height: 16), - _header(colors, text), - Expanded( - child: _step == _Step.success ? _successBody(colors, text) : _pinBody(colors, text), - ), - if (_step == _Step.success) ...[ - _doneButton(colors, text), - const SizedBox(height: 24), + Column( + children: [ + const SizedBox(height: 16), + _header(colors, text), + Expanded( + child: _step == _Step.success ? _successBody(colors, text) : _pinBody(colors, text), + ), + if (_step == _Step.success) ...[ + _doneButton(colors, text), + const SizedBox(height: 24), + ], ], - ], - ), - ], + ), + ], + ), ), ), ); diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 2b576667..c51661e2 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -14,6 +14,7 @@ import 'package:resonance_network_wallet/providers/notification_config_provider. import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/screens/settings/change_pin_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -150,84 +151,86 @@ class _SettingsScreenV2State extends ConsumerState { return Scaffold( backgroundColor: colors.background, - body: SafeArea( - child: Column( - children: [ - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), - ), - Text('Settings', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - const SizedBox(width: 24), - ], - ), - ), - const SizedBox(height: 48), - Expanded( - child: ListView( + body: GradientBackground( + child: SafeArea( + child: Column( + children: [ + const SizedBox(height: 16), + Padding( padding: const EdgeInsets.symmetric(horizontal: 24), - children: [ - _section('Security', colors, text, [ - _toggleItem('Biometric Lock', _biometricDesc, _biometricEnabled, _toggleBiometric, colors, text), - _divider(colors), - _chevronItem('PIN Code', _hasPinSet ? '6-digit code' : 'Not set', colors, text, - onTap: () async { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const ChangePinScreen())); - _loadPinState(); - }), - _divider(colors), - _chevronItem('Auto-Lock', _autoLockLabel(), colors, text, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AuthenticationSettingsScreen()))), - ]), - const SizedBox(height: 40), - _section('Wallet', colors, text, [ - _chevronItem('Recovery Phase', 'View Backup', colors, text, onTap: _navigateToRecoveryPhrase), - ]), - const SizedBox(height: 40), - _section('Reversible Transactions', colors, text, [ - _toggleItem('Reversible Transactions', _reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, _toggleReversible, colors, text), - _divider(colors), - _chevronItem('Time Limit', _timeLimitLabel(), colors, text, onTap: () {}), - _divider(colors), - _chevronItem('Amount Limit', 'No Limit', colors, text, onTap: () {}), - ]), - const SizedBox(height: 40), - _section('Account Type', colors, text, [ - _comingSoonItem('High Security Account', 'Guardian Approval', colors, text), - _divider(colors), - _comingSoonItem('Multi-Signature', 'Multiple Accounts', colors, text), - _divider(colors), - _comingSoonItem('Hardware Wallet', 'Pair Device', colors, text), - ]), - const SizedBox(height: 40), - _section('Preferences', colors, text, [ - _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), - _divider(colors), - _toggleItem('Notifications', notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', notifConfig.enabled, _toggleNotifications, colors, text), - ]), - const SizedBox(height: 40), - _section('About & Support', colors, text, [ - _externalItem('Help & Support', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl))), - _divider(colors), - _chevronItem('About Quantus', 'Version 1.0.1', colors, text, onTap: () {}), - _divider(colors), - _externalItem('Terms of Service', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), - _divider(colors), - _externalItem('Privacy Policy', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), - ]), - const SizedBox(height: 40), - _resetButton(colors, text), - const SizedBox(height: 48), - ], + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + ), + Text('Settings', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), ), - ), - ], + const SizedBox(height: 48), + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 24), + children: [ + _section('Security', colors, text, [ + _toggleItem('Biometric Lock', _biometricDesc, _biometricEnabled, _toggleBiometric, colors, text), + _divider(colors), + _chevronItem('PIN Code', _hasPinSet ? '6-digit code' : 'Not set', colors, text, + onTap: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const ChangePinScreen())); + _loadPinState(); + }), + _divider(colors), + _chevronItem('Auto-Lock', _autoLockLabel(), colors, text, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AuthenticationSettingsScreen()))), + ]), + const SizedBox(height: 40), + _section('Wallet', colors, text, [ + _chevronItem('Recovery Phase', 'View Backup', colors, text, onTap: _navigateToRecoveryPhrase), + ]), + const SizedBox(height: 40), + _section('Reversible Transactions', colors, text, [ + _toggleItem('Reversible Transactions', _reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, _toggleReversible, colors, text), + _divider(colors), + _chevronItem('Time Limit', _timeLimitLabel(), colors, text, onTap: () {}), + _divider(colors), + _chevronItem('Amount Limit', 'No Limit', colors, text, onTap: () {}), + ]), + const SizedBox(height: 40), + _section('Account Type', colors, text, [ + _comingSoonItem('High Security Account', 'Guardian Approval', colors, text), + _divider(colors), + _comingSoonItem('Multi-Signature', 'Multiple Accounts', colors, text), + _divider(colors), + _comingSoonItem('Hardware Wallet', 'Pair Device', colors, text), + ]), + const SizedBox(height: 40), + _section('Preferences', colors, text, [ + _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), + _divider(colors), + _toggleItem('Notifications', notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', notifConfig.enabled, _toggleNotifications, colors, text), + ]), + const SizedBox(height: 40), + _section('About & Support', colors, text, [ + _externalItem('Help & Support', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl))), + _divider(colors), + _chevronItem('About Quantus', 'Version 1.0.1', colors, text, onTap: () {}), + _divider(colors), + _externalItem('Terms of Service', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + _divider(colors), + _externalItem('Privacy Policy', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + ]), + const SizedBox(height: 40), + _resetButton(colors, text), + const SizedBox(height: 48), + ], + ), + ), + ], + ), ), ), ); diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/mobile-app/lib/v2/theme/app_colors.dart index d2abce1f..20e06520 100644 --- a/mobile-app/lib/v2/theme/app_colors.dart +++ b/mobile-app/lib/v2/theme/app_colors.dart @@ -27,11 +27,14 @@ class AppColorsV2 extends ThemeExtension { final Color danger; final Color success; + // Glow & Gradients + final Color backgroundGlow; + final List buttonPrimaryGradient; + // UI elements final Color separator; final Color border; final Color buttonDisabled; - final List buttonPrimaryGradient; final Color skeletonBase; final Color skeletonHighlight; @@ -56,10 +59,11 @@ class AppColorsV2 extends ThemeExtension { required this.error, required this.danger, required this.success, + required this.backgroundGlow, + required this.buttonPrimaryGradient, required this.separator, required this.border, required this.buttonDisabled, - required this.buttonPrimaryGradient, required this.skeletonBase, required this.skeletonHighlight, required this.tagGuardian, @@ -84,10 +88,11 @@ class AppColorsV2 extends ThemeExtension { error: const Color(0xFFFF2D54), danger: const Color(0xFFFF1F45), success: const Color(0xFF1FFFA7), + backgroundGlow: const Color(0xFFFFFFFF), + buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], separator: const Color(0x1AFFFFFF), border: const Color(0x33FFFFFF), buttonDisabled: const Color(0xFF3D3C44), - buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], skeletonBase: const Color(0xFF3D3C44), skeletonHighlight: const Color(0xFF5A5A5A), tagGuardian: const Color(0xFF9747FF), @@ -112,10 +117,11 @@ class AppColorsV2 extends ThemeExtension { Color? error, Color? danger, Color? success, + Color? backgroundGlow, + List? buttonPrimaryGradient, Color? separator, Color? border, Color? buttonDisabled, - List? buttonPrimaryGradient, Color? skeletonBase, Color? skeletonHighlight, Color? tagGuardian, @@ -138,10 +144,11 @@ class AppColorsV2 extends ThemeExtension { error: error ?? this.error, danger: danger ?? this.danger, success: success ?? this.success, + backgroundGlow: backgroundGlow ?? this.backgroundGlow, + buttonPrimaryGradient: buttonPrimaryGradient ?? this.buttonPrimaryGradient, separator: separator ?? this.separator, border: border ?? this.border, buttonDisabled: buttonDisabled ?? this.buttonDisabled, - buttonPrimaryGradient: buttonPrimaryGradient ?? this.buttonPrimaryGradient, skeletonBase: skeletonBase ?? this.skeletonBase, skeletonHighlight: skeletonHighlight ?? this.skeletonHighlight, tagGuardian: tagGuardian ?? this.tagGuardian, @@ -169,10 +176,11 @@ class AppColorsV2 extends ThemeExtension { error: Color.lerp(error, other.error, t) ?? error, danger: Color.lerp(danger, other.danger, t) ?? danger, success: Color.lerp(success, other.success, t) ?? success, + backgroundGlow: Color.lerp(backgroundGlow, other.backgroundGlow, t) ?? backgroundGlow, + buttonPrimaryGradient: other.buttonPrimaryGradient, separator: Color.lerp(separator, other.separator, t) ?? separator, border: Color.lerp(border, other.border, t) ?? border, buttonDisabled: Color.lerp(buttonDisabled, other.buttonDisabled, t) ?? buttonDisabled, - buttonPrimaryGradient: other.buttonPrimaryGradient, skeletonBase: Color.lerp(skeletonBase, other.skeletonBase, t) ?? skeletonBase, skeletonHighlight: Color.lerp(skeletonHighlight, other.skeletonHighlight, t) ?? skeletonHighlight, tagGuardian: Color.lerp(tagGuardian, other.tagGuardian, t) ?? tagGuardian, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 60b5890a..4b201f3c 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -103,6 +103,7 @@ flutter: - assets/quests/quests_top_logo.png - assets/v2/green_checkmark.png - assets/v2/pin_number_background.png + - assets/v2/caret_left.svg fonts: - family: Fira Code From b48652820c258db55dfc89f528c0869ad15bf29d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 10 Feb 2026 21:26:08 +0800 Subject: [PATCH 10/49] fix back buttons and activity screen --- mobile-app/lib/v2/components/back_button.dart | 17 +++ .../v2/screens/activity/activity_screen.dart | 129 +++++++++--------- .../v2/screens/send/address_picker_sheet.dart | 6 +- .../lib/v2/screens/send/send_sheet.dart | 3 +- .../screens/settings/change_pin_screen.dart | 6 +- .../v2/screens/settings/settings_screen.dart | 6 +- 6 files changed, 90 insertions(+), 77 deletions(-) create mode 100644 mobile-app/lib/v2/components/back_button.dart diff --git a/mobile-app/lib/v2/components/back_button.dart b/mobile-app/lib/v2/components/back_button.dart new file mode 100644 index 00000000..65bb64c0 --- /dev/null +++ b/mobile-app/lib/v2/components/back_button.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; + +class AppBackButton extends StatelessWidget { + final VoidCallback? onTap; + + const AppBackButton({super.key, this.onTap}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap ?? () => Navigator.pop(context), + child: SvgPicture.asset('assets/v2/caret_left.svg', width: 24, height: 24, colorFilter: ColorFilter.mode(context.colors.textPrimary, BlendMode.srcIn)), + ); + } +} diff --git a/mobile-app/lib/v2/screens/activity/activity_screen.dart b/mobile-app/lib/v2/screens/activity/activity_screen.dart index fb41d1ab..5d80ca92 100644 --- a/mobile-app/lib/v2/screens/activity/activity_screen.dart +++ b/mobile-app/lib/v2/screens/activity/activity_screen.dart @@ -4,6 +4,8 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; import 'package:resonance_network_wallet/services/transaction_service.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:resonance_network_wallet/v2/screens/activity/tx_item.dart'; @@ -21,72 +23,71 @@ class ActivityScreen extends ConsumerWidget { return Scaffold( backgroundColor: colors.background, - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Column( - children: [ - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Activity', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Icon(Icons.info_outline, color: colors.textPrimary, size: 24), + ], + ), + const SizedBox(height: 48), + Expanded( + child: accountAsync.when( + loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), + error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + data: (active) { + if (active == null) return const Center(child: Text('No account')); + return txAsync.when( + loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), + error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + data: (data) { + final txService = ref.read(transactionServiceProvider); + final all = txService.combineAndDeduplicateTransactions( + pendingCancellationIds: data.pendingCancellationIds, + pendingTransactions: data.pendingTransactions, + reversibleTransfers: data.reversibleTransfers, + otherTransfers: data.otherTransfers, + ); + if (all.isEmpty) { + return Center(child: Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary))); + } + final grouped = _groupByDate(all); + return ListView.builder( + padding: EdgeInsets.zero, + itemCount: grouped.length, + itemBuilder: (context, i) { + final group = grouped[i]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (i > 0) const SizedBox(height: 40), + Text(group.label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + const SizedBox(height: 20), + ...group.transactions.map((tx) { + final itemData = TxItemData.from(tx, active.account.accountId); + return buildTxItem(tx, itemData, colors, text, onTap: () { + showTransactionDetailSheet(context, tx, active.account.accountId); + }); + }), + ], + ); + }, + ); + }, + ); + }, ), - Text('Activity', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - Icon(Icons.info_outline, color: colors.textPrimary, size: 24), - ], - ), - const SizedBox(height: 48), - Expanded( - child: accountAsync.when( - loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), - error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), - data: (active) { - if (active == null) return const Center(child: Text('No account')); - return txAsync.when( - loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), - error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), - data: (data) { - final txService = ref.read(transactionServiceProvider); - final all = txService.combineAndDeduplicateTransactions( - pendingCancellationIds: data.pendingCancellationIds, - pendingTransactions: data.pendingTransactions, - reversibleTransfers: data.reversibleTransfers, - otherTransfers: data.otherTransfers, - ); - if (all.isEmpty) { - return Center(child: Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary))); - } - final grouped = _groupByDate(all); - return ListView.builder( - padding: EdgeInsets.zero, - itemCount: grouped.length, - itemBuilder: (context, i) { - final group = grouped[i]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (i > 0) const SizedBox(height: 40), - Text(group.label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), - const SizedBox(height: 20), - ...group.transactions.map((tx) { - final itemData = TxItemData.from(tx, active.account.accountId); - return buildTxItem(tx, itemData, colors, text, onTap: () { - showTransactionDetailSheet(context, tx, active.account.accountId); - }); - }), - ], - ); - }, - ); - }, - ); - }, ), - ), - ], + ], + ), ), ), ), diff --git a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart index 46407fad..0207a0a4 100644 --- a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/account_gradient_image.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -82,10 +83,7 @@ class _AddressPickerSheetState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), - ), + const AppBackButton(), Text('Send To', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), GestureDetector( onTap: () => Navigator.pop(context), diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index f9a8e8b7..575df6bf 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -8,6 +8,7 @@ import 'package:resonance_network_wallet/features/main/screens/send/send_screen_ import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -184,7 +185,7 @@ class _SendSheetState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ if (onBack != null) - GestureDetector(onTap: onBack, child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24)) + AppBackButton(onTap: onBack) else Text('Send', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), GestureDetector( diff --git a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart index 822815db..ba2e3730 100644 --- a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart +++ b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -148,10 +149,7 @@ class _ChangePinScreenState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), - ), + const AppBackButton(), Text('Change PIN', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), const SizedBox(width: 24), ], diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index c51661e2..2ca6dd22 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -14,6 +14,7 @@ import 'package:resonance_network_wallet/providers/notification_config_provider. import 'package:resonance_network_wallet/providers/pending_transactions_provider.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/screens/settings/change_pin_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -161,10 +162,7 @@ class _SettingsScreenV2State extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.chevron_left, color: colors.textPrimary, size: 24), - ), + const AppBackButton(), Text('Settings', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), const SizedBox(width: 24), ], From e6de8b19421323f6a7bd09484ba0e6ca4f1d106a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 09:56:05 +0800 Subject: [PATCH 11/49] glossy front page buttons --- mobile-app/assets/v2/receive_button.png | Bin 0 -> 8415 bytes mobile-app/assets/v2/send_button.png | Bin 0 -> 7872 bytes mobile-app/assets/v2/swap_button.png | Bin 0 -> 8643 bytes .../lib/v2/screens/home/home_screen.dart | 48 ++---------------- mobile-app/pubspec.yaml | 3 ++ 5 files changed, 8 insertions(+), 43 deletions(-) create mode 100644 mobile-app/assets/v2/receive_button.png create mode 100644 mobile-app/assets/v2/send_button.png create mode 100644 mobile-app/assets/v2/swap_button.png diff --git a/mobile-app/assets/v2/receive_button.png b/mobile-app/assets/v2/receive_button.png new file mode 100644 index 0000000000000000000000000000000000000000..97223961a6c7f67c9d9830acca3d7162f8bcfb52 GIT binary patch literal 8415 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 I#5U@`0C(6KLI3~& literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/send_button.png b/mobile-app/assets/v2/send_button.png new file mode 100644 index 0000000000000000000000000000000000000000..a612c22f57851af84fe25771a9ad2ff794b88589 GIT binary patch literal 7872 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 mK6wtDfo?Imfx5v-cbq#XP>^8Wz=_z$fB literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/swap_button.png b/mobile-app/assets/v2/swap_button.png new file mode 100644 index 0000000000000000000000000000000000000000..ff9b7506b215b6259244d709e8e803f5a0fee5e0 GIT binary patch literal 8643 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 { Widget _buildActionButtons(AppColorsV2 colors, AppTextTheme text) { return Row( children: [ - _actionCard( - icon: Icons.arrow_downward_rounded, - label: 'Receive', - colors: colors, - text: text, - onTap: () => showReceiveSheetV2(context), - ), + _actionCard('assets/v2/receive_button.png', () => showReceiveSheetV2(context)), const SizedBox(width: 15), - _actionCard( - icon: Icons.arrow_upward_rounded, - label: 'Send', - colors: colors, - text: text, - onTap: () => showSendSheetV2(context), - ), + _actionCard('assets/v2/send_button.png', () => showSendSheetV2(context)), const SizedBox(width: 15), - _actionCard( - icon: Icons.swap_horiz_rounded, - label: 'Swap', - colors: colors, - text: text, - onTap: () {}, - ), + _actionCard('assets/v2/swap_button.png', () {}), ], ); } - Widget _actionCard({ - required IconData icon, - required String label, - required AppColorsV2 colors, - required AppTextTheme text, - required VoidCallback onTap, - }) { + Widget _actionCard(String asset, VoidCallback onTap) { return Expanded( child: GestureDetector( onTap: onTap, - child: Container( - height: 80, - decoration: BoxDecoration( - color: colors.surfaceGlass, - borderRadius: BorderRadius.circular(14), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: colors.textPrimary, size: 24), - const SizedBox(height: 8), - Text(label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), - ], - ), - ), + child: Image.asset(asset), ), ); } diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 4b201f3c..e323585e 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -104,6 +104,9 @@ flutter: - assets/v2/green_checkmark.png - assets/v2/pin_number_background.png - assets/v2/caret_left.svg + - assets/v2/send_button.png + - assets/v2/receive_button.png + - assets/v2/swap_button.png fonts: - family: Fira Code From 3346df382099ffa6346db8a9a2bd8dc6ef1c201c Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 11:40:33 +0800 Subject: [PATCH 12/49] glassy buttons --- mobile-app/assets/v2/glass_border_bg.png | Bin 0 -> 5560 bytes mobile-app/assets/v2/glass_button_40_bg.png | Bin 0 -> 3057 bytes .../assets/v2/glass_medium_button_bg.png | Bin 0 -> 2995 bytes .../lib/v2/components/glass_container.dart | 32 ++ .../lib/v2/screens/home/home_screen.dart | 3 +- .../lib/v2/screens/swap/deposit_screen.dart | 269 +++++++++++ .../v2/screens/swap/review_quote_sheet.dart | 146 ++++++ .../lib/v2/screens/swap/swap_screen.dart | 421 ++++++++++++++++++ .../v2/screens/swap/token_picker_sheet.dart | 86 ++++ mobile-app/pubspec.yaml | 4 + quantus_sdk/lib/quantus_sdk.dart | 1 + .../lib/src/services/swap_service.dart | 179 ++++++++ 12 files changed, 1140 insertions(+), 1 deletion(-) create mode 100644 mobile-app/assets/v2/glass_border_bg.png create mode 100644 mobile-app/assets/v2/glass_button_40_bg.png create mode 100644 mobile-app/assets/v2/glass_medium_button_bg.png create mode 100644 mobile-app/lib/v2/components/glass_container.dart create mode 100644 mobile-app/lib/v2/screens/swap/deposit_screen.dart create mode 100644 mobile-app/lib/v2/screens/swap/review_quote_sheet.dart create mode 100644 mobile-app/lib/v2/screens/swap/swap_screen.dart create mode 100644 mobile-app/lib/v2/screens/swap/token_picker_sheet.dart create mode 100644 quantus_sdk/lib/src/services/swap_service.dart diff --git a/mobile-app/assets/v2/glass_border_bg.png b/mobile-app/assets/v2/glass_border_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..d820670c61392e42aedf0a392ab64b5a2097fb76 GIT binary patch literal 5560 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 MTroE)zT_73f1Ap^tpET3 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/glass_button_40_bg.png b/mobile-app/assets/v2/glass_button_40_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..09728ca9e796188f7a133c6d78e042a9e6c802d2 GIT binary patch literal 3057 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 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/glass_medium_button_bg.png b/mobile-app/assets/v2/glass_medium_button_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..32ed0c4ab802bbac100f0b4ceefc9c28ec4c03e4 GIT binary patch literal 2995 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 { const SizedBox(width: 15), _actionCard('assets/v2/send_button.png', () => showSendSheetV2(context)), const SizedBox(width: 15), - _actionCard('assets/v2/swap_button.png', () {}), + _actionCard('assets/v2/swap_button.png', () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen()))), ], ); } diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart new file mode 100644 index 00000000..e754763e --- /dev/null +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -0,0 +1,269 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/components/success_check.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class DepositScreen extends StatefulWidget { + final SwapOrder order; + const DepositScreen({super.key, required this.order}); + + @override + State createState() => _DepositScreenState(); +} + +class _DepositScreenState extends State { + final _swapService = SwapService(); + late SwapOrder _order; + bool _confirming = false; + + @override + void initState() { + super.initState(); + _order = widget.order; + } + + Future _confirmSent() async { + setState(() => _confirming = true); + try { + final updated = await _swapService.confirmFundsSent(_order.orderId); + if (!mounted) return; + setState(() { + _order = updated; + _confirming = false; + }); + _pollStatus(); + } catch (e) { + setState(() => _confirming = false); + } + } + + Future _pollStatus() async { + while (mounted && _order.status == SwapStatus.processing) { + await Future.delayed(const Duration(seconds: 2)); + if (!mounted) return; + try { + final updated = await _swapService.getSwapStatus(_order.orderId); + if (!mounted) return; + setState(() => _order = updated); + } catch (_) {} + } + } + + void _copyAddress() { + Clipboard.setData(ClipboardData(text: _order.depositAddress)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Address copied'), duration: Duration(seconds: 1)), + ); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final quote = _order.quote; + final usd = quote.fromAmount * _swapService.getUsdPrice(quote.fromToken); + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + _header(colors, text), + const SizedBox(height: 40), + if (_order.status == SwapStatus.complete) + _completedBody(colors, text) + else if (_order.status == SwapStatus.processing) + _processingBody(colors, text) + else + _depositBody(colors, text, quote, usd), + const Spacer(), + if (_order.status == SwapStatus.depositing) _sentButton(colors, text), + if (_order.status == SwapStatus.complete) _doneButton(colors, text), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + Widget _header(AppColorsV2 colors, AppTextTheme text) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Swap', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Icon(Icons.info_outline, color: colors.textPrimary, size: 24), + ], + ); + } + + Widget _depositBody(AppColorsV2 colors, AppTextTheme text, SwapQuote quote, double usd) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Deposit Amount', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: quote.totalAmount.toStringAsFixed(2))); + }, + child: Icon(Icons.copy, color: colors.textTertiary, size: 14), + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 28, height: 28, + decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle), + ), + const SizedBox(width: 8), + Text(quote.totalAmount.toStringAsFixed(2), style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 24)), + ], + ), + Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), + child: QrImageView(data: _order.depositAddress, version: QrVersions.auto, size: 180), + ), + const SizedBox(height: 16), + GestureDetector( + onTap: _copyAddress, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + _order.depositAddress, + style: text.detail?.copyWith(color: colors.textSecondary, fontSize: 11), + textAlign: TextAlign.center, + maxLines: 2, + ), + ), + const SizedBox(width: 4), + Icon(Icons.copy, color: colors.textTertiary, size: 12), + ], + ), + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded(child: _actionBtn(Icons.copy, 'Copy', _copyAddress, colors, text)), + const SizedBox(width: 16), + Expanded(child: _actionBtn(Icons.qr_code, 'Share QR', () {}, colors, text)), + ], + ), + const SizedBox(height: 24), + Text( + 'Use your ${quote.fromToken.symbol} or ${quote.fromToken.network} wallet to deposit funds. Depositing other assets may result in loss of funds.', + style: text.detail?.copyWith(color: colors.textTertiary), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _actionBtn(IconData icon, String label, VoidCallback onTap, AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.2)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: colors.textPrimary, size: 18), + const SizedBox(width: 8), + Text(label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ); + } + + Widget _processingBody(AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + const SizedBox(height: 80), + CircularProgressIndicator(color: colors.accentGreen), + const SizedBox(height: 32), + Text('Processing Swap', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(height: 12), + Text('This may take a few minutes...', style: text.paragraph?.copyWith(color: colors.textSecondary)), + ], + ); + } + + Widget _completedBody(AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + const SizedBox(height: 80), + const SuccessCheck(), + const SizedBox(height: 32), + Text('Swap Complete', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(height: 12), + Text( + '${_order.quote.toAmount.toStringAsFixed(2)} QU has been added to your wallet', + style: text.paragraph?.copyWith(color: colors.textSecondary), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _sentButton(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: _confirming ? null : _confirmSent, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Center( + child: _confirming + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) + : Text("I've sent the funds", style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + ); + } + + Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: () => Navigator.popUntil(context, (r) => r.isFirst), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Center( + child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart new file mode 100644 index 00000000..3fcda830 --- /dev/null +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -0,0 +1,146 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/screens/swap/deposit_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +void showReviewQuoteSheet(BuildContext context, SwapQuote quote, String refundAddress) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (_) => _ReviewQuoteContent(quote: quote, refundAddress: refundAddress), + ); +} + +class _ReviewQuoteContent extends StatelessWidget { + final SwapQuote quote; + final String refundAddress; + const _ReviewQuoteContent({required this.quote, required this.refundAddress}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final swapService = SwapService(); + final fromUsd = quote.fromAmount * swapService.getUsdPrice(quote.fromToken); + final toUsd = quote.toAmount * swapService.getUsdPrice(quote.toToken); + + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: colors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Review Quote', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 18)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 24), + _swapVisual(colors, text, fromUsd, toUsd), + const SizedBox(height: 32), + _feeRow('Total fees', '${quote.networkFee.toStringAsFixed(3)} ${quote.fromToken.symbol}', colors, text), + Divider(color: colors.separator, height: 32), + _feeRow('Total Amount', '${quote.totalAmount.toStringAsFixed(2)} ${quote.fromToken.symbol}', colors, text), + const SizedBox(height: 24), + Text( + 'You could receive up to \$${(quote.fromAmount * quote.slippageTolerance).toStringAsFixed(2)} less based on the ${(quote.slippageTolerance * 100).toStringAsFixed(0)}% slippage you set', + style: text.detail?.copyWith(color: colors.textTertiary), + ), + const SizedBox(height: 24), + _confirmButton(context, colors, text), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + Widget _swapVisual(AppColorsV2 colors, AppTextTheme text, double fromUsd, double toUsd) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + child: Row( + children: [ + Expanded(child: _tokenCard(quote.fromToken, quote.fromAmount, fromUsd, colors, text)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Icon(Icons.arrow_forward, color: colors.textSecondary, size: 20), + ), + Expanded(child: _tokenCard(quote.toToken, quote.toAmount, toUsd, colors, text)), + ], + ), + ); + } + + Widget _tokenCard(SwapToken token, double amount, double usd, AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20, height: 20, + decoration: BoxDecoration( + color: token.symbol == 'QU' ? colors.accentGreen.withValues(alpha: 0.3) : colors.accentPink.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 6), + Flexible(child: Text(token.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ], + ), + const SizedBox(height: 8), + Text(amount.toStringAsFixed(2), style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ); + } + + Widget _feeRow(String label, String value, AppColorsV2 colors, AppTextTheme text) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: text.detail?.copyWith(color: colors.textTertiary)), + Text(value, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ); + } + + Widget _confirmButton(BuildContext context, AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: () async { + final swapService = SwapService(); + final order = await swapService.createSwap(quote); + if (!context.mounted) return; + Navigator.pop(context); + Navigator.push(context, MaterialPageRoute(builder: (_) => DepositScreen(order: order))); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: 0.44)), + ), + child: Center( + child: Text('Confirm', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart new file mode 100644 index 00000000..355b5f96 --- /dev/null +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -0,0 +1,421 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/swap/review_quote_sheet.dart'; +import 'package:resonance_network_wallet/v2/screens/swap/token_picker_sheet.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class SwapScreen extends StatefulWidget { + const SwapScreen({super.key}); + + @override + State createState() => _SwapScreenState(); +} + +class _SwapScreenState extends State { + final _swapService = SwapService(); + final _fromController = TextEditingController(); + final _addressController = TextEditingController(); + SwapToken _fromToken = SwapService.availableTokens.first; + double _toAmount = 0; + double _fromUsd = 0; + double _toUsd = 0; + bool _loading = false; + + double get _rate => _swapService.getRate(_fromToken); + String get _rateLabel => '1 QU = ${(1 / _rate).toStringAsFixed(4)} ${_fromToken.symbol}'; + + @override + void initState() { + super.initState(); + _fromController.addListener(_recalculate); + } + + @override + void dispose() { + _fromController.dispose(); + _addressController.dispose(); + super.dispose(); + } + + void _recalculate() { + final amount = double.tryParse(_fromController.text) ?? 0; + setState(() { + _toAmount = amount * _rate; + _fromUsd = amount * _swapService.getUsdPrice(_fromToken); + _toUsd = _toAmount * _swapService.getUsdPrice(_swapService.getQuToken()); + }); + } + + bool get _canGetQuote => + _fromController.text.isNotEmpty && + (double.tryParse(_fromController.text) ?? 0) > 0 && + _addressController.text.isNotEmpty; + + Future _getQuote() async { + final amount = double.tryParse(_fromController.text) ?? 0; + if (amount <= 0 || _addressController.text.isEmpty) return; + + setState(() => _loading = true); + try { + final quote = await _swapService.getQuote(fromToken: _fromToken, fromAmount: amount); + if (!mounted) return; + setState(() => _loading = false); + showReviewQuoteSheet(context, quote, _addressController.text); + } catch (e) { + setState(() => _loading = false); + } + } + + void _pickToken() async { + final token = await showTokenPickerSheet(context, _swapService.getFromTokens(), _fromToken); + if (token != null && token != _fromToken) { + setState(() => _fromToken = token); + _recalculate(); + } + } + + void _scanQr() { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => _QrScanPage(onScanned: (v) { + _addressController.text = v; + Navigator.pop(context); + }), + ), + ); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + _header(colors, text), + const SizedBox(height: 64), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _fromSection(colors, text), + const SizedBox(height: 32), + _refundAddressSection(colors, text), + const SizedBox(height: 32), + _swapDivider(colors), + const SizedBox(height: 32), + _toSection(colors, text), + const SizedBox(height: 32), + _infoSection(colors, text), + ], + ), + ), + ), + const SizedBox(height: 16), + _quoteButton(colors, text), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + Widget _header(AppColorsV2 colors, AppTextTheme text) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Swap', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Icon(Icons.info_outline, color: colors.textPrimary, size: 24), + ], + ); + } + + Widget _fromSection(AppColorsV2 colors, AppTextTheme text) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('From', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: SizedBox( + height: 56, + child: TextField( + controller: _fromController, + style: text.smallTitle?.copyWith(color: colors.textPrimary), + keyboardType: const TextInputType.numberWithOptions(decimal: true), + decoration: InputDecoration( + hintText: '0.00', + hintStyle: text.smallTitle?.copyWith(color: colors.textTertiary), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 16), + ), + ), + ), + ), + const SizedBox(width: 16), + GestureDetector( + onTap: _pickToken, + child: SizedBox( + width: 119, + child: GlassContainer( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + child: Row( + children: [ + Container( + width: 25, height: 25, + decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(_fromToken.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), + Text(_fromToken.network, style: text.detail?.copyWith(color: colors.textTertiary, fontSize: 10), overflow: TextOverflow.ellipsis), + ], + ), + ), + const SizedBox(width: 4), + Icon(Icons.keyboard_arrow_down, color: colors.textSecondary, size: 16), + ], + ), + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text('\$${_fromUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ); + } + + Widget _refundAddressSection(AppColorsV2 colors, AppTextTheme text) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('Refund Address', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(width: 4), + Icon(Icons.info_outline, color: colors.textTertiary, size: 14), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + child: TextField( + controller: _addressController, + style: text.smallParagraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration( + fillColor: Colors.transparent, + hintText: '${_fromToken.network} Address', + hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + ), + onChanged: (_) => setState(() {}), + ), + ), + ), + GestureDetector( + onTap: _scanQr, + child: SizedBox( + width: 40, height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.qr_code_scanner, color: colors.textPrimary, size: 20)), + ), + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () async { + final data = await Clipboard.getData('text/plain'); + if (data?.text != null) { + _addressController.text = data!.text!; + setState(() {}); + } + }, + child: SizedBox( + width: 40, height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.history, color: colors.textPrimary, size: 20)), + ), + ), + ), + const SizedBox(width: 8), + ], + ), + ], + ); + } + + Widget _swapDivider(AppColorsV2 colors) { + return Row( + children: [ + Expanded(child: Divider(color: colors.separator)), + SizedBox( + width: 40, height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.swap_vert, color: colors.textPrimary, size: 20)), + ), + ), + Expanded(child: Divider(color: colors.separator)), + ], + ); + } + + Widget _toSection(AppColorsV2 colors, AppTextTheme text) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('To', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Container( + height: 56, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + alignment: Alignment.centerLeft, + child: Text( + _toAmount > 0 ? _toAmount.toStringAsFixed(2) : '0.00', + style: text.smallTitle?.copyWith(color: _toAmount > 0 ? colors.textPrimary : colors.textTertiary), + ), + ), + ), + const SizedBox(width: 16), + SizedBox( + width: 119, + child: GlassContainer( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 25, height: 25, + decoration: BoxDecoration(color: colors.accentGreen.withValues(alpha: 0.3), shape: BoxShape.circle), + ), + const SizedBox(width: 8), + Text('QU', style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text('\$${_toUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ); + } + + Widget _infoSection(AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Slippage Tolerance', style: text.detail?.copyWith(color: colors.textTertiary)), + Row( + children: [ + Text('1%', style: text.detail?.copyWith(color: colors.textTertiary)), + const SizedBox(width: 4), + Icon(Icons.settings, color: colors.textTertiary, size: 12), + ], + ), + ], + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Rate', style: text.detail?.copyWith(color: colors.textTertiary)), + Text(_rateLabel, style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ], + ); + } + + Widget _quoteButton(AppColorsV2 colors, AppTextTheme text) { + final enabled = _canGetQuote && !_loading; + return GestureDetector( + onTap: enabled ? _getQuote : null, + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: enabled ? 0.44 : 0.1)), + ), + child: Center( + child: _loading + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) + : Text('Get a Quote', style: text.paragraph?.copyWith(color: colors.textPrimary.withValues(alpha: enabled ? 1.0 : 0.2), fontWeight: FontWeight.w500)), + ), + ), + ); + } +} + +class _QrScanPage extends StatelessWidget { + final ValueChanged onScanned; + const _QrScanPage({required this.onScanned}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + MobileScanner( + onDetect: (capture) { + final code = capture.barcodes.firstOrNull?.rawValue; + if (code != null) onScanned(code); + }, + ), + Positioned( + bottom: 60, left: 24, right: 24, + child: GestureDetector( + onTap: () => Navigator.pop(context), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + child: Center(child: Text('Cancel', style: TextStyle(color: colors.textPrimary, fontSize: 16))), + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart b/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart new file mode 100644 index 00000000..0bb17183 --- /dev/null +++ b/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart @@ -0,0 +1,86 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +Future showTokenPickerSheet(BuildContext context, List tokens, SwapToken current) { + return showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + builder: (_) => _TokenPickerContent(tokens: tokens, current: current), + ); +} + +class _TokenPickerContent extends StatelessWidget { + final List tokens; + final SwapToken current; + const _TokenPickerContent({required this.tokens, required this.current}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: colors.surface, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Select Token', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 18)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 24), + ...tokens.map((token) => _tokenRow(context, token, colors, text)), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + Widget _tokenRow(BuildContext context, SwapToken token, AppColorsV2 colors, AppTextTheme text) { + final selected = token == current; + return GestureDetector( + onTap: () => Navigator.pop(context, token), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + children: [ + Container( + width: 36, height: 36, + decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.2), shape: BoxShape.circle), + child: Center(child: Text(token.symbol[0], style: text.smallParagraph?.copyWith(color: colors.textPrimary))), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(token.symbol, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text('${token.name} · ${token.network}', style: text.detail?.copyWith(color: colors.textTertiary)), + ], + ), + ), + if (selected) Icon(Icons.check_circle, color: colors.accentGreen, size: 20), + ], + ), + ), + ); + } +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index e323585e..5fa8b34c 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -107,6 +107,10 @@ flutter: - assets/v2/send_button.png - assets/v2/receive_button.png - assets/v2/swap_button.png + - assets/v2/glass_border_bg.png + - assets/v2/glass_medium_button_bg.png + - assets/v2/glass_button_40_bg.png + - assets/v2/glass_button_40_bg.png fonts: - family: Fira Code diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 8de791c5..3628c09e 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -57,6 +57,7 @@ export 'src/services/recovery_service.dart'; export 'src/services/reversible_transfers_service.dart'; export 'src/services/settings_service.dart'; export 'src/services/substrate_service.dart'; +export 'src/services/swap_service.dart'; export 'src/services/taskmaster_service.dart'; export 'src/extensions/account_extension.dart'; export 'src/quantus_signing_payload.dart'; diff --git a/quantus_sdk/lib/src/services/swap_service.dart b/quantus_sdk/lib/src/services/swap_service.dart new file mode 100644 index 00000000..935d847b --- /dev/null +++ b/quantus_sdk/lib/src/services/swap_service.dart @@ -0,0 +1,179 @@ +import 'dart:math'; + +enum SwapStatus { pending, depositing, processing, complete, failed, expired } + +class SwapToken { + final String symbol; + final String name; + final String network; + final int decimals; + + const SwapToken({required this.symbol, required this.name, required this.network, this.decimals = 18}); + + @override + bool operator ==(Object other) => other is SwapToken && symbol == other.symbol && network == other.network; + + @override + int get hashCode => Object.hash(symbol, network); +} + +class SwapQuote { + final String quoteId; + final SwapToken fromToken; + final SwapToken toToken; + final double fromAmount; + final double toAmount; + final double rate; + final double networkFee; + final double totalAmount; + final double slippageTolerance; + final Duration expiresIn; + + const SwapQuote({ + required this.quoteId, + required this.fromToken, + required this.toToken, + required this.fromAmount, + required this.toAmount, + required this.rate, + required this.networkFee, + required this.totalAmount, + required this.slippageTolerance, + required this.expiresIn, + }); +} + +class SwapOrder { + final String orderId; + final SwapQuote quote; + final String depositAddress; + final SwapStatus status; + final DateTime createdAt; + + const SwapOrder({ + required this.orderId, + required this.quote, + required this.depositAddress, + required this.status, + required this.createdAt, + }); + + SwapOrder copyWith({SwapStatus? status}) => + SwapOrder(orderId: orderId, quote: quote, depositAddress: depositAddress, status: status ?? this.status, createdAt: createdAt); +} + +class SwapService { + static final SwapService _instance = SwapService._(); + factory SwapService() => _instance; + SwapService._(); + + final _orders = {}; + + static const availableTokens = [ + SwapToken(symbol: 'USDC', name: 'USD Coin', network: 'Ethereum'), + SwapToken(symbol: 'USDT', name: 'Tether', network: 'Ethereum'), + SwapToken(symbol: 'ETH', name: 'Ethereum', network: 'Ethereum'), + SwapToken(symbol: 'BTC', name: 'Bitcoin', network: 'Bitcoin', decimals: 8), + SwapToken(symbol: 'SOL', name: 'Solana', network: 'Solana', decimals: 9), + SwapToken(symbol: 'QU', name: 'Quantus', network: 'Quantus'), + ]; + + static const _quToken = SwapToken(symbol: 'QU', name: 'Quantus', network: 'Quantus'); + + List getFromTokens() => availableTokens.where((t) => t.symbol != 'QU').toList(); + + SwapToken getQuToken() => _quToken; + + double getRate(SwapToken from) { + switch (from.symbol) { + case 'USDC': + case 'USDT': + return 10.0; + case 'ETH': + return 25000.0; + case 'BTC': + return 60000.0; + case 'SOL': + return 1500.0; + default: + return 1.0; + } + } + + double getUsdPrice(SwapToken token) { + switch (token.symbol) { + case 'USDC': + case 'USDT': + return 1.0; + case 'ETH': + return 2500.0; + case 'BTC': + return 60000.0; + case 'SOL': + return 150.0; + case 'QU': + return 0.10; + default: + return 0.0; + } + } + + Future getQuote({required SwapToken fromToken, required double fromAmount, double slippage = 0.01}) async { + await Future.delayed(const Duration(milliseconds: 500)); + final rate = getRate(fromToken); + final toAmount = fromAmount * rate; + final networkFee = fromAmount * 0.005; + final totalAmount = fromAmount + networkFee; + + return SwapQuote( + quoteId: 'quote_${DateTime.now().millisecondsSinceEpoch}', + fromToken: fromToken, + toToken: _quToken, + fromAmount: fromAmount, + toAmount: toAmount, + rate: rate, + networkFee: networkFee, + totalAmount: totalAmount, + slippageTolerance: slippage, + expiresIn: const Duration(minutes: 5), + ); + } + + Future createSwap(SwapQuote quote) async { + await Future.delayed(const Duration(milliseconds: 300)); + final rng = Random(); + final hex = List.generate(40, (_) => rng.nextInt(16).toRadixString(16)).join(); + final order = SwapOrder( + orderId: 'swap_${DateTime.now().millisecondsSinceEpoch}', + quote: quote, + depositAddress: '0x$hex', + status: SwapStatus.depositing, + createdAt: DateTime.now(), + ); + _orders[order.orderId] = order; + return order; + } + + Future getSwapStatus(String orderId) async { + await Future.delayed(const Duration(milliseconds: 200)); + final order = _orders[orderId]; + if (order == null) throw Exception('Order not found'); + return order; + } + + Future confirmFundsSent(String orderId) async { + await Future.delayed(const Duration(milliseconds: 300)); + final order = _orders[orderId]; + if (order == null) throw Exception('Order not found'); + final updated = order.copyWith(status: SwapStatus.processing); + _orders[orderId] = updated; + + Future.delayed(const Duration(seconds: 5), () { + if (_orders.containsKey(orderId)) { + _orders[orderId] = _orders[orderId]!.copyWith(status: SwapStatus.complete); + } + }); + + return updated; + } +} From 925d5841aadcb3ec54fb2186be2cfe2bd12fa1e3 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 12:34:06 +0800 Subject: [PATCH 13/49] swap screens --- .../assets/v2/glass_button_wide_340_bg.png | Bin 0 -> 8423 bytes .../lib/v2/components/glass_container.dart | 1 + .../v2/screens/swap/review_quote_sheet.dart | 108 ++++++++++-------- .../lib/v2/screens/swap/swap_screen.dart | 92 ++++++++------- mobile-app/pubspec.yaml | 2 +- 5 files changed, 113 insertions(+), 90 deletions(-) create mode 100644 mobile-app/assets/v2/glass_button_wide_340_bg.png diff --git a/mobile-app/assets/v2/glass_button_wide_340_bg.png b/mobile-app/assets/v2/glass_button_wide_340_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..b4002a53cbb17e93d6f1b72a8867a4524bf87efd GIT binary patch literal 8423 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 Zc8E`(@pAQ$Y8F_;)WpKL=$cFP{{TWDb$$Q< literal 0 HcmV?d00001 diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index 90001170..911c1677 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -7,6 +7,7 @@ class GlassContainer extends StatelessWidget { static const mediumAsset = 'assets/v2/glass_medium_button_bg.png'; static const smallAsset = 'assets/v2/glass_button_40_bg.png'; + static const wideAsset = 'assets/v2/glass_button_wide_340_bg.png'; const GlassContainer({ super.key, diff --git a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart index 3fcda830..008375f1 100644 --- a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/screens/swap/deposit_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -30,9 +31,10 @@ class _ReviewQuoteContent extends StatelessWidget { return BackdropFilter( filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), child: Container( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40), decoration: BoxDecoration( - color: colors.surface, + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), ), child: Column( @@ -42,81 +44,91 @@ class _ReviewQuoteContent extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Review Quote', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 18)), + Text('Review Quote', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), GestureDetector( onTap: () => Navigator.pop(context), child: Icon(Icons.close, color: colors.textPrimary, size: 20), ), ], ), - const SizedBox(height: 24), - _swapVisual(colors, text, fromUsd, toUsd), const SizedBox(height: 32), + _swapVisual(context, colors, text, fromUsd, toUsd), + const SizedBox(height: 48), _feeRow('Total fees', '${quote.networkFee.toStringAsFixed(3)} ${quote.fromToken.symbol}', colors, text), Divider(color: colors.separator, height: 32), - _feeRow('Total Amount', '${quote.totalAmount.toStringAsFixed(2)} ${quote.fromToken.symbol}', colors, text), + _feeRow('Total Amount', '${quote.totalAmount.toStringAsFixed(2)} ${quote.fromToken.symbol}', colors, text, highlight: true), const SizedBox(height: 24), Text( 'You could receive up to \$${(quote.fromAmount * quote.slippageTolerance).toStringAsFixed(2)} less based on the ${(quote.slippageTolerance * 100).toStringAsFixed(0)}% slippage you set', - style: text.detail?.copyWith(color: colors.textTertiary), + style: text.tiny?.copyWith(color: colors.textSecondary, height: 1.35), ), const SizedBox(height: 24), _confirmButton(context, colors, text), - const SizedBox(height: 16), ], ), ), ); } - Widget _swapVisual(AppColorsV2 colors, AppTextTheme text, double fromUsd, double toUsd) { + Widget _swapVisual(BuildContext context, AppColorsV2 colors, AppTextTheme text, double fromUsd, double toUsd) { + final cardWidth = MediaQuery.of(context).size.width / 3; + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _tokenCard(quote.fromToken, quote.fromAmount, fromUsd, cardWidth, colors, text), + Icon(Icons.arrow_forward, color: colors.textSecondary, size: 20), + _tokenCard(quote.toToken, quote.toAmount, toUsd, cardWidth, colors, text), + ], + ); + } + + Widget _tokenCard(SwapToken token, double amount, double usd, double width, AppColorsV2 colors, AppTextTheme text) { + final isQu = token.symbol == 'QU'; return Container( - padding: const EdgeInsets.all(16), + width: width, height: 111, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), - child: Row( + child: Column( children: [ - Expanded(child: _tokenCard(quote.fromToken, quote.fromAmount, fromUsd, colors, text)), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Icon(Icons.arrow_forward, color: colors.textSecondary, size: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 22, height: 22, + decoration: BoxDecoration( + color: isQu ? colors.accentGreen.withValues(alpha: 0.3) : colors.accentPink.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(token.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text(token.network, style: text.tiny?.copyWith(color: colors.textSecondary)), + ], + ), + ], ), - Expanded(child: _tokenCard(quote.toToken, quote.toAmount, toUsd, colors, text)), + const SizedBox(height: 6), + Text(amount.toStringAsFixed(2), style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + const SizedBox(height: 0), + Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), ], ), ); } - Widget _tokenCard(SwapToken token, double amount, double usd, AppColorsV2 colors, AppTextTheme text) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 20, height: 20, - decoration: BoxDecoration( - color: token.symbol == 'QU' ? colors.accentGreen.withValues(alpha: 0.3) : colors.accentPink.withValues(alpha: 0.3), - shape: BoxShape.circle, - ), - ), - const SizedBox(width: 6), - Flexible(child: Text(token.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), - ], - ), - const SizedBox(height: 8), - Text(amount.toStringAsFixed(2), style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), - Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), - ], - ); - } - - Widget _feeRow(String label, String value, AppColorsV2 colors, AppTextTheme text) { + Widget _feeRow(String label, String value, AppColorsV2 colors, AppTextTheme text, {bool highlight = false}) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(label, style: text.detail?.copyWith(color: colors.textTertiary)), - Text(value, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text(label, style: text.detail?.copyWith(color: colors.textSecondary)), + Text(value, style: text.detail?.copyWith( + color: highlight ? colors.textPrimary : colors.textSecondary, + fontWeight: highlight ? FontWeight.w500 : null, + )), ], ); } @@ -130,13 +142,9 @@ class _ReviewQuoteContent extends StatelessWidget { Navigator.pop(context); Navigator.push(context, MaterialPageRoute(builder: (_) => DepositScreen(order: order))); }, - child: Container( - width: double.infinity, + child: GlassContainer( + asset: GlassContainer.wideAsset, padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.44)), - ), child: Center( child: Text('Confirm', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), ), diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 355b5f96..770debef 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -152,22 +152,28 @@ class _SwapScreenState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('From', style: text.detail?.copyWith(color: colors.textSecondary)), + Text('From', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), const SizedBox(height: 12), Row( children: [ Expanded( - child: SizedBox( + child: Container( height: 56, + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + alignment: Alignment.centerLeft, child: TextField( controller: _fromController, - style: text.smallTitle?.copyWith(color: colors.textPrimary), + style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.bold), keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( hintText: '0.00', - hintStyle: text.smallTitle?.copyWith(color: colors.textTertiary), + hintStyle: text.mediumTitle?.copyWith(color: colors.textTertiary, fontWeight: FontWeight.bold), border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 16), + isDense: true, + contentPadding: EdgeInsets.zero, + filled: true, + fillColor: Colors.transparent, ), ), ), @@ -190,8 +196,8 @@ class _SwapScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_fromToken.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), - Text(_fromToken.network, style: text.detail?.copyWith(color: colors.textTertiary, fontSize: 10), overflow: TextOverflow.ellipsis), + Text(_fromToken.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis), + Text(_fromToken.network, style: text.tiny?.copyWith(color: colors.textSecondary), overflow: TextOverflow.ellipsis), ], ), ), @@ -205,7 +211,13 @@ class _SwapScreenState extends State { ], ), const SizedBox(height: 4), - Text('\$${_fromUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + Row( + children: [ + Text('\$${_fromUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(width: 4), + Icon(Icons.swap_vert, color: colors.textSecondary, size: 12), + ], + ), ], ); } @@ -216,31 +228,34 @@ class _SwapScreenState extends State { children: [ Row( children: [ - Text('Refund Address', style: text.detail?.copyWith(color: colors.textSecondary)), + Text('Refund Address', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), const SizedBox(width: 4), - Icon(Icons.info_outline, color: colors.textTertiary, size: 14), + Icon(Icons.info_outline, color: colors.textSecondary, size: 14), ], ), const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: Container( - decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + Container( + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), + padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 8), + child: Row( + children: [ + Expanded( child: TextField( controller: _addressController, style: text.smallParagraph?.copyWith(color: colors.textPrimary), decoration: InputDecoration( - fillColor: Colors.transparent, hintText: '${_fromToken.network} Address', hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), + isDense: true, + contentPadding: EdgeInsets.zero, + filled: true, + fillColor: Colors.transparent, ), onChanged: (_) => setState(() {}), ), ), - ), + const SizedBox(width: 8), GestureDetector( onTap: _scanQr, child: SizedBox( @@ -268,9 +283,9 @@ class _SwapScreenState extends State { ), ), ), - const SizedBox(width: 8), ], ), + ), ], ); } @@ -295,7 +310,7 @@ class _SwapScreenState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('To', style: text.detail?.copyWith(color: colors.textSecondary)), + Text('To', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), const SizedBox(height: 12), Row( children: [ @@ -307,7 +322,7 @@ class _SwapScreenState extends State { alignment: Alignment.centerLeft, child: Text( _toAmount > 0 ? _toAmount.toStringAsFixed(2) : '0.00', - style: text.smallTitle?.copyWith(color: _toAmount > 0 ? colors.textPrimary : colors.textTertiary), + style: text.mediumTitle?.copyWith(fontWeight: FontWeight.bold, color: _toAmount > 0 ? colors.textPrimary : colors.textTertiary), ), ), ), @@ -324,7 +339,7 @@ class _SwapScreenState extends State { decoration: BoxDecoration(color: colors.accentGreen.withValues(alpha: 0.3), shape: BoxShape.circle), ), const SizedBox(width: 8), - Text('QU', style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text('QU', style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), ], ), ), @@ -332,7 +347,7 @@ class _SwapScreenState extends State { ], ), const SizedBox(height: 4), - Text('\$${_toUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), + Text('\$${_toUsd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textSecondary)), ], ); } @@ -343,12 +358,12 @@ class _SwapScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Slippage Tolerance', style: text.detail?.copyWith(color: colors.textTertiary)), + Text('Slippage Tolerance', style: text.detail?.copyWith(color: colors.textSecondary)), Row( children: [ - Text('1%', style: text.detail?.copyWith(color: colors.textTertiary)), + Text('1%', style: text.detail?.copyWith(color: colors.textSecondary)), const SizedBox(width: 4), - Icon(Icons.settings, color: colors.textTertiary, size: 12), + Icon(Icons.settings, color: colors.textSecondary, size: 12), ], ), ], @@ -357,8 +372,8 @@ class _SwapScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Rate', style: text.detail?.copyWith(color: colors.textTertiary)), - Text(_rateLabel, style: text.detail?.copyWith(color: colors.textTertiary)), + Text('Rate', style: text.detail?.copyWith(color: colors.textSecondary)), + Text(_rateLabel, style: text.detail?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500)), ], ), ], @@ -369,17 +384,16 @@ class _SwapScreenState extends State { final enabled = _canGetQuote && !_loading; return GestureDetector( onTap: enabled ? _getQuote : null, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: enabled ? 0.44 : 0.1)), - ), - child: Center( - child: _loading - ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) - : Text('Get a Quote', style: text.paragraph?.copyWith(color: colors.textPrimary.withValues(alpha: enabled ? 1.0 : 0.2), fontWeight: FontWeight.w500)), + child: Opacity( + opacity: enabled ? 1.0 : 0.4, + child: GlassContainer( + asset: GlassContainer.wideAsset, + padding: const EdgeInsets.symmetric(vertical: 20), + child: Center( + child: _loading + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) + : Text('Get a Quote', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), ), ), ); diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 5fa8b34c..cb2a90a5 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -110,7 +110,7 @@ flutter: - assets/v2/glass_border_bg.png - assets/v2/glass_medium_button_bg.png - assets/v2/glass_button_40_bg.png - - assets/v2/glass_button_40_bg.png + - assets/v2/glass_button_wide_340_bg.png fonts: - family: Fira Code From 69c1203adec58e4d38858b4e3b5a607352a9111a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 12:58:13 +0800 Subject: [PATCH 14/49] token symbol quan --- mobile-app/lib/v2/screens/swap/deposit_screen.dart | 2 +- mobile-app/lib/v2/screens/swap/review_quote_sheet.dart | 2 +- mobile-app/lib/v2/screens/swap/swap_screen.dart | 4 ++-- quantus_sdk/lib/src/constants/app_constants.dart | 2 +- .../lib/src/services/number_formatting_service.dart | 2 +- quantus_sdk/lib/src/services/swap_service.dart | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index e754763e..7229a980 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -223,7 +223,7 @@ class _DepositScreenState extends State { Text('Swap Complete', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), const SizedBox(height: 12), Text( - '${_order.quote.toAmount.toStringAsFixed(2)} QU has been added to your wallet', + '${_order.quote.toAmount.toStringAsFixed(2)} QUAN has been added to your wallet', style: text.paragraph?.copyWith(color: colors.textSecondary), textAlign: TextAlign.center, ), diff --git a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart index 008375f1..29cecb0f 100644 --- a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -83,7 +83,7 @@ class _ReviewQuoteContent extends StatelessWidget { } Widget _tokenCard(SwapToken token, double amount, double usd, double width, AppColorsV2 colors, AppTextTheme text) { - final isQu = token.symbol == 'QU'; + final isQu = token.symbol == 'QUAN'; return Container( width: width, height: 111, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 770debef..c6293386 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -28,7 +28,7 @@ class _SwapScreenState extends State { bool _loading = false; double get _rate => _swapService.getRate(_fromToken); - String get _rateLabel => '1 QU = ${(1 / _rate).toStringAsFixed(4)} ${_fromToken.symbol}'; + String get _rateLabel => '1 QUAN = ${(1 / _rate).toStringAsFixed(4)} ${_fromToken.symbol}'; @override void initState() { @@ -339,7 +339,7 @@ class _SwapScreenState extends State { decoration: BoxDecoration(color: colors.accentGreen.withValues(alpha: 0.3), shape: BoxShape.circle), ), const SizedBox(width: 8), - Text('QU', style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text('QUAN', style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), ], ), ), diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index a6014055..faabc0b4 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -2,7 +2,7 @@ class AppConstants { static const globalDebug = false; static const String appName = 'Quantus Wallet'; - static const String tokenSymbol = 'QU'; // fetch this from chain eventually + static const String tokenSymbol = 'QUAN'; // fetch this from chain eventually static const String shareUrl = 'https://linktr.ee/quantusnetwork'; static const String websiteBaseUrl = 'https://www.quantus.com'; diff --git a/quantus_sdk/lib/src/services/number_formatting_service.dart b/quantus_sdk/lib/src/services/number_formatting_service.dart index 40debb76..bbfd30f5 100644 --- a/quantus_sdk/lib/src/services/number_formatting_service.dart +++ b/quantus_sdk/lib/src/services/number_formatting_service.dart @@ -14,7 +14,7 @@ class NumberFormattingService { /// Example: 1234500000000 -> "1.2345" (with maxDecimals = 4) String formatBalance( BigInt balance, { - int maxDecimals = 4, + int maxDecimals = 2, bool addThousandsSeparators = true, bool addSymbol = false, }) { diff --git a/quantus_sdk/lib/src/services/swap_service.dart b/quantus_sdk/lib/src/services/swap_service.dart index 935d847b..15bd4f74 100644 --- a/quantus_sdk/lib/src/services/swap_service.dart +++ b/quantus_sdk/lib/src/services/swap_service.dart @@ -75,12 +75,12 @@ class SwapService { SwapToken(symbol: 'ETH', name: 'Ethereum', network: 'Ethereum'), SwapToken(symbol: 'BTC', name: 'Bitcoin', network: 'Bitcoin', decimals: 8), SwapToken(symbol: 'SOL', name: 'Solana', network: 'Solana', decimals: 9), - SwapToken(symbol: 'QU', name: 'Quantus', network: 'Quantus'), + SwapToken(symbol: 'QUAN', name: 'Quantus', network: 'Quantus'), ]; - static const _quToken = SwapToken(symbol: 'QU', name: 'Quantus', network: 'Quantus'); + static const _quToken = SwapToken(symbol: 'QUAN', name: 'Quantus', network: 'Quantus'); - List getFromTokens() => availableTokens.where((t) => t.symbol != 'QU').toList(); + List getFromTokens() => availableTokens.where((t) => t.symbol != 'QUAN').toList(); SwapToken getQuToken() => _quToken; @@ -111,7 +111,7 @@ class SwapService { return 60000.0; case 'SOL': return 150.0; - case 'QU': + case 'QUAN': return 0.10; default: return 0.0; From 0a50f884e876a81e1e51fbccf425132b28928d68 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 13:00:18 +0800 Subject: [PATCH 15/49] glass buttons --- .../lib/v2/components/glass_button.dart | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 mobile-app/lib/v2/components/glass_button.dart diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart new file mode 100644 index 00000000..8ea76a90 --- /dev/null +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -0,0 +1,99 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; + +class OutlinedGlassButton extends StatelessWidget { + final VoidCallback? onTap; + final Widget child; + final EdgeInsetsGeometry padding; + + const OutlinedGlassButton({ + super.key, + this.onTap, + required this.child, + this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: ClipRRect( + borderRadius: BorderRadius.circular(999), + child: BackdropFilter( + filter: ImageFilter.compose( + outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), + inner: ColorFilter.matrix([ + 0.8, 0, 0, 0, 0, + 0, 0.8, 0, 0, 0, + 0, 0, 0.8, 0, 0, + 0, 0, 0, 1, 0, + ]), + ), + child: Container( + padding: padding, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(999), + border: Border.all(color: Colors.white.withValues(alpha: 0.44), width: 1.5), + ), + child: child, + ), + ), + ), + ); + } +} + +class FilledGlassButton extends StatelessWidget { + final VoidCallback? onTap; + final Widget child; + final EdgeInsetsGeometry padding; + + const FilledGlassButton({ + super.key, + this.onTap, + required this.child, + this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: ClipRRect( + borderRadius: BorderRadius.circular(20), + child: BackdropFilter( + filter: ImageFilter.compose( + outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), + inner: ColorFilter.matrix([ + 1.05, 0, 0, 0, 0, + 0, 1.05, 0, 0, 0, + 0, 0, 1.05, 0, 0, + 0, 0, 0, 1, 0, + ]), + ), + child: Container( + padding: padding, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.white.withValues(alpha: 0.08)), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.15), blurRadius: 16, offset: const Offset(0, 4)), + ], + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.topCenter, + stops: [0, 0.01], + colors: [Color(0x33FFFFFF), Colors.transparent], + ), + ), + child: child, + ), + ), + ), + ); + } +} From 8a9c7b07b87fe665e18406435332d548be2f7cea Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 15:09:47 +0800 Subject: [PATCH 16/49] adding new glass button package --- .../lib/v2/components/glass_button.dart | 102 ++++++++--- .../lib/v2/screens/swap/deposit_screen.dart | 168 ++++++++++-------- .../swap/refund_address_picker_sheet.dart | 110 ++++++++++++ .../lib/v2/screens/swap/swap_screen.dart | 9 +- mobile-app/pubspec.yaml | 1 + .../lib/src/services/swap_service.dart | 21 +++ 6 files changed, 301 insertions(+), 110 deletions(-) create mode 100644 mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index 8ea76a90..b508740c 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -5,11 +5,13 @@ class OutlinedGlassButton extends StatelessWidget { final VoidCallback? onTap; final Widget child; final EdgeInsetsGeometry padding; + final double radius; const OutlinedGlassButton({ super.key, this.onTap, required this.child, + this.radius = 14, this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), }); @@ -18,24 +20,32 @@ class OutlinedGlassButton extends StatelessWidget { return GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: BorderRadius.circular(999), + borderRadius: BorderRadius.circular(radius), child: BackdropFilter( filter: ImageFilter.compose( outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - inner: ColorFilter.matrix([ + inner: const ColorFilter.matrix([ 0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0, ]), ), - child: Container( - padding: padding, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(999), - border: Border.all(color: Colors.white.withValues(alpha: 0.44), width: 1.5), + child: CustomPaint( + painter: _ShimmerBorderPainter(radius: radius), + child: Container( + padding: padding, + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(radius), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0, 0.03, 0.97, 1], + colors: [Color(0x1AFFFFFF), Colors.transparent, Colors.transparent, Color(0x0D000000)], + ), + ), + child: child, ), - child: child, ), ), ), @@ -43,15 +53,52 @@ class OutlinedGlassButton extends StatelessWidget { } } +class _ShimmerBorderPainter extends CustomPainter { + final double radius; + _ShimmerBorderPainter({required this.radius}); + + @override + void paint(Canvas canvas, Size size) { + final rect = Offset.zero & size; + final rrect = RRect.fromRectAndRadius(rect.deflate(0.5), Radius.circular(radius)); + + final shimmer = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5 + ..shader = const RadialGradient( + center: Alignment.topCenter, + radius: 1.8, + colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + ).createShader(rect) + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 0.8); + canvas.drawRRect(rrect, shimmer); + + final crisp = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 0.889 + ..shader = const RadialGradient( + center: Alignment.topCenter, + radius: 1.8, + colors: [Color(0x66FFFFFF), Color(0x28FFFFFF)], + ).createShader(rect); + canvas.drawRRect(rrect, crisp); + } + + @override + bool shouldRepaint(covariant _ShimmerBorderPainter old) => old.radius != radius; +} + class FilledGlassButton extends StatelessWidget { final VoidCallback? onTap; final Widget child; final EdgeInsetsGeometry padding; + final double radius; const FilledGlassButton({ super.key, this.onTap, required this.child, + this.radius = 14, this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), }); @@ -60,37 +107,36 @@ class FilledGlassButton extends StatelessWidget { return GestureDetector( onTap: onTap, child: ClipRRect( - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(radius), child: BackdropFilter( filter: ImageFilter.compose( outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - inner: ColorFilter.matrix([ + inner: const ColorFilter.matrix([ 1.05, 0, 0, 0, 0, 0, 1.05, 0, 0, 0, 0, 0, 1.05, 0, 0, 0, 0, 0, 1, 0, ]), ), - child: Container( - padding: padding, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.white.withValues(alpha: 0.08)), - boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.15), blurRadius: 16, offset: const Offset(0, 4)), - ], - ), - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.topCenter, - stops: [0, 0.01], - colors: [Color(0x33FFFFFF), Colors.transparent], + child: CustomPaint( + painter: _ShimmerBorderPainter(radius: radius), + child: Container( + padding: padding, + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(radius), + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(radius), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0, 0.03, 0.97, 1], + colors: [Color(0x1AFFFFFF), Colors.transparent, Colors.transparent, Color(0x0D000000)], + ), ), + child: child, ), - child: child, ), ), ), diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index 7229a980..8cec1f20 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -114,17 +115,19 @@ class _DepositScreenState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Deposit Amount', style: text.detail?.copyWith(color: colors.textSecondary)), - const SizedBox(width: 8), + Text('Deposit Amount', style: text.smallParagraph?.copyWith(color: colors.textPrimary, height: 1.35)), + const SizedBox(width: 6), GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: quote.totalAmount.toStringAsFixed(2))); - }, - child: Icon(Icons.copy, color: colors.textTertiary, size: 14), + onTap: () => Clipboard.setData(ClipboardData(text: quote.totalAmount.toStringAsFixed(2))), + child: Container( + width: 20, height: 20, + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(4)), + child: Center(child: Icon(Icons.copy, color: colors.textPrimary, size: 12)), + ), ), ], ), - const SizedBox(height: 12), + const SizedBox(height: 14), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -133,74 +136,97 @@ class _DepositScreenState extends State { decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle), ), const SizedBox(width: 8), - Text(quote.totalAmount.toStringAsFixed(2), style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 24)), + Text(quote.totalAmount.toStringAsFixed(2), style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), ], ), - Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textTertiary)), - const SizedBox(height: 32), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12)), - child: QrImageView(data: _order.depositAddress, version: QrVersions.auto, size: 180), + const SizedBox(height: 8), + Text('\$${usd.toStringAsFixed(2)}', style: text.smallParagraph?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500)), + const SizedBox(height: 40), + ClipRRect( + borderRadius: BorderRadius.circular(9), + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(8), + child: QrImageView(data: _order.depositAddress, version: QrVersions.auto, size: 184), + ), ), const SizedBox(height: 16), - GestureDetector( - onTap: _copyAddress, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, + SizedBox( + width: 264, + child: Stack( children: [ - Flexible( - child: Text( - _order.depositAddress, - style: text.detail?.copyWith(color: colors.textSecondary, fontSize: 11), - textAlign: TextAlign.center, - maxLines: 2, + Text( + _order.depositAddress.toLowerCase(), + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500, height: 1.35), + textAlign: TextAlign.center, + ), + Positioned( + right: 0, + top: 19, + child: GestureDetector( + onTap: _copyAddress, + child: Container( + width: 20, height: 20, + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(4)), + child: Center(child: Icon(Icons.copy, color: colors.textPrimary, size: 12)), + ), ), ), - const SizedBox(width: 4), - Icon(Icons.copy, color: colors.textTertiary, size: 12), ], ), ), - const SizedBox(height: 24), + const SizedBox(height: 40), Row( children: [ - Expanded(child: _actionBtn(Icons.copy, 'Copy', _copyAddress, colors, text)), + Expanded( + child: OutlinedGlassButton( + onTap: _copyAddress, + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.copy, color: colors.textPrimary, size: 20), + const SizedBox(width: 8), + Text('Copy', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ), const SizedBox(width: 16), - Expanded(child: _actionBtn(Icons.qr_code, 'Share QR', () {}, colors, text)), + Expanded( + child: OutlinedGlassButton( + onTap: () {}, + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.qr_code, color: colors.textPrimary, size: 20), + const SizedBox(width: 8), + Text('Share QR', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ], + ), + ), + ), ], ), - const SizedBox(height: 24), - Text( - 'Use your ${quote.fromToken.symbol} or ${quote.fromToken.network} wallet to deposit funds. Depositing other assets may result in loss of funds.', - style: text.detail?.copyWith(color: colors.textTertiary), + const SizedBox(height: 40), + Text.rich( + TextSpan( + style: text.detail?.copyWith(color: colors.textSecondary, height: 1.35), + children: [ + const TextSpan(text: 'Use your '), + TextSpan(text: quote.fromToken.symbol, style: const TextStyle(fontWeight: FontWeight.w600)), + const TextSpan(text: ' or '), + TextSpan(text: quote.fromToken.network, style: const TextStyle(fontWeight: FontWeight.w600)), + const TextSpan(text: ' wallet to deposit funds. Depositing other assets may result in loss of funds.'), + ], + ), textAlign: TextAlign.center, ), ], ); } - Widget _actionBtn(IconData icon, String label, VoidCallback onTap, AppColorsV2 colors, AppTextTheme text) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.2)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: colors.textPrimary, size: 18), - const SizedBox(width: 8), - Text(label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), - ], - ), - ), - ); - } - Widget _processingBody(AppColorsV2 colors, AppTextTheme text) { return Column( children: [ @@ -232,37 +258,23 @@ class _DepositScreenState extends State { } Widget _sentButton(AppColorsV2 colors, AppTextTheme text) { - return GestureDetector( + return FilledGlassButton( onTap: _confirming ? null : _confirmSent, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.44)), - ), - child: Center( - child: _confirming - ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) - : Text("I've sent the funds", style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), - ), + padding: const EdgeInsets.symmetric(vertical: 20), + child: Center( + child: _confirming + ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) + : Text("I've sent the funds", style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), ), ); } Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { - return GestureDetector( + return FilledGlassButton( onTap: () => Navigator.popUntil(context, (r) => r.isFirst), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.44)), - ), - child: Center( - child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), - ), + padding: const EdgeInsets.symmetric(vertical: 20), + child: Center( + child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), ), ); } diff --git a/mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart b/mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart new file mode 100644 index 00000000..d6961238 --- /dev/null +++ b/mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart @@ -0,0 +1,110 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +Future showRefundAddressPickerSheet(BuildContext context, String network) { + return showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + builder: (_) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), + child: _RefundAddressPickerContent(network: network), + ), + ); +} + +class _RefundAddressPickerContent extends StatefulWidget { + final String network; + const _RefundAddressPickerContent({required this.network}); + + @override + State<_RefundAddressPickerContent> createState() => _RefundAddressPickerContentState(); +} + +class _RefundAddressPickerContentState extends State<_RefundAddressPickerContent> { + List _addresses = []; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final addresses = await SwapService().getRefundAddresses(widget.network); + if (mounted) setState(() => _addresses = addresses); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Refund Addresses', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 8), + Align( + alignment: Alignment.centerLeft, + child: Text(widget.network, style: text.detail?.copyWith(color: colors.textSecondary)), + ), + const SizedBox(height: 24), + if (_addresses.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 32), + child: Text('No recent refund addresses', style: text.detail?.copyWith(color: colors.textTertiary)), + ) + else + ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 300), + child: ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: _addresses.length, + separatorBuilder: (_, _) => Divider(color: colors.separator, height: 1), + itemBuilder: (_, i) => _addressItem(_addresses[i], colors, text), + ), + ), + ], + ), + ), + ); + } + + Widget _addressItem(String address, AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: () => Navigator.pop(context, address), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text( + AddressFormattingService.formatAddress(address), + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index c6293386..4f9abd07 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/swap/refund_address_picker_sheet.dart'; import 'package:resonance_network_wallet/v2/screens/swap/review_quote_sheet.dart'; import 'package:resonance_network_wallet/v2/screens/swap/token_picker_sheet.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -66,6 +66,7 @@ class _SwapScreenState extends State { final quote = await _swapService.getQuote(fromToken: _fromToken, fromAmount: amount); if (!mounted) return; setState(() => _loading = false); + _swapService.addRefundAddress(_fromToken.network, _addressController.text.trim()); showReviewQuoteSheet(context, quote, _addressController.text); } catch (e) { setState(() => _loading = false); @@ -269,9 +270,9 @@ class _SwapScreenState extends State { const SizedBox(width: 8), GestureDetector( onTap: () async { - final data = await Clipboard.getData('text/plain'); - if (data?.text != null) { - _addressController.text = data!.text!; + final address = await showRefundAddressPickerSheet(context, _fromToken.network); + if (address != null) { + _addressController.text = address; setState(() {}); } }, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index cb2a90a5..73a5ef06 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -57,6 +57,7 @@ dependencies: timezone: ^0.10.1 flutter_timezone: ^5.0.1 collection: ^1.19.1 + liquid_glass_container_plus: ^1.0.5 dev_dependencies: flutter_test: diff --git a/quantus_sdk/lib/src/services/swap_service.dart b/quantus_sdk/lib/src/services/swap_service.dart index 15bd4f74..0d1f9237 100644 --- a/quantus_sdk/lib/src/services/swap_service.dart +++ b/quantus_sdk/lib/src/services/swap_service.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:shared_preferences/shared_preferences.dart'; enum SwapStatus { pending, depositing, processing, complete, failed, expired } @@ -67,6 +68,8 @@ class SwapService { factory SwapService() => _instance; SwapService._(); + static const _refundAddressKey = 'recent_refund_addresses'; + static const _maxRefundAddresses = 50; final _orders = {}; static const availableTokens = [ @@ -176,4 +179,22 @@ class SwapService { return updated; } + + Future addRefundAddress(String network, String address) async { + final prefs = await SharedPreferences.getInstance(); + final key = '${_refundAddressKey}_${network.toLowerCase()}'; + var addresses = prefs.getStringList(key) ?? []; + addresses.remove(address); + addresses.insert(0, address); + if (addresses.length > _maxRefundAddresses) { + addresses = addresses.sublist(0, _maxRefundAddresses); + } + await prefs.setStringList(key, addresses); + } + + Future> getRefundAddresses(String network) async { + final prefs = await SharedPreferences.getInstance(); + final key = '${_refundAddressKey}_${network.toLowerCase()}'; + return prefs.getStringList(key) ?? []; + } } From 7d10e9f88acebec77258ff78ba2b94bbe25b9428 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 15:22:22 +0800 Subject: [PATCH 17/49] testnet flag added --- mobile-app/lib/v2/screens/swap/deposit_screen.dart | 8 ++++++++ quantus_sdk/lib/src/constants/app_constants.dart | 5 +---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index 8cec1f20..f8fddf2c 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -253,6 +253,14 @@ class _DepositScreenState extends State { style: text.paragraph?.copyWith(color: colors.textSecondary), textAlign: TextAlign.center, ), + const SizedBox(height: 40), + if (AppConstants.stillOnTestnet) + Text( + 'DEMO ONLY - WE ARE STILL ON TESTNET', + style: text.paragraph?.copyWith(color: Colors.yellow), + textAlign: TextAlign.center, + ), + ], ); } diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index faabc0b4..38e9d074 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -9,6 +9,7 @@ class AppConstants { // static const List rpcEndpoints = ['ws://127.0.0.1:9944']; // local testing // static const List graphQlEndpoints = ['http://127.0.0.1:4350']; // local testing + static const stillOnTestnet = true; static const List rpcEndpoints = [ 'https://a1-dirac.quantus.cat', 'https://a2-dirac.quantus.cat', @@ -31,10 +32,6 @@ class AppConstants { static const String communityUrl = 'https://t.me/quantusnetwork'; static const String faucetBotUrl = 'https://t.me/QuantusFaucetBot'; - // Old Resonance chain endpoints - previous chain - static const String oldResonanceRpcEndpoint = 'wss://a.t.res.fm:443'; - static const String odlGraphQlEndpoint = 'https://gql.res.fm'; - // Development accounts static const String crystalAlice = '//Crystal Alice'; static const String crystalBob = '//Crystal Bob'; From a93e6a46d687cada7e5fe992297cecfdb13995f0 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 17:20:24 +0800 Subject: [PATCH 18/49] recovery phrase screen added --- .../v2/screens/settings/auto_lock_screen.dart | 119 ++++++++++ .../settings/recovery_phrase_screen.dart | 211 ++++++++++++++++++ .../settings/select_wallet_screen.dart | 78 +++++++ .../v2/screens/settings/settings_screen.dart | 15 +- .../lib/v2/screens/swap/deposit_screen.dart | 2 +- 5 files changed, 418 insertions(+), 7 deletions(-) create mode 100644 mobile-app/lib/v2/screens/settings/auto_lock_screen.dart create mode 100644 mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart create mode 100644 mobile-app/lib/v2/screens/settings/select_wallet_screen.dart diff --git a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart new file mode 100644 index 00000000..e7acd2f5 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/services/local_auth_service.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class AutoLockScreen extends StatefulWidget { + const AutoLockScreen({super.key}); + + @override + State createState() => _AutoLockScreenState(); +} + +class _AutoLockScreenState extends State { + final _authService = LocalAuthService(); + late int _selected; + + static const _options = [ + (value: 0, label: '30 Seconds'), + (value: 1, label: '1 minute'), + (value: 5, label: '5 minutes'), + (value: 15, label: '15 minutes'), + (value: 60, label: '1 hour'), + (value: -1, label: 'Never'), + ]; + + @override + void initState() { + super.initState(); + _selected = _authService.getAuthTimeoutMinutes(); + } + + void _confirm() { + _authService.setAuthTimeoutMinutes(_selected); + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Auto-Lock', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), + const SizedBox(height: 80), + Text( + 'Automatically lock wallet after\nperiod of inactivity', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500, height: 1.35), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + child: Column( + children: [ + for (var i = 0; i < _options.length; i++) ...[ + if (i > 0) Divider(color: colors.separator, height: 1), + _optionRow(_options[i].value, _options[i].label, colors, text), + ], + ], + ), + ), + const Spacer(), + OutlinedGlassButton( + onTap: _confirm, + padding: const EdgeInsets.symmetric(vertical: 20), + child: Center( + child: Text('Confirm', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + Widget _optionRow(int value, String label, AppColorsV2 colors, AppTextTheme text) { + final selected = _selected == value; + return GestureDetector( + onTap: () => setState(() => _selected = value), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + children: [ + Icon( + selected ? Icons.radio_button_checked : Icons.radio_button_unchecked, + color: selected ? colors.textPrimary : colors.textSecondary, + size: 24, + ), + const SizedBox(width: 8), + Text(label, style: text.paragraph?.copyWith(color: colors.textPrimary)), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart new file mode 100644 index 00000000..127ae2a8 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -0,0 +1,211 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/features/components/snackbar_helper.dart'; +import 'package:resonance_network_wallet/services/local_auth_service.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class RecoveryPhraseScreen extends StatefulWidget { + const RecoveryPhraseScreen({super.key, this.walletIndex = 0}); + + final int walletIndex; + + @override + State createState() => _RecoveryPhraseScreenState(); +} + +class _RecoveryPhraseScreenState extends State { + final _settingsService = SettingsService(); + final _authService = LocalAuthService(); + List _words = []; + bool _revealed = false; + + Future _toggleReveal() async { + if (_revealed) { + setState(() { + _revealed = false; + _words = []; + }); + return; + } + if (_authService.isLocalAuthEnabled()) { + final ok = await _authService.authenticate( + localizedReason: 'Authenticate to reveal recovery phrase', + biometricOnly: false, + ); + if (!ok || !mounted) return; + } + final mnemonic = await _settingsService.getMnemonic(widget.walletIndex); + if (mnemonic != null && mounted) { + setState(() { + _words = mnemonic.split(' '); + _revealed = true; + }); + } + } + + void _copyToClipboard() { + Clipboard.setData(ClipboardData(text: _words.join(' '))); + showCopySnackbar(context, title: 'Copied!', message: 'Recovery phrase copied to clipboard'); + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Recovery Phrase', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), + const SizedBox(height: 40), + _warning(colors, text), + const SizedBox(height: 40), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + _wordGrid(colors, text), + if (_revealed) ...[ + const SizedBox(height: 24), + _copyRow(colors, text), + ], + ], + ), + ), + ), + const SizedBox(height: 16), + _revealButton(colors, text), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + Widget _warning(AppColorsV2 colors, AppTextTheme text) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.warning_amber_rounded, color: colors.accentPink, size: 24), + const SizedBox(width: 8), + Text('Important Warning', style: text.smallTitle?.copyWith(color: colors.accentPink)), + ], + ), + const SizedBox(height: 8), + Text( + 'Your recovery phrase is the only way to restore your wallet. Never share it with anyone. Anyone with your recovery phrase has full access to your funds.', + style: text.smallParagraph?.copyWith(color: colors.textSecondary, height: 1.5), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _wordGrid(AppColorsV2 colors, AppTextTheme text) { + final count = _revealed ? _words.length : 24; + final rows = []; + for (var i = 0; i < count; i += 3) { + final chips = []; + for (var j = i; j < i + 3 && j < count; j++) { + final word = _revealed ? _words[j] : 'blurred'; + chips.add(Expanded(child: _wordChip(j + 1, word, colors, text))); + if (j < i + 2 && j < count - 1) chips.add(const SizedBox(width: 9)); + } + if (rows.isNotEmpty) rows.add(const SizedBox(height: 9)); + rows.add(Row(children: chips)); + } + return Column(children: rows); + } + + Widget _wordChip(int index, String word, AppColorsV2 colors, AppTextTheme text) { + final wordWidget = Text( + word, + style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis, + ); + + return Container( + height: 36, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: colors.border), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(width: 6), + Expanded( + child: _revealed + ? wordWidget + : Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ImageFiltered(imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), child: wordWidget), + ), + ), + ], + ), + ); + } + + Widget _copyRow(AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: _copyToClipboard, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Copy to clipboard', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(width: 8), + Icon(Icons.copy, color: colors.textPrimary, size: 14), + ], + ), + ); + } + + Widget _revealButton(AppColorsV2 colors, AppTextTheme text) { + return OutlinedGlassButton( + onTap: _toggleReveal, + padding: const EdgeInsets.symmetric(vertical: 20), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _revealed ? Icons.visibility_off_outlined : Icons.visibility_outlined, + color: colors.textPrimary, + size: 16, + ), + const SizedBox(width: 8), + Text( + _revealed ? 'Hide Recovery Phrase' : 'Reveal Recovery Phrase', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart b/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart new file mode 100644 index 00000000..40407e67 --- /dev/null +++ b/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class SelectWalletScreen extends ConsumerWidget { + const SelectWalletScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final colors = context.colors; + final text = context.themeText; + final accountsAsync = ref.watch(accountsProvider); + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Select Wallet', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(width: 24), + ], + ), + const SizedBox(height: 48), + Expanded( + child: accountsAsync.when( + loading: () => const Center(child: CircularProgressIndicator(color: Colors.white24)), + error: (e, _) => Center(child: Text('Failed to load wallets', style: text.paragraph?.copyWith(color: colors.textSecondary))), + data: (accounts) { + final indices = getNonHardwareWalletIndices(accounts); + if (indices.isEmpty) { + return Center(child: Text('No wallets found', style: text.paragraph?.copyWith(color: colors.textSecondary))); + } + return ListView.separated( + itemCount: indices.length, + separatorBuilder: (_, _) => const SizedBox(height: 12), + itemBuilder: (_, i) => _walletItem(context, indices[i], colors, text), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _walletItem(BuildContext context, int walletIndex, AppColorsV2 colors, AppTextTheme text) { + return GestureDetector( + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndex))), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration(color: colors.surfaceCard, borderRadius: BorderRadius.circular(14)), + child: Row( + children: [ + Expanded(child: Text('Wallet ${walletIndex + 1}', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + Icon(Icons.chevron_right, color: colors.textSecondary, size: 20), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 2ca6dd22..b4cbafc4 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -4,9 +4,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/reset_confirmation_bottom_sheet.dart'; import 'package:resonance_network_wallet/features/components/snackbar_helper.dart'; -import 'package:resonance_network_wallet/features/main/screens/authentication_settings_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/select_wallet_for_recovery_phrase_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/show_recovery_phrase_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/select_wallet_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/welcome_screen.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; @@ -16,6 +15,7 @@ import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/settings/auto_lock_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/change_pin_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -102,9 +102,9 @@ class _SettingsScreenV2State extends ConsumerState { final walletIndices = getNonHardwareWalletIndices(accounts); if (walletIndices.isEmpty) return; if (walletIndices.length == 1) { - Navigator.push(context, MaterialPageRoute(builder: (_) => ShowRecoveryPhraseScreen(walletIndex: walletIndices.first))); + Navigator.push(context, MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndices.first))); } else { - Navigator.push(context, MaterialPageRoute(builder: (_) => const SelectWalletForRecoveryPhraseScreen())); + Navigator.push(context, MaterialPageRoute(builder: (_) => const SelectWalletScreen())); } }); } @@ -183,7 +183,10 @@ class _SettingsScreenV2State extends ConsumerState { }), _divider(colors), _chevronItem('Auto-Lock', _autoLockLabel(), colors, text, - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const AuthenticationSettingsScreen()))), + onTap: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const AutoLockScreen())); + _loadSettings(); + }), ]), const SizedBox(height: 40), _section('Wallet', colors, text, [ diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index f8fddf2c..abebcf14 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -249,7 +249,7 @@ class _DepositScreenState extends State { Text('Swap Complete', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), const SizedBox(height: 12), Text( - '${_order.quote.toAmount.toStringAsFixed(2)} QUAN has been added to your wallet', + 'Your swap for ${_order.quote.toAmount.toStringAsFixed(2)} QUAN is processing.', style: text.paragraph?.copyWith(color: colors.textSecondary), textAlign: TextAlign.center, ), From 9506d4f87a663406d075b761cc3e66458c91d140 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 17:57:52 +0800 Subject: [PATCH 19/49] formatting --- mobile-app/lib/v2/components/back_button.dart | 7 +- .../lib/v2/components/glass_button.dart | 31 ++-- .../lib/v2/components/glass_container.dart | 16 +- .../v2/components/gradient_background.dart | 10 +- .../v2/screens/activity/activity_screen.dart | 35 ++++- .../activity/transaction_detail_sheet.dart | 47 +++--- .../lib/v2/screens/activity/tx_item.dart | 17 +- .../lib/v2/screens/home/activity_section.dart | 17 +- .../lib/v2/screens/home/home_screen.dart | 31 ++-- .../lib/v2/screens/receive/receive_sheet.dart | 33 ++-- .../v2/screens/send/address_picker_sheet.dart | 17 +- .../lib/v2/screens/send/send_sheet.dart | 44 ++++-- .../v2/screens/settings/auto_lock_screen.dart | 5 +- .../screens/settings/change_pin_screen.dart | 26 +-- .../settings/recovery_phrase_screen.dart | 5 +- .../settings/select_wallet_screen.dart | 21 ++- .../v2/screens/settings/settings_screen.dart | 123 +++++++++++---- .../lib/v2/screens/swap/deposit_screen.dart | 78 ++++++--- .../v2/screens/swap/review_quote_sheet.dart | 57 +++++-- .../lib/v2/screens/swap/swap_screen.dart | 81 +++++++--- .../v2/screens/swap/token_picker_sheet.dart | 12 +- mobile-app/lib/v2/theme/app_colors.dart | 54 +++---- mobile-app/lib/v2/theme/app_spacing.dart | 148 +++++++++--------- mobile-app/lib/v2/theme/app_text_styles.dart | 148 ++++-------------- mobile-app/lib/v2/theme/app_theme.dart | 26 +-- .../lib/src/services/swap_service.dart | 9 +- 26 files changed, 622 insertions(+), 476 deletions(-) diff --git a/mobile-app/lib/v2/components/back_button.dart b/mobile-app/lib/v2/components/back_button.dart index 65bb64c0..09cafb2a 100644 --- a/mobile-app/lib/v2/components/back_button.dart +++ b/mobile-app/lib/v2/components/back_button.dart @@ -11,7 +11,12 @@ class AppBackButton extends StatelessWidget { Widget build(BuildContext context) { return GestureDetector( onTap: onTap ?? () => Navigator.pop(context), - child: SvgPicture.asset('assets/v2/caret_left.svg', width: 24, height: 24, colorFilter: ColorFilter.mode(context.colors.textPrimary, BlendMode.srcIn)), + child: SvgPicture.asset( + 'assets/v2/caret_left.svg', + width: 24, + height: 24, + colorFilter: ColorFilter.mode(context.colors.textPrimary, BlendMode.srcIn), + ), ); } } diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index b508740c..0f38b595 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -24,12 +24,7 @@ class OutlinedGlassButton extends StatelessWidget { child: BackdropFilter( filter: ImageFilter.compose( outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - inner: const ColorFilter.matrix([ - 0.8, 0, 0, 0, 0, - 0, 0.8, 0, 0, 0, - 0, 0, 0.8, 0, 0, - 0, 0, 0, 1, 0, - ]), + inner: const ColorFilter.matrix([0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0]), ), child: CustomPaint( painter: _ShimmerBorderPainter(radius: radius), @@ -112,10 +107,26 @@ class FilledGlassButton extends StatelessWidget { filter: ImageFilter.compose( outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), inner: const ColorFilter.matrix([ - 1.05, 0, 0, 0, 0, - 0, 1.05, 0, 0, 0, - 0, 0, 1.05, 0, 0, - 0, 0, 0, 1, 0, + 1.05, + 0, + 0, + 0, + 0, + 0, + 1.05, + 0, + 0, + 0, + 0, + 0, + 1.05, + 0, + 0, + 0, + 0, + 0, + 1, + 0, ]), ), child: CustomPaint( diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index 911c1677..f46d7554 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -9,24 +9,14 @@ class GlassContainer extends StatelessWidget { static const smallAsset = 'assets/v2/glass_button_40_bg.png'; static const wideAsset = 'assets/v2/glass_button_wide_340_bg.png'; - const GlassContainer({ - super.key, - required this.child, - this.padding, - this.asset = mediumAsset, - }); + const GlassContainer({super.key, required this.child, this.padding, this.asset = mediumAsset}); @override Widget build(BuildContext context) { return Stack( children: [ - Positioned.fill( - child: Image.asset(asset, fit: BoxFit.fill), - ), - Padding( - padding: padding ?? EdgeInsets.zero, - child: child, - ), + Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), + Padding(padding: padding ?? EdgeInsets.zero, child: child), ], ); } diff --git a/mobile-app/lib/v2/components/gradient_background.dart b/mobile-app/lib/v2/components/gradient_background.dart index 9670b58d..4fcfd208 100644 --- a/mobile-app/lib/v2/components/gradient_background.dart +++ b/mobile-app/lib/v2/components/gradient_background.dart @@ -54,19 +54,13 @@ class _EllipseGlowPainter extends CustomPainter { canvas.save(); canvas.translate(176.56 * sx + ox, 77.88 * sy + oy); canvas.rotate(30 * pi / 180); - canvas.drawOval( - Rect.fromCenter(center: Offset.zero, width: 66.46 * sx, height: 406.13 * sy), - paint, - ); + canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: 66.46 * sx, height: 406.13 * sy), paint); canvas.restore(); canvas.save(); canvas.translate(367.38 * sx + ox, 41.54 * sy + oy); canvas.rotate(30 * pi / 180); - canvas.drawOval( - Rect.fromCenter(center: Offset.zero, width: 33.41 * sx, height: 446.53 * sy), - paint, - ); + canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: 33.41 * sx, height: 446.53 * sy), paint); canvas.restore(); } diff --git a/mobile-app/lib/v2/screens/activity/activity_screen.dart b/mobile-app/lib/v2/screens/activity/activity_screen.dart index 5d80ca92..20fd5246 100644 --- a/mobile-app/lib/v2/screens/activity/activity_screen.dart +++ b/mobile-app/lib/v2/screens/activity/activity_screen.dart @@ -42,12 +42,16 @@ class ActivityScreen extends ConsumerWidget { Expanded( child: accountAsync.when( loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), - error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + error: (e, _) => Center( + child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError)), + ), data: (active) { if (active == null) return const Center(child: Text('No account')); return txAsync.when( loading: () => Center(child: CircularProgressIndicator(color: colors.textPrimary)), - error: (e, _) => Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + error: (e, _) => Center( + child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError)), + ), data: (data) { final txService = ref.read(transactionServiceProvider); final all = txService.combineAndDeduplicateTransactions( @@ -57,7 +61,12 @@ class ActivityScreen extends ConsumerWidget { otherTransfers: data.otherTransfers, ); if (all.isEmpty) { - return Center(child: Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary))); + return Center( + child: Text( + 'No transactions yet', + style: text.paragraph?.copyWith(color: colors.textSecondary), + ), + ); } final grouped = _groupByDate(all); return ListView.builder( @@ -69,13 +78,25 @@ class ActivityScreen extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (i > 0) const SizedBox(height: 40), - Text(group.label, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + group.label, + style: text.paragraph?.copyWith( + color: colors.textPrimary, + fontWeight: FontWeight.w500, + ), + ), const SizedBox(height: 20), ...group.transactions.map((tx) { final itemData = TxItemData.from(tx, active.account.accountId); - return buildTxItem(tx, itemData, colors, text, onTap: () { - showTransactionDetailSheet(context, tx, active.account.accountId); - }); + return buildTxItem( + tx, + itemData, + colors, + text, + onTap: () { + showTransactionDetailSheet(context, tx, active.account.accountId); + }, + ); }), ], ); diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index bd566f65..49fe47cc 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -84,8 +84,8 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { final label = widget.tx.isReversibleScheduled ? (_isSend ? 'Pending' : 'Receiving') : _isSend - ? 'Sent' - : 'Received'; + ? 'Sent' + : 'Received'; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -107,24 +107,26 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { return Container( width: double.infinity, padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(14), - ), + decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(14)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('$amount ${AppConstants.tokenSymbol}', - style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 32, fontWeight: FontWeight.w600)), + Text( + '$amount ${AppConstants.tokenSymbol}', + style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 32, fontWeight: FontWeight.w600), + ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(date, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + date, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), const SizedBox(height: 8), Text('At $time', style: text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5))), ], @@ -141,14 +143,19 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(direction, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + direction, + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), const SizedBox(height: 12), Row( children: [ Expanded( - child: Text(address, - style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), - overflow: TextOverflow.ellipsis), + child: Text( + address, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis, + ), ), const SizedBox(width: 8), _copyButton(colors, _counterparty), @@ -176,10 +183,7 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { child: Container( width: 20, height: 20, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), - ), + decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4)), child: const Icon(Icons.copy, size: 12, color: Colors.white), ), ); @@ -219,7 +223,10 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('View in Explorer', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'View in Explorer', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), const SizedBox(width: 8), Icon(Icons.open_in_new, size: 16, color: colors.textPrimary), ], @@ -234,8 +241,8 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { final transactionType = isMinerReward ? 'miner-rewards' : (tx.isReversibleScheduled || tx.isReversibleExecuted || tx.isReversibleCancelled) - ? 'reversible-transactions' - : 'immediate-transactions'; + ? 'reversible-transactions' + : 'immediate-transactions'; String? path; if (tx.extrinsicHash != null) { diff --git a/mobile-app/lib/v2/screens/activity/tx_item.dart b/mobile-app/lib/v2/screens/activity/tx_item.dart index ddfe4713..fe5d7167 100644 --- a/mobile-app/lib/v2/screens/activity/tx_item.dart +++ b/mobile-app/lib/v2/screens/activity/tx_item.dart @@ -32,19 +32,19 @@ class TxItemData { label: isScheduled ? (isSend ? 'Pending' : 'Receiving') : isSend - ? 'Sent' - : 'Received', + ? 'Sent' + : 'Received', timeLabel: isScheduled ? _formatDuration(tx.timeRemaining) : _timeAgo(tx.timestamp), iconBg: isScheduled && !isSend ? const Color(0x2927F027) : isScheduled && isSend - ? const Color(0x29FFBC42) - : const Color(0xFF292929), + ? const Color(0x29FFBC42) + : const Color(0xFF292929), iconColor: isScheduled && !isSend ? const Color(0xFF27F027) : isScheduled && isSend - ? const Color(0xFFFFBC42) - : const Color(0x80FFFFFF), + ? const Color(0xFFFFBC42) + : const Color(0x80FFFFFF), isSend: isSend, amount: '${fmt.formatBalance(tx.amount)} ${AppConstants.tokenSymbol}', counterpartyAddr: _shortenAddress(isSend ? tx.to : tx.from), @@ -87,7 +87,10 @@ Widget buildTxItem(TransactionEvent tx, TxItemData data, AppColorsV2 colors, App children: [ Text(data.amount, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), const SizedBox(height: 2), - Text('${data.isSend ? "To" : "From"}: ${data.counterpartyAddr}', style: text.detail?.copyWith(color: colors.textTertiary)), + Text( + '${data.isSend ? "To" : "From"}: ${data.counterpartyAddr}', + style: text.detail?.copyWith(color: colors.textTertiary), + ), ], ), ], diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 51eb418f..ffe26f36 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -43,9 +43,15 @@ class ActivitySection extends ConsumerWidget { const SizedBox(height: 24), ...all.take(5).map((tx) { final data = TxItemData.from(tx, activeAccount.accountId); - return buildTxItem(tx, data, colors, text, onTap: () { - showTransactionDetailSheet(context, tx, activeAccount.accountId); - }); + return buildTxItem( + tx, + data, + colors, + text, + onTap: () { + showTransactionDetailSheet(context, tx, activeAccount.accountId); + }, + ); }), ], ); @@ -85,7 +91,10 @@ class ActivitySection extends ConsumerWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Activity', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'Activity', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), GestureDetector( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ActivityScreen())), child: Text( diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index 45edf45c..ae2a1e38 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -37,11 +37,7 @@ class _HomeScreenState extends ConsumerState { if (active != null) { ref.invalidate(balanceProviderFamily); await ref - .read( - filteredPaginationControllerProviderFamily( - AccountIdListCache.get([active.account.accountId]), - ).notifier, - ) + .read(filteredPaginationControllerProviderFamily(AccountIdListCache.get([active.account.accountId])).notifier) .loadingRefresh(); } ref.invalidate(balanceProviderRaw); @@ -75,7 +71,9 @@ class _HomeScreenState extends ConsumerState { ), error: (e, _) => Scaffold( backgroundColor: colors.background, - body: Center(child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError))), + body: Center( + child: Text('Error: $e', style: text.detail?.copyWith(color: colors.textError)), + ), ), data: (active) { if (active == null) { @@ -94,11 +92,7 @@ class _HomeScreenState extends ConsumerState { slivers: [ SliverToBoxAdapter(child: _buildContent(active, balanceAsync, colors, text)), SliverToBoxAdapter( - child: ActivitySection( - txAsync: txAsync, - activeAccount: active.account, - onRetry: _refresh, - ), + child: ActivitySection(txAsync: txAsync, activeAccount: active.account, onRetry: _refresh), ), const SliverToBoxAdapter(child: SizedBox(height: 120)), ], @@ -199,10 +193,7 @@ class _HomeScreenState extends ConsumerState { Text(' ${AppConstants.tokenSymbol}', style: text.smallTitle?.copyWith(color: colors.textPrimary)), ], ), - error: (_, _) => Text( - 'Error loading balance', - style: text.detail?.copyWith(color: colors.textError), - ), + error: (_, _) => Text('Error loading balance', style: text.detail?.copyWith(color: colors.textError)), ), if (!_balanceHidden) ...[ const SizedBox(height: 6), @@ -219,17 +210,17 @@ class _HomeScreenState extends ConsumerState { const SizedBox(width: 15), _actionCard('assets/v2/send_button.png', () => showSendSheetV2(context)), const SizedBox(width: 15), - _actionCard('assets/v2/swap_button.png', () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen()))), + _actionCard( + 'assets/v2/swap_button.png', + () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen())), + ), ], ); } Widget _actionCard(String asset, VoidCallback onTap) { return Expanded( - child: GestureDetector( - onTap: onTap, - child: Image.asset(asset), - ), + child: GestureDetector(onTap: onTap, child: Image.asset(asset)), ); } } diff --git a/mobile-app/lib/v2/screens/receive/receive_sheet.dart b/mobile-app/lib/v2/screens/receive/receive_sheet.dart index 3dabdc4f..e72b3e23 100644 --- a/mobile-app/lib/v2/screens/receive/receive_sheet.dart +++ b/mobile-app/lib/v2/screens/receive/receive_sheet.dart @@ -59,7 +59,12 @@ class _ReceiveSheetState extends State { '${_checksum != null ? '\n\nCheckphrase: $_checksum' : ''}' '\n\nTo open in the app or download:\n${AppConstants.websiteBaseUrl}/account?id=$_accountId'; SharePlus.instance.share( - ShareParams(text: text, subject: 'Shared Address', title: 'Shared Address', sharePositionOrigin: context.sharePositionRect()), + ShareParams( + text: text, + subject: 'Shared Address', + title: 'Shared Address', + sharePositionOrigin: context.sharePositionRect(), + ), ); } } @@ -167,7 +172,11 @@ class _ReceiveSheetState extends State { future: _checksumFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { - return SizedBox(height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textSecondary)); + return SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator(strokeWidth: 2, color: colors.textSecondary), + ); } if (!snapshot.hasData || snapshot.data == null || snapshot.data!.isEmpty) return const SizedBox.shrink(); @@ -222,7 +231,10 @@ class _ReceiveSheetState extends State { children: [ Icon(Icons.copy, size: 20, color: colors.textPrimary), const SizedBox(width: 8), - Text('Copy', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'Copy', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ], ), ), @@ -234,16 +246,16 @@ class _ReceiveSheetState extends State { onTap: _share, child: Container( padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - color: colors.surfaceGlass, - borderRadius: BorderRadius.circular(14), - ), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.share, size: 20, color: colors.textPrimary), const SizedBox(width: 8), - Text('Share', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'Share', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ], ), ), @@ -260,9 +272,6 @@ void showReceiveSheetV2(BuildContext context) { backgroundColor: Colors.transparent, isScrollControlled: true, constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), - builder: (_) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), - child: const ReceiveSheet(), - ), + builder: (_) => BackdropFilter(filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), child: const ReceiveSheet()), ); } diff --git a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart index 0207a0a4..a486a69d 100644 --- a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart @@ -119,12 +119,17 @@ class _AddressPickerSheetState extends State { const SizedBox(height: 40), Align( alignment: Alignment.centerLeft, - child: Text('Recents', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + 'Recents', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), const SizedBox(height: 24), Expanded( child: _filtered.isEmpty - ? Center(child: Text('No recent addresses', style: text.detail?.copyWith(color: colors.textTertiary))) + ? Center( + child: Text('No recent addresses', style: text.detail?.copyWith(color: colors.textTertiary)), + ) : ListView.separated( padding: EdgeInsets.zero, itemCount: _filtered.length, @@ -151,8 +156,7 @@ class _AddressPickerSheetState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (checksum != null) - Text(checksum, style: text.smallParagraph?.copyWith(color: colors.accentPink)), + if (checksum != null) Text(checksum, style: text.smallParagraph?.copyWith(color: colors.accentPink)), const SizedBox(height: 4), Text( AddressFormattingService.formatAddress(address), @@ -174,9 +178,6 @@ Future showAddressPickerSheet(BuildContext context) { backgroundColor: Colors.transparent, isScrollControlled: true, constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), - builder: (_) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), - child: const AddressPickerSheet(), - ), + builder: (_) => BackdropFilter(filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), child: const AddressPickerSheet()), ); } diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index 575df6bf..543e2b4e 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -114,7 +114,10 @@ class _SendSheetState extends ConsumerState { } Future _scanQr() async { - final address = await Navigator.push(context, MaterialPageRoute(fullscreenDialog: true, builder: (_) => const _QrScanPage())); + final address = await Navigator.push( + context, + MaterialPageRoute(fullscreenDialog: true, builder: (_) => const _QrScanPage()), + ); if (address != null && mounted) { _recipientController.text = address; } @@ -139,7 +142,13 @@ class _SendSheetState extends ConsumerState { final settings = SettingsService(); final account = (await settings.getActiveRegularAccount())!; final submissionService = ref.read(transactionSubmissionServiceProvider); - await submissionService.balanceTransfer(account, _recipientController.text.trim(), _amount, _networkFee, _blockHeight); + await submissionService.balanceTransfer( + account, + _recipientController.text.trim(), + _amount, + _networkFee, + _blockHeight, + ); RecentAddressesService().addAddress(_recipientController.text.trim()); if (mounted) setState(() => _step = _Step.complete); } catch (e) { @@ -262,7 +271,12 @@ class _SendSheetState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AddressFormattingService.formatAddress(_recipientController.text.trim(), prefix: 15, ellipses: '.......', postFix: 14), + AddressFormattingService.formatAddress( + _recipientController.text.trim(), + prefix: 15, + ellipses: '.......', + postFix: 14, + ), style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -367,9 +381,15 @@ class _SendSheetState extends ConsumerState { children: [ _header(colors, text, onBack: _backToForm), const SizedBox(height: 72), - Text('${_fmt.formatBalance(_amount)} ${AppConstants.tokenSymbol}', style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontSize: 32)), + Text( + '${_fmt.formatBalance(_amount)} ${AppConstants.tokenSymbol}', + style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontSize: 32), + ), const SizedBox(height: 64), - Text('To:', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + 'To:', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), const SizedBox(height: 12), Text( AddressFormattingService.formatAddress(recipient, prefix: 15, ellipses: '.......', postFix: 14), @@ -456,7 +476,11 @@ class _SendSheetState extends ConsumerState { border: Border.all(color: Colors.white.withValues(alpha: 0.44), width: 0.889), borderRadius: BorderRadius.circular(14), ), - child: Text(label, textAlign: TextAlign.center, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + label, + textAlign: TextAlign.center, + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ), ); @@ -525,11 +549,11 @@ class _QrScanPageState extends State<_QrScanPage> { onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), - decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), - borderRadius: BorderRadius.circular(14), + decoration: BoxDecoration(color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(14)), + child: const Text( + 'Cancel', + style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), ), - child: const Text('Cancel', style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500)), ), ), ), diff --git a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart index e7acd2f5..f5c59104 100644 --- a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart +++ b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart @@ -83,7 +83,10 @@ class _AutoLockScreenState extends State { onTap: _confirm, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( - child: Text('Confirm', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + 'Confirm', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ), const SizedBox(height: 24), diff --git a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart index ba2e3730..b0eb538b 100644 --- a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart +++ b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart @@ -127,13 +127,8 @@ class _ChangePinScreenState extends State { children: [ const SizedBox(height: 16), _header(colors, text), - Expanded( - child: _step == _Step.success ? _successBody(colors, text) : _pinBody(colors, text), - ), - if (_step == _Step.success) ...[ - _doneButton(colors, text), - const SizedBox(height: 24), - ], + Expanded(child: _step == _Step.success ? _successBody(colors, text) : _pinBody(colors, text)), + if (_step == _Step.success) ...[_doneButton(colors, text), const SizedBox(height: 24)], ], ), ], @@ -164,7 +159,10 @@ class _ChangePinScreenState extends State { child: Column( children: [ const SizedBox(height: 80), - Text(_stepTitle, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 24, fontWeight: FontWeight.w400)), + Text( + _stepTitle, + style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 24, fontWeight: FontWeight.w400), + ), const SizedBox(height: 32), _pinDots(colors), if (_error != null) ...[ @@ -214,8 +212,11 @@ class _ChangePinScreenState extends State { const SizedBox(height: 24), Padding( padding: const EdgeInsets.symmetric(horizontal: 24), - child: Text('Your PIN has been updated successfully', - style: text.paragraph?.copyWith(color: colors.textSecondary), textAlign: TextAlign.center), + child: Text( + 'Your PIN has been updated successfully', + style: text.paragraph?.copyWith(color: colors.textSecondary), + textAlign: TextAlign.center, + ), ), const Spacer(flex: 3), ], @@ -235,7 +236,10 @@ class _ChangePinScreenState extends State { border: Border.all(color: Colors.white.withValues(alpha: 0.44)), ), child: Center( - child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + 'Done', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ), ), diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 127ae2a8..5ca06e9a 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -84,10 +84,7 @@ class _RecoveryPhraseScreenState extends State { child: Column( children: [ _wordGrid(colors, text), - if (_revealed) ...[ - const SizedBox(height: 24), - _copyRow(colors, text), - ], + if (_revealed) ...[const SizedBox(height: 24), _copyRow(colors, text)], ], ), ), diff --git a/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart b/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart index 40407e67..3c834205 100644 --- a/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart @@ -38,11 +38,18 @@ class SelectWalletScreen extends ConsumerWidget { Expanded( child: accountsAsync.when( loading: () => const Center(child: CircularProgressIndicator(color: Colors.white24)), - error: (e, _) => Center(child: Text('Failed to load wallets', style: text.paragraph?.copyWith(color: colors.textSecondary))), + error: (e, _) => Center( + child: Text( + 'Failed to load wallets', + style: text.paragraph?.copyWith(color: colors.textSecondary), + ), + ), data: (accounts) { final indices = getNonHardwareWalletIndices(accounts); if (indices.isEmpty) { - return Center(child: Text('No wallets found', style: text.paragraph?.copyWith(color: colors.textSecondary))); + return Center( + child: Text('No wallets found', style: text.paragraph?.copyWith(color: colors.textSecondary)), + ); } return ListView.separated( itemCount: indices.length, @@ -62,13 +69,19 @@ class SelectWalletScreen extends ConsumerWidget { Widget _walletItem(BuildContext context, int walletIndex, AppColorsV2 colors, AppTextTheme text) { return GestureDetector( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndex))), + onTap: () => + Navigator.push(context, MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndex))), child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration(color: colors.surfaceCard, borderRadius: BorderRadius.circular(14)), child: Row( children: [ - Expanded(child: Text('Wallet ${walletIndex + 1}', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + Expanded( + child: Text( + 'Wallet ${walletIndex + 1}', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), + ), Icon(Icons.chevron_right, color: colors.textSecondary, size: 20), ], ), diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index b4cbafc4..30677120 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -102,7 +102,10 @@ class _SettingsScreenV2State extends ConsumerState { final walletIndices = getNonHardwareWalletIndices(accounts); if (walletIndices.isEmpty) return; if (walletIndices.length == 1) { - Navigator.push(context, MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndices.first))); + Navigator.push( + context, + MaterialPageRoute(builder: (_) => RecoveryPhraseScreen(walletIndex: walletIndices.first)), + ); } else { Navigator.push(context, MaterialPageRoute(builder: (_) => const SelectWalletScreen())); } @@ -176,17 +179,27 @@ class _SettingsScreenV2State extends ConsumerState { _section('Security', colors, text, [ _toggleItem('Biometric Lock', _biometricDesc, _biometricEnabled, _toggleBiometric, colors, text), _divider(colors), - _chevronItem('PIN Code', _hasPinSet ? '6-digit code' : 'Not set', colors, text, - onTap: () async { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const ChangePinScreen())); - _loadPinState(); - }), + _chevronItem( + 'PIN Code', + _hasPinSet ? '6-digit code' : 'Not set', + colors, + text, + onTap: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const ChangePinScreen())); + _loadPinState(); + }, + ), _divider(colors), - _chevronItem('Auto-Lock', _autoLockLabel(), colors, text, - onTap: () async { - await Navigator.push(context, MaterialPageRoute(builder: (_) => const AutoLockScreen())); - _loadSettings(); - }), + _chevronItem( + 'Auto-Lock', + _autoLockLabel(), + colors, + text, + onTap: () async { + await Navigator.push(context, MaterialPageRoute(builder: (_) => const AutoLockScreen())); + _loadSettings(); + }, + ), ]), const SizedBox(height: 40), _section('Wallet', colors, text, [ @@ -194,7 +207,14 @@ class _SettingsScreenV2State extends ConsumerState { ]), const SizedBox(height: 40), _section('Reversible Transactions', colors, text, [ - _toggleItem('Reversible Transactions', _reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, _toggleReversible, colors, text), + _toggleItem( + 'Reversible Transactions', + _reversibleEnabled ? 'Enabled' : 'Disabled', + _reversibleEnabled, + _toggleReversible, + colors, + text, + ), _divider(colors), _chevronItem('Time Limit', _timeLimitLabel(), colors, text, onTap: () {}), _divider(colors), @@ -212,17 +232,42 @@ class _SettingsScreenV2State extends ConsumerState { _section('Preferences', colors, text, [ _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), _divider(colors), - _toggleItem('Notifications', notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', notifConfig.enabled, _toggleNotifications, colors, text), + _toggleItem( + 'Notifications', + notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', + notifConfig.enabled, + _toggleNotifications, + colors, + text, + ), ]), const SizedBox(height: 40), _section('About & Support', colors, text, [ - _externalItem('Help & Support', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl))), + _externalItem( + 'Help & Support', + null, + colors, + text, + onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl)), + ), _divider(colors), _chevronItem('About Quantus', 'Version 1.0.1', colors, text, onTap: () {}), _divider(colors), - _externalItem('Terms of Service', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + _externalItem( + 'Terms of Service', + null, + colors, + text, + onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl)), + ), _divider(colors), - _externalItem('Privacy Policy', null, colors, text, onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl))), + _externalItem( + 'Privacy Policy', + null, + colors, + text, + onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl)), + ), ]), const SizedBox(height: 40), _resetButton(colors, text), @@ -241,21 +286,28 @@ class _SettingsScreenV2State extends ConsumerState { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + title, + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: colors.surfaceCard, - borderRadius: BorderRadius.circular(14), - ), + decoration: BoxDecoration(color: colors.surfaceCard, borderRadius: BorderRadius.circular(14)), child: Column(children: children), ), ], ); } - Widget _toggleItem(String title, String subtitle, bool value, ValueChanged onChanged, AppColorsV2 colors, AppTextTheme text) { + Widget _toggleItem( + String title, + String subtitle, + bool value, + ValueChanged onChanged, + AppColorsV2 colors, + AppTextTheme text, + ) { return Row( children: [ Expanded( @@ -268,16 +320,18 @@ class _SettingsScreenV2State extends ConsumerState { ], ), ), - CupertinoSwitch( - value: value, - onChanged: onChanged, - activeTrackColor: colors.accentGreen, - ), + CupertinoSwitch(value: value, onChanged: onChanged, activeTrackColor: colors.accentGreen), ], ); } - Widget _chevronItem(String title, String subtitle, AppColorsV2 colors, AppTextTheme text, {required VoidCallback onTap}) { + Widget _chevronItem( + String title, + String subtitle, + AppColorsV2 colors, + AppTextTheme text, { + required VoidCallback onTap, + }) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, @@ -299,7 +353,13 @@ class _SettingsScreenV2State extends ConsumerState { ); } - Widget _externalItem(String title, String? subtitle, AppColorsV2 colors, AppTextTheme text, {required VoidCallback onTap}) { + Widget _externalItem( + String title, + String? subtitle, + AppColorsV2 colors, + AppTextTheme text, { + required VoidCallback onTap, + }) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, @@ -359,7 +419,10 @@ class _SettingsScreenV2State extends ConsumerState { border: Border.all(color: colors.danger), ), child: Center( - child: Text('Reset Quantus', style: text.paragraph?.copyWith(color: colors.danger, fontWeight: FontWeight.w500)), + child: Text( + 'Reset Quantus', + style: text.paragraph?.copyWith(color: colors.danger, fontWeight: FontWeight.w500), + ), ), ), ); diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index abebcf14..adef505c 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -57,9 +57,9 @@ class _DepositScreenState extends State { void _copyAddress() { Clipboard.setData(ClipboardData(text: _order.depositAddress)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Address copied'), duration: Duration(seconds: 1)), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Address copied'), duration: Duration(seconds: 1))); } @override @@ -120,7 +120,8 @@ class _DepositScreenState extends State { GestureDetector( onTap: () => Clipboard.setData(ClipboardData(text: quote.totalAmount.toStringAsFixed(2))), child: Container( - width: 20, height: 20, + width: 20, + height: 20, decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(4)), child: Center(child: Icon(Icons.copy, color: colors.textPrimary, size: 12)), ), @@ -132,15 +133,22 @@ class _DepositScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - width: 28, height: 28, + width: 28, + height: 28, decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle), ), const SizedBox(width: 8), - Text(quote.totalAmount.toStringAsFixed(2), style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + quote.totalAmount.toStringAsFixed(2), + style: text.mediumTitle?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), ], ), const SizedBox(height: 8), - Text('\$${usd.toStringAsFixed(2)}', style: text.smallParagraph?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500)), + Text( + '\$${usd.toStringAsFixed(2)}', + style: text.smallParagraph?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500), + ), const SizedBox(height: 40), ClipRRect( borderRadius: BorderRadius.circular(9), @@ -157,7 +165,11 @@ class _DepositScreenState extends State { children: [ Text( _order.depositAddress.toLowerCase(), - style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500, height: 1.35), + style: text.smallParagraph?.copyWith( + color: colors.textPrimary, + fontWeight: FontWeight.w500, + height: 1.35, + ), textAlign: TextAlign.center, ), Positioned( @@ -166,7 +178,8 @@ class _DepositScreenState extends State { child: GestureDetector( onTap: _copyAddress, child: Container( - width: 20, height: 20, + width: 20, + height: 20, decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(4)), child: Center(child: Icon(Icons.copy, color: colors.textPrimary, size: 12)), ), @@ -187,7 +200,10 @@ class _DepositScreenState extends State { children: [ Icon(Icons.copy, color: colors.textPrimary, size: 20), const SizedBox(width: 8), - Text('Copy', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'Copy', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ], ), ), @@ -202,7 +218,10 @@ class _DepositScreenState extends State { children: [ Icon(Icons.qr_code, color: colors.textPrimary, size: 20), const SizedBox(width: 8), - Text('Share QR', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'Share QR', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ], ), ), @@ -215,9 +234,15 @@ class _DepositScreenState extends State { style: text.detail?.copyWith(color: colors.textSecondary, height: 1.35), children: [ const TextSpan(text: 'Use your '), - TextSpan(text: quote.fromToken.symbol, style: const TextStyle(fontWeight: FontWeight.w600)), + TextSpan( + text: quote.fromToken.symbol, + style: const TextStyle(fontWeight: FontWeight.w600), + ), const TextSpan(text: ' or '), - TextSpan(text: quote.fromToken.network, style: const TextStyle(fontWeight: FontWeight.w600)), + TextSpan( + text: quote.fromToken.network, + style: const TextStyle(fontWeight: FontWeight.w600), + ), const TextSpan(text: ' wallet to deposit funds. Depositing other assets may result in loss of funds.'), ], ), @@ -255,12 +280,11 @@ class _DepositScreenState extends State { ), const SizedBox(height: 40), if (AppConstants.stillOnTestnet) - Text( - 'DEMO ONLY - WE ARE STILL ON TESTNET', - style: text.paragraph?.copyWith(color: Colors.yellow), - textAlign: TextAlign.center, - ), - + Text( + 'DEMO ONLY - WE ARE STILL ON TESTNET', + style: text.paragraph?.copyWith(color: Colors.yellow), + textAlign: TextAlign.center, + ), ], ); } @@ -271,8 +295,15 @@ class _DepositScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _confirming - ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) - : Text("I've sent the funds", style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2), + ) + : Text( + "I've sent the funds", + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ); } @@ -282,7 +313,10 @@ class _DepositScreenState extends State { onTap: () => Navigator.popUntil(context, (r) => r.isFirst), padding: const EdgeInsets.symmetric(vertical: 20), child: Center( - child: Text('Done', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + 'Done', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ); } diff --git a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart index 29cecb0f..f7340fe0 100644 --- a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -56,7 +56,13 @@ class _ReviewQuoteContent extends StatelessWidget { const SizedBox(height: 48), _feeRow('Total fees', '${quote.networkFee.toStringAsFixed(3)} ${quote.fromToken.symbol}', colors, text), Divider(color: colors.separator, height: 32), - _feeRow('Total Amount', '${quote.totalAmount.toStringAsFixed(2)} ${quote.fromToken.symbol}', colors, text, highlight: true), + _feeRow( + 'Total Amount', + '${quote.totalAmount.toStringAsFixed(2)} ${quote.fromToken.symbol}', + colors, + text, + highlight: true, + ), const SizedBox(height: 24), Text( 'You could receive up to \$${(quote.fromAmount * quote.slippageTolerance).toStringAsFixed(2)} less based on the ${(quote.slippageTolerance * 100).toStringAsFixed(0)}% slippage you set', @@ -85,7 +91,8 @@ class _ReviewQuoteContent extends StatelessWidget { Widget _tokenCard(SwapToken token, double amount, double usd, double width, AppColorsV2 colors, AppTextTheme text) { final isQu = token.symbol == 'QUAN'; return Container( - width: width, height: 111, + width: width, + height: 111, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), child: Column( @@ -95,26 +102,36 @@ class _ReviewQuoteContent extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 22, height: 22, + width: 22, + height: 22, decoration: BoxDecoration( color: isQu ? colors.accentGreen.withValues(alpha: 0.3) : colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle, ), ), const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(token.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), - Text(token.network, style: text.tiny?.copyWith(color: colors.textSecondary)), - ], - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + token.symbol, + style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), + Text(token.network, style: text.tiny?.copyWith(color: colors.textSecondary)), + ], + ), ], ), const SizedBox(height: 6), - Text(amount.toStringAsFixed(2), style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + amount.toStringAsFixed(2), + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), const SizedBox(height: 0), - Text('\$${usd.toStringAsFixed(2)}', style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + '\$${usd.toStringAsFixed(2)}', + style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ], ), ); @@ -125,10 +142,13 @@ class _ReviewQuoteContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: text.detail?.copyWith(color: colors.textSecondary)), - Text(value, style: text.detail?.copyWith( - color: highlight ? colors.textPrimary : colors.textSecondary, - fontWeight: highlight ? FontWeight.w500 : null, - )), + Text( + value, + style: text.detail?.copyWith( + color: highlight ? colors.textPrimary : colors.textSecondary, + fontWeight: highlight ? FontWeight.w500 : null, + ), + ), ], ); } @@ -146,7 +166,10 @@ class _ReviewQuoteContent extends StatelessWidget { asset: GlassContainer.wideAsset, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( - child: Text('Confirm', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + child: Text( + 'Confirm', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ), ); diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 4f9abd07..05f5ffe5 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -85,10 +85,12 @@ class _SwapScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (_) => _QrScanPage(onScanned: (v) { - _addressController.text = v; - Navigator.pop(context); - }), + builder: (_) => _QrScanPage( + onScanned: (v) { + _addressController.text = v; + Navigator.pop(context); + }, + ), ), ); } @@ -189,16 +191,28 @@ class _SwapScreenState extends State { child: Row( children: [ Container( - width: 25, height: 25, - decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.3), shape: BoxShape.circle), + width: 25, + height: 25, + decoration: BoxDecoration( + color: colors.accentPink.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), ), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(_fromToken.symbol, style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), overflow: TextOverflow.ellipsis), - Text(_fromToken.network, style: text.tiny?.copyWith(color: colors.textSecondary), overflow: TextOverflow.ellipsis), + Text( + _fromToken.symbol, + style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + Text( + _fromToken.network, + style: text.tiny?.copyWith(color: colors.textSecondary), + overflow: TextOverflow.ellipsis, + ), ], ), ), @@ -260,7 +274,8 @@ class _SwapScreenState extends State { GestureDetector( onTap: _scanQr, child: SizedBox( - width: 40, height: 40, + width: 40, + height: 40, child: GlassContainer( asset: GlassContainer.smallAsset, child: Center(child: Icon(Icons.qr_code_scanner, color: colors.textPrimary, size: 20)), @@ -277,7 +292,8 @@ class _SwapScreenState extends State { } }, child: SizedBox( - width: 40, height: 40, + width: 40, + height: 40, child: GlassContainer( asset: GlassContainer.smallAsset, child: Center(child: Icon(Icons.history, color: colors.textPrimary, size: 20)), @@ -296,7 +312,8 @@ class _SwapScreenState extends State { children: [ Expanded(child: Divider(color: colors.separator)), SizedBox( - width: 40, height: 40, + width: 40, + height: 40, child: GlassContainer( asset: GlassContainer.smallAsset, child: Center(child: Icon(Icons.swap_vert, color: colors.textPrimary, size: 20)), @@ -323,7 +340,10 @@ class _SwapScreenState extends State { alignment: Alignment.centerLeft, child: Text( _toAmount > 0 ? _toAmount.toStringAsFixed(2) : '0.00', - style: text.mediumTitle?.copyWith(fontWeight: FontWeight.bold, color: _toAmount > 0 ? colors.textPrimary : colors.textTertiary), + style: text.mediumTitle?.copyWith( + fontWeight: FontWeight.bold, + color: _toAmount > 0 ? colors.textPrimary : colors.textTertiary, + ), ), ), ), @@ -336,11 +356,18 @@ class _SwapScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 25, height: 25, - decoration: BoxDecoration(color: colors.accentGreen.withValues(alpha: 0.3), shape: BoxShape.circle), + width: 25, + height: 25, + decoration: BoxDecoration( + color: colors.accentGreen.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), ), const SizedBox(width: 8), - Text('QUAN', style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + 'QUAN', + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), ], ), ), @@ -374,7 +401,10 @@ class _SwapScreenState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Rate', style: text.detail?.copyWith(color: colors.textSecondary)), - Text(_rateLabel, style: text.detail?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500)), + Text( + _rateLabel, + style: text.detail?.copyWith(color: colors.textSecondary, fontWeight: FontWeight.w500), + ), ], ), ], @@ -392,8 +422,15 @@ class _SwapScreenState extends State { padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _loading - ? SizedBox(width: 16, height: 16, child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2)) - : Text('Get a Quote', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(color: colors.textPrimary, strokeWidth: 2), + ) + : Text( + 'Get a Quote', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), ), ), @@ -419,13 +456,17 @@ class _QrScanPage extends StatelessWidget { }, ), Positioned( - bottom: 60, left: 24, right: 24, + bottom: 60, + left: 24, + right: 24, child: GestureDetector( onTap: () => Navigator.pop(context), child: Container( padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), - child: Center(child: Text('Cancel', style: TextStyle(color: colors.textPrimary, fontSize: 16))), + child: Center( + child: Text('Cancel', style: TextStyle(color: colors.textPrimary, fontSize: 16)), + ), ), ), ), diff --git a/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart b/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart index 0bb17183..2ad1da51 100644 --- a/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart @@ -63,16 +63,22 @@ class _TokenPickerContent extends StatelessWidget { child: Row( children: [ Container( - width: 36, height: 36, + width: 36, + height: 36, decoration: BoxDecoration(color: colors.accentPink.withValues(alpha: 0.2), shape: BoxShape.circle), - child: Center(child: Text(token.symbol[0], style: text.smallParagraph?.copyWith(color: colors.textPrimary))), + child: Center( + child: Text(token.symbol[0], style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(token.symbol, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + token.symbol, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), Text('${token.name} · ${token.network}', style: text.detail?.copyWith(color: colors.textTertiary)), ], ), diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/mobile-app/lib/v2/theme/app_colors.dart index 20e06520..d3deccf4 100644 --- a/mobile-app/lib/v2/theme/app_colors.dart +++ b/mobile-app/lib/v2/theme/app_colors.dart @@ -72,33 +72,33 @@ class AppColorsV2 extends ThemeExtension { }); const AppColorsV2.dark() - : this( - background: const Color(0xFF141414), - backgroundAlt: const Color(0xFF1F1F1F), - surface: const Color(0xFF292929), - surfaceGlass: const Color(0x1AFFFFFF), - surfaceCard: const Color(0x0FFFFFFF), - textPrimary: const Color(0xFFFFFFFF), - textSecondary: const Color(0x80FFFFFF), - textTertiary: const Color(0x52FFFFFF), - textError: const Color(0xFFFF5252), - accentGreen: const Color(0xFF34C759), - accentPink: const Color(0xFFED4CCE), - checksum: const Color(0xFF4CEDE7), - error: const Color(0xFFFF2D54), - danger: const Color(0xFFFF1F45), - success: const Color(0xFF1FFFA7), - backgroundGlow: const Color(0xFFFFFFFF), - buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], - separator: const Color(0x1AFFFFFF), - border: const Color(0x33FFFFFF), - buttonDisabled: const Color(0xFF3D3C44), - skeletonBase: const Color(0xFF3D3C44), - skeletonHighlight: const Color(0xFF5A5A5A), - tagGuardian: const Color(0xFF9747FF), - tagEntrusted: const Color(0xFFFFD541), - tagHighSecurity: const Color(0xFF4CEDE7), - ); + : this( + background: const Color(0xFF141414), + backgroundAlt: const Color(0xFF1F1F1F), + surface: const Color(0xFF292929), + surfaceGlass: const Color(0x1AFFFFFF), + surfaceCard: const Color(0x0FFFFFFF), + textPrimary: const Color(0xFFFFFFFF), + textSecondary: const Color(0x80FFFFFF), + textTertiary: const Color(0x52FFFFFF), + textError: const Color(0xFFFF5252), + accentGreen: const Color(0xFF34C759), + accentPink: const Color(0xFFED4CCE), + checksum: const Color(0xFF4CEDE7), + error: const Color(0xFFFF2D54), + danger: const Color(0xFFFF1F45), + success: const Color(0xFF1FFFA7), + backgroundGlow: const Color(0xFFFFFFFF), + buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], + separator: const Color(0x1AFFFFFF), + border: const Color(0x33FFFFFF), + buttonDisabled: const Color(0xFF3D3C44), + skeletonBase: const Color(0xFF3D3C44), + skeletonHighlight: const Color(0xFF5A5A5A), + tagGuardian: const Color(0xFF9747FF), + tagEntrusted: const Color(0xFFFFD541), + tagHighSecurity: const Color(0xFF4CEDE7), + ); @override AppColorsV2 copyWith({ diff --git a/mobile-app/lib/v2/theme/app_spacing.dart b/mobile-app/lib/v2/theme/app_spacing.dart index bacc8d44..ce7c34de 100644 --- a/mobile-app/lib/v2/theme/app_spacing.dart +++ b/mobile-app/lib/v2/theme/app_spacing.dart @@ -79,82 +79,82 @@ class AppSizeTheme extends ThemeExtension { }); const AppSizeTheme.defaultTheme() - : this( - logoHeight: 158.0, - mainMenuHeight: 20, - mainMenuWidth: 20, - mainMenuIconSize: 21.0, - navbarHeight: 67.0, - navbarItemHeight: 32, - navbarItemWidth: 40, - navbarIconWidth: 23, - floatingBtnHeight: 49.0, - floatingBtnWidth: 52.0, - settingMenuIconSize: 11.0, - settingMenuShareIconSize: 20.0, - accountListItemHeight: 110.0, - accountListItemLogoWidth: 36.0, - appbarIconSize: 18.0, - sendOverlayContainerWidth: double.infinity, - overlayCloseIconSize: 24.0, - mnemonicCellDesiredHeight: 31.0, - txListItemIconWidth: 21.0, - txDetailsIconHeight: 43.0, - txDetailsIconWidth: 51.0, - copyIconSize: 20.0, - pasteIconSize: 18.0, - timePickerSubtitleWidth: 249, - bottomButtonSpacing: 16, - buttonsHorizontalSpacing: 28, - infoSheetTitleIcon: 25, - screenPadding: 24.0, - cardPadding: 20.0, - sectionGap: 40.0, - itemGap: 12.0, - sectionHeaderToContent: 36.0, - radiusFull: 30.0, - radiusCard: 14.0, - radiusSmall: 6.0, - ); + : this( + logoHeight: 158.0, + mainMenuHeight: 20, + mainMenuWidth: 20, + mainMenuIconSize: 21.0, + navbarHeight: 67.0, + navbarItemHeight: 32, + navbarItemWidth: 40, + navbarIconWidth: 23, + floatingBtnHeight: 49.0, + floatingBtnWidth: 52.0, + settingMenuIconSize: 11.0, + settingMenuShareIconSize: 20.0, + accountListItemHeight: 110.0, + accountListItemLogoWidth: 36.0, + appbarIconSize: 18.0, + sendOverlayContainerWidth: double.infinity, + overlayCloseIconSize: 24.0, + mnemonicCellDesiredHeight: 31.0, + txListItemIconWidth: 21.0, + txDetailsIconHeight: 43.0, + txDetailsIconWidth: 51.0, + copyIconSize: 20.0, + pasteIconSize: 18.0, + timePickerSubtitleWidth: 249, + bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 25, + screenPadding: 24.0, + cardPadding: 20.0, + sectionGap: 40.0, + itemGap: 12.0, + sectionHeaderToContent: 36.0, + radiusFull: 30.0, + radiusCard: 14.0, + radiusSmall: 6.0, + ); const AppSizeTheme.iPad() - : this( - logoHeight: 180.0, - mainMenuHeight: 30, - mainMenuWidth: 30, - mainMenuIconSize: 29.0, - navbarHeight: 87.0, - navbarItemHeight: 40, - navbarItemWidth: 48, - navbarIconWidth: 32, - floatingBtnHeight: 74.0, - floatingBtnWidth: 77.0, - settingMenuIconSize: 16.0, - settingMenuShareIconSize: 24.0, - accountListItemHeight: 130.0, - accountListItemLogoWidth: 48.0, - appbarIconSize: 20.0, - sendOverlayContainerWidth: 510.0, - overlayCloseIconSize: 28.0, - mnemonicCellDesiredHeight: 80.0, - txListItemIconWidth: 32.0, - txDetailsIconHeight: 82.0, - txDetailsIconWidth: 91.0, - copyIconSize: 28.0, - pasteIconSize: 24.0, - timePickerSubtitleWidth: 400, - bottomButtonSpacing: 16, - buttonsHorizontalSpacing: 28, - infoSheetTitleIcon: 28, - screenPadding: 32.0, - cardPadding: 24.0, - sectionGap: 48.0, - itemGap: 16.0, - sectionHeaderToContent: 40.0, - radiusFull: 30.0, - radiusCard: 14.0, - radiusSmall: 6.0, - ); + : this( + logoHeight: 180.0, + mainMenuHeight: 30, + mainMenuWidth: 30, + mainMenuIconSize: 29.0, + navbarHeight: 87.0, + navbarItemHeight: 40, + navbarItemWidth: 48, + navbarIconWidth: 32, + floatingBtnHeight: 74.0, + floatingBtnWidth: 77.0, + settingMenuIconSize: 16.0, + settingMenuShareIconSize: 24.0, + accountListItemHeight: 130.0, + accountListItemLogoWidth: 48.0, + appbarIconSize: 20.0, + sendOverlayContainerWidth: 510.0, + overlayCloseIconSize: 28.0, + mnemonicCellDesiredHeight: 80.0, + txListItemIconWidth: 32.0, + txDetailsIconHeight: 82.0, + txDetailsIconWidth: 91.0, + copyIconSize: 28.0, + pasteIconSize: 24.0, + timePickerSubtitleWidth: 400, + bottomButtonSpacing: 16, + buttonsHorizontalSpacing: 28, + infoSheetTitleIcon: 28, + screenPadding: 32.0, + cardPadding: 24.0, + sectionGap: 48.0, + itemGap: 16.0, + sectionHeaderToContent: 40.0, + radiusFull: 30.0, + radiusCard: 14.0, + radiusSmall: 6.0, + ); @override AppSizeTheme copyWith({ diff --git a/mobile-app/lib/v2/theme/app_text_styles.dart b/mobile-app/lib/v2/theme/app_text_styles.dart index 5d1c65e5..2771ad06 100644 --- a/mobile-app/lib/v2/theme/app_text_styles.dart +++ b/mobile-app/lib/v2/theme/app_text_styles.dart @@ -33,128 +33,36 @@ class AppTextTheme extends ThemeExtension { }); const AppTextTheme.defaultTheme() - : this( - lockTitle: const TextStyle(fontSize: 24, fontFamily: _fontFamily), - extraLargeTitle: const TextStyle( - fontSize: 40, - fontFamily: _fontFamily, - fontWeight: FontWeight.w600, - ), - largeTitle: const TextStyle( - fontSize: 30, - fontFamily: _fontFamily, - fontWeight: FontWeight.w300, - ), - mediumTitle: const TextStyle( - fontSize: 24, - fontFamily: _fontFamily, - fontWeight: FontWeight.w500, - ), - smallTitle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - fontFamily: _fontFamily, - ), - paragraph: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - smallParagraph: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - largeTag: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - tag: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w300, - fontFamily: _fontFamily, - ), - timer: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.w600, - fontFamily: _fontFamily, - ), - detail: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - tiny: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - ); + : this( + lockTitle: const TextStyle(fontSize: 24, fontFamily: _fontFamily), + extraLargeTitle: const TextStyle(fontSize: 40, fontFamily: _fontFamily, fontWeight: FontWeight.w600), + largeTitle: const TextStyle(fontSize: 30, fontFamily: _fontFamily, fontWeight: FontWeight.w300), + mediumTitle: const TextStyle(fontSize: 24, fontFamily: _fontFamily, fontWeight: FontWeight.w500), + smallTitle: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500, fontFamily: _fontFamily), + paragraph: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + smallParagraph: const TextStyle(fontSize: 14, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + largeTag: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + tag: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300, fontFamily: _fontFamily), + timer: const TextStyle(fontSize: 28, fontWeight: FontWeight.w600, fontFamily: _fontFamily), + detail: const TextStyle(fontSize: 12, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + tiny: const TextStyle(fontSize: 11, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + ); const AppTextTheme.iPad() - : this( - lockTitle: const TextStyle( - color: Colors.white, - fontSize: 28, - fontFamily: _fontFamily, - ), - extraLargeTitle: const TextStyle( - fontSize: 52, - fontFamily: _fontFamily, - fontWeight: FontWeight.w600, - ), - largeTitle: const TextStyle( - fontSize: 36, - fontFamily: _fontFamily, - fontWeight: FontWeight.w300, - ), - mediumTitle: const TextStyle( - fontSize: 28, - fontFamily: _fontFamily, - fontWeight: FontWeight.w400, - ), - smallTitle: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - fontFamily: _fontFamily, - ), - paragraph: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - smallParagraph: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - largeTag: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - tag: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w300, - fontFamily: _fontFamily, - ), - timer: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.w600, - fontFamily: _fontFamily, - ), - detail: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - tiny: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - fontFamily: _fontFamily, - ), - ); + : this( + lockTitle: const TextStyle(color: Colors.white, fontSize: 28, fontFamily: _fontFamily), + extraLargeTitle: const TextStyle(fontSize: 52, fontFamily: _fontFamily, fontWeight: FontWeight.w600), + largeTitle: const TextStyle(fontSize: 36, fontFamily: _fontFamily, fontWeight: FontWeight.w300), + mediumTitle: const TextStyle(fontSize: 28, fontFamily: _fontFamily, fontWeight: FontWeight.w400), + smallTitle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w500, fontFamily: _fontFamily), + paragraph: const TextStyle(fontSize: 20, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + smallParagraph: const TextStyle(fontSize: 18, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + largeTag: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + tag: const TextStyle(fontSize: 16, fontWeight: FontWeight.w300, fontFamily: _fontFamily), + timer: const TextStyle(fontSize: 36, fontWeight: FontWeight.w600, fontFamily: _fontFamily), + detail: const TextStyle(fontSize: 16, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + tiny: const TextStyle(fontSize: 15, fontWeight: FontWeight.w400, fontFamily: _fontFamily), + ); @override AppTextTheme copyWith({ diff --git a/mobile-app/lib/v2/theme/app_theme.dart b/mobile-app/lib/v2/theme/app_theme.dart index 6f5aa73c..fd403b05 100644 --- a/mobile-app/lib/v2/theme/app_theme.dart +++ b/mobile-app/lib/v2/theme/app_theme.dart @@ -17,34 +17,18 @@ class AppTheme { return ThemeData( scaffoldBackgroundColor: colors.background, cardColor: colors.surface, - colorScheme: ColorScheme.dark( - surface: colors.surface, - error: colors.error, - ), - appBarTheme: AppBarTheme( - backgroundColor: colors.surface, - elevation: 0, - ), + colorScheme: ColorScheme.dark(surface: colors.surface, error: colors.error), + appBarTheme: AppBarTheme(backgroundColor: colors.surface, elevation: 0), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( foregroundColor: Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), ), ), outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: colors.textPrimary, - ), + style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))), ), + textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: colors.textPrimary)), inputDecorationTheme: InputDecorationTheme( border: InputBorder.none, enabledBorder: InputBorder.none, diff --git a/quantus_sdk/lib/src/services/swap_service.dart b/quantus_sdk/lib/src/services/swap_service.dart index 0d1f9237..0a9997e6 100644 --- a/quantus_sdk/lib/src/services/swap_service.dart +++ b/quantus_sdk/lib/src/services/swap_service.dart @@ -59,8 +59,13 @@ class SwapOrder { required this.createdAt, }); - SwapOrder copyWith({SwapStatus? status}) => - SwapOrder(orderId: orderId, quote: quote, depositAddress: depositAddress, status: status ?? this.status, createdAt: createdAt); + SwapOrder copyWith({SwapStatus? status}) => SwapOrder( + orderId: orderId, + quote: quote, + depositAddress: depositAddress, + status: status ?? this.status, + createdAt: createdAt, + ); } class SwapService { From 2aadeb96d0587adeab8fd8db5b338aa00c7f74dd Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 19:00:33 +0800 Subject: [PATCH 20/49] glass borders --- .../lib/v2/components/glass_button.dart | 139 +++--------------- .../v2/screens/settings/auto_lock_screen.dart | 2 +- .../settings/recovery_phrase_screen.dart | 20 +-- .../lib/v2/screens/swap/deposit_screen.dart | 10 +- mobile-app/pubspec.yaml | 2 +- 5 files changed, 31 insertions(+), 142 deletions(-) diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index 0f38b595..b879ec3f 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -1,100 +1,27 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:glass_kit/glass_kit.dart'; -class OutlinedGlassButton extends StatelessWidget { - final VoidCallback? onTap; - final Widget child; - final EdgeInsetsGeometry padding; - final double radius; - - const OutlinedGlassButton({ - super.key, - this.onTap, - required this.child, - this.radius = 14, - this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), - }); +const _borderGradient = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], +); - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: ClipRRect( - borderRadius: BorderRadius.circular(radius), - child: BackdropFilter( - filter: ImageFilter.compose( - outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - inner: const ColorFilter.matrix([0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 0, 1, 0]), - ), - child: CustomPaint( - painter: _ShimmerBorderPainter(radius: radius), - child: Container( - padding: padding, - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(radius), - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [0, 0.03, 0.97, 1], - colors: [Color(0x1AFFFFFF), Colors.transparent, Colors.transparent, Color(0x0D000000)], - ), - ), - child: child, - ), - ), - ), - ), - ); - } -} - -class _ShimmerBorderPainter extends CustomPainter { - final double radius; - _ShimmerBorderPainter({required this.radius}); - - @override - void paint(Canvas canvas, Size size) { - final rect = Offset.zero & size; - final rrect = RRect.fromRectAndRadius(rect.deflate(0.5), Radius.circular(radius)); - - final shimmer = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = 1.5 - ..shader = const RadialGradient( - center: Alignment.topCenter, - radius: 1.8, - colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], - ).createShader(rect) - ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 0.8); - canvas.drawRRect(rrect, shimmer); - - final crisp = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = 0.889 - ..shader = const RadialGradient( - center: Alignment.topCenter, - radius: 1.8, - colors: [Color(0x66FFFFFF), Color(0x28FFFFFF)], - ).createShader(rect); - canvas.drawRRect(rrect, crisp); - } - - @override - bool shouldRepaint(covariant _ShimmerBorderPainter old) => old.radius != radius; -} - -class FilledGlassButton extends StatelessWidget { +class GlassButton extends StatelessWidget { final VoidCallback? onTap; final Widget child; final EdgeInsetsGeometry padding; final double radius; + final bool filled; - const FilledGlassButton({ + const GlassButton({ super.key, this.onTap, required this.child, this.radius = 14, this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), + this.filled = false, }); @override @@ -104,48 +31,16 @@ class FilledGlassButton extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(radius), child: BackdropFilter( - filter: ImageFilter.compose( - outer: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - inner: const ColorFilter.matrix([ - 1.05, - 0, - 0, - 0, - 0, - 0, - 1.05, - 0, - 0, - 0, - 0, - 0, - 1.05, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - ]), - ), + filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), child: CustomPaint( - painter: _ShimmerBorderPainter(radius: radius), + painter: RectBorderPainter( + borderRadius: BorderRadius.circular(radius), + strokeWidth: 0.889, + gradient: _borderGradient, + ), child: Container( padding: padding, - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(radius), - ), - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.circular(radius), - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - stops: [0, 0.03, 0.97, 1], - colors: [Color(0x1AFFFFFF), Colors.transparent, Colors.transparent, Color(0x0D000000)], - ), - ), + color: filled ? Colors.white.withValues(alpha: 0.08) : Colors.transparent, child: child, ), ), diff --git a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart index f5c59104..c837de34 100644 --- a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart +++ b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart @@ -79,7 +79,7 @@ class _AutoLockScreenState extends State { ), ), const Spacer(), - OutlinedGlassButton( + GlassButton( onTap: _confirm, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 5ca06e9a..62b944d4 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -81,14 +81,10 @@ class _RecoveryPhraseScreenState extends State { const SizedBox(height: 40), Expanded( child: SingleChildScrollView( - child: Column( - children: [ - _wordGrid(colors, text), - if (_revealed) ...[const SizedBox(height: 24), _copyRow(colors, text)], - ], - ), + child: _wordGrid(colors, text), ), ), + if (_revealed) ...[const SizedBox(height: 16), _copyRow(colors, text)], const SizedBox(height: 16), _revealButton(colors, text), const SizedBox(height: 24), @@ -144,13 +140,9 @@ class _RecoveryPhraseScreenState extends State { overflow: TextOverflow.ellipsis, ); - return Container( - height: 36, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all(color: colors.border), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), + return GlassButton( + radius: 8, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), child: Row( children: [ Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), @@ -183,7 +175,7 @@ class _RecoveryPhraseScreenState extends State { } Widget _revealButton(AppColorsV2 colors, AppTextTheme text) { - return OutlinedGlassButton( + return GlassButton( onTap: _toggleReveal, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index adef505c..eb225368 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -192,7 +192,7 @@ class _DepositScreenState extends State { Row( children: [ Expanded( - child: OutlinedGlassButton( + child: GlassButton( onTap: _copyAddress, padding: const EdgeInsets.symmetric(vertical: 20), child: Row( @@ -210,7 +210,7 @@ class _DepositScreenState extends State { ), const SizedBox(width: 16), Expanded( - child: OutlinedGlassButton( + child: GlassButton( onTap: () {}, padding: const EdgeInsets.symmetric(vertical: 20), child: Row( @@ -290,7 +290,8 @@ class _DepositScreenState extends State { } Widget _sentButton(AppColorsV2 colors, AppTextTheme text) { - return FilledGlassButton( + return GlassButton( + filled: true, onTap: _confirming ? null : _confirmSent, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( @@ -309,7 +310,8 @@ class _DepositScreenState extends State { } Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { - return FilledGlassButton( + return GlassButton( + filled: true, onTap: () => Navigator.popUntil(context, (r) => r.isFirst), padding: const EdgeInsets.symmetric(vertical: 20), child: Center( diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 73a5ef06..72af0777 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -57,7 +57,7 @@ dependencies: timezone: ^0.10.1 flutter_timezone: ^5.0.1 collection: ^1.19.1 - liquid_glass_container_plus: ^1.0.5 + glass_kit: ^4.0.2 dev_dependencies: flutter_test: From 4ca505515e2e4c76a582630d26d94b748d19af3f Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 19:00:51 +0800 Subject: [PATCH 21/49] format --- .../lib/v2/screens/settings/recovery_phrase_screen.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 62b944d4..c4299f5e 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -79,11 +79,7 @@ class _RecoveryPhraseScreenState extends State { const SizedBox(height: 40), _warning(colors, text), const SizedBox(height: 40), - Expanded( - child: SingleChildScrollView( - child: _wordGrid(colors, text), - ), - ), + Expanded(child: SingleChildScrollView(child: _wordGrid(colors, text))), if (_revealed) ...[const SizedBox(height: 16), _copyRow(colors, text)], const SizedBox(height: 16), _revealButton(colors, text), From f5fcc551e6d5f8b01b186201388e595df8a1e479 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 19:16:50 +0800 Subject: [PATCH 22/49] fixed size word chips, border radius, fill --- .../settings/recovery_phrase_screen.dart | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index c4299f5e..60fee0f5 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -80,7 +80,15 @@ class _RecoveryPhraseScreenState extends State { _warning(colors, text), const SizedBox(height: 40), Expanded(child: SingleChildScrollView(child: _wordGrid(colors, text))), - if (_revealed) ...[const SizedBox(height: 16), _copyRow(colors, text)], + const SizedBox(height: 16), + IgnorePointer( + ignoring: !_revealed, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: _revealed ? 1.0 : 0.0, + child: _copyRow(colors, text), + ), + ), const SizedBox(height: 16), _revealButton(colors, text), const SizedBox(height: 24), @@ -136,22 +144,38 @@ class _RecoveryPhraseScreenState extends State { overflow: TextOverflow.ellipsis, ); - return GlassButton( - radius: 8, - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), - child: Row( - children: [ - Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), - const SizedBox(width: 6), - Expanded( - child: _revealed - ? wordWidget - : Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: ImageFiltered(imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), child: wordWidget), + return SizedBox( + height: 36, + child: GlassButton( + radius: 14, + padding: const EdgeInsets.symmetric(horizontal: 10), + filled: true, + child: Row( + children: [ + Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), + const SizedBox(width: 6), + Expanded( + child: Stack( + alignment: Alignment.centerLeft, + children: [ + AnimatedOpacity( + duration: const Duration(milliseconds: 50), + opacity: _revealed ? 1.0 : 0.0, + child: wordWidget, ), - ), - ], + AnimatedOpacity( + duration: const Duration(milliseconds: 50), + opacity: _revealed ? 0.0 : 1.0, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 2), + child: ImageFiltered(imageFilter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), child: wordWidget), + ), + ), + ], + ), + ), + ], + ), ), ); } From 595308cf7848f6c3a573f352c9c4de5a4212c59a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 20:52:28 +0800 Subject: [PATCH 23/49] welcome screen --- mobile-app/assets/v2/quantus_white_logo.png | Bin 0 -> 21627 bytes .../main/screens/wallet_initializer.dart | 4 +- .../v2/screens/settings/settings_screen.dart | 4 +- .../v2/screens/welcome/welcome_screen.dart | 67 ++++++++++++++++++ mobile-app/pubspec.yaml | 1 + 5 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 mobile-app/assets/v2/quantus_white_logo.png create mode 100644 mobile-app/lib/v2/screens/welcome/welcome_screen.dart diff --git a/mobile-app/assets/v2/quantus_white_logo.png b/mobile-app/assets/v2/quantus_white_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2b65d5cff9dcfdfd373a5d8daec53be414bb30b8 GIT binary patch literal 21627 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 aN}p!6BR8LS^#fNZma?L{LY16(@c#h5_(i7x literal 0 HcmV?d00001 diff --git a/mobile-app/lib/features/main/screens/wallet_initializer.dart b/mobile-app/lib/features/main/screens/wallet_initializer.dart index 6f92358a..7398b6de 100644 --- a/mobile-app/lib/features/main/screens/wallet_initializer.dart +++ b/mobile-app/lib/features/main/screens/wallet_initializer.dart @@ -2,8 +2,8 @@ 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/migration_dialog.dart'; -import 'package:resonance_network_wallet/features/main/screens/welcome_screen.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/welcome/welcome_screen.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; @@ -170,7 +170,7 @@ class WalletInitializerState extends ConsumerState { if (_walletExists) { return const HomeScreen(); } else { - return const WelcomeScreen(); + return const WelcomeScreenV2(); } } } diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 30677120..194b782f 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -6,7 +6,7 @@ import 'package:resonance_network_wallet/features/components/reset_confirmation_ import 'package:resonance_network_wallet/features/components/snackbar_helper.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/select_wallet_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/welcome_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/welcome/welcome_screen.dart'; import 'package:resonance_network_wallet/providers/account_associations_providers.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/notification_config_provider.dart'; @@ -120,7 +120,7 @@ class _SettingsScreenV2State extends ConsumerState { ref.read(activeAccountProvider.notifier).reset(); ref.read(accountAssociationsProvider.notifier).reset(); if (mounted) { - Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const WelcomeScreen()), (r) => false); + Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const WelcomeScreenV2()), (r) => false); } } diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart new file mode 100644 index 00000000..dbf30bc5 --- /dev/null +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/features/main/screens/create_wallet_and_backup_screen.dart'; +import 'package:resonance_network_wallet/features/main/screens/import_wallet_screen.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class WelcomeScreenV2 extends StatelessWidget { + const WelcomeScreenV2({super.key}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 24), + Image.asset('assets/v2/quantus_white_logo.png', height: 32), + const Spacer(), + Center( + child: Text( + 'Quantum Secure\nCrypto', + textAlign: TextAlign.center, + style: text.largeTitle?.copyWith(fontSize: 32, height: 1.35, color: colors.textPrimary), + ), + ), + const SizedBox(height: 64), + GlassButton( + filled: true, + onTap: () => Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: 'create_wallet'), + builder: (_) => const CreateWalletAndBackupScreen(), + ), + ), + child: Center(child: Text('Create New Wallet', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + ), + const SizedBox(height: 32), + GlassButton( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: 'import_wallet'), + builder: (_) => const ImportWalletScreen(), + ), + ), + child: Center(child: Text('Import Existing Wallet', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + ), + const SizedBox(height: 60), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 72af0777..70d9fa2f 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -112,6 +112,7 @@ flutter: - assets/v2/glass_medium_button_bg.png - assets/v2/glass_button_40_bg.png - assets/v2/glass_button_wide_340_bg.png + fonts: - family: Fira Code From b8cab60ad60a539e9a65b96ad7aae4ffd61e1a06 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 20:55:00 +0800 Subject: [PATCH 24/49] precise radial --- mobile-app/lib/v2/components/gradient_background.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/lib/v2/components/gradient_background.dart b/mobile-app/lib/v2/components/gradient_background.dart index 4fcfd208..ab6ee31a 100644 --- a/mobile-app/lib/v2/components/gradient_background.dart +++ b/mobile-app/lib/v2/components/gradient_background.dart @@ -18,7 +18,7 @@ class GradientBackground extends StatelessWidget { decoration: BoxDecoration( gradient: RadialGradient( center: const Alignment(0.0, -0.487), - radius: 1.6, + radius: 1.609, colors: [colors.backgroundAlt, colors.background], ), ), From 9ff7a64e6cc442dd3003fe24538b7eb5f66a7bfa Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 22:16:54 +0800 Subject: [PATCH 25/49] welcome screen and gradient bg --- .../v2/components/gradient_background.dart | 36 ++++++++++++------- .../v2/screens/welcome/welcome_screen.dart | 17 ++++----- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/mobile-app/lib/v2/components/gradient_background.dart b/mobile-app/lib/v2/components/gradient_background.dart index ab6ee31a..ccb8f1e2 100644 --- a/mobile-app/lib/v2/components/gradient_background.dart +++ b/mobile-app/lib/v2/components/gradient_background.dart @@ -25,7 +25,7 @@ class GradientBackground extends StatelessWidget { ), ), Positioned.fill( - child: CustomPaint(painter: _EllipseGlowPainter(glowColor: colors.backgroundGlow.useOpacity(0.2))), + child: CustomPaint(painter: _EllipseGlowPainter(glowColor: colors.backgroundGlow.useOpacity(0.3))), ), child, ], @@ -34,9 +34,6 @@ class GradientBackground extends StatelessWidget { } class _EllipseGlowPainter extends CustomPainter { - static const xOffset = -110.0; - static const yOffset = -60.0; - final Color glowColor; _EllipseGlowPainter({required this.glowColor}); @@ -44,24 +41,39 @@ class _EllipseGlowPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final sx = size.width / 390.0; - final sy = size.height / 844.0; - final ox = xOffset * sx; - final oy = yOffset * sy; + // final sy = size.height / 844.0; // we don't want to be relative on the y axis final paint = Paint() ..color = glowColor - ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 85); + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 100); + + const e1x = 95.0; + const e1y = 3.0; + const e1width = 86.0; + const e1height = 528.0; + const e2x = 330.0; + const e2y = -48.0; + const e2width = 44.0; + const e2height = 580.0; canvas.save(); - canvas.translate(176.56 * sx + ox, 77.88 * sy + oy); + canvas.translate(e1x * sx, e1y); canvas.rotate(30 * pi / 180); - canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: 66.46 * sx, height: 406.13 * sy), paint); + canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: e1width * sx, height: e1height), paint); canvas.restore(); canvas.save(); - canvas.translate(367.38 * sx + ox, 41.54 * sy + oy); + canvas.translate(e2x * sx, e2y); canvas.rotate(30 * pi / 180); - canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: 33.41 * sx, height: 446.53 * sy), paint); + canvas.drawOval(Rect.fromCenter(center: Offset.zero, width: e2width * sx, height: e2height), paint); canvas.restore(); + + // DEBUG: vertical center lines + // final debugPaint = Paint() + // ..color = const Color(0xFFFF0000) + // ..strokeWidth = 1; + // canvas.drawLine(Offset(e1x * sx, 0), Offset(e1x * sx, size.height), debugPaint); + // debugPaint.color = const Color(0xFF00FF00); + // canvas.drawLine(Offset(e2x * sx, 0), Offset(e2x * sx, size.height), debugPaint); } @override diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index dbf30bc5..aa29c569 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -23,15 +23,16 @@ class WelcomeScreenV2 extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 24), - Image.asset('assets/v2/quantus_white_logo.png', height: 32), + const SizedBox(height:40), + Row(children: [ + // const SizedBox(width: 6), + Image.asset('assets/v2/quantus_white_logo.png', height: 32)], + ), const Spacer(), - Center( - child: Text( - 'Quantum Secure\nCrypto', - textAlign: TextAlign.center, - style: text.largeTitle?.copyWith(fontSize: 32, height: 1.35, color: colors.textPrimary), - ), + Text( + 'Quantum Secure\nCrypto', + textAlign: TextAlign.left, + style: text.largeTitle?.copyWith(fontSize: 32, height: 1.35, color: Colors.white), ), const SizedBox(height: 64), GlassButton( From 5e279514911ce648773149bd559d8e895b9ae56e Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 22:17:18 +0800 Subject: [PATCH 26/49] format --- .../v2/screens/welcome/welcome_screen.dart | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index aa29c569..f89c6430 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -23,10 +23,12 @@ class WelcomeScreenV2 extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height:40), - Row(children: [ - // const SizedBox(width: 6), - Image.asset('assets/v2/quantus_white_logo.png', height: 32)], + const SizedBox(height: 40), + Row( + children: [ + // const SizedBox(width: 6), + Image.asset('assets/v2/quantus_white_logo.png', height: 32), + ], ), const Spacer(), Text( @@ -44,7 +46,12 @@ class WelcomeScreenV2 extends StatelessWidget { builder: (_) => const CreateWalletAndBackupScreen(), ), ), - child: Center(child: Text('Create New Wallet', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + child: Center( + child: Text( + 'Create New Wallet', + style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary), + ), + ), ), const SizedBox(height: 32), GlassButton( @@ -55,7 +62,12 @@ class WelcomeScreenV2 extends StatelessWidget { builder: (_) => const ImportWalletScreen(), ), ), - child: Center(child: Text('Import Existing Wallet', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + child: Center( + child: Text( + 'Import Existing Wallet', + style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary), + ), + ), ), const SizedBox(height: 60), ], From c52591c6765f375096a9f94b4fb46aaa362de026 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 22:49:38 +0800 Subject: [PATCH 27/49] create wallet and no transactions views --- .../screens/create/wallet_ready_screen.dart | 322 ++++++++++++++++++ .../lib/v2/screens/home/activity_section.dart | 18 +- .../v2/screens/welcome/welcome_screen.dart | 4 +- 3 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 mobile-app/lib/v2/screens/create/wallet_ready_screen.dart diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart new file mode 100644 index 00000000..78be29a2 --- /dev/null +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -0,0 +1,322 @@ +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/snackbar_helper.dart'; +import 'package:resonance_network_wallet/features/main/screens/create_wallet_and_backup_screen.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/services/referral_service.dart'; +import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class WalletReadyScreenV2 extends ConsumerStatefulWidget { + const WalletReadyScreenV2({super.key, this.walletIndex = 0}); + + final int walletIndex; + + @override + ConsumerState createState() => _WalletReadyScreenV2State(); +} + +class _WalletReadyScreenV2State extends ConsumerState { + String _mnemonic = ''; + bool _isLoading = true; + bool _isSubmitting = false; + String? _error; + + final SettingsService _settingsService = SettingsService(); + final AccountsService _accountsService = AccountsService(); + final HdWalletService _hdWalletService = HdWalletService(); + final ReferralService _referralService = ReferralService(); + + final _accountName = TextEditingController(); + String? _accountNameError; + + late String _address; + late String _checksum; + + @override + void initState() { + super.initState(); + _accountName.text = 'Account 1'; + _generateMnemonic(); + } + + Future _generateMnemonic() async { + if (!mounted) return; + setState(() => _isLoading = true); + + try { + _mnemonic = await SubstrateService().generateMnemonic(); + if (_mnemonic.isEmpty) throw Exception('Mnemonic generation returned empty.'); + + _address = _hdWalletService.keyPairAtIndex(_mnemonic, 0).ss58Address; + _checksum = await HumanReadableChecksumService().getHumanReadableName(_address); + + if (mounted) setState(() => _isLoading = false); + } catch (e) { + if (mounted) setState(() { + _isLoading = false; + _error = 'Failed to generate: $e'; + }); + } + } + + Future _continue() async { + if (_mnemonic.isEmpty || _accountNameError != null) return; + + setState(() => _isSubmitting = true); + try { + await _settingsService.setMnemonic(_mnemonic, widget.walletIndex); + + final accounts = ref.read(accountsProvider).value ?? []; + final hasRoot = accounts.any((a) => a.walletIndex == widget.walletIndex && a.index == 0); + if (!hasRoot) { + await _accountsService.addAccount( + Account(walletIndex: widget.walletIndex, index: 0, name: _accountName.text.trim(), accountId: _address), + ); + try { + _referralService.submitAddressToBackend(); + } catch (_) {} + } + ref.invalidate(accountsProvider); + ref.invalidate(activeAccountProvider); + + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => const HomeScreen()), + (route) => false, + ); + } catch (e) { + if (mounted) showCopySnackbar(context, title: 'Error', message: 'Error saving wallet: $e'); + } finally { + if (mounted) setState(() => _isSubmitting = false); + } + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + final canContinue = !_isLoading && _error == null && _accountNameError == null; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Your Wallet Is Ready', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 24), + ), + ], + ), + const SizedBox(height: 40), + Expanded( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _Field( + label: 'Wallet Name', + value: _accountName.text, + isLoading: _isLoading, + actionIcon: Icons.edit, + onAction: () => _showEditNameSheet(colors, text), + ), + const SizedBox(height: 24), + _Field( + label: 'Wallet Address', + value: _isLoading ? '...' : AddressFormattingService.formatAddress(_address, prefix: 15, ellipses: '.......', postFix: 14), + isLoading: _isLoading, + actionIcon: Icons.copy, + onAction: () => ClipboardExtensions.copyTextWithSnackbar(context, _address), + ), + const SizedBox(height: 24), + _Field( + label: 'Wallet Checkphrase', + value: _isLoading ? '...' : _checksum, + isLoading: _isLoading, + valueColor: colors.accentPink, + actionIcon: Icons.copy, + onAction: () => ClipboardExtensions.copyTextWithSnackbar(context, _checksum, message: 'Checkphrase copied'), + ), + const SizedBox(height: 16), + GestureDetector( + onTap: () { + final words = _mnemonic.isNotEmpty ? _mnemonic.split(' ') : []; + showRecoveryPhraseSheet(context, words, _isLoading, _error, _mnemonic); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.visibility_outlined, size: 16, color: colors.textSecondary), + const SizedBox(width: 8), + Text('View recovery phrase', style: text.detail?.copyWith(color: colors.textSecondary)), + ], + ), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(5, (i) => _PaginationDot(active: i == 1)), + ), + ], + ), + ), + ), + const SizedBox(height: 24), + GlassButton( + onTap: canContinue ? _continue : null, + padding: const EdgeInsets.symmetric(vertical: 20), + child: _isSubmitting + ? Center(child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary))) + : Center(child: Text('Continue', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + ), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + void _showEditNameSheet(AppColorsV2 colors, AppTextTheme text) { + final controller = TextEditingController(text: _accountName.text); + showModalBottomSheet( + context: context, + backgroundColor: colors.background, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(24))), + builder: (ctx) => Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Wallet Name', style: text.smallTitle?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 12), + TextField( + controller: controller, + style: text.paragraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration( + filled: true, + fillColor: colors.surface, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(14)), + ), + ), + const SizedBox(height: 24), + GlassButton( + filled: true, + onTap: () { + final v = controller.text.trim(); + if (v.isEmpty) return; + setState(() { + _accountName.text = v; + _accountNameError = null; + }); + Navigator.pop(ctx); + }, + child: Center(child: Text('Save', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + ), + ], + ), + ), + ); + } +} + +class _Field extends StatelessWidget { + final String label; + final String value; + final bool isLoading; + final Color? valueColor; + final IconData actionIcon; + final VoidCallback onAction; + + const _Field({ + required this.label, + required this.value, + required this.isLoading, + this.valueColor, + required this.actionIcon, + required this.onAction, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label, style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 8), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Expanded( + child: Text( + value, + style: text.smallParagraph?.copyWith(color: valueColor ?? colors.textPrimary), + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox( + width: 40, + height: 40, + child: GlassButton( + filled: true, + radius: 8, + padding: EdgeInsets.zero, + onTap: isLoading ? null : onAction, + child: Icon(actionIcon, size: 20, color: colors.textPrimary), + ), + ), + ], + ), + ), + ], + ); + } +} + +class _PaginationDot extends StatelessWidget { + final bool active; + + const _PaginationDot({required this.active}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + return Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: active ? 24 : 8, + height: 8, + decoration: BoxDecoration( + color: active ? colors.textPrimary : colors.textTertiary, + borderRadius: BorderRadius.circular(4), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index ffe26f36..1e54eef8 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -34,7 +34,23 @@ class ActivitySection extends ConsumerWidget { otherTransfers: data.otherTransfers, ); - if (all.isEmpty) return const SizedBox.shrink(); + if (all.isEmpty) { + return Column( + children: [ + const SizedBox(height: 40), + _header(colors, text, context), + const SizedBox(height: 48), + Icon(Icons.receipt_long_outlined, size: 48, color: colors.textTertiary), + const SizedBox(height: 16), + Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary)), + const SizedBox(height: 8), + Text( + 'Your activity will appear here', + style: text.detail?.copyWith(color: colors.textTertiary), + ), + ], + ); + } return Column( children: [ diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index f89c6430..a0912357 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/features/main/screens/create_wallet_and_backup_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/create/wallet_ready_screen.dart'; import 'package:resonance_network_wallet/features/main/screens/import_wallet_screen.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; @@ -43,7 +43,7 @@ class WelcomeScreenV2 extends StatelessWidget { context, MaterialPageRoute( settings: const RouteSettings(name: 'create_wallet'), - builder: (_) => const CreateWalletAndBackupScreen(), + builder: (_) => const WalletReadyScreenV2(), ), ), child: Center( From 847b40978221c6153932c2abb9c3be0efd699eb4 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Wed, 11 Feb 2026 22:49:55 +0800 Subject: [PATCH 28/49] format --- .../screens/create/wallet_ready_screen.dart | 62 +++++++++++++------ .../lib/v2/screens/home/activity_section.dart | 5 +- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index 78be29a2..f88522a6 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -59,10 +59,11 @@ class _WalletReadyScreenV2State extends ConsumerState { if (mounted) setState(() => _isLoading = false); } catch (e) { - if (mounted) setState(() { - _isLoading = false; - _error = 'Failed to generate: $e'; - }); + if (mounted) + setState(() { + _isLoading = false; + _error = 'Failed to generate: $e'; + }); } } @@ -87,11 +88,7 @@ class _WalletReadyScreenV2State extends ConsumerState { ref.invalidate(activeAccountProvider); if (!mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (_) => const HomeScreen()), - (route) => false, - ); + Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const HomeScreen()), (route) => false); } catch (e) { if (mounted) showCopySnackbar(context, title: 'Error', message: 'Error saving wallet: $e'); } finally { @@ -118,7 +115,10 @@ class _WalletReadyScreenV2State extends ConsumerState { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const AppBackButton(), - Text('Your Wallet Is Ready', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Text( + 'Your Wallet Is Ready', + style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20), + ), GestureDetector( onTap: () => Navigator.pop(context), child: Icon(Icons.close, color: colors.textPrimary, size: 24), @@ -141,7 +141,14 @@ class _WalletReadyScreenV2State extends ConsumerState { const SizedBox(height: 24), _Field( label: 'Wallet Address', - value: _isLoading ? '...' : AddressFormattingService.formatAddress(_address, prefix: 15, ellipses: '.......', postFix: 14), + value: _isLoading + ? '...' + : AddressFormattingService.formatAddress( + _address, + prefix: 15, + ellipses: '.......', + postFix: 14, + ), isLoading: _isLoading, actionIcon: Icons.copy, onAction: () => ClipboardExtensions.copyTextWithSnackbar(context, _address), @@ -153,7 +160,11 @@ class _WalletReadyScreenV2State extends ConsumerState { isLoading: _isLoading, valueColor: colors.accentPink, actionIcon: Icons.copy, - onAction: () => ClipboardExtensions.copyTextWithSnackbar(context, _checksum, message: 'Checkphrase copied'), + onAction: () => ClipboardExtensions.copyTextWithSnackbar( + context, + _checksum, + message: 'Checkphrase copied', + ), ), const SizedBox(height: 16), GestureDetector( @@ -184,8 +195,19 @@ class _WalletReadyScreenV2State extends ConsumerState { onTap: canContinue ? _continue : null, padding: const EdgeInsets.symmetric(vertical: 20), child: _isSubmitting - ? Center(child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary))) - : Center(child: Text('Continue', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + ? Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary), + ), + ) + : Center( + child: Text( + 'Continue', + style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary), + ), + ), ), const SizedBox(height: 24), ], @@ -231,7 +253,12 @@ class _WalletReadyScreenV2State extends ConsumerState { }); Navigator.pop(ctx); }, - child: Center(child: Text('Save', style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary))), + child: Center( + child: Text( + 'Save', + style: text.paragraph?.copyWith(fontWeight: FontWeight.w500, color: colors.textPrimary), + ), + ), ), ], ), @@ -269,10 +296,7 @@ class _Field extends StatelessWidget { const SizedBox(height: 12), Container( padding: const EdgeInsets.only(left: 12, right: 8, top: 8, bottom: 8), - decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), + decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8)), child: Row( children: [ Expanded( diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 1e54eef8..193bfa55 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -44,10 +44,7 @@ class ActivitySection extends ConsumerWidget { const SizedBox(height: 16), Text('No transactions yet', style: text.paragraph?.copyWith(color: colors.textSecondary)), const SizedBox(height: 8), - Text( - 'Your activity will appear here', - style: text.detail?.copyWith(color: colors.textTertiary), - ), + Text('Your activity will appear here', style: text.detail?.copyWith(color: colors.textTertiary)), ], ); } From 2a5de52bb383c9a2bdfd4177b03348cea19dc3fd Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 12:14:57 +0800 Subject: [PATCH 29/49] fix warning --- mobile-app/lib/v2/screens/create/wallet_ready_screen.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index f88522a6..abd1d4ff 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -59,11 +59,12 @@ class _WalletReadyScreenV2State extends ConsumerState { if (mounted) setState(() => _isLoading = false); } catch (e) { - if (mounted) + if (mounted) { setState(() { _isLoading = false; _error = 'Failed to generate: $e'; }); + } } } From 6ed873eb6635c45611060ca117f9d8e0678f4aa2 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 12:23:56 +0800 Subject: [PATCH 30/49] add import wallet screen --- .../screens/import/import_wallet_screen.dart | 168 ++++++++++++++++++ .../v2/screens/welcome/welcome_screen.dart | 4 +- 2 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 mobile-app/lib/v2/screens/import/import_wallet_screen.dart diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart new file mode 100644 index 00000000..e313ece2 --- /dev/null +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -0,0 +1,168 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:resonance_network_wallet/providers/account_providers.dart'; +import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class ImportWalletScreenV2 extends ConsumerStatefulWidget { + const ImportWalletScreenV2({super.key, this.walletIndex = 0}); + + final int walletIndex; + + @override + ConsumerState createState() => _ImportWalletScreenV2State(); +} + +class _ImportWalletScreenV2State extends ConsumerState { + final _controller = TextEditingController(); + final _settingsService = SettingsService(); + final _accountsService = AccountsService(); + final _discoveryService = AccountDiscoveryService(HdWalletService(), SubstrateService()); + bool _isLoading = false; + String? _error; + + bool get _hasInput => _controller.text.trim().isNotEmpty; + + Future _import() async { + final mnemonic = _controller.text.trim(); + setState(() { + _isLoading = true; + _error = null; + }); + + try { + if (!mnemonic.startsWith('//')) { + final words = mnemonic.split(' ').where((w) => w.isNotEmpty).toList(); + if (words.length != 12 && words.length != 24) { + throw Exception('Recovery phrase must be 12 or 24 words'); + } + } + + final key = HdWalletService().keyPairAtIndex(mnemonic, 0); + await _settingsService.setMnemonic(mnemonic, widget.walletIndex); + await _accountsService.addAccount( + Account(walletIndex: widget.walletIndex, index: 0, name: 'Account 1', accountId: key.ss58Address), + ); + + await _discoverAccounts(mnemonic); + _settingsService.setReferralCheckCompleted(); + _settingsService.setExistingUserSeenPromoVideo(); + + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => const HomeScreen()), + (route) => false, + ); + } catch (e) { + if (mounted) setState(() => _error = e.toString()); + } finally { + if (mounted) setState(() => _isLoading = false); + } + } + + Future _discoverAccounts(String mnemonic) async { + try { + final discovered = await _discoveryService.discoverAccounts( + mnemonic: mnemonic, + walletIndex: widget.walletIndex, + ); + final existing = (await _accountsService.getAccounts()).map((e) => e.accountId).toSet(); + for (final account in discovered) { + if (!existing.contains(account.accountId)) { + await _accountsService.addAccount(account); + } + } + ref.invalidate(accountsProvider); + ref.invalidate(activeAccountProvider); + } catch (_) {} + } + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + var textSTyleSmallTitle = text.smallTitle?.copyWith(fontSize: 20, color: colors.textPrimary, fontWeight: FontWeight.w400, height: 1.35); + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const AppBackButton(), + Text('Import Wallet', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 24), + ), + ], + ), + const SizedBox(height: 80), + Text( + 'Restore an existing wallet with your 24 word recovery phrase', + textAlign: TextAlign.center, + style: textSTyleSmallTitle, + ), + const SizedBox(height: 64), + Container( + height: 202, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(14), + ), + child: TextField( + controller: _controller, + onChanged: (_) => setState(() {}), + style: textSTyleSmallTitle, + decoration: InputDecoration.collapsed( + hintText: 'Type in or paste your recovery phrase. Separate words with spaces.', + hintStyle: textSTyleSmallTitle?.copyWith(color: colors.textSecondary), + ), + maxLines: null, + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.done, + ), + ), + if (_error != null) ...[ + const SizedBox(height: 16), + Text(_error!, style: text.detail?.copyWith(color: colors.error), textAlign: TextAlign.center), + ], + const Spacer(), + Opacity( + opacity: _hasInput ? 1.0 : 0.2, + child: GlassButton( + onTap: _hasInput && !_isLoading ? _import : null, + padding: const EdgeInsets.symmetric(vertical: 20), + child: _isLoading + ? Center(child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary))) + : Center(child: Text('Import Wallet', style: text.paragraph?.copyWith(fontSize: 16, fontWeight: FontWeight.w500, color: colors.textPrimary))), + ), + ), + const SizedBox(height: 24), + ], + ), + ), + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index a0912357..4ffbf8e0 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:resonance_network_wallet/v2/screens/create/wallet_ready_screen.dart'; -import 'package:resonance_network_wallet/features/main/screens/import_wallet_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/import/import_wallet_screen.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -59,7 +59,7 @@ class WelcomeScreenV2 extends StatelessWidget { context, MaterialPageRoute( settings: const RouteSettings(name: 'import_wallet'), - builder: (_) => const ImportWalletScreen(), + builder: (_) => const ImportWalletScreenV2(), ), ), child: Center( From a2a83e104dfe64e225f37b470ea10e2194f420fb Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 12:28:43 +0800 Subject: [PATCH 31/49] better retry --- mobile-app/lib/v2/screens/home/activity_section.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 193bfa55..7ab33f61 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/skeleton.dart'; import 'package:resonance_network_wallet/models/combined_transactions_list.dart'; +import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; import 'package:resonance_network_wallet/services/transaction_service.dart'; import 'package:resonance_network_wallet/v2/screens/activity/activity_screen.dart'; import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; @@ -90,8 +91,15 @@ class ActivitySection extends ConsumerWidget { Text('Error loading transactions', style: text.detail?.copyWith(color: colors.textError)), const SizedBox(height: 12), GestureDetector( - onTap: () => onRetry?.call(), - child: Text('Retry', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + behavior: HitTestBehavior.opaque, + onTap: () { + ref.invalidate(activeAccountTransactionsProvider); + onRetry?.call(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: Text('Retry', style: text.smallParagraph?.copyWith(color: colors.textPrimary, decoration: TextDecoration.underline)), + ), ), ], ), From c843359ea1582a713c4c9edba23d58995853d193 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 12:29:19 +0800 Subject: [PATCH 32/49] formatting --- .../lib/v2/screens/home/activity_section.dart | 8 +++- .../screens/import/import_wallet_screen.dart | 43 +++++++++++++------ 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 7ab33f61..af906677 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -98,7 +98,13 @@ class ActivitySection extends ConsumerWidget { }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - child: Text('Retry', style: text.smallParagraph?.copyWith(color: colors.textPrimary, decoration: TextDecoration.underline)), + child: Text( + 'Retry', + style: text.smallParagraph?.copyWith( + color: colors.textPrimary, + decoration: TextDecoration.underline, + ), + ), ), ), ], diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart index e313ece2..83c0c2ec 100644 --- a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -54,11 +54,7 @@ class _ImportWalletScreenV2State extends ConsumerState { _settingsService.setExistingUserSeenPromoVideo(); if (!mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: (_) => const HomeScreen()), - (route) => false, - ); + Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (_) => const HomeScreen()), (route) => false); } catch (e) { if (mounted) setState(() => _error = e.toString()); } finally { @@ -68,10 +64,7 @@ class _ImportWalletScreenV2State extends ConsumerState { Future _discoverAccounts(String mnemonic) async { try { - final discovered = await _discoveryService.discoverAccounts( - mnemonic: mnemonic, - walletIndex: widget.walletIndex, - ); + final discovered = await _discoveryService.discoverAccounts(mnemonic: mnemonic, walletIndex: widget.walletIndex); final existing = (await _accountsService.getAccounts()).map((e) => e.accountId).toSet(); for (final account in discovered) { if (!existing.contains(account.accountId)) { @@ -88,7 +81,12 @@ class _ImportWalletScreenV2State extends ConsumerState { final colors = context.colors; final text = context.themeText; - var textSTyleSmallTitle = text.smallTitle?.copyWith(fontSize: 20, color: colors.textPrimary, fontWeight: FontWeight.w400, height: 1.35); + var textSTyleSmallTitle = text.smallTitle?.copyWith( + fontSize: 20, + color: colors.textPrimary, + fontWeight: FontWeight.w400, + height: 1.35, + ); return Scaffold( backgroundColor: colors.background, body: GradientBackground( @@ -138,7 +136,11 @@ class _ImportWalletScreenV2State extends ConsumerState { ), if (_error != null) ...[ const SizedBox(height: 16), - Text(_error!, style: text.detail?.copyWith(color: colors.error), textAlign: TextAlign.center), + Text( + _error!, + style: text.detail?.copyWith(color: colors.error), + textAlign: TextAlign.center, + ), ], const Spacer(), Opacity( @@ -147,8 +149,23 @@ class _ImportWalletScreenV2State extends ConsumerState { onTap: _hasInput && !_isLoading ? _import : null, padding: const EdgeInsets.symmetric(vertical: 20), child: _isLoading - ? Center(child: SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary))) - : Center(child: Text('Import Wallet', style: text.paragraph?.copyWith(fontSize: 16, fontWeight: FontWeight.w500, color: colors.textPrimary))), + ? Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2, color: colors.textPrimary), + ), + ) + : Center( + child: Text( + 'Import Wallet', + style: text.paragraph?.copyWith( + fontSize: 16, + fontWeight: FontWeight.w500, + color: colors.textPrimary, + ), + ), + ), ), ), const SizedBox(height: 24), From caa50b05d151e0b466f9261cb11663e4b1f71a78 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 17:52:20 +0800 Subject: [PATCH 33/49] bottom sheet refactor --- .../main/screens/wallet_initializer.dart | 3 + .../v2/components/bottom_sheet_container.dart | 60 ++++++ .../lib/v2/components/glass_button.dart | 41 ++--- .../activity/transaction_detail_sheet.dart | 139 +++++--------- .../screens/create/wallet_ready_screen.dart | 3 + .../v2/screens/dev/button_test_screen.dart | 174 ++++++++++++++++++ .../screens/import/import_wallet_screen.dart | 1 + .../lib/v2/screens/receive/receive_sheet.dart | 63 ++----- .../v2/screens/settings/auto_lock_screen.dart | 1 + .../settings/recovery_phrase_screen.dart | 2 + .../lib/v2/screens/swap/deposit_screen.dart | 4 + .../v2/screens/welcome/welcome_screen.dart | 2 + 12 files changed, 328 insertions(+), 165 deletions(-) create mode 100644 mobile-app/lib/v2/components/bottom_sheet_container.dart create mode 100644 mobile-app/lib/v2/screens/dev/button_test_screen.dart diff --git a/mobile-app/lib/features/main/screens/wallet_initializer.dart b/mobile-app/lib/features/main/screens/wallet_initializer.dart index 7398b6de..15a622f6 100644 --- a/mobile-app/lib/features/main/screens/wallet_initializer.dart +++ b/mobile-app/lib/features/main/screens/wallet_initializer.dart @@ -4,6 +4,7 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/migration_dialog.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/v2/screens/welcome/welcome_screen.dart'; +import 'package:resonance_network_wallet/v2/screens/dev/button_test_screen.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; @@ -167,6 +168,8 @@ class WalletInitializerState extends ConsumerState { return const Scaffold(body: SizedBox.shrink()); } + // TODO: remove test screen override + // return const ButtonTestScreen(); if (_walletExists) { return const HomeScreen(); } else { diff --git a/mobile-app/lib/v2/components/bottom_sheet_container.dart b/mobile-app/lib/v2/components/bottom_sheet_container.dart new file mode 100644 index 00000000..726cef8b --- /dev/null +++ b/mobile-app/lib/v2/components/bottom_sheet_container.dart @@ -0,0 +1,60 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class BottomSheetContainer extends StatelessWidget { + final String title; + final Widget child; + + const BottomSheetContainer({super.key, required this.title, required this.child}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Container( + padding: const EdgeInsets.fromLTRB(24, 40, 24, 40), + decoration: BoxDecoration( + color: const Color(0xFF1A1A1A), + border: Border.all(color: const Color(0xFF3D3D3D)), + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon(Icons.close, color: colors.textPrimary, size: 20), + ), + ], + ), + const SizedBox(height: 32), + child, + ], + ), + ), + ); + } + + static void show(BuildContext context, {required WidgetBuilder builder}) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), + builder: (ctx) => BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: builder(ctx), + ), + ); + } +} diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index b879ec3f..3a1fb104 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -1,49 +1,44 @@ -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:glass_kit/glass_kit.dart'; -const _borderGradient = LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], -); - class GlassButton extends StatelessWidget { final VoidCallback? onTap; final Widget child; final EdgeInsetsGeometry padding; final double radius; final bool filled; + final double height; - const GlassButton({ + GlassButton({ super.key, this.onTap, required this.child, + required this.height, this.radius = 14, this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 20), this.filled = false, }); + final filledGradient = LinearGradient(colors: [Colors.white.withValues(alpha: 0.08), Colors.white.withValues(alpha: 0.04)]); + final emptyGradient = const LinearGradient(colors: [Colors.transparent, Colors.transparent]); + @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, - child: ClipRRect( + child: GlassContainer.clearGlass( + height: height, borderRadius: BorderRadius.circular(radius), - child: BackdropFilter( - filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20), - child: CustomPaint( - painter: RectBorderPainter( - borderRadius: BorderRadius.circular(radius), - strokeWidth: 0.889, - gradient: _borderGradient, - ), - child: Container( - padding: padding, - color: filled ? Colors.white.withValues(alpha: 0.08) : Colors.transparent, - child: child, - ), - ), + gradient: filled ? filledGradient : emptyGradient, + borderGradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x70FFFFFF), Color(0x18FFFFFF)], + ), + borderWidth: 0.889, + blur: 20, + child: Center( + child: child, ), ), ); diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index 49fe47cc..58685e2b 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -1,18 +1,16 @@ -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; +import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:url_launcher/url_launcher.dart'; void showTransactionDetailSheet(BuildContext context, TransactionEvent tx, String activeAccountId) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, + BottomSheetContainer.show( + context, builder: (_) => _TransactionDetailSheet(tx: tx, activeAccountId: activeAccountId), ); } @@ -33,6 +31,11 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { bool get _isSend => widget.tx.from == widget.activeAccountId; String get _counterparty => _isSend ? widget.tx.to : widget.tx.from; + String get _title { + if (widget.tx.isReversibleScheduled) return _isSend ? 'Pending' : 'Receiving'; + return _isSend ? 'Sent' : 'Received'; + } + @override void initState() { super.initState(); @@ -46,58 +49,24 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { final colors = context.colors; final text = context.themeText; - return BackdropFilter( - filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), - child: Container( - color: Colors.black54, - child: Center( - child: Container( - margin: const EdgeInsets.symmetric(horizontal: 14), - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), - border: Border.all(color: const Color(0xFF3D3D3D)), - borderRadius: BorderRadius.circular(24), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _headerRow(colors, text), - const SizedBox(height: 72), - _amountCard(colors, text), - const SizedBox(height: 56), - _addressSection(colors, text), - const SizedBox(height: 56), - _feeRow(colors, text), - const SizedBox(height: 32), - _explorerButton(colors, text), - ], - ), - ), - ), + return BottomSheetContainer( + title: _title, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 40), + _amountCard(colors, text), + const SizedBox(height: 40), + _addressSection(colors, text), + _feeRow(colors, text), + const SizedBox(height: 32), + _explorerButton(colors, text), + ], ), ); } - Widget _headerRow(AppColorsV2 colors, AppTextTheme text) { - final label = widget.tx.isReversibleScheduled - ? (_isSend ? 'Pending' : 'Receiving') - : _isSend - ? 'Sent' - : 'Received'; - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.close, color: colors.textPrimary, size: 20), - ), - ], - ); - } - Widget _amountCard(AppColorsV2 colors, AppTextTheme text) { final fmt = NumberFormattingService(); final amount = fmt.formatBalance(widget.tx.amount); @@ -111,22 +80,14 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '$amount ${AppConstants.tokenSymbol}', - style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 32, fontWeight: FontWeight.w600), - ), - ], + Text( + '$amount ${AppConstants.tokenSymbol}', + style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 32, fontWeight: FontWeight.w600), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text( - date, - style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), - ), + Text(date, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), const SizedBox(height: 8), Text('At $time', style: text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5))), ], @@ -143,19 +104,12 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - direction, - style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), - ), + Text(direction, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), const SizedBox(height: 12), Row( children: [ Expanded( - child: Text( - address, - style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), - overflow: TextOverflow.ellipsis, - ), + child: Text(address, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), ), const SizedBox(width: 8), _copyButton(colors, _counterparty), @@ -165,14 +119,13 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { const SizedBox(height: 4), Row( children: [ - Expanded( - child: Text(_checkphrase!, style: text.smallParagraph?.copyWith(color: const Color(0xFFED4CCE))), - ), + Expanded(child: Text(_checkphrase!, style: text.smallParagraph?.copyWith(color: colors.accentPink))), const SizedBox(width: 8), _copyButton(colors, _checkphrase!), ], ), ], + const SizedBox(height: 24), ], ); } @@ -191,22 +144,19 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { Widget _feeRow(AppColorsV2 colors, AppTextTheme text) { BigInt? fee; - if (widget.tx is TransferEvent) { - fee = (widget.tx as TransferEvent).fee; - } else if (widget.tx is PendingTransactionEvent) { - fee = (widget.tx as PendingTransactionEvent).fee; - } + if (widget.tx is TransferEvent) fee = (widget.tx as TransferEvent).fee; + if (widget.tx is PendingTransactionEvent) fee = (widget.tx as PendingTransactionEvent).fee; if (fee == null || fee == BigInt.zero) return const SizedBox.shrink(); final fmt = NumberFormattingService(); final feeStr = '${fmt.formatBalance(fee)} ${AppConstants.tokenSymbol}'; final style = text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5)); - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Network Fee:', style: style), - Text(feeStr, style: style), - ], + return Padding( + padding: const EdgeInsets.only(bottom: 24), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('Network Fee:', style: style), Text(feeStr, style: style)], + ), ); } @@ -223,10 +173,7 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - 'View in Explorer', - style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), - ), + Text('View in Explorer', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), const SizedBox(width: 8), Icon(Icons.open_in_new, size: 16, color: colors.textPrimary), ], @@ -241,8 +188,8 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { final transactionType = isMinerReward ? 'miner-rewards' : (tx.isReversibleScheduled || tx.isReversibleExecuted || tx.isReversibleCancelled) - ? 'reversible-transactions' - : 'immediate-transactions'; + ? 'reversible-transactions' + : 'immediate-transactions'; String? path; if (tx.extrinsicHash != null) { @@ -250,8 +197,6 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { } else if (isMinerReward && tx.blockHash != null) { path = '$transactionType/${tx.blockHash}'; } - if (path != null) { - launchUrl(Uri.parse('${AppConstants.explorerEndpoint}/$path')); - } + if (path != null) launchUrl(Uri.parse('${AppConstants.explorerEndpoint}/$path')); } } diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index abd1d4ff..3dfd273a 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -193,6 +193,7 @@ class _WalletReadyScreenV2State extends ConsumerState { ), const SizedBox(height: 24), GlassButton( + height: 56, onTap: canContinue ? _continue : null, padding: const EdgeInsets.symmetric(vertical: 20), child: _isSubmitting @@ -244,6 +245,7 @@ class _WalletReadyScreenV2State extends ConsumerState { ), const SizedBox(height: 24), GlassButton( + height: 56, filled: true, onTap: () { final v = controller.text.trim(); @@ -311,6 +313,7 @@ class _Field extends StatelessWidget { width: 40, height: 40, child: GlassButton( + height: 40, filled: true, radius: 8, padding: EdgeInsets.zero, diff --git a/mobile-app/lib/v2/screens/dev/button_test_screen.dart b/mobile-app/lib/v2/screens/dev/button_test_screen.dart new file mode 100644 index 00000000..d21e0406 --- /dev/null +++ b/mobile-app/lib/v2/screens/dev/button_test_screen.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:glass_kit/glass_kit.dart' as gk; +import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; +import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; + +class ButtonTestScreen extends StatelessWidget { + const ButtonTestScreen({super.key}); + + @override + Widget build(BuildContext context) { + final colors = context.colors; + final text = context.themeText; + + return Scaffold( + backgroundColor: colors.background, + body: GradientBackground( + child: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Button Test', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(height: 32), + + _label('Our GlassButton (outline)', colors, text), + GlassButton( + height: 56, + onTap: () {}, + child: Center(child: Text('Outline (Clear Outline)', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + const SizedBox(height: 16), + + _label('Our GlassButton (filled - Clear Glass)', colors, text), + GlassButton( + height: 56, + filled: true, + onTap: () {}, + child: Center(child: Text('Filled (Clear Glass)', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + const SizedBox(height: 16), + + _label('Our GlassButton (disabled 20%)', colors, text), + Opacity( + opacity: 0.2, + child: GlassButton( + height: 56, + child: Center(child: Text('Disabled', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + ), + const SizedBox(height: 32), + + _label('glass_kit clearGlass', colors, text), + gk.GlassContainer.clearGlass( + height: 56, + borderRadius: BorderRadius.circular(14), + gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.08), Colors.white.withValues(alpha: 0.04)]), + borderGradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + ), + blur: 20, + child: Center(child: Text('Clear Glass', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + const SizedBox(height: 16), + + _label('glass_kit frostedGlass', colors, text), + gk.GlassContainer.frostedGlass( + height: 56, + borderRadius: BorderRadius.circular(14), + gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.1), Colors.white.withValues(alpha: 0.05)]), + borderGradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + ), + blur: 20, + frostedOpacity: 0.1, + child: Center(child: Text('Frosted Glass', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + const SizedBox(height: 16), + + _label('glass_kit clearGlass (no fill)', colors, text), + gk.GlassContainer.clearGlass( + height: 56, + borderRadius: BorderRadius.circular(14), + gradient: const LinearGradient(colors: [Colors.transparent, Colors.transparent]), + borderGradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0x70FFFFFF), Color(0x18FFFFFF)], + ), + borderWidth: 0.889, + blur: 20, + child: Center(child: Text('Clear Outline Only', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + const SizedBox(height: 32), + + _label('PNG: glass_button_wide_340_bg', colors, text), + SizedBox( + height: 56, + child: GlassContainer( + asset: GlassContainer.wideAsset, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + child: Center(child: Text('Wide PNG', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + ), + const SizedBox(height: 16), + + _label('PNG: glass_button_40_bg (small)', colors, text), + const Row( + children: [ + SizedBox( + width: 40, + height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.edit, size: 18, color: Colors.white)), + ), + ), + SizedBox(width: 12), + SizedBox( + width: 40, + height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.copy, size: 18, color: Colors.white)), + ), + ), + ], + ), + const SizedBox(height: 16), + + _label('PNG: glass_border_bg', colors, text), + SizedBox( + height: 56, + child: GlassContainer( + asset: 'assets/v2/glass_border_bg.png', + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + child: Center(child: Text('Border PNG', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + ), + ), + const SizedBox(height: 32), + + _label('Action card PNGs', colors, text), + Row( + children: [ + Expanded(child: Image.asset('assets/v2/receive_button.png')), + const SizedBox(width: 15), + Expanded(child: Image.asset('assets/v2/send_button.png')), + const SizedBox(width: 15), + Expanded(child: Image.asset('assets/v2/swap_button.png')), + ], + ), + const SizedBox(height: 60), + ], + ), + ), + ), + ), + ); + } + + Widget _label(String s, AppColorsV2 colors, AppTextTheme text) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text(s, style: text.detail?.copyWith(color: colors.textSecondary)), + ); + } +} diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart index 83c0c2ec..0176d68a 100644 --- a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -146,6 +146,7 @@ class _ImportWalletScreenV2State extends ConsumerState { Opacity( opacity: _hasInput ? 1.0 : 0.2, child: GlassButton( + height: 56, onTap: _hasInput && !_isLoading ? _import : null, padding: const EdgeInsets.symmetric(vertical: 20), child: _isLoading diff --git a/mobile-app/lib/v2/screens/receive/receive_sheet.dart b/mobile-app/lib/v2/screens/receive/receive_sheet.dart index e72b3e23..e4bccb1e 100644 --- a/mobile-app/lib/v2/screens/receive/receive_sheet.dart +++ b/mobile-app/lib/v2/screens/receive/receive_sheet.dart @@ -1,8 +1,8 @@ -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; +import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:share_plus/share_plus.dart'; @@ -74,46 +74,25 @@ class _ReceiveSheetState extends State { final colors = context.colors; final text = context.themeText; - return Container( - padding: const EdgeInsets.fromLTRB(24, 40, 24, 40), - decoration: BoxDecoration( - color: const Color(0xFF1A1A1A), - border: Border.all(color: const Color(0xFF3D3D3D)), - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), - ), - child: SafeArea( - top: false, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + return BottomSheetContainer( + title: 'Receive', + child: _accountId == null + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 80), + child: Center(child: CircularProgressIndicator(color: colors.textPrimary)), + ) + : Column( + mainAxisSize: MainAxisSize.min, children: [ - Text('Receive', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.close, color: colors.textPrimary, size: 20), - ), + _buildQrCode(colors), + const SizedBox(height: 20), + _buildAddress(colors, text), + const SizedBox(height: 9), + _buildChecksum(colors, text), + const SizedBox(height: 32), + _buildButtons(colors, text), ], ), - const SizedBox(height: 32), - if (_accountId == null) - Padding( - padding: const EdgeInsets.symmetric(vertical: 80), - child: CircularProgressIndicator(color: colors.textPrimary), - ) - else ...[ - _buildQrCode(colors), - const SizedBox(height: 20), - _buildAddress(colors, text), - const SizedBox(height: 9), - _buildChecksum(colors, text), - const SizedBox(height: 32), - _buildButtons(colors, text), - ], - ], - ), - ), ); } @@ -267,11 +246,5 @@ class _ReceiveSheetState extends State { } void showReceiveSheetV2(BuildContext context) { - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - isScrollControlled: true, - constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), - builder: (_) => BackdropFilter(filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), child: const ReceiveSheet()), - ); + BottomSheetContainer.show(context, builder: (_) => const ReceiveSheet()); } diff --git a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart index c837de34..50b7f684 100644 --- a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart +++ b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart @@ -80,6 +80,7 @@ class _AutoLockScreenState extends State { ), const Spacer(), GlassButton( + height: 56, onTap: _confirm, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 60fee0f5..725a46a6 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -147,6 +147,7 @@ class _RecoveryPhraseScreenState extends State { return SizedBox( height: 36, child: GlassButton( + height: 36, radius: 14, padding: const EdgeInsets.symmetric(horizontal: 10), filled: true, @@ -196,6 +197,7 @@ class _RecoveryPhraseScreenState extends State { Widget _revealButton(AppColorsV2 colors, AppTextTheme text) { return GlassButton( + height: 56, onTap: _toggleReveal, padding: const EdgeInsets.symmetric(vertical: 20), child: Center( diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index eb225368..687e2e77 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -193,6 +193,7 @@ class _DepositScreenState extends State { children: [ Expanded( child: GlassButton( + height: 56, onTap: _copyAddress, padding: const EdgeInsets.symmetric(vertical: 20), child: Row( @@ -211,6 +212,7 @@ class _DepositScreenState extends State { const SizedBox(width: 16), Expanded( child: GlassButton( + height: 56, onTap: () {}, padding: const EdgeInsets.symmetric(vertical: 20), child: Row( @@ -291,6 +293,7 @@ class _DepositScreenState extends State { Widget _sentButton(AppColorsV2 colors, AppTextTheme text) { return GlassButton( + height: 56, filled: true, onTap: _confirming ? null : _confirmSent, padding: const EdgeInsets.symmetric(vertical: 20), @@ -311,6 +314,7 @@ class _DepositScreenState extends State { Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { return GlassButton( + height: 56, filled: true, onTap: () => Navigator.popUntil(context, (r) => r.isFirst), padding: const EdgeInsets.symmetric(vertical: 20), diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index 4ffbf8e0..97d39795 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -38,6 +38,7 @@ class WelcomeScreenV2 extends StatelessWidget { ), const SizedBox(height: 64), GlassButton( + height: 56, filled: true, onTap: () => Navigator.push( context, @@ -55,6 +56,7 @@ class WelcomeScreenV2 extends StatelessWidget { ), const SizedBox(height: 32), GlassButton( + height: 56, onTap: () => Navigator.push( context, MaterialPageRoute( From f777b89daa1936c6e06dd2bbe05580a1fa2b5ee7 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 18:53:25 +0800 Subject: [PATCH 34/49] format --- .../main/screens/wallet_initializer.dart | 5 +- .../v2/components/bottom_sheet_container.dart | 7 +- .../lib/v2/components/glass_button.dart | 36 +- .../activity/transaction_detail_sheet.dart | 34 +- .../v2/screens/dev/button_test_screen.dart | 317 +++++++++++------- 5 files changed, 254 insertions(+), 145 deletions(-) diff --git a/mobile-app/lib/features/main/screens/wallet_initializer.dart b/mobile-app/lib/features/main/screens/wallet_initializer.dart index 15a622f6..9243f1cc 100644 --- a/mobile-app/lib/features/main/screens/wallet_initializer.dart +++ b/mobile-app/lib/features/main/screens/wallet_initializer.dart @@ -2,9 +2,9 @@ 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/migration_dialog.dart'; +// import 'package:resonance_network_wallet/v2/screens/dev/button_test_screen.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/v2/screens/welcome/welcome_screen.dart'; -import 'package:resonance_network_wallet/v2/screens/dev/button_test_screen.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/utils/env_utils.dart'; @@ -168,8 +168,9 @@ class WalletInitializerState extends ConsumerState { return const Scaffold(body: SizedBox.shrink()); } - // TODO: remove test screen override + // for testing buttons // return const ButtonTestScreen(); + if (_walletExists) { return const HomeScreen(); } else { diff --git a/mobile-app/lib/v2/components/bottom_sheet_container.dart b/mobile-app/lib/v2/components/bottom_sheet_container.dart index 726cef8b..b6ef6d80 100644 --- a/mobile-app/lib/v2/components/bottom_sheet_container.dart +++ b/mobile-app/lib/v2/components/bottom_sheet_container.dart @@ -19,7 +19,7 @@ class BottomSheetContainer extends StatelessWidget { decoration: BoxDecoration( color: const Color(0xFF1A1A1A), border: Border.all(color: const Color(0xFF3D3D3D)), - borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + borderRadius: BorderRadius.circular(24), ), child: SafeArea( top: false, @@ -51,10 +51,7 @@ class BottomSheetContainer extends StatelessWidget { backgroundColor: Colors.transparent, isScrollControlled: true, constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width), - builder: (ctx) => BackdropFilter( - filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), - child: builder(ctx), - ), + builder: (ctx) => BackdropFilter(filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), child: builder(ctx)), ); } } diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index 3a1fb104..ae0d51ff 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -9,7 +9,7 @@ class GlassButton extends StatelessWidget { final bool filled; final double height; - GlassButton({ + const GlassButton({ super.key, this.onTap, required this.child, @@ -19,9 +19,6 @@ class GlassButton extends StatelessWidget { this.filled = false, }); - final filledGradient = LinearGradient(colors: [Colors.white.withValues(alpha: 0.08), Colors.white.withValues(alpha: 0.04)]); - final emptyGradient = const LinearGradient(colors: [Colors.transparent, Colors.transparent]); - @override Widget build(BuildContext context) { return GestureDetector( @@ -29,16 +26,31 @@ class GlassButton extends StatelessWidget { child: GlassContainer.clearGlass( height: height, borderRadius: BorderRadius.circular(radius), - gradient: filled ? filledGradient : emptyGradient, - borderGradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0x70FFFFFF), Color(0x18FFFFFF)], - ), + color: filled ? const Color(0xFFFFFFFF).withValues(alpha: 0.1) : Colors.transparent, + borderColor: const Color(0xFFFFFFFF).withValues(alpha: 0.66), borderWidth: 0.889, blur: 20, - child: Center( - child: child, + child: Stack( + children: [ + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withValues(alpha: 0.2), + Colors.transparent, + Colors.transparent, + Colors.black.withValues(alpha: 0.15), + ], + stops: const [0.0, 0.25, 0.75, 1.0], + ), + ), + ), + ), + Center(child: child), + ], ), ), ); diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index 58685e2b..1ec53421 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -87,7 +87,10 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text(date, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + date, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), const SizedBox(height: 8), Text('At $time', style: text.detail?.copyWith(color: Colors.white.withValues(alpha: 0.5))), ], @@ -104,12 +107,19 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(direction, style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600)), + Text( + direction, + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w600), + ), const SizedBox(height: 12), Row( children: [ Expanded( - child: Text(address, style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis), + child: Text( + address, + style: text.smallParagraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis, + ), ), const SizedBox(width: 8), _copyButton(colors, _counterparty), @@ -119,7 +129,9 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { const SizedBox(height: 4), Row( children: [ - Expanded(child: Text(_checkphrase!, style: text.smallParagraph?.copyWith(color: colors.accentPink))), + Expanded( + child: Text(_checkphrase!, style: text.smallParagraph?.copyWith(color: colors.accentPink)), + ), const SizedBox(width: 8), _copyButton(colors, _checkphrase!), ], @@ -155,7 +167,10 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { padding: const EdgeInsets.only(bottom: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [Text('Network Fee:', style: style), Text(feeStr, style: style)], + children: [ + Text('Network Fee:', style: style), + Text(feeStr, style: style), + ], ), ); } @@ -173,7 +188,10 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('View in Explorer', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500)), + Text( + 'View in Explorer', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), const SizedBox(width: 8), Icon(Icons.open_in_new, size: 16, color: colors.textPrimary), ], @@ -188,8 +206,8 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { final transactionType = isMinerReward ? 'miner-rewards' : (tx.isReversibleScheduled || tx.isReversibleExecuted || tx.isReversibleCancelled) - ? 'reversible-transactions' - : 'immediate-transactions'; + ? 'reversible-transactions' + : 'immediate-transactions'; String? path; if (tx.extrinsicHash != null) { diff --git a/mobile-app/lib/v2/screens/dev/button_test_screen.dart b/mobile-app/lib/v2/screens/dev/button_test_screen.dart index d21e0406..ab8e2fc6 100644 --- a/mobile-app/lib/v2/screens/dev/button_test_screen.dart +++ b/mobile-app/lib/v2/screens/dev/button_test_screen.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:glass_kit/glass_kit.dart' as gk; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/glass_container.dart'; -import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -15,150 +14,232 @@ class ButtonTestScreen extends StatelessWidget { final text = context.themeText; return Scaffold( - backgroundColor: colors.background, - body: GradientBackground( - child: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text('Button Test', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - const SizedBox(height: 32), + backgroundColor: const Color(0xFF1A1A1A), + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Button Test', style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + const SizedBox(height: 32), - _label('Our GlassButton (outline)', colors, text), - GlassButton( - height: 56, - onTap: () {}, - child: Center(child: Text('Outline (Clear Outline)', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), - ), - const SizedBox(height: 16), - - _label('Our GlassButton (filled - Clear Glass)', colors, text), - GlassButton( - height: 56, - filled: true, - onTap: () {}, - child: Center(child: Text('Filled (Clear Glass)', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), - ), - const SizedBox(height: 16), - - _label('Our GlassButton (disabled 20%)', colors, text), - Opacity( - opacity: 0.2, - child: GlassButton( - height: 56, - child: Center(child: Text('Disabled', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + _label('Our GlassButton (outline)', colors, text), + GlassButton( + height: 56, + onTap: () {}, + child: Center( + child: Text( + 'Outline (Clear Outline)', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), ), - const SizedBox(height: 32), + ), + const SizedBox(height: 16), - _label('glass_kit clearGlass', colors, text), - gk.GlassContainer.clearGlass( - height: 56, - borderRadius: BorderRadius.circular(14), - gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.08), Colors.white.withValues(alpha: 0.04)]), - borderGradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + _label('Our GlassButton (filled - Clear Glass)', colors, text), + GlassButton( + height: 56, + filled: true, + onTap: () {}, + child: Center( + child: Text( + 'Filled (Clear Glass)', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), - blur: 20, - child: Center(child: Text('Clear Glass', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), ), - const SizedBox(height: 16), + ), + const SizedBox(height: 16), - _label('glass_kit frostedGlass', colors, text), - gk.GlassContainer.frostedGlass( + _label('Our GlassButton (disabled 20%)', colors, text), + Opacity( + opacity: 0.2, + child: GlassButton( height: 56, - borderRadius: BorderRadius.circular(14), - gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.1), Colors.white.withValues(alpha: 0.05)]), - borderGradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + child: Center( + child: Text( + 'Disabled', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), ), - blur: 20, - frostedOpacity: 0.1, - child: Center(child: Text('Frosted Glass', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), ), - const SizedBox(height: 16), + ), + const SizedBox(height: 32), - _label('glass_kit clearGlass (no fill)', colors, text), - gk.GlassContainer.clearGlass( - height: 56, - borderRadius: BorderRadius.circular(14), - gradient: const LinearGradient(colors: [Colors.transparent, Colors.transparent]), - borderGradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0x70FFFFFF), Color(0x18FFFFFF)], - ), - borderWidth: 0.889, - blur: 20, - child: Center(child: Text('Clear Outline Only', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), - ), - const SizedBox(height: 32), + // _label('glass_kit frostedGlass', colors, text), + // gk.GlassContainer.frostedGlass( + // height: 56, + // borderRadius: BorderRadius.circular(14), + // gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.1), Colors.white.withValues(alpha: 0.05)]), + // borderGradient: const LinearGradient( + // begin: Alignment.topCenter, + // end: Alignment.bottomCenter, + // colors: [Color(0x55FFFFFF), Color(0x18FFFFFF)], + // ), + // blur: 20, + // frostedOpacity: 0.1, + // child: Center(child: Text('Frosted Glass', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + // ), + // const SizedBox(height: 16), - _label('PNG: glass_button_wide_340_bg', colors, text), - SizedBox( - height: 56, - child: GlassContainer( - asset: GlassContainer.wideAsset, - padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), - child: Center(child: Text('Wide PNG', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + // _label('glass_kit clearGlass (no fill)', colors, text), + // gk.GlassContainer.clearGlass( + // height: 56, + // borderRadius: BorderRadius.circular(14), + // gradient: const LinearGradient(colors: [Colors.transparent, Colors.transparent]), + // borderGradient: const LinearGradient( + // begin: Alignment.topCenter, + // end: Alignment.bottomCenter, + // colors: [Color(0x70FFFFFF), Color(0x18FFFFFF)], + // ), + // borderWidth: 0.889, + // blur: 20, + // child: Center(child: Text('Clear Outline Only', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + // ), + // const SizedBox(height: 32), + _label('Plain', colors, text), + gk.GlassContainer.clearGlass( + height: 56, + borderRadius: BorderRadius.circular(14), + color: const Color(0xFFFFFFFF).withValues(alpha: 0.1), + // there is no gradient fill in our design. + // gradient: LinearGradient(colors: [Colors.white.withValues(alpha: 0.08), Colors.white.withValues(alpha: 0.04)]), + borderGradient: LinearGradient( + colors: [ + const Color(0xFFFFFFFF).withValues(alpha: 0.66), + const Color(0xFFFFFFFF).withValues(alpha: 0.66), + ], + ), + borderWidth: 0.889, + blur: 20, + child: Center( + child: Text( + 'Plain', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), ), - const SizedBox(height: 16), + ), + const SizedBox(height: 16), - _label('PNG: glass_button_40_bg (small)', colors, text), - const Row( + _label('+ inner shadow (top+bottom)', colors, text), + gk.GlassContainer.clearGlass( + height: 56, + borderRadius: BorderRadius.circular(14), + color: const Color(0xFFFFFFFF).withValues(alpha: 0.1), + borderGradient: LinearGradient( + colors: [ + const Color(0xFFFFFFFF).withValues(alpha: 0.66), + const Color(0xFFFFFFFF).withValues(alpha: 0.66), + ], + ), + borderWidth: 0.889, + blur: 20, + child: Stack( children: [ - SizedBox( - width: 40, - height: 40, - child: GlassContainer( - asset: GlassContainer.smallAsset, - child: Center(child: Icon(Icons.edit, size: 18, color: Colors.white)), + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withValues(alpha: 0.2), + Colors.transparent, + Colors.transparent, + Colors.black.withValues(alpha: 0.15), + ], + stops: const [0.0, 0.25, 0.75, 1.0], + ), + ), ), ), - SizedBox(width: 12), - SizedBox( - width: 40, - height: 40, - child: GlassContainer( - asset: GlassContainer.smallAsset, - child: Center(child: Icon(Icons.copy, size: 18, color: Colors.white)), + Center( + child: Text( + 'Top+Bottom', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), ), ], ), - const SizedBox(height: 16), + ), + const SizedBox(height: 16), - _label('PNG: glass_border_bg', colors, text), - SizedBox( - height: 56, - child: GlassContainer( - asset: 'assets/v2/glass_border_bg.png', - padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), - child: Center(child: Text('Border PNG', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + _label('Our GlassButton (filled - Clear Glass)', colors, text), + GlassButton( + height: 56, + filled: true, + onTap: () {}, + child: Center( + child: Text( + 'Current', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), ), - const SizedBox(height: 32), + ), + const SizedBox(height: 16), - _label('Action card PNGs', colors, text), - Row( - children: [ - Expanded(child: Image.asset('assets/v2/receive_button.png')), - const SizedBox(width: 15), - Expanded(child: Image.asset('assets/v2/send_button.png')), - const SizedBox(width: 15), - Expanded(child: Image.asset('assets/v2/swap_button.png')), - ], + _label('TARGET: PNG wide', colors, text), + SizedBox( + height: 56, + child: GlassContainer( + asset: GlassContainer.wideAsset, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + child: Center( + child: Text( + 'Wide PNG', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), + ), + ), ), - const SizedBox(height: 60), - ], - ), + ), + const SizedBox(height: 16), + + _label('PNG: glass_button_40_bg (small)', colors, text), + const Row( + children: [ + SizedBox( + width: 40, + height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.edit, size: 18, color: Colors.white)), + ), + ), + SizedBox(width: 12), + SizedBox( + width: 40, + height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Center(child: Icon(Icons.copy, size: 18, color: Colors.white)), + ), + ), + ], + ), + const SizedBox(height: 16), + + // _label('PNG: glass_border_bg', colors, text), + // SizedBox( + // height: 56, + // child: GlassContainer( + // asset: 'assets/v2/glass_border_bg.png', + // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + // child: Center(child: Text('Border PNG', style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500))), + // ), + // ), + // const SizedBox(height: 32), + _label('Action card PNGs', colors, text), + Row( + children: [ + Expanded(child: Image.asset('assets/v2/receive_button.png')), + const SizedBox(width: 15), + Expanded(child: Image.asset('assets/v2/send_button.png')), + const SizedBox(width: 15), + Expanded(child: Image.asset('assets/v2/swap_button.png')), + ], + ), + const SizedBox(height: 60), + ], ), ), ), From 8ab906038e3d8dfcd7e0462dd602096bb85db6f0 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Thu, 12 Feb 2026 18:55:51 +0800 Subject: [PATCH 35/49] Version 1.2.0 build 68 --- mobile-app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 70d9fa2f..e2b38f80 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -2,7 +2,7 @@ name: resonance_network_wallet description: A Flutter wallet for the Quantus blockchain. publish_to: "none" -version: 1.1.5+67 +version: 1.2.0+68 environment: sdk: ">=3.8.0 <4.0.0" From 4724ebbf4ebce59f486f2a4a8f9f75cc5ca477a2 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 09:39:29 +0800 Subject: [PATCH 36/49] improve recovery screen --- .../lib/v2/screens/settings/recovery_phrase_screen.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 725a46a6..6446e185 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -140,6 +140,7 @@ class _RecoveryPhraseScreenState extends State { Widget _wordChip(int index, String word, AppColorsV2 colors, AppTextTheme text) { final wordWidget = Text( word, + textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false), style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, ); @@ -152,7 +153,10 @@ class _RecoveryPhraseScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), filled: true, child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ + const SizedBox(width: 8), Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), const SizedBox(width: 6), Expanded( From 7829d83984de240dcdb98e2b645dba3155486f03 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:16:19 +0800 Subject: [PATCH 37/49] adding glass button pngs --- mobile-app/assets/v2/action_receive.svg | 4 + mobile-app/assets/v2/action_send.svg | 4 + mobile-app/assets/v2/action_swap.svg | 6 ++ mobile-app/assets/v2/glass_104_x_80.png | Bin 0 -> 6064 bytes mobile-app/assets/v2/glass_40.png | Bin 0 -> 3967 bytes .../assets/v2/glass_circle_icon_button_bg.png | Bin 0 -> 5445 bytes .../assets/v2/pending_send_box_arrow.png | Bin 0 -> 11627 bytes mobile-app/assets/v2/swap_arrows_down_up.svg | 6 ++ .../v2/swap_clock_counter_clockwise.svg | 5 ++ mobile-app/assets/v2/swap_qr_code.svg | 11 +++ .../lib/v2/components/glass_button.dart | 62 ++++++++++++--- .../components/glass_circle_icon_button.dart | 45 +++++++++++ .../lib/v2/screens/home/home_screen.dart | 75 ++++++++++++++---- .../lib/v2/screens/swap/swap_screen.dart | 47 ++++++----- mobile-app/pubspec.yaml | 9 +++ 15 files changed, 228 insertions(+), 46 deletions(-) create mode 100644 mobile-app/assets/v2/action_receive.svg create mode 100644 mobile-app/assets/v2/action_send.svg create mode 100644 mobile-app/assets/v2/action_swap.svg create mode 100644 mobile-app/assets/v2/glass_104_x_80.png create mode 100644 mobile-app/assets/v2/glass_40.png create mode 100644 mobile-app/assets/v2/glass_circle_icon_button_bg.png create mode 100644 mobile-app/assets/v2/pending_send_box_arrow.png create mode 100644 mobile-app/assets/v2/swap_arrows_down_up.svg create mode 100644 mobile-app/assets/v2/swap_clock_counter_clockwise.svg create mode 100644 mobile-app/assets/v2/swap_qr_code.svg create mode 100644 mobile-app/lib/v2/components/glass_circle_icon_button.dart diff --git a/mobile-app/assets/v2/action_receive.svg b/mobile-app/assets/v2/action_receive.svg new file mode 100644 index 00000000..e59c9dc0 --- /dev/null +++ b/mobile-app/assets/v2/action_receive.svg @@ -0,0 +1,4 @@ + + + + diff --git a/mobile-app/assets/v2/action_send.svg b/mobile-app/assets/v2/action_send.svg new file mode 100644 index 00000000..b958acd5 --- /dev/null +++ b/mobile-app/assets/v2/action_send.svg @@ -0,0 +1,4 @@ + + + + diff --git a/mobile-app/assets/v2/action_swap.svg b/mobile-app/assets/v2/action_swap.svg new file mode 100644 index 00000000..d4d7f033 --- /dev/null +++ b/mobile-app/assets/v2/action_swap.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/mobile-app/assets/v2/glass_104_x_80.png b/mobile-app/assets/v2/glass_104_x_80.png new file mode 100644 index 0000000000000000000000000000000000000000..07cff194214b9fad6aa79e517426b40c870721c6 GIT binary patch literal 6064 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 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/glass_40.png b/mobile-app/assets/v2/glass_40.png new file mode 100644 index 0000000000000000000000000000000000000000..ea61369487e9aa21a3c68d85df62e66861080496 GIT binary patch literal 3967 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 Z@izd#0sDn#n%e*X002ovPDHLkV1hZQ$Y%fm literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/glass_circle_icon_button_bg.png b/mobile-app/assets/v2/glass_circle_icon_button_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..2cf38beaa4bbadde942788874d9429381f7a761f GIT binary patch literal 5445 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 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/pending_send_box_arrow.png b/mobile-app/assets/v2/pending_send_box_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1de1a90046230c1836be02dc6ce14111f1444c GIT binary patch literal 11627 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 literal 0 HcmV?d00001 diff --git a/mobile-app/assets/v2/swap_arrows_down_up.svg b/mobile-app/assets/v2/swap_arrows_down_up.svg new file mode 100644 index 00000000..22a52430 --- /dev/null +++ b/mobile-app/assets/v2/swap_arrows_down_up.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/mobile-app/assets/v2/swap_clock_counter_clockwise.svg b/mobile-app/assets/v2/swap_clock_counter_clockwise.svg new file mode 100644 index 00000000..b1c5d03e --- /dev/null +++ b/mobile-app/assets/v2/swap_clock_counter_clockwise.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile-app/assets/v2/swap_qr_code.svg b/mobile-app/assets/v2/swap_qr_code.svg new file mode 100644 index 00000000..0bbb9760 --- /dev/null +++ b/mobile-app/assets/v2/swap_qr_code.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/mobile-app/lib/v2/components/glass_button.dart b/mobile-app/lib/v2/components/glass_button.dart index ae0d51ff..c30437e9 100644 --- a/mobile-app/lib/v2/components/glass_button.dart +++ b/mobile-app/lib/v2/components/glass_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:glass_kit/glass_kit.dart'; +import 'dart:ui'; class GlassButton extends StatelessWidget { final VoidCallback? onTap; @@ -21,35 +21,75 @@ class GlassButton extends StatelessWidget { @override Widget build(BuildContext context) { + final borderRadius = BorderRadius.circular(radius); + final outerBorderColor = filled + ? const Color(0xFFFFFFFF).withValues(alpha: 0.32) + : const Color(0xFFFFFFFF).withValues(alpha: 0.44); + final innerBorderColor = filled + ? const Color(0xFFFFFFFF).withValues(alpha: 0.18) + : const Color(0xFFFFFFFF).withValues(alpha: 0.12); + return GestureDetector( onTap: onTap, - child: GlassContainer.clearGlass( + child: SizedBox( height: height, - borderRadius: BorderRadius.circular(radius), - color: filled ? const Color(0xFFFFFFFF).withValues(alpha: 0.1) : Colors.transparent, - borderColor: const Color(0xFFFFFFFF).withValues(alpha: 0.66), - borderWidth: 0.889, - blur: 20, + width: double.infinity, child: Stack( + fit: StackFit.expand, children: [ + ClipRRect( + borderRadius: borderRadius, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: borderRadius, + color: filled ? const Color(0xFFFFFFFF).withValues(alpha: 0.1) : Colors.transparent, + ), + ), + ), + ), Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( + borderRadius: borderRadius, + border: Border.all(color: outerBorderColor, width: 0.889), + ), + ), + ), + Positioned.fill( + child: Padding( + padding: const EdgeInsets.all(1), + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(radius - 1), + border: Border.all(color: innerBorderColor, width: 0.6), + ), + ), + ), + ), + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: borderRadius, gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - Colors.black.withValues(alpha: 0.2), + Colors.black.withValues(alpha: 0.12), Colors.transparent, Colors.transparent, - Colors.black.withValues(alpha: 0.15), + Colors.black.withValues(alpha: 0.16), ], - stops: const [0.0, 0.25, 0.75, 1.0], + stops: const [0.0, 0.22, 0.78, 1.0], ), ), ), ), - Center(child: child), + Padding( + padding: padding, + child: Center(child: child), + ), ], ), ), diff --git a/mobile-app/lib/v2/components/glass_circle_icon_button.dart b/mobile-app/lib/v2/components/glass_circle_icon_button.dart new file mode 100644 index 00000000..a91d4af7 --- /dev/null +++ b/mobile-app/lib/v2/components/glass_circle_icon_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +class GlassCircleIconButton extends StatelessWidget { + static const _bgAsset = 'assets/v2/glass_circle_icon_button_bg.png'; + + final IconData icon; + final VoidCallback? onTap; + final double size; + final double iconSize; + final Color iconColor; + final bool filled; + + const GlassCircleIconButton({ + super.key, + required this.icon, + required this.iconColor, + this.onTap, + this.size = 48, + this.iconSize = 20, + this.filled = true, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: SizedBox( + width: size, + height: size, + child: Stack( + fit: StackFit.expand, + children: [ + Opacity( + opacity: filled ? 1 : 0.92, + child: Image.asset(_bgAsset, fit: BoxFit.cover), + ), + Center( + child: Icon(icon, color: iconColor, size: iconSize), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index ae2a1e38..543c4456 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/account_gradient_image.dart'; import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; @@ -17,6 +18,7 @@ import 'package:resonance_network_wallet/providers/filtered_all_transactions_pro import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; +import 'package:resonance_network_wallet/v2/components/glass_circle_icon_button.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:resonance_network_wallet/v2/screens/home/activity_section.dart'; @@ -29,6 +31,8 @@ class HomeScreen extends ConsumerStatefulWidget { } class _HomeScreenState extends ConsumerState { + static const _actionButtonBgAsset = 'assets/v2/glass_104_x_80.png'; + final NumberFormattingService _fmt = NumberFormattingService(); bool _balanceHidden = false; @@ -152,15 +156,7 @@ class _HomeScreenState extends ConsumerState { } Widget _glassCircleButton({required IconData icon, required AppColorsV2 colors, required VoidCallback onTap}) { - return GestureDetector( - onTap: onTap, - child: Container( - width: 40, - height: 40, - decoration: BoxDecoration(shape: BoxShape.circle, color: colors.surfaceGlass), - child: Icon(icon, color: colors.textPrimary, size: 20), - ), - ); + return GlassCircleIconButton(icon: icon, iconColor: colors.textPrimary, onTap: onTap, size: 40, iconSize: 20); } Widget _buildBalance(AsyncValue balanceAsync, AppColorsV2 colors, AppTextTheme text) { @@ -206,21 +202,70 @@ class _HomeScreenState extends ConsumerState { Widget _buildActionButtons(AppColorsV2 colors, AppTextTheme text) { return Row( children: [ - _actionCard('assets/v2/receive_button.png', () => showReceiveSheetV2(context)), + _actionCard( + iconAsset: 'assets/v2/action_receive.svg', + label: 'Receive', + colors: colors, + text: text, + onTap: () => showReceiveSheetV2(context), + ), const SizedBox(width: 15), - _actionCard('assets/v2/send_button.png', () => showSendSheetV2(context)), + _actionCard( + iconAsset: 'assets/v2/action_send.svg', + label: 'Send', + colors: colors, + text: text, + onTap: () => showSendSheetV2(context), + ), const SizedBox(width: 15), _actionCard( - 'assets/v2/swap_button.png', - () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen())), + iconAsset: 'assets/v2/action_swap.svg', + label: 'Swap', + colors: colors, + text: text, + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen())), ), ], ); } - Widget _actionCard(String asset, VoidCallback onTap) { + Widget _actionCard({ + required String iconAsset, + required String label, + required AppColorsV2 colors, + required AppTextTheme text, + required VoidCallback onTap, + }) { return Expanded( - child: GestureDetector(onTap: onTap, child: Image.asset(asset)), + child: GestureDetector( + onTap: onTap, + child: SizedBox( + height: 80, + child: Stack( + fit: StackFit.expand, + children: [ + Image.asset(_actionButtonBgAsset, fit: BoxFit.fill), + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset(iconAsset, width: 24, height: 24), + const SizedBox(height: 6), + Text( + label, + maxLines: 1, + overflow: TextOverflow.clip, + style: text.paragraph?.copyWith(color: colors.textPrimary, height: 1.0), + ), + ], + ), + ), + ], + ), + ), + ), ); } } diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 05f5ffe5..1b6c57f1 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; @@ -18,6 +19,11 @@ class SwapScreen extends StatefulWidget { } class _SwapScreenState extends State { + static const _smallGlassAsset = 'assets/v2/glass_40.png'; + static const _qrIconAsset = 'assets/v2/swap_qr_code.svg'; + static const _historyIconAsset = 'assets/v2/swap_clock_counter_clockwise.svg'; + static const _swapDirectionIconAsset = 'assets/v2/swap_arrows_down_up.svg'; + final _swapService = SwapService(); final _fromController = TextEditingController(); final _addressController = TextEditingController(); @@ -273,14 +279,7 @@ class _SwapScreenState extends State { const SizedBox(width: 8), GestureDetector( onTap: _scanQr, - child: SizedBox( - width: 40, - height: 40, - child: GlassContainer( - asset: GlassContainer.smallAsset, - child: Center(child: Icon(Icons.qr_code_scanner, color: colors.textPrimary, size: 20)), - ), - ), + child: _smallGlassIconButton(colors: colors, iconAsset: _qrIconAsset), ), const SizedBox(width: 8), GestureDetector( @@ -291,14 +290,7 @@ class _SwapScreenState extends State { setState(() {}); } }, - child: SizedBox( - width: 40, - height: 40, - child: GlassContainer( - asset: GlassContainer.smallAsset, - child: Center(child: Icon(Icons.history, color: colors.textPrimary, size: 20)), - ), - ), + child: _smallGlassIconButton(colors: colors, iconAsset: _historyIconAsset), ), ], ), @@ -314,16 +306,31 @@ class _SwapScreenState extends State { SizedBox( width: 40, height: 40, - child: GlassContainer( - asset: GlassContainer.smallAsset, - child: Center(child: Icon(Icons.swap_vert, color: colors.textPrimary, size: 20)), - ), + child: _smallGlassIconButton(colors: colors, iconAsset: _swapDirectionIconAsset), ), Expanded(child: Divider(color: colors.separator)), ], ); } + Widget _smallGlassIconButton({required AppColorsV2 colors, required String iconAsset}) { + return SizedBox( + width: 40, + height: 40, + child: GlassContainer( + asset: _smallGlassAsset, + child: Center( + child: SvgPicture.asset( + iconAsset, + width: 20, + height: 20, + colorFilter: ColorFilter.mode(colors.textPrimary, BlendMode.srcIn), + ), + ), + ), + ); + } + Widget _toSection(AppColorsV2 colors, AppTextTheme text) { return Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index e2b38f80..90710102 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -112,6 +112,15 @@ flutter: - assets/v2/glass_medium_button_bg.png - assets/v2/glass_button_40_bg.png - assets/v2/glass_button_wide_340_bg.png + - assets/v2/glass_circle_icon_button_bg.png + - assets/v2/glass_104_x_80.png + - assets/v2/glass_40.png + - assets/v2/action_receive.svg + - assets/v2/action_send.svg + - assets/v2/action_swap.svg + - assets/v2/swap_arrows_down_up.svg + - assets/v2/swap_clock_counter_clockwise.svg + - assets/v2/swap_qr_code.svg fonts: From 1837e0f720053f384218c65166a152b16a1d5b84 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:27:32 +0800 Subject: [PATCH 38/49] more buttons --- .../lib/v2/components/glass_container.dart | 23 +++++++++++++++---- .../v2/screens/dev/button_test_screen.dart | 17 ++++++-------- .../lib/v2/screens/swap/swap_screen.dart | 3 ++- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index f46d7554..556c8e51 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -9,15 +9,28 @@ class GlassContainer extends StatelessWidget { static const smallAsset = 'assets/v2/glass_button_40_bg.png'; static const wideAsset = 'assets/v2/glass_button_wide_340_bg.png'; + double get height => asset == smallAsset ? 40 : 56; + const GlassContainer({super.key, required this.child, this.padding, this.asset = mediumAsset}); + @override Widget build(BuildContext context) { - return Stack( - children: [ - Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), - Padding(padding: padding ?? EdgeInsets.zero, child: child), - ], + + return SizedBox( + width: double.infinity, + height: height, + child: Stack( + children: [ + Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), + Positioned.fill( + child: Padding( + padding: padding ?? EdgeInsets.zero, + child: Align(alignment: Alignment.center, child: child), + ), + ), + ], + ), ); } } diff --git a/mobile-app/lib/v2/screens/dev/button_test_screen.dart b/mobile-app/lib/v2/screens/dev/button_test_screen.dart index ab8e2fc6..8f309919 100644 --- a/mobile-app/lib/v2/screens/dev/button_test_screen.dart +++ b/mobile-app/lib/v2/screens/dev/button_test_screen.dart @@ -179,16 +179,13 @@ class ButtonTestScreen extends StatelessWidget { const SizedBox(height: 16), _label('TARGET: PNG wide', colors, text), - SizedBox( - height: 56, - child: GlassContainer( - asset: GlassContainer.wideAsset, - padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), - child: Center( - child: Text( - 'Wide PNG', - style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), - ), + GlassContainer( + asset: GlassContainer.wideAsset, + padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 16), + child: Center( + child: Text( + 'Wide PNG', + style: text.paragraph?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), ), ), ), diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 1b6c57f1..6a107506 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -193,7 +193,7 @@ class _SwapScreenState extends State { child: SizedBox( width: 119, child: GlassContainer( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ Container( @@ -357,6 +357,7 @@ class _SwapScreenState extends State { const SizedBox(width: 16), SizedBox( width: 119, + height: 56, child: GlassContainer( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: Row( From e31a2ace9ff189f375c26d4c1871fbc070feed36 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:27:45 +0800 Subject: [PATCH 39/49] format --- mobile-app/lib/v2/components/glass_container.dart | 2 -- mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index 556c8e51..ac35b204 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -13,10 +13,8 @@ class GlassContainer extends StatelessWidget { const GlassContainer({super.key, required this.child, this.padding, this.asset = mediumAsset}); - @override Widget build(BuildContext context) { - return SizedBox( width: double.infinity, height: height, diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 6446e185..2b3dd687 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -140,7 +140,7 @@ class _RecoveryPhraseScreenState extends State { Widget _wordChip(int index, String word, AppColorsV2 colors, AppTextTheme text) { final wordWidget = Text( word, - textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false), + textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false), style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, ); From 0f2ed72e9ca287aec91b35c60ee97edb2c84d774 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:34:55 +0800 Subject: [PATCH 40/49] remove padding - not needed --- mobile-app/lib/v2/screens/swap/review_quote_sheet.dart | 1 - mobile-app/lib/v2/screens/swap/swap_screen.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart index f7340fe0..d1945fd6 100644 --- a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -164,7 +164,6 @@ class _ReviewQuoteContent extends StatelessWidget { }, child: GlassContainer( asset: GlassContainer.wideAsset, - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: Text( 'Confirm', diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 6a107506..76304070 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -427,7 +427,6 @@ class _SwapScreenState extends State { opacity: enabled ? 1.0 : 0.4, child: GlassContainer( asset: GlassContainer.wideAsset, - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _loading ? SizedBox( From d9309ce040853f0126a0f28c4923c677182b1d9d Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:38:25 +0800 Subject: [PATCH 41/49] glassy send --- .../lib/v2/screens/send/send_sheet.dart | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index 543e2b4e..40e94b4e 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -9,6 +9,7 @@ import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -263,10 +264,9 @@ class _SendSheetState extends ConsumerState { if (hasRecipient) { return GestureDetector( onTap: () => _recipientController.clear(), - child: Container( - width: double.infinity, + child: GlassContainer( + asset: GlassContainer.wideAsset, padding: const EdgeInsets.fromLTRB(12, 14, 8, 14), - decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -290,18 +290,24 @@ class _SendSheetState extends ConsumerState { ), ); } - return TextField( - controller: _recipientController, - style: text.smallParagraph?.copyWith(color: colors.textPrimary), - decoration: InputDecoration( - filled: true, - fillColor: colors.surfaceGlass, - contentPadding: const EdgeInsets.fromLTRB(12, 14, 8, 14), - border: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), - enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), - focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none), - hintText: 'Qu Address', - hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), + return SizedBox( + width: double.infinity, + height: 56, + child: GlassContainer( + asset: GlassContainer.wideAsset, + padding: const EdgeInsets.fromLTRB(12, 14, 8, 14), + child: TextField( + controller: _recipientController, + style: text.smallParagraph?.copyWith(color: colors.textPrimary), + decoration: InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + hintText: 'Quan Address', + hintStyle: text.smallParagraph?.copyWith(color: colors.textTertiary), + ), + ), ), ); } @@ -448,11 +454,13 @@ class _SendSheetState extends ConsumerState { Widget _iconButton(IconData icon, AppColorsV2 colors, VoidCallback onTap) { return GestureDetector( onTap: onTap, - child: Container( + child: SizedBox( width: 40, height: 40, - decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), - child: Icon(icon, color: colors.textPrimary, size: 20), + child: GlassContainer( + asset: GlassContainer.smallAsset, + child: Icon(icon, color: colors.textPrimary, size: 20), + ), ), ); } @@ -468,14 +476,8 @@ class _SendSheetState extends ConsumerState { onTap: disabled ? null : onTap, child: Opacity( opacity: disabled ? 0.2 : 1.0, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - color: colors.surfaceGlass, - border: Border.all(color: Colors.white.withValues(alpha: 0.44), width: 0.889), - borderRadius: BorderRadius.circular(14), - ), + child: GlassContainer( + asset: GlassContainer.wideAsset, child: Text( label, textAlign: TextAlign.center, From 0cd19880e2dc7d294d06ac052d4422d5d980ad94 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 12:53:45 +0800 Subject: [PATCH 42/49] fix logo --- .../v2/screens/send/address_picker_sheet.dart | 2 ++ mobile-app/lib/v2/screens/send/send_sheet.dart | 16 +++++++++++----- .../lib/v2/screens/settings/settings_screen.dart | 14 ++------------ mobile-app/pubspec.yaml | 1 + quantus_sdk/lib/src/constants/app_constants.dart | 2 +- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart index a486a69d..ae8c1a36 100644 --- a/mobile-app/lib/v2/screens/send/address_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/send/address_picker_sheet.dart @@ -105,6 +105,8 @@ class _AddressPickerSheetState extends State { controller: _searchController, style: text.smallParagraph?.copyWith(color: colors.textPrimary), decoration: InputDecoration( + filled: true, + fillColor: Colors.transparent, isDense: true, contentPadding: EdgeInsets.zero, border: InputBorder.none, diff --git a/mobile-app/lib/v2/screens/send/send_sheet.dart b/mobile-app/lib/v2/screens/send/send_sheet.dart index 40e94b4e..d11a066f 100644 --- a/mobile-app/lib/v2/screens/send/send_sheet.dart +++ b/mobile-app/lib/v2/screens/send/send_sheet.dart @@ -264,9 +264,10 @@ class _SendSheetState extends ConsumerState { if (hasRecipient) { return GestureDetector( onTap: () => _recipientController.clear(), - child: GlassContainer( - asset: GlassContainer.wideAsset, + child: Container( + width: double.infinity, padding: const EdgeInsets.fromLTRB(12, 14, 8, 14), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -293,13 +294,18 @@ class _SendSheetState extends ConsumerState { return SizedBox( width: double.infinity, height: 56, - child: GlassContainer( - asset: GlassContainer.wideAsset, - padding: const EdgeInsets.fromLTRB(12, 14, 8, 14), + child: Container( + alignment: Alignment.center, + padding: const EdgeInsets.only(left: 12, right: 8), + decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(8)), child: TextField( controller: _recipientController, + textAlignVertical: TextAlignVertical.center, style: text.smallParagraph?.copyWith(color: colors.textPrimary), decoration: InputDecoration( + filled: true, + fillColor: Colors.transparent, + isDense: true, contentPadding: EdgeInsets.zero, border: InputBorder.none, enabledBorder: InputBorder.none, diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 194b782f..99a443e5 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -209,7 +209,7 @@ class _SettingsScreenV2State extends ConsumerState { _section('Reversible Transactions', colors, text, [ _toggleItem( 'Reversible Transactions', - _reversibleEnabled ? 'Enabled' : 'Disabled', + 'Coming Soon', //_reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, _toggleReversible, colors, @@ -251,18 +251,8 @@ class _SettingsScreenV2State extends ConsumerState { onTap: () => launchUrl(Uri.parse(AppConstants.helpAndSupportUrl)), ), _divider(colors), - _chevronItem('About Quantus', 'Version 1.0.1', colors, text, onTap: () {}), - _divider(colors), - _externalItem( - 'Terms of Service', - null, - colors, - text, - onTap: () => launchUrl(Uri.parse(AppConstants.termsOfServiceUrl)), - ), - _divider(colors), _externalItem( - 'Privacy Policy', + 'Privacy & Terms of Service', null, colors, text, diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 90710102..f35d45a5 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -121,6 +121,7 @@ flutter: - assets/v2/swap_arrows_down_up.svg - assets/v2/swap_clock_counter_clockwise.svg - assets/v2/swap_qr_code.svg + - assets/v2/quantus_white_logo.png fonts: diff --git a/quantus_sdk/lib/src/constants/app_constants.dart b/quantus_sdk/lib/src/constants/app_constants.dart index 38e9d074..e3bf3165 100644 --- a/quantus_sdk/lib/src/constants/app_constants.dart +++ b/quantus_sdk/lib/src/constants/app_constants.dart @@ -24,7 +24,7 @@ class AppConstants { static const String taskMasterEndpoint = 'https://quests.quantus.com/api'; static const String explorerEndpoint = 'https://explorer.quantus.com'; - static const String helpAndSupportUrl = 'https://t.me/quantustechsupport'; + static const String helpAndSupportUrl = 'https://t.me/c/quantusnetwork/2457'; static const String termsOfServiceUrl = 'https://www.quantus.com/terms-and-privacy'; static const String tutorialsAndGuidesUrl = 'https://github.com/Quantus-Network/chain'; static const String shillQuestsPageUrl = 'https://www.quantus.com/quests/shill'; From ce40b1fa269b436e729169fd8f85fc0c89c5a107 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 13:35:45 +0800 Subject: [PATCH 43/49] more buttons --- mobile-app/assets/v2/glass_medium_clear.png | Bin 0 -> 7350 bytes .../lib/v2/components/glass_container.dart | 44 +++++++++---- .../screens/create/wallet_ready_screen.dart | 60 ++++++------------ .../screens/import/import_wallet_screen.dart | 7 +- .../lib/v2/screens/receive/receive_sheet.dart | 16 ++--- .../v2/screens/settings/auto_lock_screen.dart | 7 +- .../screens/settings/change_pin_screen.dart | 11 ++-- .../settings/recovery_phrase_screen.dart | 8 +-- .../v2/screens/settings/settings_screen.dart | 13 ++-- .../lib/v2/screens/swap/deposit_screen.dart | 28 ++++---- .../lib/v2/screens/swap/swap_screen.dart | 4 ++ .../v2/screens/welcome/welcome_screen.dart | 10 +-- mobile-app/pubspec.yaml | 2 + 13 files changed, 99 insertions(+), 111 deletions(-) create mode 100644 mobile-app/assets/v2/glass_medium_clear.png diff --git a/mobile-app/assets/v2/glass_medium_clear.png b/mobile-app/assets/v2/glass_medium_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..2d723f992dba3424db25c477a944d58122113ffb GIT binary patch literal 7350 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 B&2Rt! literal 0 HcmV?d00001 diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index ac35b204..6426a74c 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -4,30 +4,46 @@ class GlassContainer extends StatelessWidget { final Widget child; final EdgeInsetsGeometry? padding; final String asset; + final GestureTapCallback? onTap; + final bool filled; - static const mediumAsset = 'assets/v2/glass_medium_button_bg.png'; + static const mediumAsset = 'assets/v2/glass_medium_clear.png'; static const smallAsset = 'assets/v2/glass_button_40_bg.png'; static const wideAsset = 'assets/v2/glass_button_wide_340_bg.png'; double get height => asset == smallAsset ? 40 : 56; - const GlassContainer({super.key, required this.child, this.padding, this.asset = mediumAsset}); + const GlassContainer({ + super.key, + required this.child, + this.padding, + required this.asset, + this.filled = false, + this.onTap, + }); @override Widget build(BuildContext context) { - return SizedBox( - width: double.infinity, - height: height, - child: Stack( - children: [ - Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), - Positioned.fill( - child: Padding( - padding: padding ?? EdgeInsets.zero, - child: Align(alignment: Alignment.center, child: child), + return GestureDetector( + onTap: onTap, + child: SizedBox( + width: double.infinity, + height: height, + child: Stack( + children: [ + Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), + if (filled) + Positioned.fill( + child: DecoratedBox(decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1))), + ), + Positioned.fill( + child: Padding( + padding: padding ?? EdgeInsets.zero, + child: Align(alignment: Alignment.center, child: child), + ), ), - ), - ], + ], + ), ), ); } diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index 3dfd273a..3589d372 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -7,7 +7,7 @@ import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -183,19 +183,14 @@ class _WalletReadyScreenV2State extends ConsumerState { ), ), const SizedBox(height: 32), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(5, (i) => _PaginationDot(active: i == 1)), - ), ], ), ), ), const SizedBox(height: 24), - GlassButton( - height: 56, + GlassContainer( + asset: GlassContainer.wideAsset, onTap: canContinue ? _continue : null, - padding: const EdgeInsets.symmetric(vertical: 20), child: _isSubmitting ? Center( child: SizedBox( @@ -244,17 +239,18 @@ class _WalletReadyScreenV2State extends ConsumerState { ), ), const SizedBox(height: 24), - GlassButton( - height: 56, + GlassContainer( + asset: GlassContainer.wideAsset, filled: true, - onTap: () { + onTap: () async { final v = controller.text.trim(); - if (v.isEmpty) return; - setState(() { - _accountName.text = v; - _accountNameError = null; - }); - Navigator.pop(ctx); + if (v.isNotEmpty) { + setState(() { + _accountName.text = v; + _accountNameError = null; + }); + Navigator.pop(ctx); + } }, child: Center( child: Text( @@ -312,12 +308,15 @@ class _Field extends StatelessWidget { SizedBox( width: 40, height: 40, - child: GlassButton( - height: 40, + child: GlassContainer( + asset: GlassContainer.smallAsset, filled: true, - radius: 8, padding: EdgeInsets.zero, - onTap: isLoading ? null : onAction, + onTap: isLoading + ? null + : () async { + onAction(); + }, child: Icon(actionIcon, size: 20, color: colors.textPrimary), ), ), @@ -329,22 +328,3 @@ class _Field extends StatelessWidget { } } -class _PaginationDot extends StatelessWidget { - final bool active; - - const _PaginationDot({required this.active}); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - return Container( - margin: const EdgeInsets.symmetric(horizontal: 4), - width: active ? 24 : 8, - height: 8, - decoration: BoxDecoration( - color: active ? colors.textPrimary : colors.textTertiary, - borderRadius: BorderRadius.circular(4), - ), - ); - } -} diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart index 0176d68a..b5bdf12e 100644 --- a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -145,10 +145,9 @@ class _ImportWalletScreenV2State extends ConsumerState { const Spacer(), Opacity( opacity: _hasInput ? 1.0 : 0.2, - child: GlassButton( - height: 56, + child: GlassContainer( + asset: GlassContainer.wideAsset, onTap: _hasInput && !_isLoading ? _import : null, - padding: const EdgeInsets.symmetric(vertical: 20), child: _isLoading ? Center( child: SizedBox( diff --git a/mobile-app/lib/v2/screens/receive/receive_sheet.dart b/mobile-app/lib/v2/screens/receive/receive_sheet.dart index e4bccb1e..0116898f 100644 --- a/mobile-app/lib/v2/screens/receive/receive_sheet.dart +++ b/mobile-app/lib/v2/screens/receive/receive_sheet.dart @@ -3,6 +3,7 @@ import 'package:qr_flutter/qr_flutter.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; import 'package:share_plus/share_plus.dart'; @@ -199,12 +200,9 @@ class _ReceiveSheetState extends State { Expanded( child: GestureDetector( onTap: _copyAddress, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.44)), - ), + child: GlassContainer( + filled: false, + asset: GlassContainer.mediumAsset, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -223,9 +221,9 @@ class _ReceiveSheetState extends State { Expanded( child: GestureDetector( onTap: _share, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration(color: colors.surfaceGlass, borderRadius: BorderRadius.circular(14)), + child: GlassContainer( + filled: true, + asset: GlassContainer.mediumAsset, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart index 50b7f684..14e19f54 100644 --- a/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart +++ b/mobile-app/lib/v2/screens/settings/auto_lock_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -79,10 +79,9 @@ class _AutoLockScreenState extends State { ), ), const Spacer(), - GlassButton( - height: 56, + GlassContainer( + asset: GlassContainer.wideAsset, onTap: _confirm, - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: Text( 'Confirm', diff --git a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart index b0eb538b..192d9137 100644 --- a/mobile-app/lib/v2/screens/settings/change_pin_screen.dart +++ b/mobile-app/lib/v2/screens/settings/change_pin_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -228,13 +229,9 @@ class _ChangePinScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 24), child: GestureDetector( onTap: () => Navigator.pop(context), - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(14), - border: Border.all(color: Colors.white.withValues(alpha: 0.44)), - ), + child: GlassContainer( + asset: GlassContainer.wideAsset, + filled: true, child: Center( child: Text( 'Done', diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index 2b3dd687..f6de1964 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -6,6 +6,7 @@ import 'package:resonance_network_wallet/features/components/snackbar_helper.dar import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -140,7 +141,7 @@ class _RecoveryPhraseScreenState extends State { Widget _wordChip(int index, String word, AppColorsV2 colors, AppTextTheme text) { final wordWidget = Text( word, - textHeightBehavior: TextHeightBehavior(applyHeightToFirstAscent: false), + textHeightBehavior: const TextHeightBehavior(applyHeightToFirstAscent: false), style: text.detail?.copyWith(color: colors.textPrimary, fontWeight: FontWeight.w500), overflow: TextOverflow.ellipsis, ); @@ -200,10 +201,9 @@ class _RecoveryPhraseScreenState extends State { } Widget _revealButton(AppColorsV2 colors, AppTextTheme text) { - return GlassButton( - height: 56, + return GlassContainer( + asset: GlassContainer.wideAsset, onTap: _toggleReveal, - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: Row( mainAxisSize: MainAxisSize.min, diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 99a443e5..e88eb742 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -86,11 +86,6 @@ class _SettingsScreenV2State extends ConsumerState { } } - void _toggleReversible(bool enable) { - _settingsService.setReversibleEnabled(enable); - setState(() => _reversibleEnabled = enable); - } - void _toggleNotifications(bool enable) { final current = ref.read(notificationConfigProvider); ref.read(notificationConfigProvider.notifier).updateConfig(current.copyWith(enabled: enable)); @@ -211,7 +206,7 @@ class _SettingsScreenV2State extends ConsumerState { 'Reversible Transactions', 'Coming Soon', //_reversibleEnabled ? 'Enabled' : 'Disabled', _reversibleEnabled, - _toggleReversible, + null, colors, text, ), @@ -230,8 +225,8 @@ class _SettingsScreenV2State extends ConsumerState { ]), const SizedBox(height: 40), _section('Preferences', colors, text, [ - _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), - _divider(colors), + // _chevronItem('Currency', 'USD (\$)', colors, text, onTap: () {}), + // _divider(colors), _toggleItem( 'Notifications', notifConfig.enabled ? 'Transaction Alerts Enabled' : 'Alerts Disabled', @@ -294,7 +289,7 @@ class _SettingsScreenV2State extends ConsumerState { String title, String subtitle, bool value, - ValueChanged onChanged, + ValueChanged? onChanged, AppColorsV2 colors, AppTextTheme text, ) { diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index 687e2e77..a0d141e4 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/components/success_check.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -192,10 +192,10 @@ class _DepositScreenState extends State { Row( children: [ Expanded( - child: GlassButton( - height: 56, + child: GlassContainer( + filled: false, + asset: GlassContainer.mediumAsset, onTap: _copyAddress, - padding: const EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -211,10 +211,10 @@ class _DepositScreenState extends State { ), const SizedBox(width: 16), Expanded( - child: GlassButton( - height: 56, + child: GlassContainer( + filled: false, + asset: GlassContainer.mediumAsset, onTap: () {}, - padding: const EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -292,11 +292,10 @@ class _DepositScreenState extends State { } Widget _sentButton(AppColorsV2 colors, AppTextTheme text) { - return GlassButton( - height: 56, - filled: true, + return GlassContainer( + asset: GlassContainer.wideAsset, + filled: false, onTap: _confirming ? null : _confirmSent, - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _confirming ? SizedBox( @@ -313,11 +312,10 @@ class _DepositScreenState extends State { } Widget _doneButton(AppColorsV2 colors, AppTextTheme text) { - return GlassButton( - height: 56, - filled: true, + return GlassContainer( + asset: GlassContainer.wideAsset, + filled: false, onTap: () => Navigator.popUntil(context, (r) => r.isFirst), - padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: Text( 'Done', diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 76304070..0127650f 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -193,6 +193,8 @@ class _SwapScreenState extends State { child: SizedBox( width: 119, child: GlassContainer( + asset: GlassContainer.mediumAsset, + filled: true, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ @@ -359,6 +361,8 @@ class _SwapScreenState extends State { width: 119, height: 56, child: GlassContainer( + asset: GlassContainer.mediumAsset, + filled: true, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index 97d39795..0788f304 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/screens/create/wallet_ready_screen.dart'; import 'package:resonance_network_wallet/v2/screens/import/import_wallet_screen.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; @@ -37,8 +37,8 @@ class WelcomeScreenV2 extends StatelessWidget { style: text.largeTitle?.copyWith(fontSize: 32, height: 1.35, color: Colors.white), ), const SizedBox(height: 64), - GlassButton( - height: 56, + GlassContainer( + asset: GlassContainer.wideAsset, filled: true, onTap: () => Navigator.push( context, @@ -55,8 +55,8 @@ class WelcomeScreenV2 extends StatelessWidget { ), ), const SizedBox(height: 32), - GlassButton( - height: 56, + GlassContainer( + asset: GlassContainer.wideAsset, onTap: () => Navigator.push( context, MaterialPageRoute( diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index f35d45a5..49d6b124 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -110,6 +110,7 @@ flutter: - assets/v2/swap_button.png - assets/v2/glass_border_bg.png - assets/v2/glass_medium_button_bg.png + - assets/v2/glass_medium_clear.png - assets/v2/glass_button_40_bg.png - assets/v2/glass_button_wide_340_bg.png - assets/v2/glass_circle_icon_button_bg.png @@ -122,6 +123,7 @@ flutter: - assets/v2/swap_clock_counter_clockwise.svg - assets/v2/swap_qr_code.svg - assets/v2/quantus_white_logo.png + fonts: From 09059ade30100ff9b9917eab5d96511546fec615 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 13:39:56 +0800 Subject: [PATCH 44/49] format --- mobile-app/lib/v2/screens/create/wallet_ready_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index 3589d372..43aaafc0 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -327,4 +327,3 @@ class _Field extends StatelessWidget { ); } } - From 6aaa045c8c1ea902ff02eb47ff5a03ce8dedf22e Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 13:43:07 +0800 Subject: [PATCH 45/49] Build 69 --- mobile-app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 49d6b124..6af1b62b 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -2,7 +2,7 @@ name: resonance_network_wallet description: A Flutter wallet for the Quantus blockchain. publish_to: "none" -version: 1.2.0+68 +version: 1.2.0+69 environment: sdk: ">=3.8.0 <4.0.0" From c072cc6a9b423e4b47480508b15e100e3ebd0e5a Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 13:50:30 +0800 Subject: [PATCH 46/49] remove insets --- mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart index 1ec53421..f02e9349 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -180,7 +180,6 @@ class _TransactionDetailSheetState extends State<_TransactionDetailSheet> { onTap: _openExplorer, child: Container( width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: Colors.white.withValues(alpha: 0.44)), From b0e2053f49e77c1cd7bd2f697b8390ff278e3d5b Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 13 Feb 2026 14:23:02 +0800 Subject: [PATCH 47/49] some stretching magic to make our png glass images stretch --- .../lib/v2/components/glass_container.dart | 79 ++++++++++++++++++- mobile-app/pubspec.yaml | 2 +- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/v2/components/glass_container.dart b/mobile-app/lib/v2/components/glass_container.dart index 6426a74c..310b9823 100644 --- a/mobile-app/lib/v2/components/glass_container.dart +++ b/mobile-app/lib/v2/components/glass_container.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; class GlassContainer extends StatelessWidget { @@ -11,6 +13,13 @@ class GlassContainer extends StatelessWidget { static const smallAsset = 'assets/v2/glass_button_40_bg.png'; static const wideAsset = 'assets/v2/glass_button_wide_340_bg.png'; + static const _inset = 42.0; + static const _scale = 3.0; + static const _slices = { + mediumAsset: Rect.fromLTRB(_inset, _inset, 480 - _inset, 180 - _inset), + wideAsset: Rect.fromLTRB(_inset, _inset, 1020 - _inset, 168 - _inset), + }; + double get height => asset == smallAsset ? 40 : 56; const GlassContainer({ @@ -24,6 +33,7 @@ class GlassContainer extends StatelessWidget { @override Widget build(BuildContext context) { + final slice = _slices[asset]; return GestureDetector( onTap: onTap, child: SizedBox( @@ -31,7 +41,11 @@ class GlassContainer extends StatelessWidget { height: height, child: Stack( children: [ - Positioned.fill(child: Image.asset(asset, fit: BoxFit.fill)), + Positioned.fill( + child: slice != null + ? _NineSliceImage(asset: asset, centerSlice: slice, scale: _scale) + : Image.asset(asset, fit: BoxFit.fill), + ), if (filled) Positioned.fill( child: DecoratedBox(decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1))), @@ -48,3 +62,66 @@ class GlassContainer extends StatelessWidget { ); } } + +class _NineSliceImage extends StatefulWidget { + final String asset; + final Rect centerSlice; + final double scale; + + const _NineSliceImage({required this.asset, required this.centerSlice, required this.scale}); + + @override + State<_NineSliceImage> createState() => _NineSliceImageState(); +} + +class _NineSliceImageState extends State<_NineSliceImage> { + ui.Image? _image; + late final _listener = ImageStreamListener((info, _) { + if (mounted) setState(() => _image = info.image); + }); + ImageStream? _stream; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _stream?.removeListener(_listener); + _stream = AssetImage(widget.asset).resolve(createLocalImageConfiguration(context)); + _stream!.addListener(_listener); + } + + @override + void dispose() { + _stream?.removeListener(_listener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_image == null) return const SizedBox.shrink(); + return CustomPaint(painter: _NineSlicePainter(_image!, widget.centerSlice, widget.scale)); + } +} + +class _NineSlicePainter extends CustomPainter { + final ui.Image image; + final Rect centerSlice; + final double scale; + + _NineSlicePainter(this.image, this.centerSlice, this.scale); + + @override + void paint(Canvas canvas, Size size) { + canvas.save(); + canvas.scale(1 / scale, 1 / scale); + canvas.drawImageNine( + image, + centerSlice, + Rect.fromLTWH(0, 0, size.width * scale, size.height * scale), + Paint()..filterQuality = FilterQuality.low, + ); + canvas.restore(); + } + + @override + bool shouldRepaint(_NineSlicePainter old) => image != old.image; +} diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index 6af1b62b..c7c3bced 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -2,7 +2,7 @@ name: resonance_network_wallet description: A Flutter wallet for the Quantus blockchain. publish_to: "none" -version: 1.2.0+69 +version: 1.2.0+70 environment: sdk: ">=3.8.0 <4.0.0" From ac1d751daa92ad6cfe7ca8bb69cb738fd18f4a87 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sat, 14 Feb 2026 12:00:51 +0800 Subject: [PATCH 48/49] small button unfortunately it has the wrong border radius --- mobile-app/assets/v2/glass_button_40_bg.png | Bin 3057 -> 0 bytes .../assets/v2/glass_medium_clear_small.png | Bin 0 -> 7041 bytes .../lib/v2/components/glass_container.dart | 9 +++++---- .../settings/recovery_phrase_screen.dart | 16 +--------------- mobile-app/pubspec.yaml | 2 +- 5 files changed, 7 insertions(+), 20 deletions(-) delete mode 100644 mobile-app/assets/v2/glass_button_40_bg.png create mode 100644 mobile-app/assets/v2/glass_medium_clear_small.png diff --git a/mobile-app/assets/v2/glass_button_40_bg.png b/mobile-app/assets/v2/glass_button_40_bg.png deleted file mode 100644 index 09728ca9e796188f7a133c6d78e042a9e6c802d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3057 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 diff --git a/mobile-app/assets/v2/glass_medium_clear_small.png b/mobile-app/assets/v2/glass_medium_clear_small.png new file mode 100644 index 0000000000000000000000000000000000000000..0f5c59ac447ed62762c024da89c5655831ecd81f GIT binary patch literal 7041 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 asset == smallAsset ? 40 : 56; + double get defaultHeight => asset == smallAsset ? 40 : asset == mediumSmallAsset ? 36 : 56; const GlassContainer({ super.key, @@ -37,8 +39,7 @@ class GlassContainer extends StatelessWidget { return GestureDetector( onTap: onTap, child: SizedBox( - width: double.infinity, - height: height, + height: defaultHeight, child: Stack( children: [ Positioned.fill( @@ -48,7 +49,7 @@ class GlassContainer extends StatelessWidget { ), if (filled) Positioned.fill( - child: DecoratedBox(decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1))), + child: DecoratedBox(decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(14))), ), Positioned.fill( child: Padding( diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart index f6de1964..cf7222db 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_screen.dart @@ -5,7 +5,6 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/snackbar_helper.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/v2/components/back_button.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/glass_container.dart'; import 'package:resonance_network_wallet/v2/components/gradient_background.dart'; import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; @@ -147,20 +146,7 @@ class _RecoveryPhraseScreenState extends State { ); return SizedBox( - height: 36, - child: GlassButton( - height: 36, - radius: 14, - padding: const EdgeInsets.symmetric(horizontal: 10), - filled: true, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(width: 8), - Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), - const SizedBox(width: 6), - Expanded( + child: GlassContainer(asset: GlassContainer.mediumSmallAsset, filled: true, child: Row(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [const SizedBox(width: 8), Text('$index', style: text.detail?.copyWith(color: colors.textSecondary)), const SizedBox(width: 6), Expanded( child: Stack( alignment: Alignment.centerLeft, children: [ diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index c7c3bced..66d8d4a3 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -111,7 +111,7 @@ flutter: - assets/v2/glass_border_bg.png - assets/v2/glass_medium_button_bg.png - assets/v2/glass_medium_clear.png - - assets/v2/glass_button_40_bg.png + - assets/v2/glass_medium_clear_small.png - assets/v2/glass_button_wide_340_bg.png - assets/v2/glass_circle_icon_button_bg.png - assets/v2/glass_104_x_80.png From e22d00406ca762ef085c2a6028c7120405983b15 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sat, 14 Feb 2026 12:04:16 +0800 Subject: [PATCH 49/49] make deposit screen safe --- mobile-app/lib/v2/screens/swap/deposit_screen.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index a0d141e4..06f7979e 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -155,7 +155,9 @@ class _DepositScreenState extends State { child: Container( color: Colors.white, padding: const EdgeInsets.all(8), - child: QrImageView(data: _order.depositAddress, version: QrVersions.auto, size: 184), + /// for now this QR Code is invalid so people don't transfer by accident + // child: QrImageView(data: _order.depositAddress, version: QrVersions.auto, size: 184), + child: QrImageView(data: 'quantum secure bitcoin - quantus!', version: QrVersions.auto, size: 184), ), ), const SizedBox(height: 16), @@ -163,8 +165,10 @@ class _DepositScreenState extends State { width: 264, child: Stack( children: [ + // for now put invalid address so people don't transfer by accident Text( - _order.depositAddress.toLowerCase(), + // _order.depositAddress.toLowerCase(), + '-------------------', style: text.smallParagraph?.copyWith( color: colors.textPrimary, fontWeight: FontWeight.w500,