diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e82d64e76..a342a1098 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,6 +40,8 @@ jobs: - name: Test Quantus SDK # Tests tagged 'native' need the Rust dylib built locally; skip them in CI. run: melos exec --concurrency=1 --scope="quantus_sdk" -- "flutter test --exclude-tags=native" + - name: Test Cold Wallet App + run: melos exec --concurrency=1 --scope="quantus_cold_wallet" -- "flutter test" # build_android: # name: Build Android diff --git a/cold-wallet-app/lib/app.dart b/cold-wallet-app/lib/app.dart index 8b6370746..b3bd6077b 100644 --- a/cold-wallet-app/lib/app.dart +++ b/cold-wallet-app/lib/app.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; import 'package:quantus_cold_wallet/screens/root_gate.dart'; -import 'package:quantus_cold_wallet/theme/app_theme.dart'; -import 'package:quantus_cold_wallet/widgets/connectivity_guard.dart'; -import 'package:quantus_cold_wallet/widgets/lock_overlay.dart'; +import 'package:quantus_cold_wallet/components/connectivity_guard.dart'; +import 'package:quantus_cold_wallet/components/lock_overlay.dart'; class ColdWalletApp extends ConsumerStatefulWidget { const ColdWalletApp({super.key}); diff --git a/cold-wallet-app/lib/components/address_with_checkphrase.dart b/cold-wallet-app/lib/components/address_with_checkphrase.dart deleted file mode 100644 index cfa1814d3..000000000 --- a/cold-wallet-app/lib/components/address_with_checkphrase.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/detail_row.dart'; -import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; - -/// An address plus its checkphrase, so the signer can verify it out loud rather -/// than character by character. -class AddressWithCheckphrase extends ConsumerWidget { - final String label; - final String address; - final String? note; - - const AddressWithCheckphrase({super.key, required this.label, required this.address, this.note}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final phrase = ref.watch(addressCheckphraseProvider(address)).asData?.value; - - return DetailRow( - label: label, - value: address, - monospace: true, - subValue: (phrase == null || phrase.isEmpty) ? null : phrase, - subValueColor: context.colors.checksum, - note: note, - ); - } -} diff --git a/cold-wallet-app/lib/components/call_detail_view.dart b/cold-wallet-app/lib/components/call_detail_view.dart deleted file mode 100644 index 5053776d6..000000000 --- a/cold-wallet-app/lib/components/call_detail_view.dart +++ /dev/null @@ -1,215 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/address_with_checkphrase.dart'; -import 'package:quantus_cold_wallet/components/detail_row.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -/// True when [summary] already puts this exact field on screen, so listing it -/// again would only add noise. Matches by identity, never by label or value, so -/// a renamed field — or a different field that merely carries an equal value — -/// can never be silently dropped. -bool coveredBySummary(CallField field, TransferSummary summary) { - if (identical(field, summary.amountField)) return true; - // With no plain recipient there is no To row, so the field must be listed. - return summary.recipient != null && identical(field, summary.recipientField); -} - -/// The transfer a call performs, or null when it dispatches another call. -/// -/// A wrapper (multisig approve, batch, `as_derivative`) inherits the summary of -/// the call it carries; the amount belongs on that inner call, where the nested -/// box shows it, not on the wrapper. -TransferSummary? heroSummary(DecodedCall call) => call.isWrapper ? null : call.summary; - -/// `4200 raw units of asset 1` — asset amounts stay in the payload's own raw -/// units, and say so: decimals are per-asset chain state an air-gapped signer -/// cannot resolve, so this integer must never read as a human asset quantity. -String assetAmountText(BigInt amount, int assetId) => '$amount raw units of asset $assetId'; - -/// The amount, recipient, and every parameter the two of them do not already -/// cover — the body shared by the top-level review and each nested call box. -List callSummaryBody(DecodedCall call, {int depth = 0}) { - final summary = heroSummary(call); - final recipient = summary?.recipient; - - return [ - if (summary != null) TransferAmount(summary: summary, large: depth == 0), - if (recipient != null) AddressWithCheckphrase(label: 'To', address: recipient), - for (final field in call.fields) - if (summary == null || !coveredBySummary(field, summary)) CallFieldView(field: field, depth: depth), - ]; -} - -/// The amount being moved, in the largest type on the screen. -class TransferAmount extends StatelessWidget { - final TransferSummary summary; - - /// Top-level calls get the hero treatment; nested ones step down a size. - final bool large; - - const TransferAmount({super.key, required this.summary, this.large = true}); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - - if (summary.assetId != null) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - assetAmountText(summary.amount, summary.assetId!), - style: text.mediumTitle?.copyWith(color: colors.textPrimary), - ), - ); - } - - final amount = NumberFormattingService().formatAmount(summary.amount); - final style = large ? text.transactionDetailAmountPrimary : text.conversionAmountPrimary; - final symbolStyle = large ? text.transactionDetailAmountSymbol : text.conversionAmountPrimary; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: Alignment.centerLeft, - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: amount, - style: style?.copyWith(color: colors.textPrimary), - ), - TextSpan( - text: ' ${AppConstants.tokenSymbol}', - style: symbolStyle?.copyWith(color: colors.textSecondary), - ), - ], - ), - ), - ), - ); - } -} - -/// A decoded call rendered the way a signer reads it: what it does, how much, -/// to whom — then every remaining parameter, nested calls included. -/// -/// The summary is a lead, never a replacement: any field it does not already -/// show is listed underneath, so if a byte is being signed it is on this screen. -/// -/// The heading is [DecodedCall.actionTitle] — an INTENTIONALLY opinionated -/// summary (`SEND`, `REVERSIBLE SEND`, `ASSET SEND`) at every depth, never -/// runtime naming. Do not add a `pallet · call` line here: that identity is -/// deliberately kept to one place, [DecodedCall.displayTitleChain] on the -/// signing screen's Advanced list. -class CallDetailView extends StatelessWidget { - final DecodedCall call; - - /// Nesting depth; nested calls are boxed and indented. - final int depth; - - const CallDetailView({super.key, required this.call, this.depth = 0}); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - call.actionTitle, - style: text.smallTitle?.copyWith(color: depth == 0 ? colors.textPrimary : colors.checksum), - ), - ...callSummaryBody(call, depth: depth), - ], - ); - } -} - -class CallFieldView extends ConsumerWidget { - final CallField field; - final int depth; - - const CallFieldView({super.key, required this.field, this.depth = 0}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; - - switch (field) { - case ValueField(:final label, :final value, :final kind, :final note): - if (kind == ValueKind.address) { - return AddressWithCheckphrase(label: label, address: value, note: note); - } - return DetailRow( - label: label, - value: value, - monospace: kind == ValueKind.hash || kind == ValueKind.bytes, - note: note, - ); - - case AmountField(:final label, :final token, :final assetId, :final note): - return DetailRow( - label: label, - value: assetId == null - ? '${NumberFormattingService().formatAmount(token)} ${AppConstants.tokenSymbol}' - : assetAmountText(token, assetId), - note: assetId == null - ? note - : [ - note, - 'Asset decimals are not part of this payload, so the raw amount is shown.', - ].whereType().join(' '), - ); - - case FieldGroup(:final label, :final items): - return Padding( - padding: const EdgeInsets.only(top: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(label.toUpperCase(), style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), - Padding( - padding: const EdgeInsets.only(left: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [for (final item in items) CallFieldView(field: item, depth: depth)], - ), - ), - ], - ), - ); - - case NestedCallField(:final label, :final call, :final note): - return Padding( - padding: const EdgeInsets.only(top: 14), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(label.toUpperCase(), style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: colors.surfaceCard, - borderRadius: BorderRadius.circular(10), - border: Border.all(color: colors.borderButton), - ), - child: CallDetailView(call: call, depth: depth + 1), - ), - if (note != null) ...[ - const SizedBox(height: 6), - Text(note, style: text.detail?.copyWith(color: colors.textMuted)), - ], - ], - ), - ); - } - } -} diff --git a/cold-wallet-app/lib/widgets/change_password_sheet.dart b/cold-wallet-app/lib/components/change_password_sheet.dart similarity index 83% rename from cold-wallet-app/lib/widgets/change_password_sheet.dart rename to cold-wallet-app/lib/components/change_password_sheet.dart index 8468bb56f..3a13eca13 100644 --- a/cold-wallet-app/lib/widgets/change_password_sheet.dart +++ b/cold-wallet-app/lib/components/change_password_sheet.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; -import 'package:quantus_cold_wallet/widgets/password_field.dart'; +import 'package:quantus_cold_wallet/components/password_field.dart'; /// Action pane for setting or changing the vault password. Resolves to true /// when the password was changed. @@ -13,7 +11,7 @@ Future showChangePasswordSheet(BuildContext context) async { context: context, isScrollControlled: true, enableDrag: false, - backgroundColor: context.colors.sheetBackground, + backgroundColor: context.colorsV3.bgSurface, builder: (_) => const _ChangePasswordSheet(), ); return changed == true; @@ -82,8 +80,8 @@ class _ChangePasswordSheetState extends ConsumerState<_ChangePasswordSheet> { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; // The rotation must not look cancellable once it is running: block barrier // taps and back navigation until the result lands in this sheet. @@ -99,16 +97,16 @@ class _ChangePasswordSheetState extends ConsumerState<_ChangePasswordSheet> { ); } - Widget _successContent(AppColorsV2 colors, AppTextTheme text) { + Widget _successContent(AppColorsV3 colors, AppTextThemeV3 text) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Icon(Icons.check_circle_outline_rounded, size: 48, color: colors.success), + Icon(Icons.check_circle_outline_rounded, size: 48, color: colors.semanticSage), const SizedBox(height: 16), Text( 'Password changed', - style: text.smallTitle?.copyWith(color: colors.textPrimary), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), if (_biometricDisabled) ...[ @@ -116,7 +114,7 @@ class _ChangePasswordSheetState extends ConsumerState<_ChangePasswordSheet> { Text( 'Biometric unlock was turned off because its key could not be updated for the new password. ' 'Unlock with your password.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted, height: 1.55), textAlign: TextAlign.center, ), ], @@ -126,17 +124,17 @@ class _ChangePasswordSheetState extends ConsumerState<_ChangePasswordSheet> { ); } - Widget _formContent(AppColorsV2 colors, AppTextTheme text) { + Widget _formContent(AppColorsV3 colors, AppTextThemeV3 text) { return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text('Change password', style: text.smallTitle?.copyWith(color: colors.textPrimary)), + Text('Change password', style: text.titleScreen.copyWith(color: colors.textContent)), const SizedBox(height: 8), Text( 'Your password encrypts the wallet key stored on this device. If you never set one, leave the current ' 'password empty.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted, height: 1.55), ), const SizedBox(height: 24), PasswordField(controller: _current, hintText: 'Current password', enabled: !_busy), @@ -151,7 +149,7 @@ class _ChangePasswordSheetState extends ConsumerState<_ChangePasswordSheet> { ), if (_error != null) ...[ const SizedBox(height: 16), - Text(_error!, style: text.detail?.copyWith(color: colors.error)), + Text(_error!, style: text.caption.copyWith(color: colors.semanticEmber)), ], const SizedBox(height: 24), QuantusButton.simple(label: 'Change password', isLoading: _busy, onTap: _busy ? null : _submit), diff --git a/cold-wallet-app/lib/components/confirm_dialog.dart b/cold-wallet-app/lib/components/confirm_dialog.dart deleted file mode 100644 index 708fe105c..000000000 --- a/cold-wallet-app/lib/components/confirm_dialog.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; - -Future showConfirmDialog( - BuildContext context, { - required String title, - required String message, - required String confirmLabel, -}) async { - final colors = context.colors; - final confirmed = await showDialog( - context: context, - builder: (ctx) => AlertDialog( - backgroundColor: colors.sheetBackground, - title: Text(title), - content: Text(message), - actions: [ - TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')), - TextButton( - onPressed: () => Navigator.pop(ctx, true), - child: Text(confirmLabel, style: TextStyle(color: colors.error)), - ), - ], - ), - ); - return confirmed == true; -} diff --git a/cold-wallet-app/lib/widgets/connectivity_guard.dart b/cold-wallet-app/lib/components/connectivity_guard.dart similarity index 83% rename from cold-wallet-app/lib/widgets/connectivity_guard.dart rename to cold-wallet-app/lib/components/connectivity_guard.dart index ecb1a917e..b5f4c0743 100644 --- a/cold-wallet-app/lib/widgets/connectivity_guard.dart +++ b/cold-wallet-app/lib/components/connectivity_guard.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/base_background.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/connectivity_provider.dart'; import 'package:quantus_cold_wallet/providers/settings_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; /// Full-screen blocker shown whenever any network is reachable. A cold wallet /// must stay air-gapped, so this overlays everything and only clears once the @@ -46,7 +43,7 @@ class _ConnectivityGuardState extends ConsumerState { return Positioned.fill( child: Material( - color: context.colors.background, + color: context.colorsV3.bgVoid, child: BaseBackground( child: SafeArea( child: Padding( @@ -60,30 +57,30 @@ class _ConnectivityGuardState extends ConsumerState { } Widget _lockContent(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.wifi_off_rounded, size: 72, color: colors.accentOrange), + Icon(Icons.wifi_off_rounded, size: 72, color: colors.semanticSand), const SizedBox(height: 32), Text( 'Network detected', - style: text.mediumTitle?.copyWith(color: colors.textPrimary), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( 'This is a cold wallet and must stay offline. Turn on Airplane Mode and disable Wi-Fi, cellular, ' 'Bluetooth and any other connections to continue.', - style: text.paragraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted, height: 1.55), textAlign: TextAlign.center, ), const SizedBox(height: 32), Text( 'The signer will unlock automatically once the device is fully offline.', - style: text.detail?.copyWith(color: colors.textMuted), + style: text.caption.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 32), @@ -97,29 +94,30 @@ class _ConnectivityGuardState extends ConsumerState { } Widget _confirmContent(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.warning_amber_rounded, size: 72, color: colors.error), + Icon(Icons.warning_amber_rounded, size: 72, color: colors.semanticEmber), const SizedBox(height: 32), Text( 'Override network lock?', - style: text.mediumTitle?.copyWith(color: colors.textPrimary), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( 'This device is connected to a network. A cold wallet is only safe while fully offline — override the ' 'lock for testing only, never with a wallet that holds real funds.', - style: text.paragraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted, height: 1.55), textAlign: TextAlign.center, ), const SizedBox(height: 40), QuantusButton.simple( label: 'Override — I accept the risk', + variant: ButtonVariant.danger, onTap: () { ref.read(coldSettingsProvider.notifier).setWifiOverrideEnabled(true); setState(() => _confirming = false); diff --git a/cold-wallet-app/lib/components/decoded_call_view.dart b/cold-wallet-app/lib/components/decoded_call_view.dart new file mode 100644 index 000000000..78cff895d --- /dev/null +++ b/cold-wallet-app/lib/components/decoded_call_view.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' as sdk; +import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; + +/// Cold-wallet decoded-call tree: stacked signing layout, checkphrases, amounts. +class DecodedCallView extends ConsumerWidget { + final sdk.DecodedCall call; + final int depth; + final Widget? signer; + + const DecodedCallView({super.key, required this.call, this.depth = 0, this.signer}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final checkphrases = _watchCheckphrases(ref, call); + return sdk.DecodedCallView( + call: call, + depth: depth, + layout: sdk.DecodedCallLayout.stacked, + showTitle: false, + recipientLabel: 'To', + amountText: (token) => '${sdk.NumberFormattingService().formatAmount(token)} ${sdk.AppConstants.tokenSymbol}', + checkphraseOf: (address) => checkphrases[address], + signer: signer, + ); + } +} + +Map _watchCheckphrases(WidgetRef ref, sdk.DecodedCall call) { + final checkphrases = {}; + for (final address in call.addresses) { + ref + .watch(checksumNameProvider(address)) + .maybeWhen( + data: (phrase) { + if (phrase.isNotEmpty) checkphrases[address] = phrase; + }, + orElse: () {}, + ); + } + return checkphrases; +} diff --git a/cold-wallet-app/lib/components/detail_row.dart b/cold-wallet-app/lib/components/detail_row.dart deleted file mode 100644 index e9f729d2c..000000000 --- a/cold-wallet-app/lib/components/detail_row.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -/// Label-above-value row used throughout the signing review screen. -/// -/// Values wrap rather than ellipsize: a signer must be able to read a full -/// address or hash, so nothing on this screen is ever visually truncated. -class DetailRow extends StatelessWidget { - final String label; - final String value; - - /// Monospace value, for addresses, hashes and byte blobs. - final bool monospace; - - final Color? valueColor; - - /// A second reading of the same value, shown directly under it — the human - /// checkphrase for an address. - final String? subValue; - - final Color? subValueColor; - - /// Caveat shown under the value, e.g. that a hash-referenced proposal's - /// contents are not part of the payload. - final String? note; - - const DetailRow({ - super.key, - required this.label, - required this.value, - this.monospace = false, - this.valueColor, - this.subValue, - this.subValueColor, - this.note, - }); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(label.toUpperCase(), style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), - const SizedBox(height: 6), - Text( - value, - style: monospace - ? text.transactionDetailRowValue?.copyWith(color: valueColor ?? colors.textPrimary) - : text.smallParagraph?.copyWith(color: valueColor ?? colors.textPrimary), - ), - if (subValue != null) ...[ - const SizedBox(height: 4), - Text(subValue!, style: text.smallParagraph?.copyWith(color: subValueColor ?? colors.textSecondary)), - ], - if (note != null) ...[ - const SizedBox(height: 4), - Text(note!, style: text.detail?.copyWith(color: colors.textMuted)), - ], - ], - ), - ); - } -} diff --git a/cold-wallet-app/lib/widgets/lock_overlay.dart b/cold-wallet-app/lib/components/lock_overlay.dart similarity index 91% rename from cold-wallet-app/lib/widgets/lock_overlay.dart rename to cold-wallet-app/lib/components/lock_overlay.dart index 935be5a75..f312fb545 100644 --- a/cold-wallet-app/lib/widgets/lock_overlay.dart +++ b/cold-wallet-app/lib/components/lock_overlay.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/screens/lock_screen.dart'; +import 'package:quantus_cold_wallet/components/lock_screen.dart'; /// Shows the lock screen as a full-screen overlay whenever the wallet is /// locked, so it covers any pushed route (Show Key, Sign) — not just the home diff --git a/cold-wallet-app/lib/screens/lock_screen.dart b/cold-wallet-app/lib/components/lock_screen.dart similarity index 81% rename from cold-wallet-app/lib/screens/lock_screen.dart rename to cold-wallet-app/lib/components/lock_screen.dart index ccc32d1c1..8d72d1203 100644 --- a/cold-wallet-app/lib/screens/lock_screen.dart +++ b/cold-wallet-app/lib/components/lock_screen.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; -import 'package:quantus_cold_wallet/widgets/password_field.dart'; +import 'package:quantus_cold_wallet/components/password_field.dart'; class LockScreen extends ConsumerStatefulWidget { const LockScreen({super.key}); @@ -53,8 +50,8 @@ class _LockScreenState extends ConsumerState { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; final biometricEnabled = ref.watch(walletControllerProvider).biometricEnabled; return ScaffoldBase( @@ -62,11 +59,11 @@ class _LockScreenState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Spacer(), - Icon(Icons.lock_outline_rounded, size: 64, color: colors.accentOrange), + Icon(Icons.lock_outline_rounded, size: 64, color: colors.accentFlare), const SizedBox(height: 24), Text( 'Cold Wallet Locked', - style: text.mediumTitle?.copyWith(color: colors.textPrimary), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 32), @@ -79,7 +76,7 @@ class _LockScreenState extends ConsumerState { const SizedBox(height: 12), Text( _error!, - style: text.detail?.copyWith(color: colors.error), + style: text.caption.copyWith(color: colors.semanticEmber), textAlign: TextAlign.center, ), ], @@ -89,7 +86,7 @@ class _LockScreenState extends ConsumerState { const SizedBox(height: 12), QuantusButton.simple( label: 'Use biometrics', - icon: Icon(Icons.fingerprint, color: colors.textPrimary, size: 18), + icon: Icon(Icons.fingerprint, color: colors.textContent, size: 18), iconPlacement: IconPlacement.leading, variant: ButtonVariant.secondary, onTap: _busy ? null : _unlockWithBiometric, diff --git a/cold-wallet-app/lib/components/mnemonic_grid.dart b/cold-wallet-app/lib/components/mnemonic_grid.dart deleted file mode 100644 index 7fbe76e2f..000000000 --- a/cold-wallet-app/lib/components/mnemonic_grid.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/shared/context_extensions.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -class MnemonicGrid extends StatelessWidget { - final List words; - final bool isRevealed; - - const MnemonicGrid({super.key, required this.words, this.isRevealed = false}); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - // Calculate the available width for each item - // constraints.maxWidth is the total width of the GridView - // 2 * crossAxisSpacing (for the gaps between 3 items) - // Adjust for any padding within the _buildMnemonicWord container - final double availableWidth = constraints.maxWidth - (2 * 9.0); // 2 gaps of 9.0 - final double itemWidth = (availableWidth / 3); // 3 items per row - - // You might need to adjust this value slightly based on padding/margins - // and font rendering. - final double desiredCellHeight = context.isTablet ? 61 : 36.0; - - // Calculate the aspect ratio - final double childAspectRatio = itemWidth / desiredCellHeight; - - return GridView.count( - crossAxisCount: 3, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - mainAxisSpacing: 10.0, - crossAxisSpacing: 9.0, - childAspectRatio: childAspectRatio, - children: List.generate(words.length, (index) { - return _buildMnemonicWord(index + 1, words[index], isRevealed, context); - }), - ); - }, - ); - } - - Widget _buildMnemonicWord(int index, String word, bool isRevealed, BuildContext context) { - final padding = const EdgeInsets.symmetric(horizontal: 10); - final double blur = isRevealed ? 0 : 5; - final effectiveWord = isRevealed ? word : 'x' * word.length; - - return Container( - padding: padding, - decoration: BoxDecoration(color: context.colors.surfaceDeep, borderRadius: BorderRadius.circular(14)), - child: Row( - children: [ - Text( - '$index', - textAlign: TextAlign.left, - style: context.themeText.detail?.copyWith(color: context.colors.textTertiary), - ), - const SizedBox(width: 8), - ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), - child: Text( - effectiveWord, - textAlign: TextAlign.left, - style: context.themeText.detail?.copyWith(color: context.colors.checksum), - ), - ), - ], - ), - ); - } -} diff --git a/cold-wallet-app/lib/components/password_field.dart b/cold-wallet-app/lib/components/password_field.dart new file mode 100644 index 000000000..72fdcd026 --- /dev/null +++ b/cold-wallet-app/lib/components/password_field.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +class PasswordField extends StatefulWidget { + final TextEditingController controller; + final String hintText; + final ValueChanged? onChanged; + final ValueChanged? onSubmitted; + final TextInputAction? textInputAction; + final bool enabled; + + const PasswordField({ + super.key, + required this.controller, + required this.hintText, + this.onChanged, + this.onSubmitted, + this.textInputAction, + this.enabled = true, + }); + + @override + State createState() => _PasswordFieldState(); +} + +class _PasswordFieldState extends State { + bool _obscured = true; + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + + return QuantusTextField( + controller: widget.controller, + hint: widget.hintText, + enabled: widget.enabled, + obscureText: _obscured, + autocorrect: false, + enableSuggestions: false, + textInputAction: + widget.textInputAction ?? (widget.onSubmitted != null ? TextInputAction.done : TextInputAction.next), + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + trailing: GestureDetector( + onTap: () => setState(() => _obscured = !_obscured), + behavior: HitTestBehavior.opaque, + child: Icon( + _obscured ? Icons.visibility_off_outlined : Icons.visibility_outlined, + size: 22, + color: colors.textMuted, + ), + ), + ); + } +} diff --git a/cold-wallet-app/lib/components/qr_tuning_controls.dart b/cold-wallet-app/lib/components/qr_tuning_controls.dart index 52d433419..79e9334e9 100644 --- a/cold-wallet-app/lib/components/qr_tuning_controls.dart +++ b/cold-wallet-app/lib/components/qr_tuning_controls.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/settings_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; /// FPS and bytes-per-frame sliders bound to [coldSettingsProvider], shared by /// the settings screen and the signature view so tuning behaves identically. @@ -62,8 +61,8 @@ class _TuningSlider extends StatelessWidget { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -71,16 +70,16 @@ class _TuningSlider extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(label, style: text.detail?.copyWith(color: colors.textSecondary)), - Text(valueText, style: text.detail?.copyWith(color: colors.textPrimary)), + Text(label, style: text.caption.copyWith(color: colors.textMuted)), + Text(valueText, style: text.caption.copyWith(color: colors.textContent)), ], ), SliderTheme( data: SliderTheme.of(context).copyWith( - activeTrackColor: colors.accentOrange, - inactiveTrackColor: colors.borderButton, - thumbColor: colors.accentOrange, - overlayColor: colors.accentOrange.withValues(alpha: 0.15), + activeTrackColor: colors.accentFlare, + inactiveTrackColor: colors.borderHairline, + thumbColor: colors.accentFlare, + overlayColor: colors.accentFlare.useOpacity(0.15), trackHeight: 2, ), child: Slider(value: value, min: min, max: max, divisions: divisions, onChanged: onChanged), diff --git a/cold-wallet-app/lib/components/quantus_button.dart b/cold-wallet-app/lib/components/quantus_button.dart deleted file mode 100644 index 499643ba7..000000000 --- a/cold-wallet-app/lib/components/quantus_button.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/loader.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -enum ButtonVariant { transparent, primary, secondary, danger, success, outline } - -enum IconPlacement { leading, trailing, top } - -class QuantusButton extends StatelessWidget { - final Widget? child; - final String? _label; - final Widget? _icon; - final IconPlacement _iconPlacement; - final TextStyle? _textStyle; - final VoidCallback? onTap; - final bool isLoading; - final double? width; - final EdgeInsets padding; - final ButtonVariant variant; - final bool isDisabled; - final double borderRadius; - final bool centered; - - static const double defaultBorderRadius = 14.0; - static const double buttonFontSize = 16.0; - - const QuantusButton({ - super.key, - required Widget this.child, - this.onTap, - this.isLoading = false, - this.width = double.infinity, - this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - this.variant = ButtonVariant.primary, - this.isDisabled = false, - this.borderRadius = defaultBorderRadius, - this.centered = true, - }) : _label = null, - _icon = null, - _iconPlacement = IconPlacement.trailing, - _textStyle = null; - - // this is a simple button with a label and an icon - const QuantusButton.simple({ - super.key, - required String label, - Widget? icon, - IconPlacement iconPlacement = IconPlacement.trailing, - this.onTap, - this.isLoading = false, - this.width = double.infinity, - this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 12), - TextStyle? textStyle, - this.variant = ButtonVariant.primary, - this.isDisabled = false, - this.borderRadius = defaultBorderRadius, - this.centered = true, - }) : _label = label, - _icon = icon, - _iconPlacement = iconPlacement, - _textStyle = textStyle, - child = null; - - @override - Widget build(BuildContext context) { - final bool disabled = onTap == null || isLoading || isDisabled; - final visibility = disabled ? 0.5 : 1.0; - final buttonContent = _buildContent(context, variant: variant, disabled: disabled); - final borderRadius = BorderRadius.circular(this.borderRadius); - final basicBorder = BorderSide(color: context.colors.borderButton, width: 1); - - Color? buttonDecorationColor; - LinearGradient? buttonDecorationGradient; - BorderSide borderSide = BorderSide.none; - - switch (variant) { - case ButtonVariant.primary: - buttonDecorationColor = context.colors.accentOrange; - break; - - case ButtonVariant.secondary: - buttonDecorationGradient = LinearGradient( - transform: const GradientRotation(90 * math.pi / 180), - colors: [context.colors.surfaceDeep, context.colors.sheetBackground], - stops: [0.0, 1.0], - ); - borderSide = basicBorder.copyWith(color: context.colors.borderButton.useOpacity(0.5)); - break; - - case ButtonVariant.danger: - buttonDecorationColor = context.colors.buttonDanger; - break; - - case ButtonVariant.success: - buttonDecorationColor = context.colors.success; - break; - - case ButtonVariant.transparent: - buttonDecorationColor = Colors.transparent; - break; - - case ButtonVariant.outline: - buttonDecorationColor = Colors.transparent; - borderSide = basicBorder; - break; - } - - return InkWell( - onTap: disabled ? null : onTap, - child: Opacity( - opacity: visibility, - child: Container( - width: width, - padding: padding, - decoration: ShapeDecoration( - color: disabled ? context.colors.sheetBackground : buttonDecorationColor, - gradient: disabled ? null : buttonDecorationGradient, - shape: RoundedRectangleBorder(borderRadius: borderRadius, side: disabled ? basicBorder : borderSide), - ), - child: buttonContent, - ), - ), - ); - } - - Widget _buildContent(BuildContext context, {ButtonVariant variant = ButtonVariant.primary, bool disabled = false}) { - final textColor = switch (variant) { - ButtonVariant.primary => context.colors.background, - ButtonVariant.secondary => context.colors.textPrimary, - ButtonVariant.danger => context.colors.textPrimary, - ButtonVariant.transparent => context.colors.textPrimary, - ButtonVariant.success => context.colors.textPrimary, - ButtonVariant.outline => context.colors.textLabel, - }; - - if (isLoading) { - final size = (_textStyle?.fontSize ?? buttonFontSize) + 6; - return Center(child: Loader(size: size)); - } - - if (child != null) return child!; - - final effectiveTextStyle = - _textStyle ?? - context.themeText.paragraph!.copyWith( - fontSize: buttonFontSize, - color: disabled ? context.colors.textPrimary.useOpacity(0.5) : textColor, - fontWeight: FontWeight.w500, - ); - - Widget content; - if (_iconPlacement == IconPlacement.top) { - content = Column( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 8, - children: [ - ?_icon, - Text(_label!, style: effectiveTextStyle), - ], - ); - } else { - content = Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 8, - children: [ - if (_iconPlacement == IconPlacement.leading && _icon != null) _icon, - Text(_label!, style: effectiveTextStyle), - if (_iconPlacement == IconPlacement.trailing && _icon != null) _icon, - ], - ); - } - - return Center(child: content); - } -} diff --git a/cold-wallet-app/lib/components/quantus_qr.dart b/cold-wallet-app/lib/components/quantus_qr.dart deleted file mode 100644 index bba9f983f..000000000 --- a/cold-wallet-app/lib/components/quantus_qr.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; - -class QuantusQr extends StatelessWidget { - final String accountId; - - const QuantusQr({super.key, required this.accountId}); - - @override - Widget build(BuildContext context) { - final qrSize = 267.0; - final qrLogoSize = 64.0; - - return Container( - decoration: BoxDecoration( - border: Border.all(color: context.colors.textTertiary, width: 1), - borderRadius: BorderRadius.circular(16), - ), - width: qrSize, - height: qrSize, - child: QrImageView( - data: accountId, - errorCorrectionLevel: QrErrorCorrectLevel.M, - embeddedImage: const AssetImage('assets/v2/uppercase_q_black_bg.png'), - embeddedImageStyle: QrEmbeddedImageStyle(size: Size(qrLogoSize, qrLogoSize)), - version: QrVersions.auto, - size: qrSize, - padding: const EdgeInsets.all(16), - eyeStyle: const QrEyeStyle(eyeShape: QrEyeShape.square, color: Colors.white), - dataModuleStyle: const QrDataModuleStyle(dataModuleShape: QrDataModuleShape.square, color: Colors.white), - ), - ); - } -} diff --git a/cold-wallet-app/lib/components/titled_sheet.dart b/cold-wallet-app/lib/components/titled_sheet.dart deleted file mode 100644 index 6aae80500..000000000 --- a/cold-wallet-app/lib/components/titled_sheet.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -/// A titled bottom sheet that sizes to its content and scrolls once that -/// content outgrows the screen. -/// -/// Disclosure that lives at the end of a long scrolling page cannot expand in -/// place: the reader is already at the bottom, so the revealed content lands -/// below the viewport and nothing appears to happen. A sheet is visible wherever -/// the page happens to be scrolled. -Future showTitledSheet(BuildContext context, {required String title, required Widget child}) { - final colors = context.colors; - final text = context.themeText; - - return showModalBottomSheet( - context: context, - backgroundColor: colors.sheetBackground, - isScrollControlled: true, - builder: (sheetContext) => SafeArea( - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: MediaQuery.of(sheetContext).size.height * 0.8), - child: Padding( - padding: const EdgeInsets.fromLTRB(24, 24, 24, 24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(title, style: text.smallTitle?.copyWith(color: colors.textPrimary)), - const SizedBox(height: 16), - Flexible(child: SingleChildScrollView(child: child)), - ], - ), - ), - ), - ), - ); -} diff --git a/cold-wallet-app/lib/providers/wallet_providers.dart b/cold-wallet-app/lib/providers/wallet_providers.dart index 5af1c1eb8..bd6f15967 100644 --- a/cold-wallet-app/lib/providers/wallet_providers.dart +++ b/cold-wallet-app/lib/providers/wallet_providers.dart @@ -165,12 +165,12 @@ final addressProvider = Provider((ref) => ref.watch(keypairProvider)?.s /// Resolves human checkphrases for every address on screen, not just the /// wallet's own — an approval or a governance call can name several accounts, /// and each one needs to be verifiable by eye. -final addressCheckphraseProvider = FutureProvider.family((ref, address) async { +final checksumNameProvider = FutureProvider.family((ref, address) async { return (await HumanReadableChecksumService().getHumanReadableName(address)) ?? ''; }); final checkphraseProvider = FutureProvider((ref) async { final address = ref.watch(addressProvider); if (address == null) return ''; - return ref.watch(addressCheckphraseProvider(address).future); + return ref.watch(checksumNameProvider(address).future); }); diff --git a/cold-wallet-app/lib/screens/create_wallet_screen.dart b/cold-wallet-app/lib/screens/create_wallet_screen.dart index 55de2f570..420cc4f5d 100644 --- a/cold-wallet-app/lib/screens/create_wallet_screen.dart +++ b/cold-wallet-app/lib/screens/create_wallet_screen.dart @@ -1,14 +1,6 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/loader.dart'; -import 'package:quantus_cold_wallet/components/mnemonic_grid.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; import 'package:quantus_cold_wallet/screens/set_password_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class CreateWalletScreen extends StatefulWidget { const CreateWalletScreen({super.key}); @@ -40,8 +32,8 @@ class _CreateWalletScreenState extends State { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; final words = _words; return ScaffoldBase( @@ -54,7 +46,7 @@ class _CreateWalletScreenState extends State { Text( 'Write these words down in order and keep them offline. Anyone with this phrase controls your funds. ' 'It is never shown again and cannot be copied from this device.', - style: text.smallParagraph?.copyWith(color: colors.textTertiary), + style: text.body.copyWith(color: colors.textMuted), ), const SizedBox(height: 24), Expanded( diff --git a/cold-wallet-app/lib/screens/home_screen.dart b/cold-wallet-app/lib/screens/home_screen.dart index a39735692..dad2e46ec 100644 --- a/cold-wallet-app/lib/screens/home_screen.dart +++ b/cold-wallet-app/lib/screens/home_screen.dart @@ -1,36 +1,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/connectivity_provider.dart'; import 'package:quantus_cold_wallet/providers/settings_providers.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; import 'package:quantus_cold_wallet/screens/scan_transaction_screen.dart'; import 'package:quantus_cold_wallet/screens/settings_screen.dart'; import 'package:quantus_cold_wallet/screens/show_key_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( appBar: V2AppBar( title: 'Quantus Cold Wallet', leading: GestureDetector( onTap: () => ref.read(walletControllerProvider.notifier).lock(), - child: Icon(Icons.lock_outline, color: colors.textPrimary, size: 22), + child: Icon(Icons.lock_outline, color: colors.textContent, size: 22), ), trailing: GestureDetector( onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SettingsScreen())), - child: Icon(Icons.settings_outlined, color: colors.textPrimary, size: 22), + child: Icon(Icons.settings_outlined, color: colors.textContent, size: 22), ), ), mainContent: Column( @@ -39,7 +34,7 @@ class HomeScreen extends ConsumerWidget { const SizedBox(height: 8), Text( 'Choose an action. This signer stays offline at all times.', - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 32), @@ -66,7 +61,7 @@ class HomeScreen extends ConsumerWidget { : ScaffoldBaseBottomContent( child: QuantusButton.simple( label: 'Online — tap to re-lock', - icon: Icon(Icons.wifi_rounded, size: 18, color: colors.textPrimary), + icon: Icon(Icons.wifi_rounded, size: 18, color: colors.textContent), iconPlacement: IconPlacement.leading, variant: ButtonVariant.danger, onTap: () => ref.read(coldSettingsProvider.notifier).setWifiOverrideEnabled(false), @@ -82,33 +77,33 @@ class HomeScreen extends ConsumerWidget { required String subtitle, required VoidCallback onTap, }) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: colors.surfaceDeep, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: colors.borderButton, width: 1), + color: colors.bgSurface, + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.borderHairline, width: 1), ), child: Row( children: [ Container( width: 52, height: 52, - decoration: BoxDecoration(color: colors.surface, borderRadius: BorderRadius.circular(12)), - child: Icon(icon, color: colors.accentOrange, size: 28), + decoration: BoxDecoration(color: colors.bgSurface2, borderRadius: context.radiusV3.smBorder), + child: Icon(icon, color: colors.accentFlare, size: 28), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title, style: text.smallTitle?.copyWith(color: colors.textPrimary)), + Text(title, style: text.headingRow.copyWith(color: colors.textContent)), const SizedBox(height: 4), - Text(subtitle, style: text.detail?.copyWith(color: colors.textSecondary)), + Text(subtitle, style: text.caption.copyWith(color: colors.textMuted)), ], ), ), diff --git a/cold-wallet-app/lib/screens/import_wallet_screen.dart b/cold-wallet-app/lib/screens/import_wallet_screen.dart index 18c662f0f..39414c959 100644 --- a/cold-wallet-app/lib/screens/import_wallet_screen.dart +++ b/cold-wallet-app/lib/screens/import_wallet_screen.dart @@ -1,12 +1,6 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; import 'package:quantus_cold_wallet/screens/set_password_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class ImportWalletScreen extends StatefulWidget { const ImportWalletScreen({super.key}); @@ -91,9 +85,10 @@ class _ImportWalletScreenState extends State { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - final fieldTextStyle = text.smallTitle?.copyWith(color: colors.checksum, fontWeight: FontWeight.w400); + final colors = context.colorsV3; + final text = context.themeTextV3; + final fieldTextStyle = text.dataAddressLarge.copyWith(color: colors.textContent); + final hintStyle = text.dataAddressLarge.copyWith(color: colors.textMuted); return ScaffoldBase( appBar: const V2AppBar(title: 'Import Wallet'), @@ -105,16 +100,16 @@ class _ImportWalletScreenState extends State { children: [ Text( 'Restore an existing wallet with your 12 or 24 words recovery phrase', - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), ), const SizedBox(height: 16), Container( height: 202, padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: colors.surfaceDeep, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: colors.borderButton, width: 1), + color: colors.bgSurface, + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.borderHairline, width: 1), ), child: Stack( children: [ @@ -124,10 +119,11 @@ class _ImportWalletScreenState extends State { controller: _controller, focusNode: _focusNode, onChanged: (_) => setState(() {}), + cursorColor: colors.accentFlare, style: fieldTextStyle, decoration: InputDecoration.collapsed( hintText: 'Type in or paste your recovery phrase. Separate words with spaces.', - hintStyle: fieldTextStyle?.copyWith(color: colors.textSecondary), + hintStyle: hintStyle, ), maxLines: null, keyboardType: TextInputType.multiline, @@ -145,7 +141,7 @@ class _ImportWalletScreenState extends State { child: Icon( _controller.obscured ? Icons.visibility_off_outlined : Icons.visibility_outlined, size: 22, - color: colors.textSecondary, + color: colors.textMuted, ), ), ), @@ -156,7 +152,7 @@ class _ImportWalletScreenState extends State { const SizedBox(height: 16), Text( _error!, - style: text.detail?.copyWith(color: colors.error), + style: text.caption.copyWith(color: colors.semanticEmber), textAlign: TextAlign.center, ), ], diff --git a/cold-wallet-app/lib/screens/root_gate.dart b/cold-wallet-app/lib/screens/root_gate.dart index cba403c24..5b4bae9f0 100644 --- a/cold-wallet-app/lib/screens/root_gate.dart +++ b/cold-wallet-app/lib/screens/root_gate.dart @@ -1,13 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/base_background.dart'; -import 'package:quantus_cold_wallet/components/loader.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; import 'package:quantus_cold_wallet/screens/home_screen.dart'; import 'package:quantus_cold_wallet/screens/welcome_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; /// Routes the app to setup, lock, or home based on the wallet status. class RootGate extends ConsumerWidget { @@ -41,8 +37,8 @@ class _InitError extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Scaffold( body: BaseBackground( child: SafeArea( @@ -51,17 +47,17 @@ class _InitError extends ConsumerWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 64, color: colors.error), + Icon(Icons.error_outline, size: 64, color: colors.semanticEmber), const SizedBox(height: 24), Text( 'Storage error', - style: text.mediumTitle?.copyWith(color: colors.textPrimary), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( message, - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 32), diff --git a/cold-wallet-app/lib/screens/scan_transaction_screen.dart b/cold-wallet-app/lib/screens/scan_transaction_screen.dart index b0d43397f..0e54906c2 100644 --- a/cold-wallet-app/lib/screens/scan_transaction_screen.dart +++ b/cold-wallet-app/lib/screens/scan_transaction_screen.dart @@ -2,11 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; import 'package:quantus_cold_wallet/debug/debug_payloads.dart'; import 'package:quantus_cold_wallet/screens/sign_transaction_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; /// Scans a (possibly multi-part / animated) UR QR code, accumulating parts /// until [isCompleteUr] is satisfied, then decodes to the raw payload bytes @@ -80,12 +77,13 @@ class _ScanTransactionScreenState extends State { /// One button per payload in [DebugPayloads.all], so every screen the signer /// can be shown is one tap away on a simulator. Widget _debugPayloadButtons(BuildContext context) { - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Column( mainAxisSize: MainAxisSize.min, children: [ - Text('DEBUG PAYLOADS', style: text.detail?.copyWith(color: Colors.white38, letterSpacing: 1.2)), + Text('DEBUG PAYLOADS', style: text.caption.copyWith(color: colors.textMuted)), const SizedBox(height: 8), Wrap( alignment: WrapAlignment.center, @@ -95,12 +93,12 @@ class _ScanTransactionScreenState extends State { for (final entry in DebugPayloads.all.entries) ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Colors.white24, + backgroundColor: colors.bgSurface, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), visualDensity: VisualDensity.compact, ), onPressed: () => _loadDebugPayload(entry.value()), - child: Text(entry.key, style: text.detail?.copyWith(color: Colors.white)), + child: Text(entry.key, style: text.caption.copyWith(color: colors.textContent)), ), ], ), @@ -125,22 +123,24 @@ class _ScanTransactionScreenState extends State { } Widget _cameraError(BuildContext context, MobileScannerException error) { + final colors = context.colorsV3; + final text = context.themeTextV3; final denied = error.errorCode == MobileScannerErrorCode.permissionDenied; final detail = error.errorDetails?.message; return ColoredBox( - color: Colors.black, + color: colors.bgVoid, child: Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.no_photography_outlined, color: Colors.white70, size: 64), + Icon(Icons.no_photography_outlined, color: colors.textMuted, size: 64), const SizedBox(height: 24), - const Text( + Text( 'Camera unavailable', - style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.w600), + style: text.titleScreen.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 12), @@ -149,11 +149,11 @@ class _ScanTransactionScreenState extends State { ? 'Grant camera access in Settings to scan the transaction QR.' : 'The camera could not be started (${error.errorCode.name})' '${detail == null ? '' : ': $detail'}.', - style: const TextStyle(color: Colors.white70), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 24), - QuantusButton.simple(label: 'Try again', width: null, onTap: _restartCamera), + QuantusButton.simple(label: 'Try again', width: 240, onTap: _restartCamera), ], ), ), @@ -163,15 +163,16 @@ class _ScanTransactionScreenState extends State { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; + final radius = context.radiusV3; final size = MediaQuery.of(context).size; final frame = (size.width - 96).clamp(220.0, 300.0); final progress = _expectedParts == null ? null : (_seenSeq.length / _expectedParts!).clamp(0.0, 1.0); return Scaffold( - backgroundColor: Colors.black, + backgroundColor: colors.bgVoid, body: Stack( children: [ MobileScanner(controller: _controller, onDetect: _onDetect, errorBuilder: _cameraError), @@ -180,8 +181,8 @@ class _ScanTransactionScreenState extends State { width: frame, height: frame, decoration: BoxDecoration( - border: Border.all(color: colors.accentOrange, width: 2), - borderRadius: BorderRadius.circular(16), + border: Border.all(color: colors.accentFlare, width: 2), + borderRadius: radius.mdBorder, ), ), ), @@ -194,12 +195,12 @@ class _ScanTransactionScreenState extends State { children: [ IconButton( onPressed: () => Navigator.pop(context), - icon: const Icon(Icons.arrow_back, color: Colors.white), + icon: Icon(Icons.arrow_back, color: colors.textContent), ), const Spacer(), IconButton( onPressed: _controller.toggleTorch, - icon: const Icon(Icons.flash_on, color: Colors.white), + icon: Icon(Icons.flash_on, color: colors.textContent), ), ], ), @@ -215,27 +216,27 @@ class _ScanTransactionScreenState extends State { if (kDebugMode) _debugPayloadButtons(context), Text( _error ?? 'Scan the transaction QR from your hot wallet', - style: text.paragraph?.copyWith(color: _error != null ? colors.error : Colors.white), + style: text.body.copyWith(color: _error != null ? colors.semanticEmber : colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 16), if (progress != null) ...[ ClipRRect( - borderRadius: BorderRadius.circular(4), + borderRadius: radius.xsBorder, child: LinearProgressIndicator( value: progress, minHeight: 6, - backgroundColor: colors.surface, - color: colors.accentOrange, + backgroundColor: colors.bgSurface, + color: colors.accentFlare, ), ), const SizedBox(height: 8), Text( '${_seenSeq.length} / ${_expectedParts!} parts', - style: text.detail?.copyWith(color: Colors.white70), + style: text.caption.copyWith(color: colors.textMuted), ), ] else if (_parts.isNotEmpty) - Text('${_parts.length} parts scanned', style: text.detail?.copyWith(color: Colors.white70)), + Text('${_parts.length} parts scanned', style: text.caption.copyWith(color: colors.textMuted)), ], ), ), diff --git a/cold-wallet-app/lib/screens/secure_element_warning_screen.dart b/cold-wallet-app/lib/screens/secure_element_warning_screen.dart index ad2203fb4..9e8679405 100644 --- a/cold-wallet-app/lib/screens/secure_element_warning_screen.dart +++ b/cold-wallet-app/lib/screens/secure_element_warning_screen.dart @@ -1,10 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; /// Shown during setup when the device reports no biometrics / device lock, /// which we treat as a proxy for a missing hardware-backed secure element. @@ -14,26 +9,28 @@ class SecureElementWarningScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( appBar: const V2AppBar(title: 'Security warning', leading: SizedBox(width: 24)), - mainContent: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox(height: 20), - Icon(Icons.warning_amber_rounded, size: 64, color: colors.accentOrange), - const SizedBox(height: 24), - Text('No secure element detected', style: text.mediumTitle?.copyWith(color: colors.textPrimary)), - const SizedBox(height: 16), - Text( - 'This device has no biometrics or device lock configured, so your wallet key cannot be stored in a ' - 'hardware-backed secure element. Your encrypted key will be protected only by your password.\n\n' - 'For a cold wallet we strongly recommend a device with a secure element and a device lock.', - style: text.paragraph?.copyWith(color: colors.textSecondary), - ), - ], + mainContent: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 20), + Icon(Icons.warning_amber_rounded, size: 64, color: colors.semanticSand), + const SizedBox(height: 24), + Text('No secure element detected', style: text.titleScreen.copyWith(color: colors.textContent)), + const SizedBox(height: 16), + Text( + 'This device has no biometrics or device lock configured, so your wallet key cannot be stored in a ' + 'hardware-backed secure element. Your encrypted key will be protected only by your password.\n\n' + 'For a cold wallet we strongly recommend a device with a secure element and a device lock.', + style: text.body.copyWith(color: colors.textMuted), + ), + ], + ), ), bottomContent: ScaffoldBaseBottomContent( child: Column( diff --git a/cold-wallet-app/lib/screens/set_password_screen.dart b/cold-wallet-app/lib/screens/set_password_screen.dart index afcaaf5e9..0038ae04e 100644 --- a/cold-wallet-app/lib/screens/set_password_screen.dart +++ b/cold-wallet-app/lib/screens/set_password_screen.dart @@ -1,13 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; -import 'package:quantus_cold_wallet/widgets/password_field.dart'; +import 'package:quantus_cold_wallet/components/password_field.dart'; class SetPasswordScreen extends ConsumerStatefulWidget { final String mnemonic; @@ -79,8 +74,8 @@ class _SetPasswordScreenState extends ConsumerState { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( appBar: const V2AppBar(title: 'Set password'), @@ -90,7 +85,7 @@ class _SetPasswordScreenState extends ConsumerState { children: [ Text( 'Your password encrypts the wallet key stored on this device. There is no recovery if you forget it.', - style: text.smallParagraph?.copyWith(color: colors.textTertiary), + style: text.body.copyWith(color: colors.textMuted), ), const SizedBox(height: 24), PasswordField(controller: _password, hintText: 'Password'), @@ -99,7 +94,7 @@ class _SetPasswordScreenState extends ConsumerState { if (_biometricAvailable) ...[const SizedBox(height: 24), _biometricToggle(colors, text)], if (_error != null) ...[ const SizedBox(height: 16), - Text(_error!, style: text.detail?.copyWith(color: colors.error)), + Text(_error!, style: text.caption.copyWith(color: colors.semanticEmber)), ], ], ), @@ -110,24 +105,24 @@ class _SetPasswordScreenState extends ConsumerState { ); } - Widget _biometricToggle(AppColorsV2 colors, AppTextTheme text) { + Widget _biometricToggle(AppColorsV3 colors, AppTextThemeV3 text) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( - color: colors.surfaceDeep, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: colors.borderButton, width: 1), + color: colors.bgSurface, + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.borderHairline, width: 1), ), child: Row( children: [ - Icon(Icons.fingerprint, color: colors.textPrimary, size: 20), + Icon(Icons.fingerprint, color: colors.textContent, size: 20), const SizedBox(width: 12), Expanded( - child: Text('Enable biometric unlock', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + child: Text('Enable biometric unlock', style: text.body.copyWith(color: colors.textContent)), ), Switch( value: _enableBiometric, - activeThumbColor: colors.accentOrange, + activeTrackColor: colors.accentFlare, onChanged: (v) => setState(() => _enableBiometric = v), ), ], diff --git a/cold-wallet-app/lib/screens/settings_screen.dart b/cold-wallet-app/lib/screens/settings_screen.dart index 0ffa1075d..a1cafd2c1 100644 --- a/cold-wallet-app/lib/screens/settings_screen.dart +++ b/cold-wallet-app/lib/screens/settings_screen.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/confirm_dialog.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/components/qr_tuning_controls.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; import 'package:quantus_cold_wallet/providers/settings_providers.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; -import 'package:quantus_cold_wallet/widgets/change_password_sheet.dart'; +import 'package:quantus_cold_wallet/components/change_password_sheet.dart'; class SettingsScreen extends ConsumerWidget { const SettingsScreen({super.key}); @@ -18,23 +14,25 @@ class SettingsScreen extends ConsumerWidget { await ref.read(coldSettingsProvider.notifier).setWifiOverrideEnabled(false); return; } - final confirmed = await showConfirmDialog( + final confirmed = await showQuantusDialog( context, title: 'Override network lock?', - message: + body: 'A cold wallet is only safe while fully offline — override the lock for testing only, never with a ' 'wallet that holds real funds.', - confirmLabel: 'Override', + actionLabel: 'Override', + isDestructive: true, ); if (confirmed) await ref.read(coldSettingsProvider.notifier).setWifiOverrideEnabled(true); } Future _confirmReset(BuildContext context, WidgetRef ref) async { - final confirmed = await showConfirmDialog( + final confirmed = await showQuantusDialog( context, title: 'Reset wallet?', - message: 'This erases the encrypted key from this device. You can only restore it with your recovery phrase.', - confirmLabel: 'Reset', + body: 'This erases the encrypted key from this device. You can only restore it with your recovery phrase.', + actionLabel: 'Reset', + isDestructive: true, ); if (!confirmed) return; await ref.read(walletControllerProvider.notifier).wipe(); @@ -43,8 +41,8 @@ class SettingsScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; final settings = ref.watch(coldSettingsProvider); return ScaffoldBase( @@ -54,7 +52,7 @@ class SettingsScreen extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 8), - Text('SECURITY', style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), + Text('SECURITY', style: text.labelMonogram.copyWith(color: colors.textMuted)), const SizedBox(height: 8), GestureDetector( onTap: () => showChangePasswordSheet(context), @@ -65,11 +63,11 @@ class SettingsScreen extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Change password', style: text.smallParagraph?.copyWith(color: colors.textPrimary)), + Text('Change password', style: text.body.copyWith(color: colors.textContent)), const SizedBox(height: 4), Text( 'Set or change the password that encrypts the wallet key on this device.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.caption.copyWith(color: colors.textMuted), ), ], ), @@ -96,13 +94,13 @@ class SettingsScreen extends ConsumerWidget { children: [ Text( 'Wi-Fi Lock Override (debugging only)', - style: text.smallParagraph?.copyWith(color: colors.textPrimary), + style: text.body.copyWith(color: colors.textContent), ), const SizedBox(height: 4), Text( 'For debugging only — disables the network lock so the signer can be used while online. ' 'Not safe: never use this with a wallet that holds real funds.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.caption.copyWith(color: colors.textMuted), ), ], ), @@ -110,26 +108,26 @@ class SettingsScreen extends ConsumerWidget { const SizedBox(width: 16), Switch( value: settings.wifiOverrideEnabled, - activeTrackColor: colors.accentOrange, + activeTrackColor: colors.accentFlare, onChanged: (v) => _setWifiOverride(context, ref, v), ), ], ), const SizedBox(height: 24), - Divider(color: colors.borderButton), + Divider(color: colors.borderHairline), const SizedBox(height: 16), - Text('SIGNATURE QR', style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), + Text('SIGNATURE QR', style: text.labelMonogram.copyWith(color: colors.textMuted)), const SizedBox(height: 8), Text( 'How the animated signature QR is displayed. Higher values transfer faster but are harder to scan.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.caption.copyWith(color: colors.textMuted), ), const SizedBox(height: 16), const QrTuningControls(), const SizedBox(height: 24), - Divider(color: colors.borderButton), + Divider(color: colors.borderHairline), const SizedBox(height: 16), - Text('DANGER ZONE', style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), + Text('DANGER ZONE', style: text.labelMonogram.copyWith(color: colors.textMuted)), const SizedBox(height: 8), GestureDetector( onTap: () => _confirmReset(context, ref), @@ -140,11 +138,11 @@ class SettingsScreen extends ConsumerWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Reset wallet', style: text.smallParagraph?.copyWith(color: colors.error)), + Text('Reset wallet', style: text.body.copyWith(color: colors.semanticEmber)), const SizedBox(height: 4), Text( 'Erase the encrypted key from this device.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.caption.copyWith(color: colors.textMuted), ), ], ), diff --git a/cold-wallet-app/lib/screens/show_key_screen.dart b/cold-wallet-app/lib/screens/show_key_screen.dart index de0cec21e..94bd3be7f 100644 --- a/cold-wallet-app/lib/screens/show_key_screen.dart +++ b/cold-wallet-app/lib/screens/show_key_screen.dart @@ -1,20 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/loader.dart'; -import 'package:quantus_cold_wallet/components/quantus_qr.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class ShowKeyScreen extends ConsumerWidget { const ShowKeyScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; final address = ref.watch(addressProvider); final checkphrase = ref.watch(checkphraseProvider); @@ -29,7 +24,7 @@ class ShowKeyScreen extends ConsumerWidget { const SizedBox(height: 8), Text( 'Scan with your Quantus hot wallet to add this account.', - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 24), @@ -38,7 +33,7 @@ class ShowKeyScreen extends ConsumerWidget { checkphrase.when( data: (phrase) => Text( phrase, - style: text.smallTitle?.copyWith(color: colors.checksum), + style: text.headingRow.copyWith(color: colors.semanticLilac), textAlign: TextAlign.center, ), loading: () => const Loader(size: 16), @@ -47,13 +42,10 @@ class ShowKeyScreen extends ConsumerWidget { const SizedBox(height: 16), Container( padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: colors.surfaceDeep, borderRadius: BorderRadius.circular(14)), + decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), child: Text( address, - style: text.detail?.copyWith( - color: colors.textPrimary, - fontFamily: AppTextTheme.fontFamilySecondary, - ), + style: text.dataAddressLarge.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), ), diff --git a/cold-wallet-app/lib/screens/sign_transaction_screen.dart b/cold-wallet-app/lib/screens/sign_transaction_screen.dart index be4e49345..02201db24 100644 --- a/cold-wallet-app/lib/screens/sign_transaction_screen.dart +++ b/cold-wallet-app/lib/screens/sign_transaction_screen.dart @@ -3,20 +3,11 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/address_with_checkphrase.dart'; -import 'package:quantus_cold_wallet/components/animated_ur_qr.dart'; -import 'package:quantus_cold_wallet/components/call_detail_view.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide DecodedCallView; +import 'package:quantus_cold_wallet/components/decoded_call_view.dart'; import 'package:quantus_cold_wallet/components/qr_tuning_controls.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; -import 'package:quantus_cold_wallet/components/titled_sheet.dart'; -import 'package:quantus_cold_wallet/components/v2_app_bar.dart'; import 'package:quantus_cold_wallet/providers/settings_providers.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; /// Reviews a scanned signing payload and, on approval, produces the signature QR. /// @@ -92,27 +83,27 @@ class _SignTransactionScreenState extends ConsumerState { } Widget _errorView(BuildContext context, String reason) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( appBar: const V2AppBar(title: 'Sign Transaction'), mainContent: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error_outline, size: 64, color: colors.error), + Icon(Icons.error_outline, size: 64, color: colors.semanticEmber), const SizedBox(height: 24), - Text('Could not read transaction', style: text.mediumTitle?.copyWith(color: colors.textPrimary)), + Text('Could not read transaction', style: text.titleScreen.copyWith(color: colors.textContent)), const SizedBox(height: 12), Text( 'This QR code is not a transaction this wallet can read in full, so it will not be signed. ' 'Nothing was signed.', - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( reason, - style: text.detail?.copyWith(color: colors.textMuted), + style: text.caption.copyWith(color: colors.textMuted2), textAlign: TextAlign.center, ), ], @@ -124,8 +115,8 @@ class _SignTransactionScreenState extends ConsumerState { } Widget _reviewView(BuildContext context, ParsedPayload parsed) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( appBar: const V2AppBar(title: 'Review & Sign'), @@ -138,18 +129,15 @@ class _SignTransactionScreenState extends ConsumerState { // The headline is an opinionated human summary (SEND / REVERSIBLE // SEND / …) by design; the exact pallet · call chain is the Call // line in the Advanced sheet. See [DecodedCall.actionTitle]. - Text( - parsed.call.actionTitle, - style: text.mediumTitle?.copyWith(color: colors.accentOrange, letterSpacing: 1.2), - ), - ..._callBody(context, parsed.call), + Text(parsed.call.actionTitle, style: text.titleScreen.copyWith(color: colors.accentFlare)), + DecodedCallView(call: parsed.call, signer: _signer(parsed.call)), const SizedBox(height: 20), _advancedSection(context, parsed), if (_error != null) ...[ const SizedBox(height: 16), Text( _error!, - style: text.detail?.copyWith(color: colors.error), + style: text.caption.copyWith(color: colors.semanticEmber), textAlign: TextAlign.center, ), ], @@ -177,60 +165,45 @@ class _SignTransactionScreenState extends ConsumerState { ); } - /// What is being authorised, then who is authorising it, then the parameters - /// neither of those already showed. - /// - /// A call that dispatches another one — a multisig approval, a batch — leads - /// with the boxed inner call: the amount and recipient inside it are what the - /// signer is really approving, and the wrapper's own parameters (which - /// multisig, which proposal) are context that follows. - /// /// The signer's row claims `From` only when the summary shows a plain send /// and the call names no other account the funds could leave instead — a /// `force_transfer` moves its Source's funds, not the signer's. Anything /// else says no more than `Signed by`. - List _callBody(BuildContext context, DecodedCall call) { + Widget? _signer(DecodedCall call) { final signerAddress = ref.watch(addressProvider); + if (signerAddress == null) return null; + final transfer = heroSummary(call); final fromSigner = transfer?.recipient != null && !call.fields.any( (f) => f is ValueField && f.kind == ValueKind.address && !identical(f, transfer!.recipientField), ); - final signer = signerAddress == null - ? null - : AddressWithCheckphrase(label: fromSigner ? 'From' : 'Signed by', address: signerAddress); + final label = fromSigner ? 'From' : 'Signed by'; + final phrase = ref + .watch(checksumNameProvider(signerAddress)) + .maybeWhen(data: (value) => value.isEmpty ? null : value, orElse: () => null); - if (!call.isWrapper) return [...callSummaryBody(call), ?signer]; - - return [ - for (final field in call.fields.whereType()) CallFieldView(field: field), - ?signer, - for (final field in call.fields.where((f) => f is! NestedCallField)) CallFieldView(field: field), - ]; + return DetailSummaryRow.stacked(label: label, value: signerAddress, monospace: true, checkphrase: phrase); } /// Everything a signer never verifies by eye: the signed extensions and the /// bytes themselves, listed plainly for the rare reader who wants them. Widget _advancedSection(BuildContext context, ParsedPayload parsed) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return GestureDetector( behavior: HitTestBehavior.opaque, - onTap: () => showTitledSheet( - context, + onTap: () => _showSheet( title: 'Advanced', - child: Text( - _advancedLines(parsed).join('\n'), - style: text.detail?.copyWith(color: colors.textMuted, fontFamily: AppTextTheme.fontFamilySecondary), - ), + child: Text(_advancedLines(parsed).join('\n'), style: text.dataAddress.copyWith(color: colors.textMuted)), ), child: Row( children: [ - Text('ADVANCED', style: text.transactionDetailRowLabel?.copyWith(color: colors.textLabel)), + Text('ADVANCED', style: text.labelData.copyWith(color: colors.textMuted)), const SizedBox(width: 6), - Icon(Icons.chevron_right, size: 18, color: colors.textLabel), + Icon(Icons.chevron_right, size: 18, color: colors.textMuted), ], ), ); @@ -255,30 +228,27 @@ class _SignTransactionScreenState extends ConsumerState { } Widget _specDriftBanner(BuildContext context, SignedExtensions ext) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return Container( margin: const EdgeInsets.only(bottom: 20), padding: const EdgeInsets.all(14), decoration: BoxDecoration( - color: colors.error.withValues(alpha: 0.12), - borderRadius: BorderRadius.circular(10), - border: Border.all(color: colors.error), + color: colors.semanticEmber.useOpacity(0.12), + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.semanticEmber), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.warning_amber_rounded, color: colors.error, size: 22), + Icon(Icons.warning_amber_rounded, color: colors.semanticEmber, size: 22), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Built for a different runtime', - style: text.smallParagraph?.copyWith(color: colors.error, fontWeight: FontWeight.w700), - ), + Text('Built for a different runtime', style: text.bodyEmphasis.copyWith(color: colors.semanticEmber)), const SizedBox(height: 4), Text( 'This payload targets spec ${ext.specVersion} / tx version ${ext.transactionVersion}, but this ' @@ -286,7 +256,7 @@ class _SignTransactionScreenState extends ConsumerState { '${AppConstants.bundledTransactionVersion}. Across runtime versions the same index can mean a ' 'different call, so the parameters below may be mislabelled. Update the cold wallet before signing ' 'anything you cannot verify another way.', - style: text.detail?.copyWith(color: colors.textSecondary), + style: text.caption.copyWith(color: colors.textMuted), ), ], ), @@ -296,16 +266,23 @@ class _SignTransactionScreenState extends ConsumerState { ); } + Future _showSheet({required String title, required Widget child}) { + return BottomSheetContainer.show( + context, + builder: (ctx) => BottomSheetContainer(title: title, child: child), + ); + } + /// Pauses the animation and opens the tuning sheet; resumes when it closes. Future _pauseAndTune() async { setState(() => _qrPaused = true); - await showTitledSheet(context, title: 'QR display options', child: const QrTuningControls()); + await _showSheet(title: 'QR display options', child: const QrTuningControls()); if (mounted) setState(() => _qrPaused = false); } Widget _signatureView(BuildContext context, Uint8List signed) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; final settings = ref.watch(coldSettingsProvider); if (_urParts == null || _urPartsBytes != settings.qrBytes) { @@ -323,7 +300,7 @@ class _SignTransactionScreenState extends ConsumerState { const SizedBox(height: 8), Text( 'Scan this with your hot wallet to broadcast the transaction.', - style: text.smallParagraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const SizedBox(height: 24), @@ -337,7 +314,7 @@ class _SignTransactionScreenState extends ConsumerState { Text( '${parts.length} ${parts.length == 1 ? 'frame' : 'frames'} · ${settings.qrFps} FPS · ' '${settings.qrBytes} bytes', - style: text.detail?.copyWith(color: colors.textMuted), + style: text.caption.copyWith(color: colors.textMuted), ), if (parts.length > 1) ...[ const SizedBox(width: 12), @@ -346,8 +323,8 @@ class _SignTransactionScreenState extends ConsumerState { child: Container( width: 32, height: 32, - decoration: BoxDecoration(color: colors.surfaceDeep, shape: BoxShape.circle), - child: Icon(Icons.pause_rounded, size: 20, color: colors.textPrimary), + decoration: BoxDecoration(color: colors.bgSurface2, shape: BoxShape.circle), + child: Icon(Icons.pause_rounded, size: 20, color: colors.textContent), ), ), ], @@ -357,7 +334,7 @@ class _SignTransactionScreenState extends ConsumerState { const SizedBox(height: 16), Text( 'Animated QR — keep both devices steady until the hot wallet finishes scanning.', - style: text.detail?.copyWith(color: colors.textMuted), + style: text.caption.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), ], diff --git a/cold-wallet-app/lib/screens/welcome_screen.dart b/cold-wallet-app/lib/screens/welcome_screen.dart index 24089ea88..be323e58e 100644 --- a/cold-wallet-app/lib/screens/welcome_screen.dart +++ b/cold-wallet-app/lib/screens/welcome_screen.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_cold_wallet/components/quantus_button.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base.dart'; -import 'package:quantus_cold_wallet/components/scaffold_base_bottom_content.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; import 'package:quantus_cold_wallet/screens/create_wallet_screen.dart'; import 'package:quantus_cold_wallet/screens/import_wallet_screen.dart'; import 'package:quantus_cold_wallet/screens/secure_element_warning_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class WelcomeScreen extends ConsumerWidget { const WelcomeScreen({super.key}); @@ -32,8 +28,8 @@ class WelcomeScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; return ScaffoldBase( mainContent: Column( @@ -44,13 +40,13 @@ class WelcomeScreen extends ConsumerWidget { const SizedBox(height: 32), Text( 'Quantus Cold Wallet', - style: text.largeTitle?.copyWith(color: colors.textPrimary), + style: text.titleHero.copyWith(color: colors.textContent), textAlign: TextAlign.center, ), const SizedBox(height: 12), Text( 'An air-gapped signer for the Quantus network. This device never touches the internet.', - style: text.paragraph?.copyWith(color: colors.textSecondary), + style: text.body.copyWith(color: colors.textMuted), textAlign: TextAlign.center, ), const Spacer(flex: 3), diff --git a/cold-wallet-app/lib/shared/context_extensions.dart b/cold-wallet-app/lib/shared/context_extensions.dart deleted file mode 100644 index d47f98bde..000000000 --- a/cold-wallet-app/lib/shared/context_extensions.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter/material.dart'; - -extension MediaQueryDataExtension on BuildContext { - bool get isTablet => MediaQuery.of(this).size.width > 600; - bool get isSmallHeight => MediaQuery.of(this).size.height < 750; -} diff --git a/cold-wallet-app/lib/theme/app_colors.dart b/cold-wallet-app/lib/theme/app_colors.dart deleted file mode 100644 index 4210495df..000000000 --- a/cold-wallet-app/lib/theme/app_colors.dart +++ /dev/null @@ -1,332 +0,0 @@ -import 'package:flutter/material.dart'; - -@immutable -class AppColorsV2 extends ThemeExtension { - // Backgrounds - final Color background; - - // Surfaces - final Color surface; - final Color surfaceGlass; - final Color surfaceCard; - - // Text - final Color textPrimary; - final Color textSecondary; - final Color textTertiary; - final Color textMuted; - final Color textError; - final Color textLabel; - final Color textLightGray; - - // Accents - final Color accentOrange; - final Color accentGreen; - final Color checksum; - - // Semantic - final Color error; - final Color danger; - final Color success; - - // Glow & Gradients - final Color backgroundGlow; - final List buttonPrimaryGradient; - - // UI elements - final Color separator; - final Color txItemSeparator; - final Color txItemIncomingHighlightBg; - final Color txItemOutgoingHighlightBg; - final Color txItemOutgoingHighlightBorder; - final Color txItemIconDefault; - final Color txItemIncomingHighlightBorder; - final Color txItemBorderDefault; - final Color border; - final Color buttonDisabled; - final Color buttonDanger; - final Color skeletonBase; - final Color skeletonHighlightA; - final Color skeletonHighlightB; - final Color toasterBorder; - final Color toasterBackground; - final Color sheetBackground; - final Color borderButton; - final Color borderSubtle; - final Color borderDanger; - - // Segmented control - final Color segmentedControlPill; - - // Receive screen - final Color surfaceDeep; - final Color copyButtonCopiedBg; - final Color copyButtonCopiedBorder; - - // Account tags - final Color tagGuardian; - final Color tagEntrusted; - final Color tagHighSecurity; - - const AppColorsV2({ - required this.background, - required this.toasterBackground, - required this.toasterBorder, - required this.sheetBackground, - required this.borderSubtle, - required this.borderDanger, - required this.borderButton, - required this.surface, - required this.surfaceGlass, - required this.surfaceCard, - required this.textPrimary, - required this.textSecondary, - required this.textTertiary, - required this.textMuted, - required this.textError, - required this.textLabel, - required this.textLightGray, - required this.accentOrange, - required this.accentGreen, - required this.checksum, - required this.error, - required this.danger, - required this.success, - required this.backgroundGlow, - required this.buttonPrimaryGradient, - required this.separator, - required this.txItemSeparator, - required this.txItemIncomingHighlightBg, - required this.txItemOutgoingHighlightBg, - required this.txItemOutgoingHighlightBorder, - required this.txItemIconDefault, - required this.txItemIncomingHighlightBorder, - required this.txItemBorderDefault, - required this.border, - required this.buttonDisabled, - required this.buttonDanger, - required this.skeletonBase, - required this.skeletonHighlightA, - required this.skeletonHighlightB, - required this.segmentedControlPill, - required this.surfaceDeep, - required this.copyButtonCopiedBg, - required this.copyButtonCopiedBorder, - required this.tagGuardian, - required this.tagEntrusted, - required this.tagHighSecurity, - }); - - const AppColorsV2.dark() - : this( - background: const Color(0xFF0E0E0E), - toasterBackground: const Color(0xFF181818), - toasterBorder: const Color(0xFF272727), - sheetBackground: const Color(0xFF1A1A1A), - border: const Color(0x33FFFFFF), - borderSubtle: const Color(0x70FFFFFF), - borderDanger: const Color(0x70FF0000), - borderButton: const Color(0xFF272727), - surface: const Color(0xFF292929), - surfaceGlass: const Color(0x1AFFFFFF), - surfaceCard: const Color(0x0FFFFFFF), - textPrimary: const Color(0xFFFFFFFF), - textSecondary: const Color(0x80FFFFFF), - textTertiary: const Color(0xFF3D3D3D), - textMuted: const Color(0xFF888888), - textError: const Color(0xFFC0392B), - textLabel: const Color(0xFF787878), - textLightGray: const Color(0xFFEBEBEB), - accentOrange: const Color(0xFFFF6B35), - accentGreen: const Color(0xFF34C759), - checksum: const Color(0xFF95A7FB), - error: const Color(0xFFFF2D54), - danger: const Color(0xFFFF1F45), - success: const Color(0xFF22A27F), - backgroundGlow: const Color(0xFFFFFFFF), - buttonPrimaryGradient: const [Color(0xFF0000FF), Color(0xFFED4CCE)], - separator: const Color(0x1AFFFFFF), - txItemSeparator: const Color(0x7F272727), - txItemIncomingHighlightBg: const Color(0x14408C6B), - txItemOutgoingHighlightBg: const Color(0x1440618C), - txItemOutgoingHighlightBorder: const Color(0x2640618C), - txItemIconDefault: const Color(0xFF363636), - txItemIncomingHighlightBorder: const Color(0x26408C6B), - txItemBorderDefault: const Color(0xFF191919), - segmentedControlPill: const Color(0xFF1C1C1C), - surfaceDeep: const Color(0xFF141414), - copyButtonCopiedBg: const Color(0xFF0C1C14), - copyButtonCopiedBorder: const Color(0xFF1A3226), - buttonDisabled: const Color(0xFF3D3C44), - buttonDanger: const Color(0x1AFF0000), - skeletonBase: const Color(0xFF161616), - skeletonHighlightA: const Color(0xFF000000), - skeletonHighlightB: const Color(0xFF666666), - tagGuardian: const Color(0xFF9747FF), - tagEntrusted: const Color(0xFFFFD541), - tagHighSecurity: const Color(0xFF4CEDE7), - ); - - @override - AppColorsV2 copyWith({ - Color? background, - Color? surface, - Color? surfaceGlass, - Color? surfaceCard, - Color? textPrimary, - Color? textSecondary, - Color? textTertiary, - Color? textMuted, - Color? textError, - Color? textLabel, - Color? textLightGray, - Color? accentOrange, - Color? accentGreen, - Color? checksum, - Color? error, - Color? danger, - Color? success, - Color? backgroundGlow, - List? buttonPrimaryGradient, - Color? separator, - Color? txItemSeparator, - Color? txItemIncomingHighlightBg, - Color? txItemOutgoingHighlightBg, - Color? txItemOutgoingHighlightBorder, - Color? txItemIconDefault, - Color? txItemIncomingHighlightBorder, - Color? txItemBorderDefault, - Color? border, - Color? borderButton, - Color? toasterBackground, - Color? toasterBorder, - Color? sheetBackground, - Color? borderSubtle, - Color? buttonDisabled, - Color? buttonDanger, - Color? borderDanger, - Color? skeletonBase, - Color? skeletonHighlightA, - Color? skeletonHighlightB, - Color? segmentedControlPill, - Color? surfaceDeep, - Color? copyButtonCopiedBg, - Color? copyButtonCopiedBorder, - Color? tagGuardian, - Color? tagEntrusted, - Color? tagHighSecurity, - }) { - return AppColorsV2( - background: background ?? this.background, - toasterBackground: toasterBackground ?? this.toasterBackground, - toasterBorder: toasterBorder ?? this.toasterBorder, - sheetBackground: sheetBackground ?? this.sheetBackground, - borderSubtle: borderSubtle ?? this.borderSubtle, - borderButton: borderButton ?? this.borderButton, - surface: surface ?? this.surface, - surfaceGlass: surfaceGlass ?? this.surfaceGlass, - surfaceCard: surfaceCard ?? this.surfaceCard, - textPrimary: textPrimary ?? this.textPrimary, - textSecondary: textSecondary ?? this.textSecondary, - textTertiary: textTertiary ?? this.textTertiary, - textMuted: textMuted ?? this.textMuted, - textError: textError ?? this.textError, - textLabel: textLabel ?? this.textLabel, - textLightGray: textLightGray ?? this.textLightGray, - accentOrange: accentOrange ?? this.accentOrange, - accentGreen: accentGreen ?? this.accentGreen, - checksum: checksum ?? this.checksum, - error: error ?? this.error, - danger: danger ?? this.danger, - success: success ?? this.success, - backgroundGlow: backgroundGlow ?? this.backgroundGlow, - buttonPrimaryGradient: buttonPrimaryGradient ?? this.buttonPrimaryGradient, - separator: separator ?? this.separator, - txItemSeparator: txItemSeparator ?? this.txItemSeparator, - txItemIncomingHighlightBg: txItemIncomingHighlightBg ?? this.txItemIncomingHighlightBg, - txItemOutgoingHighlightBg: txItemOutgoingHighlightBg ?? this.txItemOutgoingHighlightBg, - txItemOutgoingHighlightBorder: txItemOutgoingHighlightBorder ?? this.txItemOutgoingHighlightBorder, - txItemIconDefault: txItemIconDefault ?? this.txItemIconDefault, - txItemIncomingHighlightBorder: txItemIncomingHighlightBorder ?? this.txItemIncomingHighlightBorder, - txItemBorderDefault: txItemBorderDefault ?? this.txItemBorderDefault, - border: border ?? this.border, - buttonDisabled: buttonDisabled ?? this.buttonDisabled, - buttonDanger: buttonDanger ?? this.buttonDanger, - borderDanger: borderDanger ?? this.borderDanger, - skeletonBase: skeletonBase ?? this.skeletonBase, - skeletonHighlightA: skeletonHighlightA ?? this.skeletonHighlightA, - skeletonHighlightB: skeletonHighlightB ?? this.skeletonHighlightB, - segmentedControlPill: segmentedControlPill ?? this.segmentedControlPill, - surfaceDeep: surfaceDeep ?? this.surfaceDeep, - copyButtonCopiedBg: copyButtonCopiedBg ?? this.copyButtonCopiedBg, - copyButtonCopiedBorder: copyButtonCopiedBorder ?? this.copyButtonCopiedBorder, - tagGuardian: tagGuardian ?? this.tagGuardian, - tagEntrusted: tagEntrusted ?? this.tagEntrusted, - tagHighSecurity: tagHighSecurity ?? this.tagHighSecurity, - ); - } - - @override - AppColorsV2 lerp(AppColorsV2? other, double t) { - if (other is! AppColorsV2) return this; - return AppColorsV2( - background: Color.lerp(background, other.background, t) ?? background, - toasterBackground: Color.lerp(toasterBackground, other.toasterBackground, t) ?? toasterBackground, - toasterBorder: Color.lerp(toasterBorder, other.toasterBorder, t) ?? toasterBorder, - sheetBackground: Color.lerp(sheetBackground, other.sheetBackground, t) ?? sheetBackground, - borderSubtle: Color.lerp(borderSubtle, other.borderSubtle, t) ?? borderSubtle, - borderButton: Color.lerp(borderButton, other.borderButton, t) ?? borderButton, - surface: Color.lerp(surface, other.surface, t) ?? surface, - 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, - textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted, - textError: Color.lerp(textError, other.textError, t) ?? textError, - textLabel: Color.lerp(textLabel, other.textLabel, t) ?? textLabel, - textLightGray: Color.lerp(textLightGray, other.textLightGray, t) ?? textLightGray, - accentOrange: Color.lerp(accentOrange, other.accentOrange, t) ?? accentOrange, - accentGreen: Color.lerp(accentGreen, other.accentGreen, t) ?? accentGreen, - 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, - backgroundGlow: Color.lerp(backgroundGlow, other.backgroundGlow, t) ?? backgroundGlow, - buttonPrimaryGradient: other.buttonPrimaryGradient, - separator: Color.lerp(separator, other.separator, t) ?? separator, - txItemSeparator: Color.lerp(txItemSeparator, other.txItemSeparator, t) ?? txItemSeparator, - txItemIncomingHighlightBg: - Color.lerp(txItemIncomingHighlightBg, other.txItemIncomingHighlightBg, t) ?? txItemIncomingHighlightBg, - txItemOutgoingHighlightBg: - Color.lerp(txItemOutgoingHighlightBg, other.txItemOutgoingHighlightBg, t) ?? txItemOutgoingHighlightBg, - txItemOutgoingHighlightBorder: - Color.lerp(txItemOutgoingHighlightBorder, other.txItemOutgoingHighlightBorder, t) ?? - txItemOutgoingHighlightBorder, - txItemIconDefault: Color.lerp(txItemIconDefault, other.txItemIconDefault, t) ?? txItemIconDefault, - txItemIncomingHighlightBorder: - Color.lerp(txItemIncomingHighlightBorder, other.txItemIncomingHighlightBorder, t) ?? - txItemIncomingHighlightBorder, - txItemBorderDefault: Color.lerp(txItemBorderDefault, other.txItemBorderDefault, t) ?? txItemBorderDefault, - border: Color.lerp(border, other.border, t) ?? border, - buttonDisabled: Color.lerp(buttonDisabled, other.buttonDisabled, t) ?? buttonDisabled, - buttonDanger: Color.lerp(buttonDanger, other.buttonDanger, t) ?? buttonDanger, - borderDanger: Color.lerp(borderDanger, other.borderDanger, t) ?? borderDanger, - skeletonBase: Color.lerp(skeletonBase, other.skeletonBase, t) ?? skeletonBase, - skeletonHighlightA: Color.lerp(skeletonHighlightA, other.skeletonHighlightA, t) ?? skeletonHighlightA, - skeletonHighlightB: Color.lerp(skeletonHighlightB, other.skeletonHighlightB, t) ?? skeletonHighlightB, - segmentedControlPill: Color.lerp(segmentedControlPill, other.segmentedControlPill, t) ?? segmentedControlPill, - surfaceDeep: Color.lerp(surfaceDeep, other.surfaceDeep, t) ?? surfaceDeep, - copyButtonCopiedBg: Color.lerp(copyButtonCopiedBg, other.copyButtonCopiedBg, t) ?? copyButtonCopiedBg, - copyButtonCopiedBorder: - Color.lerp(copyButtonCopiedBorder, other.copyButtonCopiedBorder, t) ?? copyButtonCopiedBorder, - 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 AppColorsV2Extension on BuildContext { - AppColorsV2 get colors => Theme.of(this).extension()!; -} diff --git a/cold-wallet-app/lib/widgets/password_field.dart b/cold-wallet-app/lib/widgets/password_field.dart deleted file mode 100644 index 8798021e8..000000000 --- a/cold-wallet-app/lib/widgets/password_field.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; - -class PasswordField extends StatefulWidget { - final TextEditingController controller; - final String hintText; - final ValueChanged? onChanged; - final ValueChanged? onSubmitted; - final TextInputAction? textInputAction; - final bool enabled; - - const PasswordField({ - super.key, - required this.controller, - required this.hintText, - this.onChanged, - this.onSubmitted, - this.textInputAction, - this.enabled = true, - }); - - @override - State createState() => _PasswordFieldState(); -} - -class _PasswordFieldState extends State { - bool _obscured = true; - final _focusNode = FocusNode(); - - @override - void initState() { - super.initState(); - _focusNode.addListener(_onFocusChanged); - } - - void _onFocusChanged() => setState(() {}); - - @override - void dispose() { - _focusNode.removeListener(_onFocusChanged); - _focusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - final focused = _focusNode.hasFocus; - - return Container( - height: 56, - padding: const EdgeInsets.only(left: 16, right: 4), - decoration: BoxDecoration( - color: colors.surfaceDeep, - borderRadius: BorderRadius.circular(14), - border: Border.all(color: focused ? colors.accentOrange : colors.borderButton, width: 1), - ), - child: Row( - children: [ - Expanded( - child: TextField( - controller: widget.controller, - focusNode: _focusNode, - enabled: widget.enabled, - obscureText: _obscured, - autocorrect: false, - enableSuggestions: false, - textInputAction: - widget.textInputAction ?? (widget.onSubmitted != null ? TextInputAction.done : TextInputAction.next), - onChanged: widget.onChanged, - onSubmitted: widget.onSubmitted, - style: text.smallParagraph?.copyWith(color: colors.textPrimary), - decoration: InputDecoration(hintText: widget.hintText), - ), - ), - GestureDetector( - onTap: () => setState(() => _obscured = !_obscured), - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.all(12), - child: Icon( - _obscured ? Icons.visibility_off_outlined : Icons.visibility_outlined, - size: 22, - color: focused ? colors.textSecondary : colors.textMuted, - ), - ), - ), - ], - ), - ); - } -} diff --git a/cold-wallet-app/test/call_display_test.dart b/cold-wallet-app/test/call_display_test.dart index 11e28b93b..241a3f163 100644 --- a/cold-wallet-app/test/call_display_test.dart +++ b/cold-wallet-app/test/call_display_test.dart @@ -7,15 +7,11 @@ import 'package:quantus_sdk/generated/planck/pallets/assets.dart' as assets_pall import 'package:quantus_sdk/generated/planck/pallets/balances.dart' as balances_pallet; import 'package:quantus_sdk/generated/planck/pallets/multisig.dart' as multisig_pallet; import 'package:quantus_sdk/generated/planck/pallets/reversible_transfers.dart' as reversible_pallet; -import 'package:quantus_sdk/generated/planck/pallets/tech_collective.dart' as collective_pallet; import 'package:quantus_sdk/generated/planck/types/sp_runtime/multiaddress/multi_address.dart' as multi_address; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/components/address_with_checkphrase.dart'; -import 'package:quantus_cold_wallet/components/call_detail_view.dart'; import 'package:quantus_cold_wallet/debug/debug_payloads.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; import 'package:quantus_cold_wallet/screens/sign_transaction_screen.dart'; -import 'package:quantus_cold_wallet/theme/app_theme.dart'; final aliceId = Uint8List.fromList(List.filled(32, 0xAA)); final bobId = Uint8List.fromList(List.filled(32, 0xBB)); @@ -24,37 +20,12 @@ const signerAddress = 'qz-test-signer-address'; multi_address.MultiAddress account(Uint8List id) => multi_address.MultiAddress.values.id(id); -DecodedCall roundTrip(RuntimeCall call) => CallDecoder.decodeBytes(call.encode()); - -/// The screen's contract: every field of [call] reaches the screen exactly -/// once — as its own row, or restated by the summary hero the body leads with. -/// A field is only ever suppressed for the exact hero widget that restates it. -void expectEveryFieldShownOnce(DecodedCall call) { - final body = callSummaryBody(call); - final rows = body.whereType().map((w) => w.field).toList(); - final summary = heroSummary(call); - final showsAmount = body.any((w) => w is TransferAmount); - final showsRecipient = body.any((w) => w is AddressWithCheckphrase && w.label == 'To'); - - for (final field in call.fields) { - final restated = - (showsAmount && identical(field, summary?.amountField)) || - (showsRecipient && identical(field, summary?.recipientField)); - expect( - rows.where((f) => identical(f, field)).length, - restated ? 0 : 1, - reason: '${call.displayTitle}: "${field.label}" must reach the screen exactly once', - ); - if (field is NestedCallField) expectEveryFieldShownOnce(field.call); - } -} - Future pumpSignScreen(WidgetTester tester, Uint8List payload) async { await tester.pumpWidget( ProviderScope( overrides: [ addressProvider.overrideWith((ref) => signerAddress), - addressCheckphraseProvider.overrideWith((ref, address) async => 'check phrase'), + checksumNameProvider.overrideWith((ref, address) async => 'check phrase'), ], child: MaterialApp( home: Builder( @@ -69,43 +40,7 @@ Future pumpSignScreen(WidgetTester tester, Uint8List payload) async { await tester.pumpAndSettle(); } -AddressWithCheckphrase signerRow(WidgetTester tester) => tester.widget( - find.byWidgetPredicate((w) => w is AddressWithCheckphrase && w.address == signerAddress), -); - void main() { - group('every field reaches the screen exactly once', () { - final fixtures = { - 'plain transfer': const balances_pallet.Txs().transferAllowDeath(dest: account(bobId), value: oneToken), - 'force_transfer with source == destination': const balances_pallet.Txs().forceTransfer( - source: account(bobId), - dest: account(bobId), - value: oneToken, - ), - 'multisig approve wrapping a transfer': const multisig_pallet.Txs().approve( - multisigAddress: aliceId, - proposalId: 12, - call: const balances_pallet.Txs().transferAllowDeath(dest: account(bobId), value: oneToken).encode(), - ), - 'governance vote (no transfer summary)': const collective_pallet.Txs().vote(poll: 7, aye: true), - }; - - for (final entry in fixtures.entries) { - test(entry.key, () => expectEveryFieldShownOnce(roundTrip(entry.value))); - } - }); - - test('force_transfer keeps its Source row even when it equals the recipient', () { - final call = roundTrip( - const balances_pallet.Txs().forceTransfer(source: account(bobId), dest: account(bobId), value: oneToken), - ); - final body = callSummaryBody(call); - final labels = body.whereType().map((w) => w.field.label).toList(); - expect(labels, contains('Source')); - expect(labels, isNot(contains('Destination')), reason: 'the To row already restates the destination'); - expect(body.any((w) => w is AddressWithCheckphrase && w.label == 'To'), isTrue); - }); - group('sign screen', () { testWidgets('headlines summarise; the exact call chain lives in Advanced', (tester) async { final inner = const balances_pallet.Txs().transferAllowDeath(dest: account(bobId), value: oneToken); @@ -155,7 +90,7 @@ void main() { ), ); expect(find.text('SEND'), findsOneWidget); - expect(signerRow(tester).label, 'From'); + expect(find.text('FROM'), findsOneWidget); }); testWidgets('signer row reads Signed by when the funds leave an explicit source', (tester) async { @@ -165,12 +100,12 @@ void main() { const balances_pallet.Txs().forceTransfer(source: account(aliceId), dest: account(bobId), value: oneToken), ), ); - expect(signerRow(tester).label, 'Signed by'); + expect(find.text('SIGNED BY'), findsOneWidget); }); testWidgets('signer row reads Signed by when nothing is sent', (tester) async { await pumpSignScreen(tester, DebugPayloads.governanceVoteAye()); - expect(signerRow(tester).label, 'Signed by'); + expect(find.text('SIGNED BY'), findsOneWidget); }); }); } diff --git a/cold-wallet-app/test/change_password_sheet_test.dart b/cold-wallet-app/test/change_password_sheet_test.dart index ab21dd0f8..2de9c594b 100644 --- a/cold-wallet-app/test/change_password_sheet_test.dart +++ b/cold-wallet-app/test/change_password_sheet_test.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; -import 'package:quantus_cold_wallet/theme/app_theme.dart'; -import 'package:quantus_cold_wallet/widgets/change_password_sheet.dart'; +import 'package:quantus_cold_wallet/components/change_password_sheet.dart'; class _HangingController extends WalletController { final completer = Completer(); @@ -17,13 +17,16 @@ class _HangingController extends WalletController { } void main() { - testWidgets('password fields freeze while the rotation is in flight', (tester) async { - FlutterSecureStorage.setMockInitialValues({}); - final controller = _HangingController(); + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + Future openSheet(WidgetTester tester, {_HangingController? controller}) async { + FlutterSecureStorage.setMockInitialValues({}); await tester.pumpWidget( ProviderScope( - overrides: [walletControllerProvider.overrideWith(() => controller)], + overrides: [if (controller != null) walletControllerProvider.overrideWith(() => controller)], child: MaterialApp( home: Builder( builder: (context) => Theme( @@ -40,9 +43,13 @@ void main() { ), ), ); - await tester.tap(find.text('open')); await tester.pumpAndSettle(); + } + + testWidgets('password fields freeze while the rotation is in flight', (tester) async { + final controller = _HangingController(); + await openSheet(tester, controller: controller); await tester.enterText(find.byType(TextField).at(0), 'alpha'); await tester.enterText(find.byType(TextField).at(1), 'beta'); @@ -60,4 +67,49 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Password changed'), findsOneWidget); }); + + testWidgets('uses v3 title, body, error, and surface tokens', (tester) async { + await openSheet(tester); + + final title = tester.widget(find.text('Change password').first); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + + final body = tester.widget( + find.textContaining('Your password encrypts the wallet key stored on this device'), + ); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + + expect(tester.widget(find.byType(BottomSheet)).backgroundColor, colors.bgSurface); + + await tester.enterText(find.byType(TextField).at(1), 'beta'); + await tester.enterText(find.byType(TextField).at(2), 'gamma'); + await tester.tap(find.text('Change password').last); + await tester.pump(); + + final error = tester.widget(find.text('New passwords do not match')); + expect(error.style?.color, colors.semanticEmber); + expect(error.style?.fontSize, text.caption.fontSize); + }); + + testWidgets('success state uses sage icon and v3 title', (tester) async { + final controller = _HangingController(); + await openSheet(tester, controller: controller); + + await tester.enterText(find.byType(TextField).at(1), 'beta'); + await tester.enterText(find.byType(TextField).at(2), 'beta'); + await tester.tap(find.text('Change password').last); + controller.completer.complete(PasswordChangeResult.changed); + await tester.pumpAndSettle(); + + final icon = tester.widget(find.byIcon(Icons.check_circle_outline_rounded)); + expect(icon.color, colors.semanticSage); + + final title = tester.widget(find.text('Password changed')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + }); } diff --git a/cold-wallet-app/test/create_wallet_screen_test.dart b/cold-wallet-app/test/create_wallet_screen_test.dart new file mode 100644 index 000000000..31b185666 --- /dev/null +++ b/cold-wallet-app/test/create_wallet_screen_test.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/create_wallet_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + testWidgets('uses v3 body tokens for recovery-phrase instructions', (tester) async { + await tester.pumpWidget( + Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const CreateWalletScreen()), + ), + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 50)); + + final body = tester.widget(find.textContaining('Write these words down in order')); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + }); +} diff --git a/cold-wallet-app/test/home_screen_test.dart b/cold-wallet-app/test/home_screen_test.dart new file mode 100644 index 000000000..5e9ef66a9 --- /dev/null +++ b/cold-wallet-app/test/home_screen_test.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/providers/connectivity_provider.dart'; +import 'package:quantus_cold_wallet/screens/home_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + const radius = AppRadiusV3.standard(); + + Future pumpHome(WidgetTester tester) async { + FlutterSecureStorage.setMockInitialValues({}); + SharedPreferences.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + overrides: [isOnlineProvider.overrideWith((ref) => false)], + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const HomeScreen()), + ), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 intro, card, and icon tokens', (tester) async { + await pumpHome(tester); + + final intro = tester.widget(find.textContaining('Choose an action')); + expect(intro.style?.color, colors.textMuted); + expect(intro.style?.fontSize, text.body.fontSize); + + final title = tester.widget(find.text('Show Key')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.headingRow.fontSize); + expect(title.style?.fontWeight, text.headingRow.fontWeight); + + final subtitle = tester.widget(find.textContaining('Display your public address')); + expect(subtitle.style?.color, colors.textMuted); + expect(subtitle.style?.fontSize, text.caption.fontSize); + + final actionIcon = tester.widget(find.byIcon(Icons.qr_code_2_rounded)); + expect(actionIcon.color, colors.accentFlare); + + final card = tester.widget( + find.ancestor(of: find.text('Show Key'), matching: find.byType(Container)).first, + ); + final decoration = card.decoration! as BoxDecoration; + expect(decoration.color, colors.bgSurface); + expect(decoration.borderRadius, radius.mdBorder); + expect((decoration.border as Border?)?.top.color, colors.borderHairline); + + expect(tester.widget(find.byIcon(Icons.lock_outline)).color, colors.textContent); + expect(tester.widget(find.byIcon(Icons.settings_outlined)).color, colors.textContent); + }); +} diff --git a/cold-wallet-app/test/import_wallet_screen_test.dart b/cold-wallet-app/test/import_wallet_screen_test.dart new file mode 100644 index 000000000..82e9bdb0a --- /dev/null +++ b/cold-wallet-app/test/import_wallet_screen_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/import_wallet_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + const radius = AppRadiusV3.standard(); + + Future pumpScreen(WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const ImportWalletScreen()), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 helper, field, and surface tokens', (tester) async { + await pumpScreen(tester); + + final helper = tester.widget(find.textContaining('Restore an existing wallet')); + expect(helper.style?.color, colors.textMuted); + expect(helper.style?.fontSize, text.body.fontSize); + + final field = tester.widget(find.byType(TextField)); + expect(field.style?.color, colors.textContent); + expect(field.style?.fontSize, text.dataAddressLarge.fontSize); + expect(field.cursorColor, colors.accentFlare); + expect(field.decoration?.hintStyle?.color, colors.textMuted); + + final fieldBox = tester + .widgetList(find.byType(Container)) + .map((c) => c.decoration) + .whereType() + .firstWhere((d) => d.color == colors.bgSurface); + expect(fieldBox.borderRadius, radius.mdBorder); + expect((fieldBox.border as Border?)?.top.color, colors.borderHairline); + + final eye = tester.widget(find.byIcon(Icons.visibility_off_outlined)); + expect(eye.color, colors.textMuted); + }); + + testWidgets('uses v3 ember caption for validation errors', (tester) async { + await pumpScreen(tester); + + await tester.enterText(find.byType(TextField), 'one two three'); + await tester.pump(); + await tester.tap(find.text('Import')); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 400)); + + final error = tester.widget(find.text('Recovery phrase must be 12 or 24 words')); + expect(error.style?.color, colors.semanticEmber); + expect(error.style?.fontSize, text.caption.fontSize); + }); +} diff --git a/cold-wallet-app/test/lock_screen_test.dart b/cold-wallet-app/test/lock_screen_test.dart new file mode 100644 index 000000000..11d250151 --- /dev/null +++ b/cold-wallet-app/test/lock_screen_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/components/lock_screen.dart'; +import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +class _LockedController extends WalletController { + _LockedController({this.biometric = false}); + + final bool biometric; + + @override + WalletState build() => WalletState(status: WalletStatus.locked, biometricEnabled: biometric); + + @override + Future unlockWithPassword(String password) async => false; +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpLockScreen(WidgetTester tester, {_LockedController? controller}) async { + FlutterSecureStorage.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + overrides: [if (controller != null) walletControllerProvider.overrideWith(() => controller)], + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const LockScreen()), + ), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 lock icon, title, and error tokens', (tester) async { + await pumpLockScreen(tester, controller: _LockedController()); + + final icon = tester.widget(find.byIcon(Icons.lock_outline_rounded)); + expect(icon.color, colors.accentFlare); + + final title = tester.widget(find.text('Cold Wallet Locked')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + + await tester.tap(find.text('Unlock')); + await tester.pump(); + + final error = tester.widget(find.text('Incorrect password')); + expect(error.style?.color, colors.semanticEmber); + expect(error.style?.fontSize, text.caption.fontSize); + }); + + testWidgets('biometric button uses v3 content icon color', (tester) async { + await pumpLockScreen(tester, controller: _LockedController(biometric: true)); + + expect(find.text('Use biometrics'), findsOneWidget); + final icon = tester.widget(find.byIcon(Icons.fingerprint)); + expect(icon.color, colors.textContent); + }); +} diff --git a/cold-wallet-app/test/password_field_test.dart b/cold-wallet-app/test/password_field_test.dart new file mode 100644 index 000000000..bb7dd1362 --- /dev/null +++ b/cold-wallet-app/test/password_field_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_cold_wallet/components/password_field.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpField(WidgetTester tester, PasswordField field) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: field), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('wraps QuantusTextField and starts obscured', (tester) async { + final controller = TextEditingController(text: 'secret'); + addTearDown(controller.dispose); + + await pumpField(tester, PasswordField(controller: controller, hintText: 'Password')); + + expect(find.byType(QuantusTextField), findsOneWidget); + final field = tester.widget(find.byType(TextField)); + expect(field.obscureText, isTrue); + expect(field.autocorrect, isFalse); + expect(field.enableSuggestions, isFalse); + expect(find.byIcon(Icons.visibility_off_outlined), findsOneWidget); + }); + + testWidgets('visibility toggle reveals the password', (tester) async { + final controller = TextEditingController(text: 'secret'); + addTearDown(controller.dispose); + + await pumpField(tester, PasswordField(controller: controller, hintText: 'Password')); + + await tester.tap(find.byIcon(Icons.visibility_off_outlined)); + await tester.pump(); + + final field = tester.widget(find.byType(TextField)); + expect(field.obscureText, isFalse); + expect(find.byIcon(Icons.visibility_outlined), findsOneWidget); + expect(tester.widget(find.byIcon(Icons.visibility_outlined)).color, colors.textMuted); + }); +} diff --git a/cold-wallet-app/test/qr_tuning_controls_test.dart b/cold-wallet-app/test/qr_tuning_controls_test.dart new file mode 100644 index 000000000..559789e64 --- /dev/null +++ b/cold-wallet-app/test/qr_tuning_controls_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/components/qr_tuning_controls.dart'; +import 'package:quantus_cold_wallet/providers/settings_providers.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpControls(WidgetTester tester) async { + SharedPreferences.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + child: MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: const Scaffold(body: QrTuningControls()), + ), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 caption, muted labels, content values, and flare slider chrome', (tester) async { + await pumpControls(tester); + + for (final label in ['Frame rate', 'Bytes per frame']) { + final widget = tester.widget(find.text(label)); + expect(widget.style?.color, colors.textMuted); + expect(widget.style?.fontSize, text.caption.fontSize); + } + + for (final value in ['${ColdSettings.defaultQrFps} FPS', '${ColdSettings.defaultQrBytes} bytes']) { + final widget = tester.widget(find.text(value)); + expect(widget.style?.color, colors.textContent); + expect(widget.style?.fontSize, text.caption.fontSize); + } + + final themes = tester.widgetList(find.byType(SliderTheme)); + expect(themes, hasLength(2)); + for (final theme in themes) { + expect(theme.data.activeTrackColor, colors.accentFlare); + expect(theme.data.inactiveTrackColor, colors.borderHairline); + expect(theme.data.thumbColor, colors.accentFlare); + expect(theme.data.overlayColor, colors.accentFlare.useOpacity(0.15)); + } + }); +} diff --git a/cold-wallet-app/test/root_gate_test.dart b/cold-wallet-app/test/root_gate_test.dart new file mode 100644 index 000000000..da596f1cc --- /dev/null +++ b/cold-wallet-app/test/root_gate_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; +import 'package:quantus_cold_wallet/screens/root_gate.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +class _InitErrorController extends WalletController { + @override + WalletState build() => const WalletState(status: WalletStatus.initializing, error: 'Could not read secure storage.'); +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + testWidgets('init error uses v3 ember icon, title, and muted body tokens', (tester) async { + FlutterSecureStorage.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + overrides: [walletControllerProvider.overrideWith(_InitErrorController.new)], + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const RootGate()), + ), + ), + ); + await tester.pump(); + + final icon = tester.widget(find.byIcon(Icons.error_outline)); + expect(icon.color, colors.semanticEmber); + + final title = tester.widget(find.text('Storage error')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + + final body = tester.widget(find.text('Could not read secure storage.')); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + }); +} diff --git a/cold-wallet-app/test/scan_transaction_screen_test.dart b/cold-wallet-app/test/scan_transaction_screen_test.dart new file mode 100644 index 000000000..30aaf9037 --- /dev/null +++ b/cold-wallet-app/test/scan_transaction_screen_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/scan_transaction_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + const radius = AppRadiusV3.standard(); + + Future pumpScan(WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const ScanTransactionScreen()), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 overlay, frame, and instruction tokens', (tester) async { + await pumpScan(tester); + + expect(tester.widget(find.byType(Scaffold)).backgroundColor, colors.bgVoid); + + final instruction = tester.widget(find.textContaining('Scan the transaction QR from your hot wallet')); + expect(instruction.style?.color, colors.textContent); + expect(instruction.style?.fontSize, text.body.fontSize); + + expect(tester.widget(find.byIcon(Icons.arrow_back)).color, colors.textContent); + expect(tester.widget(find.byIcon(Icons.flash_on)).color, colors.textContent); + + final frame = tester.widgetList(find.byType(Container)).firstWhere((container) { + final decoration = container.decoration; + return decoration is BoxDecoration && decoration.border != null; + }); + final decoration = frame.decoration! as BoxDecoration; + expect((decoration.border as Border?)?.top.color, colors.accentFlare); + expect(decoration.borderRadius, radius.mdBorder); + + final debugLabel = tester.widget(find.text('DEBUG PAYLOADS')); + expect(debugLabel.style?.color, colors.textMuted); + expect(debugLabel.style?.fontSize, text.caption.fontSize); + + final sendButton = tester.widget(find.widgetWithText(ElevatedButton, 'Send')); + expect(sendButton.style?.backgroundColor?.resolve({}), colors.bgSurface); + final sendLabel = tester.widget( + find.descendant(of: find.widgetWithText(ElevatedButton, 'Send'), matching: find.text('Send')), + ); + expect(sendLabel.style?.color, colors.textContent); + expect(sendLabel.style?.fontSize, text.caption.fontSize); + }); +} diff --git a/cold-wallet-app/test/secure_element_warning_screen_test.dart b/cold-wallet-app/test/secure_element_warning_screen_test.dart new file mode 100644 index 000000000..7762455cf --- /dev/null +++ b/cold-wallet-app/test/secure_element_warning_screen_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/secure_element_warning_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + testWidgets('uses v3 warning icon, title, and body tokens', (tester) async { + await tester.pumpWidget( + Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const SecureElementWarningScreen()), + ), + ); + await tester.pump(); + + final icon = tester.widget(find.byIcon(Icons.warning_amber_rounded)); + expect(icon.color, colors.semanticSand); + + final title = tester.widget(find.text('No secure element detected')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + + final body = tester.widget(find.textContaining('This device has no biometrics')); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + }); +} diff --git a/cold-wallet-app/test/set_password_screen_test.dart b/cold-wallet-app/test/set_password_screen_test.dart new file mode 100644 index 000000000..0e2369315 --- /dev/null +++ b/cold-wallet-app/test/set_password_screen_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/set_password_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + testWidgets('uses v3 body and ember error tokens', (tester) async { + FlutterSecureStorage.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: const SetPasswordScreen(mnemonic: 'test mnemonic'), + ), + ), + ), + ); + await tester.pump(); + + final body = tester.widget(find.textContaining('Your password encrypts the wallet key')); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + + await tester.enterText(find.byType(TextField).at(0), 'alpha'); + await tester.enterText(find.byType(TextField).at(1), 'beta'); + await tester.tap(find.text('Create wallet')); + await tester.pump(); + + final error = tester.widget(find.text('Passwords do not match')); + expect(error.style?.color, colors.semanticEmber); + expect(error.style?.fontSize, text.caption.fontSize); + }); +} diff --git a/cold-wallet-app/test/settings_screen_test.dart b/cold-wallet-app/test/settings_screen_test.dart new file mode 100644 index 000000000..c57c45b17 --- /dev/null +++ b/cold-wallet-app/test/settings_screen_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/settings_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpSettings(WidgetTester tester) async { + FlutterSecureStorage.setMockInitialValues({}); + SharedPreferences.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const SettingsScreen()), + ), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 section, row, divider, and switch tokens', (tester) async { + await pumpSettings(tester); + + for (final label in ['SECURITY', 'SIGNATURE QR', 'DANGER ZONE']) { + final widget = tester.widget(find.text(label)); + expect(widget.style?.color, colors.textMuted); + expect(widget.style?.fontSize, text.labelMonogram.fontSize); + expect(widget.style?.fontFamily, text.labelMonogram.fontFamily); + } + + final rowTitle = tester.widget(find.text('Change password')); + expect(rowTitle.style?.color, colors.textContent); + expect(rowTitle.style?.fontSize, text.body.fontSize); + + final helper = tester.widget(find.textContaining('Set or change the password')); + expect(helper.style?.color, colors.textMuted); + expect(helper.style?.fontSize, text.caption.fontSize); + + final reset = tester.widget(find.text('Reset wallet')); + expect(reset.style?.color, colors.semanticEmber); + expect(reset.style?.fontSize, text.body.fontSize); + + final dividers = tester.widgetList(find.byType(Divider)); + expect(dividers, isNotEmpty); + for (final divider in dividers) { + expect(divider.color, colors.borderHairline); + } + + final wifiSwitch = tester.widget(find.byType(Switch)); + expect(wifiSwitch.activeTrackColor, colors.accentFlare); + }); +} diff --git a/cold-wallet-app/test/show_key_screen_test.dart b/cold-wallet-app/test/show_key_screen_test.dart new file mode 100644 index 000000000..cffd9c451 --- /dev/null +++ b/cold-wallet-app/test/show_key_screen_test.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/providers/wallet_providers.dart'; +import 'package:quantus_cold_wallet/screens/show_key_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + const radius = AppRadiusV3.standard(); + const address = '5FakeSs58AddressForTests'; + const checkphrase = 'amber glacier quartz'; + + Future pumpShowKey(WidgetTester tester) async { + await tester.pumpWidget( + ProviderScope( + overrides: [ + addressProvider.overrideWith((ref) => address), + checkphraseProvider.overrideWith((ref) async => checkphrase), + ], + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const ShowKeyScreen()), + ), + ), + ); + await tester.pump(); + await tester.pump(); + } + + testWidgets('uses v3 helper, lilac checkphrase, and address tokens', (tester) async { + await pumpShowKey(tester); + + final helper = tester.widget(find.textContaining('Scan with your Quantus hot wallet')); + expect(helper.style?.color, colors.textMuted); + expect(helper.style?.fontSize, text.body.fontSize); + + final phrase = tester.widget(find.text(checkphrase)); + expect(phrase.style?.color, colors.semanticLilac); + expect(phrase.style?.fontSize, text.headingRow.fontSize); + expect(phrase.style?.fontWeight, text.headingRow.fontWeight); + + final addressText = tester.widget(find.text(address)); + expect(addressText.style?.color, colors.textContent); + expect(addressText.style?.fontSize, text.dataAddressLarge.fontSize); + expect(addressText.style?.fontFamily, text.dataAddressLarge.fontFamily); + + final box = tester.widget(find.ancestor(of: find.text(address), matching: find.byType(Container)).first); + final decoration = box.decoration! as BoxDecoration; + expect(decoration.color, colors.bgSurface); + expect(decoration.borderRadius, radius.mdBorder); + }); +} diff --git a/cold-wallet-app/test/welcome_screen_test.dart b/cold-wallet-app/test/welcome_screen_test.dart new file mode 100644 index 000000000..d8da93069 --- /dev/null +++ b/cold-wallet-app/test/welcome_screen_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_cold_wallet/screens/welcome_screen.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + testWidgets('uses v3 hero title and muted body tokens', (tester) async { + FlutterSecureStorage.setMockInitialValues({}); + await tester.pumpWidget( + ProviderScope( + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: const WelcomeScreen()), + ), + ), + ); + await tester.pump(); + + final title = tester.widget(find.text('Quantus Cold Wallet')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleHero.fontSize); + expect(title.style?.fontWeight, text.titleHero.fontWeight); + + final body = tester.widget(find.textContaining('An air-gapped signer')); + expect(body.style?.color, colors.textMuted); + expect(body.style?.fontSize, text.body.fontSize); + }); +} diff --git a/miner-app/pubspec.lock b/miner-app/pubspec.lock index b9a9bff98..83951c528 100644 --- a/miner-app/pubspec.lock +++ b/miner-app/pubspec.lock @@ -744,6 +744,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: transitive + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" quantus_sdk: dependency: "direct main" description: diff --git a/mobile-app/lib/app.dart b/mobile-app/lib/app.dart index 8bd6229bd..42485eb77 100644 --- a/mobile-app/lib/app.dart +++ b/mobile-app/lib/app.dart @@ -1,9 +1,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/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/wallet_initializer.dart'; import 'package:resonance_network_wallet/v2/screens/auth/auth_wrapper.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/telemetry_navigator_observer.dart'; diff --git a/mobile-app/lib/features/components/custom_text_field.dart b/mobile-app/lib/features/components/custom_text_field.dart deleted file mode 100644 index 9453c8d60..000000000 --- a/mobile-app/lib/features/components/custom_text_field.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; - -enum TextFieldVariant { primary, secondary } - -class CustomTextField extends StatelessWidget { - final String? labelText; - final TextStyle? textStyle; - final String? initialValue; - final Color? fillColor; - final String? hintText; - final TextStyle? hintStyle; - final Icon? icon; - final Widget? trailing; - final bool obscureText; - final ValueChanged? onChanged; - final TextEditingController? controller; - final String? errorMsg; - final double? leftPadding; - final bool? disabled; - final TextFieldVariant variant; - - const CustomTextField({ - super.key, - this.labelText, - this.textStyle, - this.initialValue, - this.fillColor, - this.hintText, - this.hintStyle, - this.icon, - this.trailing, - this.obscureText = false, - this.errorMsg, - this.onChanged, - this.leftPadding, - this.controller, - this.disabled = false, - this.variant = TextFieldVariant.primary, - }) : assert(initialValue == null || controller == null, 'Cannot provide both an initialValue and a controller.'); - - @override - Widget build(BuildContext context) { - final effectiveTextStyle = variant == TextFieldVariant.primary - ? context.themeText.smallTitle - : context.themeText.paragraph; - final effectiveHintStyle = variant == TextFieldVariant.primary - ? context.themeText.smallTitle?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)) - : context.themeText.paragraph?.copyWith(color: context.themeColors.textPrimary.useOpacity(0.5)); - - // The main container for the entire widget - return SizedBox( - width: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, // Takes up minimum vertical space - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // The label is now optional and will only be displayed if labelText is not null. - if (labelText != null) ...[Text(labelText!, style: context.themeText.smallTitle), const SizedBox(height: 4)], - // The container that forms the background of the input field - Stack( - alignment: AlignmentGeometry.center, - children: [ - TextFormField( - controller: controller, - initialValue: initialValue, - enabled: disabled != true, - onChanged: onChanged, - obscureText: obscureText, - // Styling for the text inside the input field - style: textStyle ?? effectiveTextStyle, - decoration: InputDecoration( - fillColor: fillColor, - isDense: true, - enabledBorder: errorMsg != null - ? const OutlineInputBorder(borderSide: BorderSide(color: Colors.red, width: 1)) - : InputBorder.none, - focusedBorder: errorMsg != null - ? const OutlineInputBorder(borderSide: BorderSide(color: Colors.red, width: 1)) - : InputBorder.none, - contentPadding: EdgeInsets.only( - top: 10, - bottom: 10, - left: leftPadding ?? 11, - right: (trailing != null || icon != null) ? 40 : 11, - ), - hintText: hintText, - hintStyle: hintStyle ?? effectiveHintStyle, - ), - ), - - if (icon != null) Positioned(right: 16, child: icon!), - if (trailing != null) Positioned(right: 10, child: trailing!), - ], - ), - - if (errorMsg != null) ...[ - const SizedBox(height: 4), - Text(errorMsg!, style: context.themeText.tiny?.copyWith(color: context.themeColors.textError)), - ], - ], - ), - ); - } -} diff --git a/mobile-app/lib/features/components/notification_card.dart b/mobile-app/lib/features/components/notification_card.dart deleted file mode 100644 index 3288c0513..000000000 --- a/mobile-app/lib/features/components/notification_card.dart +++ /dev/null @@ -1,342 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/models/notification_models.dart'; -import 'package:resonance_network_wallet/providers/account_providers.dart'; -import 'package:resonance_network_wallet/services/transaction_service.dart'; -import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; - -class NotificationCard extends ConsumerStatefulWidget { - final NotificationData notification; - final VoidCallback onDismiss; - - const NotificationCard({super.key, required this.notification, required this.onDismiss}); - - @override - ConsumerState createState() => _NotificationCardState(); -} - -class _NotificationCardState extends ConsumerState with TickerProviderStateMixin { - late AnimationController _slideController; - late AnimationController _swipeController; - late Animation _slideAnimation; - late Animation _swipeAnimation; - bool _isDismissing = false; - double _swipeOffset = 0.0; - - Color get titleColor { - switch (widget.notification.type) { - case NotificationType.success: - return const Color(0xFF8AF9A8); // Green - case NotificationType.warning: - return const Color(0xFFFADC34); // Yellow - case NotificationType.alert: - return const Color(0xFFFF2D54); // Red - default: - return const Color(0xFF16CECE); // Info - } - } - - SvgPicture get icon { - switch (widget.notification.type) { - case NotificationType.success: - return SvgPicture.asset('assets/notification/tick_icon.svg', width: 22, height: 22); - case NotificationType.warning: - return SvgPicture.asset('assets/notification/alert_icon.svg', width: 21, height: 20); - case NotificationType.alert: - return SvgPicture.asset('assets/notification/red_alert_icon.svg', width: 21, height: 20); - default: - return SvgPicture.asset('assets/notification/hourglass_icon.svg', width: 21, height: 21); - } - } - - @override - void initState() { - super.initState(); - _slideController = AnimationController(duration: const Duration(milliseconds: 300), vsync: this); - - _swipeController = AnimationController(duration: const Duration(milliseconds: 200), vsync: this); - - _slideAnimation = Tween( - begin: const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOut)); - - _swipeAnimation = Tween( - begin: Offset.zero, - end: Offset.zero, - ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeOut)); - - _slideController.forward(); - } - - @override - void dispose() { - _slideController.dispose(); - _swipeController.dispose(); - super.dispose(); - } - - void _handleSwipeDismiss() async { - if (_isDismissing) return; - - setState(() { - _isDismissing = true; - }); - - // Animate the swipe out - final swipeDirection = _swipeOffset > 0 ? 2.0 : -2.0; - _swipeAnimation = Tween( - begin: Offset(_swipeOffset / MediaQuery.of(context).size.width, 0), - end: Offset(swipeDirection, 0), - ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeIn)); - - await _swipeController.forward(); - widget.onDismiss(); - } - - void _handleSwipeUpdate(double delta) { - if (_isDismissing) return; - - setState(() { - _swipeOffset += delta; - // Limit the swipe offset to prevent over-swiping - _swipeOffset = _swipeOffset.clamp(-150.0, 150.0); - }); - } - - void _handleSwipeEnd(double velocity) { - if (_isDismissing) return; - - const double threshold = 80.0; // Threshold distance for dismissal - const double velocityThreshold = 500.0; // Velocity threshold for quick swipes - - // Check if swipe meets dismissal criteria - bool shouldDismiss = _swipeOffset.abs() > threshold || velocity.abs() > velocityThreshold; - - if (shouldDismiss) { - _handleSwipeDismiss(); - } else { - // Animate back to original position - _resetSwipePosition(); - } - } - - void _resetSwipePosition() { - _swipeAnimation = Tween( - begin: Offset(_swipeOffset / MediaQuery.of(context).size.width, 0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: _swipeController, curve: Curves.easeOut)); - - _swipeController.forward().then((_) { - setState(() { - _swipeOffset = 0.0; - }); - _swipeController.reset(); - }); - } - - void _onViewDetails() { - final txService = ref.read(transactionServiceProvider); - final transaction = txService.deserializeTxEventFromJsonIfPossible(widget.notification.metadata); - final activeAccount = ref.read(activeAccountProvider).value; - - if (transaction != null && activeAccount != null) { - showTransactionDetailSheet(context, transaction, activeAccount.account.accountId); - } - } - - @override - Widget build(BuildContext context) { - final notification = widget.notification; - final txService = ref.read(transactionServiceProvider); - final transaction = txService.deserializeTxEventFromJsonIfPossible(notification.metadata); - - return GestureDetector( - onHorizontalDragUpdate: (details) { - _handleSwipeUpdate(details.delta.dx); - }, - onHorizontalDragEnd: (details) { - _handleSwipeEnd(details.primaryVelocity ?? 0); - }, - child: SlideTransition( - position: _slideAnimation, - child: AnimatedBuilder( - animation: _slideController, - builder: (context, child) { - // Calculate opacity based on swipe offset - final swipeProgress = (_swipeOffset.abs() / 150.0).clamp(0.0, 1.0); - final opacity = 1.0 - (swipeProgress * 0.3); - - return Transform.translate( - offset: _isDismissing - ? _swipeAnimation.value * MediaQuery.of(context).size.width - : Offset(_swipeOffset, 0), - child: Opacity( - opacity: opacity, - child: Material( - elevation: 8, - borderRadius: BorderRadius.circular(4), - child: Container( - decoration: ShapeDecoration( - color: const Color(0x99313131), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), - ), - child: Column( - children: [ - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3), - decoration: ShapeDecoration( - color: Colors.white.withValues(alpha: 0.15), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(4), - topRight: Radius.circular(4), - ), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 13, - children: [ - Text( - notification.accountName, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: Column( - spacing: 13, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 13, - children: [ - icon, - Expanded( - child: Column( - spacing: 2, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text.rich( - TextSpan( - children: [ - TextSpan( - text: notification.title, - style: TextStyle( - color: titleColor /* Green */, - fontSize: 14, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), - ), - const TextSpan( - text: ' ', - style: TextStyle( - color: Color(0xFFD9D9D9), - fontSize: 10, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), - ), - TextSpan( - text: - // ignore: lines_longer_than_80_chars - DatetimeFormattingService.format(notification.timestamp), - style: const TextStyle( - color: Color(0xFFD9D9D9), - fontSize: 10, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - Text( - notification.message, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), - ), - ], - ), - ), - - GestureDetector( - onTap: widget.onDismiss, - child: const Icon(Icons.close, color: Colors.white70, size: 16), - ), - ], - ), - if (transaction != null) _buildViewDetailsButton(), - ], - ), - ), - ], - ), - ), - ), - ), - ); - }, - ), - ), - ); - } - - Widget _buildViewDetailsButton() { - return Align( - alignment: Alignment.bottomRight, - child: GestureDetector( - onTap: _onViewDetails, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 3), - decoration: ShapeDecoration( - color: Colors.black.withValues(alpha: 0.25), - shape: RoundedRectangleBorder( - side: BorderSide(width: 1, color: Colors.white.withValues(alpha: 0.15)), - borderRadius: BorderRadius.circular(4), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 10, - children: [ - const Text( - 'View Details', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 12, - fontFamily: 'Fira Code', - fontWeight: FontWeight.w400, - ), - ), - SvgPicture.asset('assets/notification/outward_arrow_icon.svg'), - ], - ), - ), - ), - ); - } -} diff --git a/mobile-app/lib/features/components/notification_group.dart b/mobile-app/lib/features/components/notification_group.dart deleted file mode 100644 index 16a8ce7a7..000000000 --- a/mobile-app/lib/features/components/notification_group.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/features/components/notification_card.dart'; -import 'package:resonance_network_wallet/models/notification_models.dart'; - -class NotificationGroup extends StatefulWidget { - final List notifications; - final VoidCallback onDismissAll; - final Function(String) onDismissSingle; - - const NotificationGroup({ - super.key, - required this.notifications, - required this.onDismissAll, - required this.onDismissSingle, - }); - - @override - State createState() => _NotificationGroupState(); -} - -class _NotificationGroupState extends State with TickerProviderStateMixin { - late AnimationController _slideController; - late Animation _slideAnimation; - - @override - void initState() { - super.initState(); - _slideController = AnimationController(duration: const Duration(milliseconds: 300), vsync: this); - - _slideAnimation = Tween( - begin: const Offset(1.0, 0.0), - end: Offset.zero, - ).animate(CurvedAnimation(parent: _slideController, curve: Curves.easeOut)); - - _slideController.forward(); - } - - @override - void dispose() { - _slideController.dispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final reveresedNotifications = widget.notifications.reversed; - - return SlideTransition( - position: _slideAnimation, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Dismiss All button - Align( - alignment: Alignment.centerRight, - child: InkWell( - onTap: widget.onDismissAll, - child: const Text('Dismiss All', style: TextStyle(color: Colors.red)), - ), - ), - const SizedBox(height: 16), - // Notifications list - flexible height - Flexible( - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - spacing: 24, - children: reveresedNotifications - .map( - (notification) => NotificationCard( - key: ValueKey(notification.id), - notification: notification, - onDismiss: () => widget.onDismissSingle(notification.id), - ), - ) - .toList(), - ), - ), - ), - ], - ), - ); - } -} diff --git a/mobile-app/lib/features/styles/app_colors_theme.dart b/mobile-app/lib/features/styles/app_colors_theme.dart deleted file mode 100644 index 5ab1e1343..000000000 --- a/mobile-app/lib/features/styles/app_colors_theme.dart +++ /dev/null @@ -1,289 +0,0 @@ -import 'package:flutter/material.dart'; - -@immutable -class AppColorsTheme extends ThemeExtension { - // I don't know about this two colors, it's already there since the beginning - // Keep it for now since I'm no sure if it will break something if I remove it - final Color primary; - final Color secondary; - - // What we use - 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 accountTagGuardian; - final Color accountTagEntrusted; - final Color accountTagHighSecurity; - - 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.accountTagGuardian, - required this.accountTagEntrusted, - required this.accountTagHighSecurity, - }); - - const AppColorsTheme.light() - : 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(0xFF0B0F14), - background2: const Color(0xFF0C1014), - surface: const Color(0xFF000000), - surfaceActive: const Color(0xFFF4F6F9), - error: const Color(0xFFFF2D54), - textError: const Color(0xFFFF5252), - textPrimary: const Color(0xFFF4F6F9), - textSecondary: const Color(0xFF0B0F14), - inputLabel: const Color(0xFFD4D3E0), - light: const Color(0xFFE6E6E6), - circularLoader: Colors.white, - authButtonBg: const Color(0xFF16CECE), - textMuted: const Color(0xFFD4D3E0), - border: const Color(0xcfe6e6e6), - 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(0x14F4F6F9), - buttonGlass: const Color(0x14F4F6F9), - buttonDanger: const Color(0xFFFF1F45), - buttonSuccess: const Color(0xFF1FFFA7), - buttonNeutral: const Color(0xFFF4F6F9), - buttonPrimary: const [Color(0xFF0000FF), Color(0xFFED4CCE)], - accountTagGuardian: const Color(0xFF9747FF), - accountTagEntrusted: const Color(0xFFFFD541), - accountTagHighSecurity: const Color(0xFF4CEDE7), - ); - - 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(0xFF0B0F14), - background2: const Color(0xFF0C1014), - surface: const Color(0xFF000000), - surfaceActive: const Color(0xFFF4F6F9), - error: const Color(0xFFFF2D54), - textError: const Color(0xFFFF5252), - textPrimary: const Color(0xFFF4F6F9), - textSecondary: const Color(0xFF0B0F14), - inputLabel: const Color(0xFFD4D3E0), - light: const Color(0xFFE6E6E6), - circularLoader: Colors.white, - authButtonBg: const Color(0xFF16CECE), - textMuted: const Color(0xFFD4D3E0), - border: const Color(0xe6e6e6cf), - 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(0x14F4F6F9), - buttonGlass: const Color(0x14F4F6F9), - buttonDanger: const Color(0xFFFF1F45), - buttonSuccess: const Color(0xFF1FFFA7), - buttonNeutral: const Color(0xFFF4F6F9), - buttonPrimary: const [Color(0xFF0000FF), Color(0xFFED4CCE)], - accountTagGuardian: const Color(0xFF9747FF), - accountTagEntrusted: const Color(0xFFFFD541), - accountTagHighSecurity: const Color(0xFF4CEDE7), - ); - - @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? buttonGlass, - Color? buttonGlassAlpha, - Color? darkGray, - Color? settingCard, - List? buttonPrimary, - Color? buttonNeutral, - Color? buttonDanger, - Color? buttonSuccess, - Color? accountTagGuardian, - Color? accountTagEntrusted, - Color? accountTagHighSecurity, - }) { - 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, - buttonPrimary: buttonPrimary ?? this.buttonPrimary, - buttonNeutral: buttonNeutral ?? this.buttonNeutral, - buttonDanger: buttonDanger ?? this.buttonDanger, - buttonSuccess: buttonSuccess ?? this.buttonSuccess, - accountTagGuardian: accountTagGuardian ?? this.accountTagGuardian, - accountTagEntrusted: accountTagEntrusted ?? this.accountTagEntrusted, - accountTagHighSecurity: accountTagHighSecurity ?? this.accountTagHighSecurity, - ); - } - - @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, - buttonNeutral: Color.lerp(buttonNeutral, other.buttonNeutral, t) ?? buttonNeutral, - buttonDanger: Color.lerp(buttonDanger, other.buttonDanger, t) ?? buttonDanger, - buttonSuccess: Color.lerp(buttonSuccess, other.buttonSuccess, t) ?? buttonSuccess, - buttonPrimary: other.buttonPrimary, - accountTagGuardian: Color.lerp(accountTagGuardian, other.accountTagGuardian, t) ?? accountTagGuardian, - accountTagEntrusted: Color.lerp(accountTagEntrusted, other.accountTagEntrusted, t) ?? accountTagEntrusted, - accountTagHighSecurity: - Color.lerp(accountTagHighSecurity, other.accountTagHighSecurity, t) ?? accountTagHighSecurity, - ); - } -} - -// Extension on BuildContext to access AppTextTheme styles directly -extension AppColorsThemeExtension on BuildContext { - AppColorsTheme get themeColors => Theme.of(this).extension()!; -} diff --git a/mobile-app/lib/features/styles/app_size_theme.dart b/mobile-app/lib/features/styles/app_size_theme.dart deleted file mode 100644 index 9da58c2eb..000000000 --- a/mobile-app/lib/features/styles/app_size_theme.dart +++ /dev/null @@ -1,228 +0,0 @@ -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; - - 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, - }); - - 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, - ); - - 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, - ); - - @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, - }) { - 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, - ); - } - - @override - AppSizeTheme lerp(AppSizeTheme? other, double t) { - if (other is! AppSizeTheme) return this; - return AppSizeTheme( - logoHeight: logoHeight + (other.logoHeight - logoHeight) * t, - mainMenuHeight: mainMenuHeight + (other.mainMenuHeight - mainMenuHeight) * t, - mainMenuWidth: mainMenuWidth + (other.mainMenuWidth - mainMenuWidth) * t, - mainMenuIconSize: mainMenuIconSize + (other.mainMenuIconSize - mainMenuIconSize) * t, - navbarHeight: navbarHeight + (other.navbarHeight - navbarHeight) * t, - navbarItemHeight: navbarItemHeight + (other.navbarItemHeight - navbarItemHeight) * t, - navbarItemWidth: navbarItemWidth + (other.navbarItemWidth - navbarItemWidth) * t, - navbarIconWidth: navbarIconWidth + (other.navbarIconWidth - navbarIconWidth) * t, - floatingBtnHeight: floatingBtnHeight + (other.floatingBtnHeight - floatingBtnHeight) * t, - floatingBtnWidth: floatingBtnWidth + (other.floatingBtnWidth - floatingBtnWidth) * t, - settingMenuIconSize: settingMenuIconSize + (other.settingMenuIconSize - settingMenuIconSize) * t, - settingMenuShareIconSize: - settingMenuShareIconSize + (other.settingMenuShareIconSize - settingMenuShareIconSize) * t, - accountListItemHeight: accountListItemHeight + (other.accountListItemHeight - accountListItemHeight) * t, - accountListItemLogoWidth: - accountListItemLogoWidth + (other.accountListItemLogoWidth - accountListItemLogoWidth) * t, - appbarIconSize: appbarIconSize + (other.appbarIconSize - appbarIconSize) * t, - sendOverlayContainerWidth: - sendOverlayContainerWidth + (other.sendOverlayContainerWidth - sendOverlayContainerWidth) * t, - overlayCloseIconSize: overlayCloseIconSize + (other.overlayCloseIconSize - overlayCloseIconSize) * t, - mnemonicCellDesiredHeight: - mnemonicCellDesiredHeight + (other.mnemonicCellDesiredHeight - mnemonicCellDesiredHeight) * t, - txListItemIconWidth: txListItemIconWidth + (other.txListItemIconWidth - txListItemIconWidth) * t, - txDetailsIconHeight: txDetailsIconHeight + (other.txDetailsIconHeight - txDetailsIconHeight) * t, - txDetailsIconWidth: txDetailsIconWidth + (other.txDetailsIconWidth - txDetailsIconWidth) * t, - copyIconSize: copyIconSize + (other.copyIconSize - copyIconSize) * t, - pasteIconSize: pasteIconSize + (other.pasteIconSize - pasteIconSize) * t, - timePickerSubtitleWidth: timePickerSubtitleWidth + (other.timePickerSubtitleWidth - timePickerSubtitleWidth) * t, - bottomButtonSpacing: bottomButtonSpacing + (other.bottomButtonSpacing - bottomButtonSpacing) * t, - buttonsHorizontalSpacing: - buttonsHorizontalSpacing + (other.buttonsHorizontalSpacing - buttonsHorizontalSpacing) * t, - infoSheetTitleIcon: infoSheetTitleIcon + (other.infoSheetTitleIcon - infoSheetTitleIcon) * t, - ); - } -} - -extension AppSizeThemeExtension on BuildContext { - AppSizeTheme get themeSize => Theme.of(this).extension()!; -} diff --git a/mobile-app/lib/features/styles/app_text_theme.dart b/mobile-app/lib/features/styles/app_text_theme.dart deleted file mode 100644 index ced8b8631..000000000 --- a/mobile-app/lib/features/styles/app_text_theme.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'package:flutter/material.dart'; - -@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: 'Fira Code'), - extraLargeTitle: const TextStyle(fontSize: 40, fontFamily: 'Fira Code', fontWeight: FontWeight.w600), - largeTitle: const TextStyle(fontSize: 30, fontFamily: 'Fira Code', fontWeight: FontWeight.w300), - mediumTitle: const TextStyle(fontSize: 24, fontFamily: 'Fira Code', fontWeight: FontWeight.w500), - smallTitle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - paragraph: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - smallParagraph: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - largeTag: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - tag: const TextStyle(fontSize: 12, fontWeight: FontWeight.w300, fontFamily: 'Fira Code'), - timer: const TextStyle(fontSize: 28, fontWeight: FontWeight.w600, fontFamily: 'Fira Code'), - detail: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - tiny: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - ); - - const AppTextTheme.iPad() - : this( - lockTitle: const TextStyle(color: Colors.white, fontSize: 28, fontFamily: 'Fira Code'), - extraLargeTitle: const TextStyle(fontSize: 52, fontFamily: 'Fira Code', fontWeight: FontWeight.w600), - largeTitle: const TextStyle(fontSize: 36, fontFamily: 'Fira Code', fontWeight: FontWeight.w300), - mediumTitle: const TextStyle(fontSize: 28, fontFamily: 'Fira Code', fontWeight: FontWeight.w400), - smallTitle: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w500, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - paragraph: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - smallParagraph: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - largeTag: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - tag: const TextStyle(fontSize: 16, fontWeight: FontWeight.w300, fontFamily: 'Fira Code'), - timer: const TextStyle(fontSize: 36, fontWeight: FontWeight.w600, fontFamily: 'Fira Code'), - detail: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - tiny: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.w400, - fontFamily: 'Fira Code', - fontStyle: FontStyle.normal, - decoration: TextDecoration.none, - ), - ); - - @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 on BuildContext to access AppTextTheme styles directly -extension AppTextThemeExtension on BuildContext { - AppTextTheme get themeText => Theme.of(this).extension()!; -} diff --git a/mobile-app/lib/features/styles/app_theme.dart b/mobile-app/lib/features/styles/app_theme.dart deleted file mode 100644 index f877fd85a..000000000 --- a/mobile-app/lib/features/styles/app_theme.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_size_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; - -class AppTheme { - static ThemeData lightTheme(BuildContext context) { - final appColors = const AppColorsTheme.light(); - - 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: appColors.textPrimary, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: appColors.secondary, - side: BorderSide(color: appColors.secondary), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ), - textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: appColors.textPrimary)), - inputDecorationTheme: InputDecorationTheme( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - filled: true, - fillColor: appColors.surface, - ), - ); - } - - 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(12)), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: appColors.secondary, - side: BorderSide(color: appColors.secondary), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ), - 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, - ], - ); - } -} diff --git a/mobile-app/lib/l10n/app_en.arb b/mobile-app/lib/l10n/app_en.arb index 083d7f178..066195c36 100644 --- a/mobile-app/lib/l10n/app_en.arb +++ b/mobile-app/lib/l10n/app_en.arb @@ -2683,6 +2683,10 @@ "@componentNameFieldHint": { "description": "Hint text on account name field" }, + "componentAmountApproximatePrefix": "≈", + "@componentAmountApproximatePrefix": { + "description": "Prefix before a converted secondary amount" + }, "commonLoading": "Loading...", "@commonLoading": { diff --git a/mobile-app/lib/l10n/app_id.arb b/mobile-app/lib/l10n/app_id.arb index 1137652ed..2f02f322c 100644 --- a/mobile-app/lib/l10n/app_id.arb +++ b/mobile-app/lib/l10n/app_id.arb @@ -523,6 +523,7 @@ "componentCheckphraseLabel": "CHECKPHRASE", "componentCheckphraseCopied": "Checkphrase disalin", "componentNameFieldHint": "Masukkan nama untuk akun Anda", + "componentAmountApproximatePrefix": "≈", "commonLoading": "Memuat...", "commonCancel": "Batal", "commonCanceling": "Membatalkan...", diff --git a/mobile-app/lib/l10n/app_localizations.dart b/mobile-app/lib/l10n/app_localizations.dart index 73c8fdb89..4c9379813 100644 --- a/mobile-app/lib/l10n/app_localizations.dart +++ b/mobile-app/lib/l10n/app_localizations.dart @@ -3458,6 +3458,12 @@ abstract class AppLocalizations { /// **'Enter a name for your account'** String get componentNameFieldHint; + /// Prefix before a converted secondary amount + /// + /// In en, this message translates to: + /// **'≈'** + String get componentAmountApproximatePrefix; + /// Text for generic loading state /// /// In en, this message translates to: diff --git a/mobile-app/lib/l10n/app_localizations_en.dart b/mobile-app/lib/l10n/app_localizations_en.dart index 012568855..9679da28e 100644 --- a/mobile-app/lib/l10n/app_localizations_en.dart +++ b/mobile-app/lib/l10n/app_localizations_en.dart @@ -1843,6 +1843,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get componentNameFieldHint => 'Enter a name for your account'; + @override + String get componentAmountApproximatePrefix => '≈'; + @override String get commonLoading => 'Loading...'; diff --git a/mobile-app/lib/l10n/app_localizations_id.dart b/mobile-app/lib/l10n/app_localizations_id.dart index 5da9e59e1..c7b7e8bf3 100644 --- a/mobile-app/lib/l10n/app_localizations_id.dart +++ b/mobile-app/lib/l10n/app_localizations_id.dart @@ -1846,6 +1846,9 @@ class AppLocalizationsId extends AppLocalizations { @override String get componentNameFieldHint => 'Masukkan nama untuk akun Anda'; + @override + String get componentAmountApproximatePrefix => '≈'; + @override String get commonLoading => 'Memuat...'; diff --git a/mobile-app/lib/shared/extensions/clipboard_extensions.dart b/mobile-app/lib/shared/extensions/clipboard_extensions.dart index 97ee41c35..2682b9f8c 100644 --- a/mobile-app/lib/shared/extensions/clipboard_extensions.dart +++ b/mobile-app/lib/shared/extensions/clipboard_extensions.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:resonance_network_wallet/services/secure_clipboard_service.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; extension ClipboardWithToasterExtensions on BuildContext { Future copyTextWithToaster(String text, {String message = 'Address copied to clipboard'}) async { diff --git a/mobile-app/lib/v2/components/account_badge.dart b/mobile-app/lib/v2/components/account_badge.dart index 34d26274f..53bee1686 100644 --- a/mobile-app/lib/v2/components/account_badge.dart +++ b/mobile-app/lib/v2/components/account_badge.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AccountBadge extends StatelessWidget { final String name; diff --git a/mobile-app/lib/v2/components/address_checkphrase_with_initial.dart b/mobile-app/lib/v2/components/address_checkphrase_with_initial.dart index 00ef9ee3f..5f9906fde 100644 --- a/mobile-app/lib/v2/components/address_checkphrase_with_initial.dart +++ b/mobile-app/lib/v2/components/address_checkphrase_with_initial.dart @@ -1,8 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AddressCheckphraseWithInitial extends StatelessWidget { final String recipientChecksum; diff --git a/mobile-app/lib/v2/components/address_details_card.dart b/mobile-app/lib/v2/components/address_details_card.dart index c8eb5b675..6db6b9618 100644 --- a/mobile-app/lib/v2/components/address_details_card.dart +++ b/mobile-app/lib/v2/components/address_details_card.dart @@ -6,8 +6,7 @@ import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/v2/components/split_card.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class AddressDetailsCard extends ConsumerStatefulWidget { final String accountId; diff --git a/mobile-app/lib/v2/components/address_input_field.dart b/mobile-app/lib/v2/components/address_input_field.dart index d60a3764e..9e0d8e219 100644 --- a/mobile-app/lib/v2/components/address_input_field.dart +++ b/mobile-app/lib/v2/components/address_input_field.dart @@ -1,7 +1,5 @@ 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'; /// Dual-state address input shared by every flow where the user enters a /// destination address (send, redeem, ...). When the controller holds a valid diff --git a/mobile-app/lib/v2/components/amount_display_with_conversion.dart b/mobile-app/lib/v2/components/amount_display_with_conversion.dart index 23143a979..4ac16a924 100644 --- a/mobile-app/lib/v2/components/amount_display_with_conversion.dart +++ b/mobile-app/lib/v2/components/amount_display_with_conversion.dart @@ -2,9 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AmountDisplayWithConversion extends StatelessWidget { final CurrencyDisplayState amountDisplay; diff --git a/mobile-app/lib/v2/components/animated_qr_scanner.dart b/mobile-app/lib/v2/components/animated_qr_scanner.dart index 86ace9089..cba4316d3 100644 --- a/mobile-app/lib/v2/components/animated_qr_scanner.dart +++ b/mobile-app/lib/v2/components/animated_qr_scanner.dart @@ -3,10 +3,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Sequence metadata encoded in one frame of an animated QR payload. class AnimatedQrSequence { diff --git a/mobile-app/lib/v2/components/animated_ur_qr.dart b/mobile-app/lib/v2/components/animated_ur_qr.dart deleted file mode 100644 index 4c61f08cc..000000000 --- a/mobile-app/lib/v2/components/animated_ur_qr.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:qr_flutter/qr_flutter.dart'; - -/// Renders a UR payload as a QR code. A multi-part UR is animated by cycling -/// through its fragments with a [Timer]. A single-part UR renders statically. -class AnimatedUrQr extends StatefulWidget { - final List parts; - final Duration interval; - final double size; - - const AnimatedUrQr({ - super.key, - required this.parts, - this.interval = const Duration(milliseconds: 200), - this.size = 280, - }); - - @override - State createState() => _AnimatedUrQrState(); -} - -class _AnimatedUrQrState extends State { - Timer? _timer; - int _index = 0; - - @override - void initState() { - super.initState(); - if (widget.parts.length > 1) { - _timer = Timer.periodic(widget.interval, (_) { - setState(() => _index = (_index + 1) % widget.parts.length); - }); - } - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - width: widget.size, - height: widget.size, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)), - child: QrImageView( - data: widget.parts[_index], - errorCorrectionLevel: QrErrorCorrectLevel.L, - version: QrVersions.auto, - backgroundColor: Colors.white, - eyeStyle: const QrEyeStyle(eyeShape: QrEyeShape.square, color: Colors.black), - dataModuleStyle: const QrDataModuleStyle(dataModuleShape: QrDataModuleShape.square, color: Colors.black), - ), - ); - } -} diff --git a/mobile-app/lib/v2/components/back_button.dart b/mobile-app/lib/v2/components/back_button.dart deleted file mode 100644 index 5de337377..000000000 --- a/mobile-app/lib/v2/components/back_button.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button_base.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) { - final double containerSize = 28; - final double iconSize = 24; - - return GestureDetector( - onTap: onTap ?? () => Navigator.pop(context), - child: GlassButtonBase( - buttonHeight: containerSize, - buttonWidth: containerSize, - borderRadius: BorderRadius.circular(containerSize / 2), - padding: const EdgeInsets.all(2), - child: SvgPicture.asset( - 'assets/v2/caret_left.svg', - width: iconSize, - height: iconSize, - colorFilter: ColorFilter.mode(context.colors.textPrimary, BlendMode.srcIn), - ), - ), - ); - } -} diff --git a/mobile-app/lib/v2/components/base_background.dart b/mobile-app/lib/v2/components/base_background.dart deleted file mode 100644 index 80bea0eaf..000000000 --- a/mobile-app/lib/v2/components/base_background.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; - -class BaseBackground extends StatelessWidget { - final Widget child; - - const BaseBackground({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - return Container(color: context.colors.background, child: child); - } -} diff --git a/mobile-app/lib/v2/components/bottom_sheet_container.dart b/mobile-app/lib/v2/components/bottom_sheet_container.dart deleted file mode 100644 index 3fc43ab12..000000000 --- a/mobile-app/lib/v2/components/bottom_sheet_container.dart +++ /dev/null @@ -1,110 +0,0 @@ -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 Function(String title)? titleBuilder; - final Widget child; - final VoidCallback? onBack; - final double? height; - final bool showDragHandle; - - const BottomSheetContainer({ - super.key, - required this.title, - required this.child, - this.titleBuilder, - this.onBack, - this.height, - this.showDragHandle = true, - }); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - final topPadding = showDragHandle ? 12.0 : 40.0; - return Container( - height: height, - padding: EdgeInsets.fromLTRB(24, topPadding, 24, 24), - decoration: BoxDecoration( - color: colors.background, - border: Border.all(color: colors.borderButton, width: 1), - borderRadius: BorderRadius.circular(32), - ), - child: Column( - mainAxisSize: height != null ? MainAxisSize.max : MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (showDragHandle) ...[ - Center( - child: Container( - width: 56, - height: 4, - decoration: BoxDecoration(color: colors.borderButton, borderRadius: BorderRadius.circular(23)), - ), - ), - const SizedBox(height: 20), - ], - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (onBack != null) - GestureDetector( - onTap: onBack, - child: Icon(Icons.arrow_back_ios_new, color: colors.textPrimary, size: 20), - ), - - if (titleBuilder != null) - titleBuilder!(title) - else - Text( - title, - style: text.smallTitle?.copyWith( - color: colors.textPrimary, - fontSize: 20, - fontWeight: FontWeight.w500, - ), - textAlign: TextAlign.center, - ), - - GestureDetector( - onTap: () => Navigator.pop(context), - child: Icon(Icons.close, color: colors.textPrimary, size: 20), - ), - ], - ), - SizedBox(height: showDragHandle ? 28 : 32), - if (height != null) Expanded(child: child) else Flexible(child: SingleChildScrollView(child: child)), - ], - ), - ); - } - - static Future show(BuildContext context, {required WidgetBuilder builder, RouteSettings? routeSettings}) { - return showModalBottomSheet( - context: context, - routeSettings: routeSettings, - backgroundColor: Colors.transparent, - isScrollControlled: true, - enableDrag: true, - isDismissible: true, - constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width), - builder: (ctx) { - final maxSheetHeight = MediaQuery.sizeOf(ctx).height * 0.85; - return BackdropFilter( - filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), - child: Align( - alignment: Alignment.bottomCenter, - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: maxSheetHeight), - child: builder(ctx), - ), - ), - ); - }, - ); - } -} diff --git a/mobile-app/lib/v2/components/decoded_call_view.dart b/mobile-app/lib/v2/components/decoded_call_view.dart index bf15ed812..51a24597e 100644 --- a/mobile-app/lib/v2/components/decoded_call_view.dart +++ b/mobile-app/lib/v2/components/decoded_call_view.dart @@ -1,166 +1,37 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' as sdk; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; -import 'package:resonance_network_wallet/v2/components/detail_summary_row.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/providers/wallet_providers.dart'; -/// Renders every parameter of a [DecodedCall], nested calls included. -/// -/// Multisig proposals are not always transfers — governance votes, runtime -/// upgrade authorisations and batches all arrive through the same pallet — so a -/// transfer-shaped layout silently misreports them. This shows the call the -/// runtime actually declared, with each of its parameters. +/// App-level decoded-call tree: maps [txAmountDisplayProvider] onto the +/// presentational SDK widget. class DecodedCallView extends ConsumerWidget { - final DecodedCall call; - - /// Nesting depth; nested calls indent and drop a level of visual weight. + final sdk.DecodedCall call; final int depth; const DecodedCallView({super.key, required this.call, this.depth = 0}); @override Widget build(BuildContext context, WidgetRef ref) { - final colors = context.colors; - final text = context.themeText; - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - call.displayTitle, - style: text.detail?.copyWith( - color: depth == 0 ? colors.textPrimary : colors.textSecondary, - fontWeight: FontWeight.w600, - fontFamily: AppTextTheme.fontFamilySecondary, - ), - ), - const SizedBox(height: 4), - for (final field in call.fields) _field(context, ref, field), - ], - ); - } - - Widget _field(BuildContext context, WidgetRef ref, CallField field) { - final colors = context.colors; - final text = context.themeText; - - switch (field) { - case ValueField(:final label, :final value, :final note): - return Padding( - padding: const EdgeInsets.only(top: 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - DetailSummaryRow.review(label: label, value: value), - if (note != null) - Padding( - padding: const EdgeInsets.only(top: 2), - child: Text(note, style: text.detail?.copyWith(color: colors.textTertiary, fontSize: 11)), - ), - ], - ), - ); - - case AmountField(:final label, :final token, :final assetId, :final note): - final value = assetId == null - ? ref.watch(txAmountDisplayProvider)(token, isSend: true).primaryAmount - : '$token (asset #$assetId, raw units)'; - return Padding( - padding: const EdgeInsets.only(top: 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - DetailSummaryRow.review(label: label, value: value), - if (note != null) - Padding( - padding: const EdgeInsets.only(top: 2), - child: Text(note, style: text.detail?.copyWith(color: colors.textTertiary, fontSize: 11)), - ), - ], - ), - ); - - case FieldGroup(:final label, :final items): - return Padding( - padding: const EdgeInsets.only(top: 6), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(label, style: text.transactionDetailRowLabel?.copyWith(color: colors.textTertiary)), - Padding( - padding: const EdgeInsets.only(left: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [for (final item in items) _field(context, ref, item)], - ), - ), - ], - ), - ); - - case NestedCallField(:final label, :final call, :final note): - return Padding( - padding: const EdgeInsets.only(top: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text(label, style: text.transactionDetailRowLabel?.copyWith(color: colors.textTertiary)), - const SizedBox(height: 4), - Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: colors.surfaceDeep, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: colors.borderButton.useOpacity(0.4)), - ), - child: DecodedCallView(call: call, depth: depth + 1), - ), - if (note != null) - Padding( - padding: const EdgeInsets.only(top: 4), - child: Text(note, style: text.detail?.copyWith(color: colors.textTertiary, fontSize: 11)), - ), - ], - ), - ); - } - } -} - -/// The one-line-plus-subtitle form of a decoded call, for list rows and sheets. -/// -/// A transfer keeps reading as an amount and a recipient; anything else names -/// the call rather than pretending to be a zero-value transfer. -class DecodedCallHeadline { - /// Primary line: formatted amount for transfers, otherwise the call name. - final String primary; - - /// Shortened recipient address when this call moves value, else null. - final String? recipient; - - /// Subtitle for calls that move no value (the owning pallet), else null. - final String? palletSubtitle; - - const DecodedCallHeadline({required this.primary, this.recipient, this.palletSubtitle}); - - /// Either the recipient or the pallet, whichever this call has. - String? get secondary => recipient ?? palletSubtitle; - - /// [amountText] formats a native token amount for the current locale and - /// currency display; injected so this stays independent of Riverpod. - static DecodedCallHeadline of(DecodedCall call, {required String Function(BigInt token) amountText}) { - final summary = call.summary; - if (summary == null) { - return DecodedCallHeadline( - primary: call.call.isEmpty ? call.pallet : call.humanCall, - palletSubtitle: call.call.isEmpty ? null : call.pallet, - ); + final formatAmount = ref.watch(txAmountDisplayProvider); + final checkphrases = {}; + for (final address in call.addresses) { + ref + .watch(checksumNameProvider(address)) + .maybeWhen( + data: (phrase) { + if (phrase.isNotEmpty) checkphrases[address] = phrase; + }, + orElse: () {}, + ); } - return DecodedCallHeadline( - primary: summary.assetId == null ? amountText(summary.amount) : '${summary.amount} (asset #${summary.assetId})', - recipient: summary.recipient == null ? null : AddressFormattingService.formatAddress(summary.recipient!), + return sdk.DecodedCallView( + call: call, + depth: depth, + layout: sdk.DecodedCallLayout.compact, + amountText: (token) => formatAmount(token, isSend: true).primaryAmount, + checkphraseOf: (address) => checkphrases[address], ); } } diff --git a/mobile-app/lib/v2/components/detail_summary_row.dart b/mobile-app/lib/v2/components/detail_summary_row.dart deleted file mode 100644 index a7a7cac60..000000000 --- a/mobile-app/lib/v2/components/detail_summary_row.dart +++ /dev/null @@ -1,77 +0,0 @@ -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'; - -/// Label/value row used in review screens and detail sheets. -class DetailSummaryRow extends StatelessWidget { - final String label; - final String? value; - final Widget? valueWidget; - final int labelFlex; - final int valueFlex; - final EdgeInsetsGeometry padding; - final TextStyle? labelStyle; - final TextStyle? valueStyle; - - const DetailSummaryRow({ - super.key, - required this.label, - this.value, - this.valueWidget, - this.labelFlex = 2, - this.valueFlex = 3, - this.padding = const EdgeInsets.symmetric(vertical: 8), - this.labelStyle, - this.valueStyle, - }) : assert(value != null || valueWidget != null); - - factory DetailSummaryRow.review({ - Key? key, - required String label, - String? value, - Widget? valueWidget, - int valueFlex = 3, - TextStyle? valueStyle, - }) { - return DetailSummaryRow( - key: key, - label: label, - value: value, - valueWidget: valueWidget, - valueFlex: valueFlex, - valueStyle: valueStyle, - padding: EdgeInsets.zero, - ); - } - - @override - Widget build(BuildContext context) { - final text = context.themeText; - final colors = context.colors; - final effectiveLabelStyle = labelStyle ?? text.transactionDetailRowLabel?.copyWith(color: colors.textTertiary); - final effectiveValueStyle = - valueStyle ?? text.transactionDetailRowValue?.copyWith(color: Colors.white.withValues(alpha: 0.8)); - - return Container( - padding: padding, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: labelFlex, - child: Text(label, style: effectiveLabelStyle), - ), - const SizedBox(width: 8), - Expanded( - flex: valueFlex, - child: Align( - alignment: Alignment.centerRight, - child: - valueWidget ?? Text(value!, style: effectiveValueStyle, textAlign: TextAlign.right, softWrap: true), - ), - ), - ], - ), - ); - } -} diff --git a/mobile-app/lib/features/components/dotted_border.dart b/mobile-app/lib/v2/components/dotted_border.dart similarity index 100% rename from mobile-app/lib/features/components/dotted_border.dart rename to mobile-app/lib/v2/components/dotted_border.dart diff --git a/mobile-app/lib/v2/components/explorer_link.dart b/mobile-app/lib/v2/components/explorer_link.dart index dee140aa5..dbcaa438b 100644 --- a/mobile-app/lib/v2/components/explorer_link.dart +++ b/mobile-app/lib/v2/components/explorer_link.dart @@ -1,9 +1,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/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Underlined "View in Explorer ↗" link that opens [url] in an external /// browser. Shared across the send terminal, POS receipt and the diff --git a/mobile-app/lib/v2/components/glass_button_base.dart b/mobile-app/lib/v2/components/glass_button_base.dart deleted file mode 100644 index 6306026a6..000000000 --- a/mobile-app/lib/v2/components/glass_button_base.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'dart:math' as math; -import 'package:flutter/material.dart'; - -class GlassButtonBase extends StatelessWidget { - final Widget child; - final double? buttonHeight; - final double? buttonWidth; - final BorderRadius? borderRadius; - final EdgeInsets? padding; - - const GlassButtonBase({ - super.key, - required this.child, - this.buttonHeight, - this.buttonWidth, - this.borderRadius, - this.padding, - }); - - @override - Widget build(BuildContext context) { - return Container( - height: buttonHeight, - width: buttonWidth, - decoration: BoxDecoration( - borderRadius: borderRadius, - gradient: const LinearGradient( - transform: GradientRotation(30 * math.pi / 180), - colors: [ - Color(0xFF6F6F6F), // 0% - Color(0xFF1F1F1F), // 25% - Color(0xFF0E0E0E), // 50% - Color(0xFF1F1F1F), // 75% - Color(0xFF6F6F6F), // 100% - ], - stops: [0.0, 0.25, 0.50, 0.75, 1.0], - ), - ), - // The padding acts as the border thickness! - padding: const EdgeInsets.all(0.5), - child: Container( - decoration: BoxDecoration( - borderRadius: borderRadius, - gradient: const LinearGradient( - transform: GradientRotation(45 * math.pi / 180), - colors: [Color(0xFF050505), Color(0xFF171717)], - stops: [0, 1], - ), - ), - child: Container( - padding: padding, - decoration: BoxDecoration(borderRadius: borderRadius, color: const Color.fromRGBO(255, 255, 255, 0.02)), - child: child, - ), - ), - ); - } -} diff --git a/mobile-app/lib/v2/components/global_toast_listener.dart b/mobile-app/lib/v2/components/global_toast_listener.dart index b54080db1..acd553f8e 100644 --- a/mobile-app/lib/v2/components/global_toast_listener.dart +++ b/mobile-app/lib/v2/components/global_toast_listener.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:resonance_network_wallet/providers/global_toast_provider.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; /// Renders app-wide toasts requested through [globalToastProvider]. /// diff --git a/mobile-app/lib/v2/components/loader.dart b/mobile-app/lib/v2/components/loader.dart deleted file mode 100644 index 21880d126..000000000 --- a/mobile-app/lib/v2/components/loader.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; - -class Loader extends StatelessWidget { - final Color? color; - final double? size; - - const Loader({super.key, this.color, this.size}); - - @override - Widget build(BuildContext context) { - final effectiveSize = size ?? (context.isTablet ? 24 : 16); - - return SizedBox( - height: effectiveSize, - width: effectiveSize, - child: CircularProgressIndicator(strokeWidth: 2, color: color ?? context.colors.textSecondary), - ); - } -} diff --git a/mobile-app/lib/v2/components/multisig_expiry_value.dart b/mobile-app/lib/v2/components/multisig_expiry_value.dart index 7e675ed2a..de3c43f37 100644 --- a/mobile-app/lib/v2/components/multisig_expiry_value.dart +++ b/mobile-app/lib/v2/components/multisig_expiry_value.dart @@ -31,21 +31,22 @@ MultisigExpiryParts resolveMultisigExpiryParts({ /// Two-line expiry: block height, then estimated datetime on its own line. class MultisigExpiryValue extends StatelessWidget { - const MultisigExpiryValue({super.key, required this.parts, required this.style}); + const MultisigExpiryValue({super.key, required this.parts, this.style}); final MultisigExpiryParts parts; final TextStyle? style; @override Widget build(BuildContext context) { + final resolved = style ?? context.themeTextV3.body.copyWith(color: context.colorsV3.textContent); return Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ - Text(parts.blockLabel, style: style, textAlign: TextAlign.right), + Text(parts.blockLabel, style: resolved, textAlign: TextAlign.right), if (parts.estimatedDateTime != null) ...[ const SizedBox(height: 2), - Text(parts.estimatedDateTime!, style: style, textAlign: TextAlign.right, softWrap: true), + Text(parts.estimatedDateTime!, style: resolved, textAlign: TextAlign.right, softWrap: true), ], ], ); diff --git a/mobile-app/lib/v2/components/multisig_signer_list_tile.dart b/mobile-app/lib/v2/components/multisig_signer_list_tile.dart index 578cd7b00..af7d476ca 100644 --- a/mobile-app/lib/v2/components/multisig_signer_list_tile.dart +++ b/mobile-app/lib/v2/components/multisig_signer_list_tile.dart @@ -2,8 +2,6 @@ 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/wallet_providers.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Displays a multisig signer with checksum, address, and optional badges. class MultisigSignerListTile extends ConsumerStatefulWidget { diff --git a/mobile-app/lib/v2/components/multisig_tag.dart b/mobile-app/lib/v2/components/multisig_tag.dart index dbe80439b..4cdc35dd3 100644 --- a/mobile-app/lib/v2/components/multisig_tag.dart +++ b/mobile-app/lib/v2/components/multisig_tag.dart @@ -1,7 +1,5 @@ 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'; class MultisigTag extends StatelessWidget { final String label; diff --git a/mobile-app/lib/v2/components/multisig_threshold_slider.dart b/mobile-app/lib/v2/components/multisig_threshold_slider.dart index 6eea0e5b2..057e5b839 100644 --- a/mobile-app/lib/v2/components/multisig_threshold_slider.dart +++ b/mobile-app/lib/v2/components/multisig_threshold_slider.dart @@ -1,7 +1,5 @@ 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'; /// Slider for choosing how many signer approvals a multisig proposal needs. class MultisigThresholdSlider extends StatelessWidget { diff --git a/mobile-app/lib/v2/components/name_field.dart b/mobile-app/lib/v2/components/name_field.dart index 68878f5a4..b789a9a2e 100644 --- a/mobile-app/lib/v2/components/name_field.dart +++ b/mobile-app/lib/v2/components/name_field.dart @@ -1,8 +1,7 @@ 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/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class NameField extends ConsumerWidget { final TextEditingController controller; @@ -15,76 +14,21 @@ class NameField extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final l10n = ref.watch(l10nProvider); - final textStyle = context.themeText.smallTitle!.copyWith(fontWeight: FontWeight.w400); return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration(color: context.colors.surfaceDeep, borderRadius: BorderRadius.circular(14)), - child: Row( - children: [ - Expanded( - child: TextField( - controller: controller, - style: textStyle, - decoration: InputDecoration.collapsed( - hintText: hint ?? l10n.componentNameFieldHint, - hintStyle: textStyle.copyWith(color: context.colors.textSecondary), - ), - ), - ), - if (controller.text.isNotEmpty) ...[ - const SizedBox(width: 8), - _EditAccountClearButton(onTap: () => controller.clear()), - ], - ], - ), + QuantusTextField( + controller: controller, + hint: hint ?? l10n.componentNameFieldHint, + error: error, + showClearButton: true, ), if (subtitle != null) ...[ - const SizedBox(height: 16), - Text(subtitle!, style: context.themeText.smallParagraph?.copyWith(color: context.colors.textSecondary)), - ], - if (error != null) ...[ - const SizedBox(height: 16), - Text( - error!, - style: context.themeText.detail?.copyWith(color: context.colors.textError), - textAlign: TextAlign.center, - ), + const SizedBox(height: 8), + Text(subtitle!, style: context.themeTextV3.caption.copyWith(color: context.colorsV3.textMuted)), ], ], ); } } - -class _EditAccountClearButton extends StatelessWidget { - final VoidCallback onTap; - - const _EditAccountClearButton({required this.onTap}); - - @override - Widget build(BuildContext context) { - final size = 20.0; - final borderRadius = 16.0; - - return Material( - color: Colors.transparent, - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(borderRadius), - child: Container( - width: size, - height: size, - alignment: Alignment.center, - decoration: BoxDecoration( - color: context.colors.textMuted, - border: Border.all(color: context.colors.borderButton, width: 0.5), - borderRadius: BorderRadius.circular(borderRadius), - ), - child: Icon(Icons.close, size: 12, color: context.colors.background), - ), - ), - ); - } -} diff --git a/mobile-app/lib/features/components/network_status_banner.dart b/mobile-app/lib/v2/components/network_status_banner.dart similarity index 88% rename from mobile-app/lib/features/components/network_status_banner.dart rename to mobile-app/lib/v2/components/network_status_banner.dart index 37485f481..42e562341 100644 --- a/mobile-app/lib/features/components/network_status_banner.dart +++ b/mobile-app/lib/v2/components/network_status_banner.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:resonance_network_wallet/features/styles/app_colors_theme.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/connectivity_provider.dart'; class NetworkStatusBanner extends ConsumerWidget { @@ -26,7 +26,7 @@ class NetworkStatusBanner extends ConsumerWidget { Text( 'Not Connected', style: TextStyle( - color: context.themeColors.textPrimary, + color: context.colors.textPrimary, fontSize: 16, fontFamily: 'Fira Code', fontWeight: FontWeight.w400, diff --git a/mobile-app/lib/v2/components/private_activity_notice.dart b/mobile-app/lib/v2/components/private_activity_notice.dart index a8d9ddbb3..da57bc4c0 100644 --- a/mobile-app/lib/v2/components/private_activity_notice.dart +++ b/mobile-app/lib/v2/components/private_activity_notice.dart @@ -1,6 +1,5 @@ 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'; +import 'package:quantus_sdk/quantus_sdk.dart'; /// Circular lock badge used for encrypted / private UI (always Quantus orange). class EncryptedLockBadge extends StatelessWidget { diff --git a/mobile-app/lib/v2/components/proposal_list_tile.dart b/mobile-app/lib/v2/components/proposal_list_tile.dart index 07e0385ae..079cbec9d 100644 --- a/mobile-app/lib/v2/components/proposal_list_tile.dart +++ b/mobile-app/lib/v2/components/proposal_list_tile.dart @@ -3,9 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/decoded_call_view.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Shared card layout for indexed and pending multisig proposal rows. class ProposalListTile extends ConsumerWidget { diff --git a/mobile-app/lib/v2/components/qr_scanner_page.dart b/mobile-app/lib/v2/components/qr_scanner_page.dart index b6af3996b..ffb6b7d61 100644 --- a/mobile-app/lib/v2/components/qr_scanner_page.dart +++ b/mobile-app/lib/v2/components/qr_scanner_page.dart @@ -5,9 +5,6 @@ import 'package:image_picker/image_picker.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; class QrScannerPage extends ConsumerStatefulWidget { final bool Function(String)? validator; diff --git a/mobile-app/lib/v2/components/recovery_phrase_body.dart b/mobile-app/lib/v2/components/recovery_phrase_body.dart index 615e3f69c..19798dba5 100644 --- a/mobile-app/lib/v2/components/recovery_phrase_body.dart +++ b/mobile-app/lib/v2/components/recovery_phrase_body.dart @@ -1,17 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:resonance_network_wallet/features/components/mnemonic_grid.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class RecoveryPhraseBody extends ConsumerStatefulWidget { final String appBarTitle; diff --git a/mobile-app/lib/v2/components/scaffold_base.dart b/mobile-app/lib/v2/components/scaffold_base.dart index a982ede31..7fefe7c67 100644 --- a/mobile-app/lib/v2/components/scaffold_base.dart +++ b/mobile-app/lib/v2/components/scaffold_base.dart @@ -1,26 +1,24 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/features/components/network_status_banner.dart'; -import 'package:resonance_network_wallet/v2/components/base_background.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' as sdk; +import 'package:resonance_network_wallet/v2/components/network_status_banner.dart'; class ScaffoldBase extends StatelessWidget { final Widget? mainContent; final Widget? bottomContent; final List? slivers; final Widget? appBar; + final Widget? networkBanner; final ScrollController? scrollController; final ScrollPhysics? scrollPhysics; final RefreshCallback? onRefresh; final EdgeInsetsGeometry padding; final Widget? backgroundWidget; - static const defaultPadding = EdgeInsets.symmetric(horizontal: 24.0); - - // Default constructor - static content const ScaffoldBase({ super.key, this.appBar, - this.padding = defaultPadding, + this.networkBanner, + this.padding = sdk.ScaffoldBase.defaultPadding, this.backgroundWidget, this.bottomContent, required Widget this.mainContent, @@ -29,11 +27,11 @@ class ScaffoldBase extends StatelessWidget { scrollPhysics = null, onRefresh = null; - // Refreshable constructor - CustomScrollView with pull-to-refresh const ScaffoldBase.refreshable({ super.key, this.appBar, - this.padding = defaultPadding, + this.networkBanner, + this.padding = sdk.ScaffoldBase.defaultPadding, this.backgroundWidget, this.scrollController, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), @@ -44,66 +42,29 @@ class ScaffoldBase extends StatelessWidget { @override Widget build(BuildContext context) { - final colors = context.colors; - - Widget bodyContent = Column( - children: [ - const NetworkStatusBanner(), - if (appBar != null) Padding(padding: padding, child: appBar!), - Expanded(child: _buildChild(colors)), - ], - ); - - Widget scaffoldBody = SafeArea(child: bodyContent); - - if (backgroundWidget != null) { - scaffoldBody = Stack(fit: StackFit.expand, children: [backgroundWidget!, scaffoldBody]); - } else { - scaffoldBody = BaseBackground(child: scaffoldBody); - } + final banner = networkBanner ?? const NetworkStatusBanner(); - return Scaffold(body: scaffoldBody); - } - - Widget _buildChild(AppColorsV2 colors) { - // Scrollable with refresh (CustomScrollView with slivers) if (onRefresh != null && slivers != null) { - return RefreshIndicator( + return sdk.ScaffoldBase.refreshable( + appBar: appBar, + networkBanner: banner, + padding: padding, + backgroundWidget: backgroundWidget, + scrollController: scrollController, + scrollPhysics: scrollPhysics, + bottomContent: bottomContent, onRefresh: onRefresh!, - color: colors.textPrimary, - backgroundColor: colors.surface, - child: Column( - children: [ - Expanded( - child: CustomScrollView( - controller: scrollController, - physics: scrollPhysics ?? const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverPadding( - padding: padding, - sliver: SliverList(delegate: SliverChildListDelegate(slivers!)), - ), - ], - ), - ), - ?bottomContent, - ], - ), - ); - } - - // Static content - if (mainContent != null) { - return Column( - children: [ - Expanded( - child: Padding(padding: padding, child: mainContent!), - ), - ?bottomContent, - ], + slivers: slivers!, ); } - return const SizedBox.shrink(); + return sdk.ScaffoldBase( + appBar: appBar, + networkBanner: banner, + padding: padding, + backgroundWidget: backgroundWidget, + bottomContent: bottomContent, + mainContent: mainContent!, + ); } } diff --git a/mobile-app/lib/v2/components/scaffold_base_bottom_content.dart b/mobile-app/lib/v2/components/scaffold_base_bottom_content.dart deleted file mode 100644 index 1d2ef42d3..000000000 --- a/mobile-app/lib/v2/components/scaffold_base_bottom_content.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; - -class ScaffoldBaseBottomContent extends StatelessWidget { - final Widget child; - - const ScaffoldBaseBottomContent({super.key, required this.child}); - - @override - Widget build(BuildContext context) { - final padding = const EdgeInsets.all(24); - - return Container( - padding: padding, - decoration: BoxDecoration( - border: Border(top: BorderSide(color: context.colors.surfaceDeep, width: 1)), - ), - child: child, - ); - } -} diff --git a/mobile-app/lib/v2/components/share_account_button.dart b/mobile-app/lib/v2/components/share_account_button.dart index 1eeb17e9b..3266a1b2a 100644 --- a/mobile-app/lib/v2/components/share_account_button.dart +++ b/mobile-app/lib/v2/components/share_account_button.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class ShareAccountButton extends ConsumerWidget { final VoidCallback onTap; diff --git a/mobile-app/lib/features/components/shared_address_action_sheet.dart b/mobile-app/lib/v2/components/shared_address_action_sheet.dart similarity index 95% rename from mobile-app/lib/features/components/shared_address_action_sheet.dart rename to mobile-app/lib/v2/components/shared_address_action_sheet.dart index 381f9f08d..5dc1c8990 100644 --- a/mobile-app/lib/features/components/shared_address_action_sheet.dart +++ b/mobile-app/lib/v2/components/shared_address_action_sheet.dart @@ -3,18 +3,13 @@ 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/styles/app_colors_theme.dart'; -import 'package:resonance_network_wallet/features/styles/app_text_theme.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/routes.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/shared/extensions/current_route_extensions.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/screens/send/input_amount_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/regular_send_strategy.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_providers.dart'; @@ -103,7 +98,7 @@ class _SharedAddressActionSheetState extends State { child: Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), decoration: ShapeDecoration( - color: context.themeColors.background, + color: context.colors.background, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), ), child: Stack( diff --git a/mobile-app/lib/v2/components/skeleton.dart b/mobile-app/lib/v2/components/skeleton.dart new file mode 100644 index 000000000..e451d1dca --- /dev/null +++ b/mobile-app/lib/v2/components/skeleton.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +class TxItemSkeleton extends StatelessWidget { + const TxItemSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + const double txItemHeight = 32.0; + const double txItemDetailHeight = 12.0; + + return const Row( + children: [ + Skeleton(width: txItemHeight, height: txItemHeight), + SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Skeleton(width: 64, height: txItemDetailHeight), + SizedBox(height: 6), + Skeleton(width: 52, height: txItemDetailHeight), + ], + ), + Spacer(), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Skeleton(width: 100, height: txItemDetailHeight), + SizedBox(height: 6), + Skeleton(width: 88, height: txItemDetailHeight), + ], + ), + ], + ); + } +} diff --git a/mobile-app/lib/v2/components/split_card.dart b/mobile-app/lib/v2/components/split_card.dart index fa4bb061b..203193f1e 100644 --- a/mobile-app/lib/v2/components/split_card.dart +++ b/mobile-app/lib/v2/components/split_card.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class SplitCard extends StatelessWidget { final Widget topChild; diff --git a/mobile-app/lib/v2/components/token_icon.dart b/mobile-app/lib/v2/components/token_icon.dart index 885ccdecc..5ceff5edf 100644 --- a/mobile-app/lib/v2/components/token_icon.dart +++ b/mobile-app/lib/v2/components/token_icon.dart @@ -1,7 +1,5 @@ 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'; class TokenIcon extends StatelessWidget { final SwapToken token; diff --git a/mobile-app/lib/v2/components/v2_app_bar.dart b/mobile-app/lib/v2/components/v2_app_bar.dart deleted file mode 100644 index ec50d2042..000000000 --- a/mobile-app/lib/v2/components/v2_app_bar.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.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'; - -class V2AppBar extends StatelessWidget { - final String title; - final Widget? leading; - final Widget? trailing; - final bool showBackButton; - final EdgeInsetsGeometry padding; - - const V2AppBar({ - super.key, - required this.title, - this.leading, - this.trailing, - this.showBackButton = true, - this.padding = const EdgeInsets.only(top: 16.0, bottom: 32.0), - }); - - @override - Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; - - Widget leftWidget = leading ?? (showBackButton ? const AppBackButton() : const SizedBox(width: 24)); - Widget rightWidget = trailing ?? const SizedBox(width: 24); - - return Padding( - padding: padding, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - leftWidget, - Text(title, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), - rightWidget, - ], - ), - ); - } -} diff --git a/mobile-app/lib/v2/components/wormhole_progress_steps.dart b/mobile-app/lib/v2/components/wormhole_progress_steps.dart index 26fdec48d..ce0c8c313 100644 --- a/mobile-app/lib/v2/components/wormhole_progress_steps.dart +++ b/mobile-app/lib/v2/components/wormhole_progress_steps.dart @@ -1,7 +1,5 @@ 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'; class WormholeProgressSteps extends StatelessWidget { final List<(int, String)> steps; diff --git a/mobile-app/lib/v2/screens/accounts/account_details_screen.dart b/mobile-app/lib/v2/screens/accounts/account_details_screen.dart index b40590b5d..23d69c827 100644 --- a/mobile-app/lib/v2/screens/accounts/account_details_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/account_details_screen.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/utils/share_utils.dart'; import 'package:resonance_network_wallet/v2/components/address_details_card.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/components/share_account_button.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; class AccountDetailsScreen extends ConsumerStatefulWidget { const AccountDetailsScreen({super.key, required this.accountId}); diff --git a/mobile-app/lib/v2/screens/accounts/account_menu_screen.dart b/mobile-app/lib/v2/screens/accounts/account_menu_screen.dart index 3e04446ec..9093af928 100644 --- a/mobile-app/lib/v2/screens/accounts/account_menu_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/account_menu_screen.dart @@ -1,26 +1,18 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/accounts_grouping.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/components/account_badge.dart'; -import 'package:resonance_network_wallet/v2/components/confirm_action_sheet.dart'; -import 'package:resonance_network_wallet/v2/components/menu_divider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/account_details_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/edit_account_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_confirmation_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AccountMenuScreen extends ConsumerWidget { final Account initialAccount; diff --git a/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart b/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart index a71cf5fd5..f8645feee 100644 --- a/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/account_ready_screen.dart @@ -1,20 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/v2/components/account_badge.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.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'; enum AccountReadyOverviewOrigin { accountCreated, walletCreated, walletImported } diff --git a/mobile-app/lib/v2/screens/accounts/accounts_screen.dart b/mobile-app/lib/v2/screens/accounts/accounts_screen.dart index e24b2f473..5b9d7775b 100644 --- a/mobile-app/lib/v2/screens/accounts/accounts_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/accounts_screen.dart @@ -1,31 +1,24 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/shared/utils/accounts_grouping.dart'; -import 'package:resonance_network_wallet/v2/components/account_badge.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; -import 'package:resonance_network_wallet/v2/components/multisig_tag.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_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/shared/utils/print.dart'; +import 'package:resonance_network_wallet/v2/components/account_badge.dart'; +import 'package:resonance_network_wallet/v2/components/multisig_tag.dart'; +import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/account_menu_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/multisig_account_menu_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/wallet_name_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/add_account_menu_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; Future openAccountsScreen(BuildContext context) { return Navigator.of(context).push( diff --git a/mobile-app/lib/v2/screens/accounts/add_hardware_account_screen.dart b/mobile-app/lib/v2/screens/accounts/add_hardware_account_screen.dart index 0105a4e42..45567fae0 100644 --- a/mobile-app/lib/v2/screens/accounts/add_hardware_account_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/add_hardware_account_screen.dart @@ -1,14 +1,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/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/connect_keystone_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'; /// Intro screen for adding a Keystone hardware wallet/account. diff --git a/mobile-app/lib/v2/screens/accounts/connect_keystone_screen.dart b/mobile-app/lib/v2/screens/accounts/connect_keystone_screen.dart index f9e19e3a1..e5c1e36ed 100644 --- a/mobile-app/lib/v2/screens/accounts/connect_keystone_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/connect_keystone_screen.dart @@ -2,17 +2,12 @@ import 'package:flutter/foundation.dart'; 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:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/components/qr_scanner_page.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/account_menu_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Second add-Keystone screen: on-device instructions, then scans the device's /// QR code to read the account address and saves the Keystone account. diff --git a/mobile-app/lib/v2/screens/accounts/create_account_screen.dart b/mobile-app/lib/v2/screens/accounts/create_account_screen.dart index 6bf2ff7df..589fa711b 100644 --- a/mobile-app/lib/v2/screens/accounts/create_account_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/create_account_screen.dart @@ -1,18 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/components/name_field.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/add_hardware_account_screen.dart'; diff --git a/mobile-app/lib/v2/screens/accounts/edit_account_screen.dart b/mobile-app/lib/v2/screens/accounts/edit_account_screen.dart index 6dbccc731..628d25da3 100644 --- a/mobile-app/lib/v2/screens/accounts/edit_account_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/edit_account_screen.dart @@ -1,15 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/v2/components/name_field.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; class EditAccountScreen extends ConsumerStatefulWidget { const EditAccountScreen({super.key, required Account initialAccount}) diff --git a/mobile-app/lib/v2/screens/accounts/multisig_account_menu_screen.dart b/mobile-app/lib/v2/screens/accounts/multisig_account_menu_screen.dart index b76bda3c5..3c3884b59 100644 --- a/mobile-app/lib/v2/screens/accounts/multisig_account_menu_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/multisig_account_menu_screen.dart @@ -1,25 +1,17 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/components/account_badge.dart'; -import 'package:resonance_network_wallet/v2/components/confirm_action_sheet.dart'; -import 'package:resonance_network_wallet/v2/components/menu_divider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/account_details_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/edit_account_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/multisig_details_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class MultisigAccountMenuScreen extends ConsumerWidget { final MultisigAccount initialAccount; diff --git a/mobile-app/lib/v2/screens/accounts/multisig_details_screen.dart b/mobile-app/lib/v2/screens/accounts/multisig_details_screen.dart index cad4f850e..b9a82dfec 100644 --- a/mobile-app/lib/v2/screens/accounts/multisig_details_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/multisig_details_screen.dart @@ -1,14 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/v2/components/multisig_signer_list_tile.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class MultisigDetailsScreen extends ConsumerWidget { const MultisigDetailsScreen({super.key, required this.account}); diff --git a/mobile-app/lib/v2/screens/accounts/open_accounts_management_button.dart b/mobile-app/lib/v2/screens/accounts/open_accounts_management_button.dart index 00cf58207..b813134ed 100644 --- a/mobile-app/lib/v2/screens/accounts/open_accounts_management_button.dart +++ b/mobile-app/lib/v2/screens/accounts/open_accounts_management_button.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button_base.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; class OpenAccountsManagementButton extends StatelessWidget { const OpenAccountsManagementButton({super.key}); diff --git a/mobile-app/lib/v2/screens/accounts/wallet_name_screen.dart b/mobile-app/lib/v2/screens/accounts/wallet_name_screen.dart index f58b10e5e..0275d329e 100644 --- a/mobile-app/lib/v2/screens/accounts/wallet_name_screen.dart +++ b/mobile-app/lib/v2/screens/accounts/wallet_name_screen.dart @@ -1,16 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/v2/components/name_field.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Names (or renames) a wallet. The name is optional: saving an empty field /// clears it and the wallet falls back to its "Wallet {n}" display name. diff --git a/mobile-app/lib/v2/screens/activity/activity_screen.dart b/mobile-app/lib/v2/screens/activity/activity_screen.dart index 18d2782d8..a7fa66a31 100644 --- a/mobile-app/lib/v2/screens/activity/activity_screen.dart +++ b/mobile-app/lib/v2/screens/activity/activity_screen.dart @@ -1,8 +1,9 @@ import 'package:collection/collection.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/skeleton.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/skeleton.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; @@ -10,11 +11,6 @@ import 'package:resonance_network_wallet/providers/currency_display_provider.dar import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/transaction_service.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.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'; 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 393ce6541..6543d8d33 100644 --- a/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart +++ b/mobile-app/lib/v2/screens/activity/transaction_detail_sheet.dart @@ -1,7 +1,7 @@ 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/dotted_border.dart'; +import 'package:resonance_network_wallet/v2/components/dotted_border.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; @@ -11,10 +11,7 @@ import 'package:resonance_network_wallet/routes.dart'; import 'package:resonance_network_wallet/shared/extensions/current_route_extensions.dart'; import 'package:resonance_network_wallet/shared/extensions/transaction_event_extension.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; import 'package:resonance_network_wallet/v2/components/explorer_link.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; void showTransactionDetailSheet(BuildContext context, TransactionEvent tx, String activeAccountId) { if (context.peekTopRouteName == transactionDetailSheetRouteSettings.name) Navigator.pop(context); @@ -114,7 +111,6 @@ class _TransactionDetailSheet extends ConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox(height: 16), _AmountSection(tx: tx, isSend: _isSend, activeAccountId: activeAccountId, colors: colors), const SizedBox(height: 20), _DetailRow( diff --git a/mobile-app/lib/v2/screens/activity/tx_item.dart b/mobile-app/lib/v2/screens/activity/tx_item.dart index 8cc58666a..d9a99a9bf 100644 --- a/mobile-app/lib/v2/screens/activity/tx_item.dart +++ b/mobile-app/lib/v2/screens/activity/tx_item.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.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; diff --git a/mobile-app/lib/v2/screens/auth/auth_wrapper.dart b/mobile-app/lib/v2/screens/auth/auth_wrapper.dart index c9ca2f3c0..a1db2c70a 100644 --- a/mobile-app/lib/v2/screens/auth/auth_wrapper.dart +++ b/mobile-app/lib/v2/screens/auth/auth_wrapper.dart @@ -1,13 +1,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/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/local_auth_provider.dart'; -import 'package:resonance_network_wallet/v2/components/base_background.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -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 AuthWrapper extends ConsumerWidget { const AuthWrapper({super.key}); @@ -56,7 +51,7 @@ class AuthWrapper extends ConsumerWidget { const Loader() else Padding( - padding: EdgeInsets.symmetric(horizontal: context.themeSize.screenPadding), + padding: const EdgeInsets.symmetric(horizontal: 24), child: QuantusButton.simple( label: l10n.authUnlockWallet, onTap: () { diff --git a/mobile-app/lib/v2/screens/home/activity_section.dart b/mobile-app/lib/v2/screens/home/activity_section.dart index 8ddb2d0c6..2b2386968 100644 --- a/mobile-app/lib/v2/screens/home/activity_section.dart +++ b/mobile-app/lib/v2/screens/home/activity_section.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.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/skeleton.dart'; +import 'package:resonance_network_wallet/v2/components/skeleton.dart'; import 'package:resonance_network_wallet/models/combined_transactions_list.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/active_account_transactions_provider.dart'; @@ -14,8 +14,6 @@ import 'package:resonance_network_wallet/shared/constants/e2e_keys.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'; class ActivitySection extends ConsumerStatefulWidget { final AsyncValue txAsync; diff --git a/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart b/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart index ef187b540..38495d200 100644 --- a/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart +++ b/mobile-app/lib/v2/screens/home/backup_reminder_banner.dart @@ -1,9 +1,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/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_confirmation_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class BackupReminderBanner extends ConsumerWidget { const BackupReminderBanner({super.key, required this.walletIndex}); diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index bd71373f5..1fbb9814e 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -3,22 +3,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/legacy.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/components/dotted_border.dart'; -import 'package:resonance_network_wallet/features/components/skeleton.dart'; -import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/dotted_border.dart'; +import 'package:resonance_network_wallet/v2/components/shared_address_action_sheet.dart'; import 'package:resonance_network_wallet/services/global_history_polling_service.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/shared/utils/url_utils.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/open_accounts_management_button.dart'; import 'package:resonance_network_wallet/v2/screens/activity/transaction_detail_sheet.dart'; import 'package:resonance_network_wallet/v2/screens/receive/receive_screen.dart'; @@ -41,10 +36,7 @@ import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/route_intent_providers.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_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/components/global_toast_listener.dart'; import 'package:resonance_network_wallet/v2/screens/home/activity_section.dart'; import 'package:resonance_network_wallet/v2/screens/home/backup_reminder_banner.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 index 213947e14..c6de62a65 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,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; @@ -11,14 +12,8 @@ import 'package:resonance_network_wallet/services/firebase_messaging_service.dar import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/wallet_name_screen.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, this.openAccountsOnComplete = false}); diff --git a/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart b/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart index 993657a76..21761de0a 100644 --- a/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart +++ b/mobile-app/lib/v2/screens/multisig/add_multisig_screen.dart @@ -1,27 +1,20 @@ 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:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/multisig_signer_list_tile.dart'; +import 'package:resonance_network_wallet/v2/components/multisig_threshold_slider.dart'; +import 'package:resonance_network_wallet/v2/components/name_field.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/pending_multisig_creations_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/multisig_signer_list_tile.dart'; -import 'package:resonance_network_wallet/v2/components/multisig_threshold_slider.dart'; -import 'package:resonance_network_wallet/v2/components/name_field.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/services/multisig_submission_service.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_navigation.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AddMultisigScreen extends ConsumerStatefulWidget { const AddMultisigScreen({super.key}); diff --git a/mobile-app/lib/v2/screens/multisig/discover_multisig_screen.dart b/mobile-app/lib/v2/screens/multisig/discover_multisig_screen.dart index 3154b044f..95c86829d 100644 --- a/mobile-app/lib/v2/screens/multisig/discover_multisig_screen.dart +++ b/mobile-app/lib/v2/screens/multisig/discover_multisig_screen.dart @@ -1,17 +1,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:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class DiscoverMultisigScreen extends ConsumerStatefulWidget { const DiscoverMultisigScreen({super.key}); diff --git a/mobile-app/lib/v2/screens/multisig/multisig_action_confirm_sheet.dart b/mobile-app/lib/v2/screens/multisig/multisig_action_confirm_sheet.dart index 18b6ca009..8c1243ff1 100644 --- a/mobile-app/lib/v2/screens/multisig/multisig_action_confirm_sheet.dart +++ b/mobile-app/lib/v2/screens/multisig/multisig_action_confirm_sheet.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide DecodedCallView; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; @@ -11,15 +11,10 @@ import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; import 'package:resonance_network_wallet/v2/components/decoded_call_view.dart'; -import 'package:resonance_network_wallet/v2/components/detail_summary_row.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_sign_cache.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_sign_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_session.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Estimates the network fee for the action being confirmed. /// @@ -348,7 +343,6 @@ class _MultisigActionConfirmSheetState extends ConsumerState l10n.commonAmountBalance(fmt.formatBalance(v, smartDecimals: AppConstants.decimals), AppConstants.tokenSymbol); return [ const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewTo, value: recipientAddress.trim(), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewTo, value: recipientAddress.trim()), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount)), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.encryptedSendFeeLabel, value: amt(feeAmount), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.encryptedSendFeeLabel, value: amt(feeAmount)), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewYouPay, value: amt(amount + feeAmount), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewYouPay, value: amt(amount + feeAmount)), const SizedBox(height: 7), ]; } diff --git a/mobile-app/lib/v2/screens/send/input_amount_screen.dart b/mobile-app/lib/v2/screens/send/input_amount_screen.dart index 373c617aa..26e513358 100644 --- a/mobile-app/lib/v2/screens/send/input_amount_screen.dart +++ b/mobile-app/lib/v2/screens/send/input_amount_screen.dart @@ -2,28 +2,20 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/misc.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/models/fiat_currency.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/send/review_send_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_screen_logic.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.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/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/amount_input_logic.dart'; import 'package:resonance_network_wallet/shared/utils/debouncer.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_icon_button.dart'; class InputAmountScreen extends ConsumerStatefulWidget { final SendStrategy strategy; diff --git a/mobile-app/lib/v2/screens/send/keystone_reject_screen.dart b/mobile-app/lib/v2/screens/send/keystone_reject_screen.dart index 9f0a5d413..dc0c46780 100644 --- a/mobile-app/lib/v2/screens/send/keystone_reject_screen.dart +++ b/mobile-app/lib/v2/screens/send/keystone_reject_screen.dart @@ -1,14 +1,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/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_widgets.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'; /// Shown when the Keystone screen does not match what the app asked it to diff --git a/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart b/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart index 160073836..167470fe2 100644 --- a/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart +++ b/mobile-app/lib/v2/screens/send/keystone_sign_screen.dart @@ -2,23 +2,16 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/animated_ur_qr.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_sign_cache.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_session.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_widgets.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_verify_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Step 1/3 of Keystone signing: shows the unsigned transaction as an /// animated UR QR for the device to scan. diff --git a/mobile-app/lib/v2/screens/send/keystone_signing_widgets.dart b/mobile-app/lib/v2/screens/send/keystone_signing_widgets.dart index 84ef7cd36..b671be17d 100644 --- a/mobile-app/lib/v2/screens/send/keystone_signing_widgets.dart +++ b/mobile-app/lib/v2/screens/send/keystone_signing_widgets.dart @@ -3,8 +3,6 @@ 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/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Orange mono "STEP n/3" label shared by the Keystone signing screens. class KeystoneStepLabel extends ConsumerWidget { diff --git a/mobile-app/lib/v2/screens/send/keystone_verify_screen.dart b/mobile-app/lib/v2/screens/send/keystone_verify_screen.dart index ce020a5d9..0631fb4e1 100644 --- a/mobile-app/lib/v2/screens/send/keystone_verify_screen.dart +++ b/mobile-app/lib/v2/screens/send/keystone_verify_screen.dart @@ -1,19 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_reject_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signature_scan_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_session.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_widgets.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Step 2/3 of Keystone signing: shows what the device screen should display, /// so the user can compare before approving on the Keystone. diff --git a/mobile-app/lib/v2/screens/send/multisig_propose_strategy.dart b/mobile-app/lib/v2/screens/send/multisig_propose_strategy.dart index b630396e5..997d4bf3e 100644 --- a/mobile-app/lib/v2/screens/send/multisig_propose_strategy.dart +++ b/mobile-app/lib/v2/screens/send/multisig_propose_strategy.dart @@ -12,11 +12,8 @@ import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/detail_summary_row.dart'; import 'package:resonance_network_wallet/v2/components/multisig_expiry_value.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Proposes a transfer from a multisig account. The multisig is a view-only /// account, so funds leave from [msig] while the proposing member pays the fee. @@ -117,7 +114,6 @@ class MultisigProposeStrategy extends SendStrategy { final multisigService = ref.watch(multisigServiceProvider); final currentBlock = ref.watch(multisigCurrentBlockProvider).value; final breakdown = (fee as ProposeFee).breakdown; - final valueStyle = context.themeText.transactionDetailRowLabel; final proposerChecksum = ref.watch(checksumNameProvider(msig.myMemberAccountId)).value ?? ''; String amt(BigInt v) => @@ -127,17 +123,15 @@ class MultisigProposeStrategy extends SendStrategy { const SizedBox(height: 4), DetailSummaryRow.review( label: l10n.multisigProposeProposerLabel, - valueWidget: _ProposerValue(address: msig.myMemberAccountId, checkphrase: proposerChecksum, style: valueStyle), + valueWidget: _ProposerValue(address: msig.myMemberAccountId, checkphrase: proposerChecksum), valueFlex: 4, - valueStyle: valueStyle, ), const SizedBox(height: 4), - DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount)), const SizedBox(height: 4), DetailSummaryRow.review( label: l10n.multisigProposeThresholdLabel, value: '${msig.threshold}/${msig.signers.length}', - valueStyle: valueStyle, ), const SizedBox(height: 4), DetailSummaryRow.review( @@ -149,35 +143,17 @@ class MultisigProposeStrategy extends SendStrategy { multisigService: multisigService, currentBlock: currentBlock, ), - style: valueStyle, ), valueFlex: 4, - valueStyle: valueStyle, ), const SizedBox(height: 4), - DetailSummaryRow.review( - label: l10n.sendReviewNetworkFee, - value: amt(breakdown.networkFee), - valueStyle: valueStyle, - ), + DetailSummaryRow.review(label: l10n.sendReviewNetworkFee, value: amt(breakdown.networkFee)), const SizedBox(height: 4), - DetailSummaryRow.review( - label: l10n.multisigProposalDepositLabel, - value: amt(breakdown.deposit), - valueStyle: valueStyle, - ), + DetailSummaryRow.review(label: l10n.multisigProposalDepositLabel, value: amt(breakdown.deposit)), const SizedBox(height: 4), - DetailSummaryRow.review( - label: l10n.multisigProposeFeeRowLabel, - value: amt(breakdown.creationFee), - valueStyle: valueStyle, - ), + DetailSummaryRow.review(label: l10n.multisigProposeFeeRowLabel, value: amt(breakdown.creationFee)), const SizedBox(height: 4), - DetailSummaryRow.review( - label: l10n.multisigProposeMemberTotalLabel, - value: amt(breakdown.memberCost), - valueStyle: valueStyle, - ), + DetailSummaryRow.review(label: l10n.multisigProposeMemberTotalLabel, value: amt(breakdown.memberCost)), const SizedBox(height: 4), ]; } @@ -259,15 +235,15 @@ class MultisigProposeStrategy extends SendStrategy { } class _ProposerValue extends StatelessWidget { - const _ProposerValue({required this.address, required this.checkphrase, required this.style}); + const _ProposerValue({required this.address, required this.checkphrase}); final String address; final String checkphrase; - final TextStyle? style; @override Widget build(BuildContext context) { - final colors = context.colors; + final colors = context.colorsV3; + final text = context.themeTextV3; return Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, @@ -275,11 +251,16 @@ class _ProposerValue extends StatelessWidget { if (checkphrase.isNotEmpty) Text( checkphrase, - style: style?.copyWith(color: colors.checksum), + style: text.caption.copyWith(color: colors.semanticLilac), textAlign: TextAlign.right, ), const SizedBox(height: 2), - Text(address, style: style, textAlign: TextAlign.right, softWrap: true), + Text( + address, + style: text.body.copyWith(color: colors.textContent), + textAlign: TextAlign.right, + softWrap: true, + ), ], ); } diff --git a/mobile-app/lib/v2/screens/send/regular_send_strategy.dart b/mobile-app/lib/v2/screens/send/regular_send_strategy.dart index 2326f060d..77553be12 100644 --- a/mobile-app/lib/v2/screens/send/regular_send_strategy.dart +++ b/mobile-app/lib/v2/screens/send/regular_send_strategy.dart @@ -11,12 +11,10 @@ import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/services/transaction_submission_service.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/shared/utils/url_utils.dart'; -import 'package:resonance_network_wallet/v2/components/detail_summary_row.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_sign_cache.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_signing_session.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_providers.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Standard single-signer transfer from the active account. Signs locally, or /// hands off to the Keystone QR flow for hardware accounts. @@ -99,7 +97,6 @@ class RegularSendStrategy extends SendStrategy { final l10n = ref.watch(l10nProvider); final fmt = ref.watch(numberFormattingServiceProvider); final networkFee = (fee as RegularFee).networkFee; - final valueStyle = context.themeText.transactionDetailRowLabel; final addr = recipientAddress.trim(); String amt(BigInt v) => @@ -107,13 +104,13 @@ class RegularSendStrategy extends SendStrategy { return [ const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewTo, value: addr, valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewTo, value: addr), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewAmount, value: amt(amount)), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewNetworkFee, value: amt(networkFee), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewNetworkFee, value: amt(networkFee)), const SizedBox(height: 7), - DetailSummaryRow.review(label: l10n.sendReviewYouPay, value: amt(amount + networkFee), valueStyle: valueStyle), + DetailSummaryRow.review(label: l10n.sendReviewYouPay, value: amt(amount + networkFee)), const SizedBox(height: 7), ]; } diff --git a/mobile-app/lib/v2/screens/send/review_send_screen.dart b/mobile-app/lib/v2/screens/send/review_send_screen.dart index b5fc32c30..aa4763972 100644 --- a/mobile-app/lib/v2/screens/send/review_send_screen.dart +++ b/mobile-app/lib/v2/screens/send/review_send_screen.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; @@ -10,17 +12,11 @@ import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/shared/utils/url_utils.dart'; import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart'; import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/components/split_card.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/send/encrypted_send_progress_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/keystone_sign_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_terminal_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class ReviewSendScreen extends ConsumerStatefulWidget { final SendStrategy strategy; diff --git a/mobile-app/lib/v2/screens/send/select_recipient_screen.dart b/mobile-app/lib/v2/screens/send/select_recipient_screen.dart index 8a54e3e77..677d5a272 100644 --- a/mobile-app/lib/v2/screens/send/select_recipient_screen.dart +++ b/mobile-app/lib/v2/screens/send/select_recipient_screen.dart @@ -1,30 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/features/components/dotted_border.dart'; -import 'package:resonance_network_wallet/features/components/skeleton.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/v2/components/dotted_border.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_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/routes.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart'; import 'package:resonance_network_wallet/v2/components/address_input_field.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/components/private_activity_notice.dart'; import 'package:resonance_network_wallet/v2/components/qr_scanner_page.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/send/input_amount_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class SelectRecipientScreen extends ConsumerStatefulWidget { final SendStrategy strategy; diff --git a/mobile-app/lib/v2/screens/send/send_terminal_screen.dart b/mobile-app/lib/v2/screens/send/send_terminal_screen.dart index f067e0eb2..10456f2d2 100644 --- a/mobile-app/lib/v2/screens/send/send_terminal_screen.dart +++ b/mobile-app/lib/v2/screens/send/send_terminal_screen.dart @@ -1,18 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/v2/components/back_button.dart'; import 'package:resonance_network_wallet/v2/components/explorer_link.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/home/home_screen.dart'; import 'package:resonance_network_wallet/v2/screens/send/send_strategy.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Shared success screen for both regular sends and multisig proposals, /// configured entirely by [SendTerminalContent]. diff --git a/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart b/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart index 938f76f87..d4e4fe3bf 100644 --- a/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart +++ b/mobile-app/lib/v2/screens/settings/about_quantus_screen.dart @@ -1,16 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/generated/version.g.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AboutQuantusScreenV2 extends ConsumerWidget { const AboutQuantusScreenV2({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/add_account_menu_screen.dart b/mobile-app/lib/v2/screens/settings/add_account_menu_screen.dart index 09d6a25dc..6af7f0330 100644 --- a/mobile-app/lib/v2/screens/settings/add_account_menu_screen.dart +++ b/mobile-app/lib/v2/screens/settings/add_account_menu_screen.dart @@ -1,22 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/multisig_providers.dart'; import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/add_hardware_account_screen.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/create_account_screen.dart'; import 'package:resonance_network_wallet/v2/screens/import/import_wallet_screen.dart'; import 'package:resonance_network_wallet/v2/screens/multisig/add_multisig_screen.dart'; import 'package:resonance_network_wallet/v2/screens/multisig/discover_multisig_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class AddAccountMenuScreen extends ConsumerStatefulWidget { const AddAccountMenuScreen({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart b/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart index 466d2f17e..d3e355716 100644 --- a/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart +++ b/mobile-app/lib/v2/screens/settings/help_and_support_screen.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; class HelpAndSupportScreenV2 extends ConsumerWidget { const HelpAndSupportScreenV2({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart index 47970de60..8634c3143 100644 --- a/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart +++ b/mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart @@ -1,21 +1,15 @@ 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:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; import 'package:resonance_network_wallet/shared/utils/open_external_url.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; import 'package:resonance_network_wallet/v2/components/split_card.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class MiningRewardsScreen extends ConsumerWidget { const MiningRewardsScreen({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/preferences_settings_screen.dart b/mobile-app/lib/v2/screens/settings/preferences_settings_screen.dart index 24b3fc215..9ec9167b3 100644 --- a/mobile-app/lib/v2/screens/settings/preferences_settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/preferences_settings_screen.dart @@ -1,17 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/currency_display_provider.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/notification_config_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/currency_picker_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/language_picker_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class PreferencesSettingsScreenV2 extends ConsumerStatefulWidget { const PreferencesSettingsScreenV2({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart b/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart index c88d18408..46d8205bd 100644 --- a/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart +++ b/mobile-app/lib/v2/screens/settings/recovery_phrase_confirmation_screen.dart @@ -1,10 +1,10 @@ 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/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_caution_scaffold.dart'; diff --git a/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart index 2e0839634..863a6fc05 100644 --- a/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart +++ b/mobile-app/lib/v2/screens/settings/redeem_address_screen.dart @@ -1,18 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/v2/components/address_input_field.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/redeem_progress_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Fullscreen flow to pick a destination address for redeeming mining /// rewards. Defaults the field to the user's primary account (Account 0). diff --git a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart index ea05313e5..34e3ebb74 100644 --- a/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart +++ b/mobile-app/lib/v2/screens/settings/redeem_progress_screen.dart @@ -1,18 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/components/wormhole_progress_steps.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class RedeemProgressScreen extends ConsumerStatefulWidget { final BigInt redeemableRewards; diff --git a/mobile-app/lib/v2/screens/settings/reset_confirmation_screen.dart b/mobile-app/lib/v2/screens/settings/reset_confirmation_screen.dart index 9ce29eb44..c29ef0a35 100644 --- a/mobile-app/lib/v2/screens/settings/reset_confirmation_screen.dart +++ b/mobile-app/lib/v2/screens/settings/reset_confirmation_screen.dart @@ -1,9 +1,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/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/services/local_auth_service.dart'; import 'package:resonance_network_wallet/services/logout_service.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_caution_scaffold.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; 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 0fe20cd2a..fe6820e43 100644 --- a/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/settings/select_wallet_screen.dart @@ -1,15 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_confirmation_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:resonance_network_wallet/l10n/app_localizations.dart'; class SelectWalletScreen extends ConsumerWidget { diff --git a/mobile-app/lib/v2/screens/settings/settings_caution_scaffold.dart b/mobile-app/lib/v2/screens/settings/settings_caution_scaffold.dart index c561ca9e9..868e1a6f5 100644 --- a/mobile-app/lib/v2/screens/settings/settings_caution_scaffold.dart +++ b/mobile-app/lib/v2/screens/settings/settings_caution_scaffold.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/l10n/app_localizations.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_checkbox.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_list_row.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class SettingsCautionScaffoldData { final String headline; diff --git a/mobile-app/lib/v2/screens/settings/settings_checkbox.dart b/mobile-app/lib/v2/screens/settings/settings_checkbox.dart index 8ef7d467f..dc83bc91f 100644 --- a/mobile-app/lib/v2/screens/settings/settings_checkbox.dart +++ b/mobile-app/lib/v2/screens/settings/settings_checkbox.dart @@ -1,6 +1,5 @@ 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'; +import 'package:quantus_sdk/quantus_sdk.dart'; class SettingsCheckbox extends StatelessWidget { final bool checked; diff --git a/mobile-app/lib/v2/screens/settings/settings_divider.dart b/mobile-app/lib/v2/screens/settings/settings_divider.dart index 7b7bf07c4..d3b9b073e 100644 --- a/mobile-app/lib/v2/screens/settings/settings_divider.dart +++ b/mobile-app/lib/v2/screens/settings/settings_divider.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; enum SettingsDividerStyle { list, walletSection, sectionEmphasis, cardInterior, currencyList } diff --git a/mobile-app/lib/v2/screens/settings/settings_list_row.dart b/mobile-app/lib/v2/screens/settings/settings_list_row.dart index a461c53f3..90ce08cab 100644 --- a/mobile-app/lib/v2/screens/settings/settings_list_row.dart +++ b/mobile-app/lib/v2/screens/settings/settings_list_row.dart @@ -1,6 +1,5 @@ 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'; +import 'package:quantus_sdk/quantus_sdk.dart'; class SettingsListRow extends StatelessWidget { const SettingsListRow({super.key, required this.label, required this.content}); diff --git a/mobile-app/lib/v2/screens/settings/settings_picker_screen.dart b/mobile-app/lib/v2/screens/settings/settings_picker_screen.dart index 7a3f8ce20..d73edebbb 100644 --- a/mobile-app/lib/v2/screens/settings/settings_picker_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_picker_screen.dart @@ -1,12 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; -import 'package:resonance_network_wallet/shared/utils/print.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; +import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_picker_widgets.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; /// Searchable list for choosing a single settings value (language, currency, etc.). class SettingsPickerScreen extends StatefulWidget { diff --git a/mobile-app/lib/v2/screens/settings/settings_picker_widgets.dart b/mobile-app/lib/v2/screens/settings/settings_picker_widgets.dart index 7bfc3a408..6957d2002 100644 --- a/mobile-app/lib/v2/screens/settings/settings_picker_widgets.dart +++ b/mobile-app/lib/v2/screens/settings/settings_picker_widgets.dart @@ -1,6 +1,5 @@ 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'; +import 'package:quantus_sdk/quantus_sdk.dart'; class SettingsPickerSearchField extends StatelessWidget { const SettingsPickerSearchField({ diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 64276b4c0..09c167d15 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -1,14 +1,14 @@ 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' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/generated/version.g.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/about_quantus_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/add_account_menu_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/help_and_support_screen.dart'; @@ -17,7 +17,6 @@ import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.da import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; import 'package:resonance_network_wallet/v2/screens/settings/mining_rewards_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/wallet_settings_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; class SettingsScreenV2 extends ConsumerStatefulWidget { const SettingsScreenV2({super.key}); diff --git a/mobile-app/lib/v2/screens/settings/settings_tappable_row.dart b/mobile-app/lib/v2/screens/settings/settings_tappable_row.dart index c3ed0a6a7..0eb0ddb5a 100644 --- a/mobile-app/lib/v2/screens/settings/settings_tappable_row.dart +++ b/mobile-app/lib/v2/screens/settings/settings_tappable_row.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; 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'; +import 'package:quantus_sdk/quantus_sdk.dart'; abstract final class SettingsTappableRowUtils { static TextStyle? title(AppTextTheme text, AppColorsV2 colors, {Color? color}) { diff --git a/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart b/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart index aa26fe24b..9baf0e219 100644 --- a/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart +++ b/mobile-app/lib/v2/screens/settings/testnet_rewards_screen.dart @@ -1,14 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/mining_rewards_provider.dart'; import 'package:resonance_network_wallet/services/mining_rewards_service.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.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/l10n/app_localizations.dart'; class TestnetRewardsScreen extends ConsumerWidget { diff --git a/mobile-app/lib/v2/screens/settings/wallet_settings_screen.dart b/mobile-app/lib/v2/screens/settings/wallet_settings_screen.dart index fa38529b5..1295a1004 100644 --- a/mobile-app/lib/v2/screens/settings/wallet_settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/wallet_settings_screen.dart @@ -1,21 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/account_utils.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_confirmation_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/reset_confirmation_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/select_wallet_screen.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_divider.dart'; import 'package:resonance_network_wallet/v2/screens/settings/settings_tappable_row.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class WalletSettingsScreenV2 extends ConsumerStatefulWidget { const WalletSettingsScreenV2({super.key}); diff --git a/mobile-app/lib/v2/screens/swap/deposit_screen.dart b/mobile-app/lib/v2/screens/swap/deposit_screen.dart index 323725aa2..917a55e6e 100644 --- a/mobile-app/lib/v2/screens/swap/deposit_screen.dart +++ b/mobile-app/lib/v2/screens/swap/deposit_screen.dart @@ -1,18 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; import 'package:resonance_network_wallet/shared/utils/share_utils.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/token_icon.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.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/components/token_icon.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; 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 index 82dae3120..adb2ee93e 100644 --- a/mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/refund_address_picker_sheet.dart @@ -2,9 +2,6 @@ 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/l10n_provider.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'; Future showRefundAddressPickerSheet(BuildContext context, String network) { return BottomSheetContainer.show(context, builder: (_) => _RefundAddressPickerContent(network: network)); @@ -43,7 +40,6 @@ class _RefundAddressPickerContentState extends ConsumerState<_RefundAddressPicke child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 8), Align( alignment: Alignment.centerLeft, child: Text(widget.network, style: text.detail?.copyWith(color: colors.textSecondary)), 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 55dfb1a6a..eceedd874 100644 --- a/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/review_quote_sheet.dart @@ -2,12 +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/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/components/token_icon.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'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; void showReviewQuoteSheet(BuildContext context, SwapQuote quote, String refundAddress) { diff --git a/mobile-app/lib/v2/screens/swap/swap_screen.dart b/mobile-app/lib/v2/screens/swap/swap_screen.dart index 3cbb42a2f..1ac108a8a 100644 --- a/mobile-app/lib/v2/screens/swap/swap_screen.dart +++ b/mobile-app/lib/v2/screens/swap/swap_screen.dart @@ -3,18 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:resonance_network_wallet/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; import 'package:resonance_network_wallet/v2/components/qr_scanner_page.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; import 'package:resonance_network_wallet/v2/components/token_icon.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; -import 'package:resonance_network_wallet/v2/components/v2_app_bar.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'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class SwapScreen extends ConsumerStatefulWidget { const SwapScreen({super.key}); @@ -200,7 +196,7 @@ class _SwapScreenState extends ConsumerState { variant: ButtonVariant.glass, onTap: _pickToken, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - borderRadius: 8, + borderRadius: BorderRadius.circular(8), child: Row( children: [ TokenIcon(token: _fromToken, size: 25, networkBadgeSize: 10), @@ -318,7 +314,7 @@ class _SwapScreenState extends ConsumerState { onTap: onTap, variant: ButtonVariant.glass, padding: EdgeInsets.zero, - borderRadius: 8, + borderRadius: BorderRadius.circular(8), child: Center( child: SvgPicture.asset( iconAsset, @@ -363,7 +359,7 @@ class _SwapScreenState extends ConsumerState { variant: ButtonVariant.glass, onTap: () {}, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), - borderRadius: 8, + borderRadius: BorderRadius.circular(8), child: Row( mainAxisSize: MainAxisSize.min, children: [ 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 ca3e4a5d0..57e36083b 100644 --- a/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart +++ b/mobile-app/lib/v2/screens/swap/token_picker_sheet.dart @@ -2,11 +2,7 @@ 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/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; import 'package:resonance_network_wallet/v2/components/token_icon.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/l10n/app_localizations.dart'; import 'package:resonance_network_wallet/shared/utils/print.dart'; diff --git a/mobile-app/lib/v2/screens/welcome/onboarding_background.dart b/mobile-app/lib/v2/screens/welcome/onboarding_background.dart index 3bb243a2b..39cfaf53b 100644 --- a/mobile-app/lib/v2/screens/welcome/onboarding_background.dart +++ b/mobile-app/lib/v2/screens/welcome/onboarding_background.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class OnboardingBackground extends StatelessWidget { const OnboardingBackground({super.key}); diff --git a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart index b0170dfc2..04fd28332 100644 --- a/mobile-app/lib/v2/screens/welcome/welcome_screen.dart +++ b/mobile-app/lib/v2/screens/welcome/welcome_screen.dart @@ -2,20 +2,17 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; import 'package:resonance_network_wallet/providers/l10n_provider.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/services/wallet_creation_service.dart'; import 'package:resonance_network_wallet/shared/constants/e2e_keys.dart'; -import 'package:resonance_network_wallet/shared/extensions/toaster_extensions.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; -import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/account_ready_screen.dart'; import 'package:resonance_network_wallet/v2/screens/import/import_wallet_screen.dart'; import 'package:resonance_network_wallet/v2/screens/welcome/onboarding_background.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; class WelcomeScreenV2 extends ConsumerStatefulWidget { const WelcomeScreenV2({super.key}); diff --git a/mobile-app/lib/v2/theme/app_spacing.dart b/mobile-app/lib/v2/theme/app_spacing.dart deleted file mode 100644 index ce7c34de8..000000000 --- a/mobile-app/lib/v2/theme/app_spacing.dart +++ /dev/null @@ -1,282 +0,0 @@ -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 deleted file mode 100644 index 441eb64c4..000000000 --- a/mobile-app/lib/v2/theme/app_text_styles.dart +++ /dev/null @@ -1,251 +0,0 @@ -import 'package:flutter/material.dart'; - -@immutable -class AppTextTheme extends ThemeExtension { - static const fontFamily = 'Geist'; - static const fontFamilySecondary = 'Geist Mono'; - - final TextStyle? lockTitle; - final TextStyle? extraLargeTitle; - final TextStyle? largeTitle; - final TextStyle? mediumTitle; - final TextStyle? smallTitle; - final TextStyle? paragraph; - final TextStyle? smallParagraph; - final TextStyle? receiveLabel; - final TextStyle? largeTag; - final TextStyle? tag; - final TextStyle? timer; - final TextStyle? detail; - final TextStyle? tiny; - final TextStyle? totalMinedBlocks; - final TextStyle? transactionDetailAmountPrimary; - final TextStyle? transactionDetailAmountSymbol; - final TextStyle? transactionDetailRowLabel; - final TextStyle? transactionDetailRowValue; - final TextStyle? sendSectionLabel; - final TextStyle? conversionAmountPrimary; - - const AppTextTheme({ - this.lockTitle, - this.extraLargeTitle, - this.largeTitle, - this.mediumTitle, - this.smallTitle, - this.paragraph, - this.smallParagraph, - this.receiveLabel, - this.largeTag, - this.tag, - this.timer, - this.detail, - this.tiny, - this.totalMinedBlocks, - this.transactionDetailAmountPrimary, - this.transactionDetailAmountSymbol, - this.transactionDetailRowLabel, - this.transactionDetailRowValue, - this.sendSectionLabel, - this.conversionAmountPrimary, - }); - - 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), - receiveLabel: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500, fontFamily: fontFamilySecondary), - 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), - totalMinedBlocks: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 56, - fontWeight: FontWeight.w400, - height: 1.0, - ), - transactionDetailAmountPrimary: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 64, - fontWeight: FontWeight.w300, - letterSpacing: -2.77, - height: 1.0, - ), - transactionDetailAmountSymbol: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 24, - fontWeight: FontWeight.w300, - ), - transactionDetailRowLabel: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 12, - fontWeight: FontWeight.w400, - letterSpacing: 0.85, - ), - transactionDetailRowValue: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 12, - fontWeight: FontWeight.w500, - ), - sendSectionLabel: const TextStyle( - fontSize: 20, - height: 1.0, - fontWeight: FontWeight.w400, - fontFamily: fontFamily, - ), - conversionAmountPrimary: const TextStyle( - fontSize: 40, - fontFamily: fontFamilySecondary, - fontWeight: FontWeight.w400, - ), - ); - - 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), - receiveLabel: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500, fontFamily: fontFamilySecondary), - 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), - totalMinedBlocks: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 60, - fontWeight: FontWeight.w400, - height: 1.0, - ), - transactionDetailAmountPrimary: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 80, - fontWeight: FontWeight.w300, - letterSpacing: -2.77, - height: 1.0, - ), - transactionDetailAmountSymbol: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 30, - fontWeight: FontWeight.w300, - ), - transactionDetailRowLabel: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 16, - fontWeight: FontWeight.w400, - letterSpacing: 0.85, - ), - transactionDetailRowValue: const TextStyle( - fontFamily: fontFamilySecondary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - sendSectionLabel: const TextStyle( - fontSize: 24, - height: 1.0, - fontWeight: FontWeight.w400, - fontFamily: fontFamily, - ), - conversionAmountPrimary: const TextStyle( - fontSize: 52, - fontFamily: fontFamilySecondary, - fontWeight: FontWeight.w400, - ), - ); - - @override - AppTextTheme copyWith({ - TextStyle? lockTitle, - TextStyle? extraLargeTitle, - TextStyle? largeTitle, - TextStyle? mediumTitle, - TextStyle? smallTitle, - TextStyle? paragraph, - TextStyle? smallParagraph, - TextStyle? receiveLabel, - TextStyle? largeTag, - TextStyle? tag, - TextStyle? timer, - TextStyle? detail, - TextStyle? tiny, - TextStyle? totalMinedBlocks, - TextStyle? transactionDetailAmountPrimary, - TextStyle? transactionDetailAmountSymbol, - TextStyle? transactionDetailRowLabel, - TextStyle? transactionDetailRowValue, - TextStyle? sendSectionLabel, - TextStyle? conversionAmountPrimary, - }) { - 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, - receiveLabel: receiveLabel ?? this.receiveLabel, - largeTag: largeTag ?? this.largeTag, - tag: tag ?? this.tag, - timer: timer ?? this.timer, - detail: detail ?? this.detail, - tiny: tiny ?? this.tiny, - totalMinedBlocks: totalMinedBlocks ?? this.totalMinedBlocks, - transactionDetailAmountPrimary: transactionDetailAmountPrimary ?? this.transactionDetailAmountPrimary, - transactionDetailAmountSymbol: transactionDetailAmountSymbol ?? this.transactionDetailAmountSymbol, - transactionDetailRowLabel: transactionDetailRowLabel ?? this.transactionDetailRowLabel, - transactionDetailRowValue: transactionDetailRowValue ?? this.transactionDetailRowValue, - sendSectionLabel: sendSectionLabel ?? this.sendSectionLabel, - conversionAmountPrimary: conversionAmountPrimary ?? this.conversionAmountPrimary, - ); - } - - @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), - receiveLabel: TextStyle.lerp(receiveLabel, other.receiveLabel, 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), - totalMinedBlocks: TextStyle.lerp(totalMinedBlocks, other.totalMinedBlocks, t), - transactionDetailAmountPrimary: TextStyle.lerp( - transactionDetailAmountPrimary, - other.transactionDetailAmountPrimary, - t, - ), - transactionDetailAmountSymbol: TextStyle.lerp( - transactionDetailAmountSymbol, - other.transactionDetailAmountSymbol, - t, - ), - transactionDetailRowLabel: TextStyle.lerp(transactionDetailRowLabel, other.transactionDetailRowLabel, t), - transactionDetailRowValue: TextStyle.lerp(transactionDetailRowValue, other.transactionDetailRowValue, t), - sendSectionLabel: TextStyle.lerp(sendSectionLabel, other.sendSectionLabel, t), - conversionAmountPrimary: TextStyle.lerp(conversionAmountPrimary, other.conversionAmountPrimary, 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 deleted file mode 100644 index b8c7acd9d..000000000 --- a/mobile-app/lib/v2/theme/app_theme.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:quantus_sdk/quantus_sdk.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 colors = const AppColorsV2.dark(); - final text = isTablet ? const AppTextTheme.iPad() : const AppTextTheme.defaultTheme(); - - return ThemeData( - scaffoldBackgroundColor: colors.background, - cardColor: colors.surface, - 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)), - ), - ), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))), - ), - textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: colors.textPrimary)), - textSelectionTheme: TextSelectionThemeData( - cursorColor: colors.accentOrange, - selectionColor: colors.accentOrange.useOpacity(0.2), - selectionHandleColor: colors.accentOrange, - ), - inputDecorationTheme: InputDecorationTheme( - hintStyle: text.smallParagraph?.copyWith(color: colors.textLabel, fontFamily: AppTextTheme.fontFamilySecondary), - contentPadding: EdgeInsets.zero, - isDense: true, - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - filled: true, - fillColor: Colors.transparent, - ), - extensions: [ - colors, - text, - isTablet ? const AppSizeTheme.iPad() : const AppSizeTheme.defaultTheme(), - // 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(), - ], - ); - } -} diff --git a/mobile-app/lib/wallet_initializer.dart b/mobile-app/lib/wallet_initializer.dart index 97b80e893..fe202db69 100644 --- a/mobile-app/lib/wallet_initializer.dart +++ b/mobile-app/lib/wallet_initializer.dart @@ -1,13 +1,10 @@ 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/l10n_provider.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_button.dart'; +import 'package:quantus_sdk/quantus_sdk.dart' hide ScaffoldBase; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; +import 'package:resonance_network_wallet/providers/l10n_provider.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/theme/app_text_styles.dart'; import 'package:resonance_network_wallet/services/logout_service.dart'; import 'package:resonance_network_wallet/services/telemetry_service.dart'; diff --git a/mobile-app/pubspec.yaml b/mobile-app/pubspec.yaml index fa8d8cd43..ef25552ba 100644 --- a/mobile-app/pubspec.yaml +++ b/mobile-app/pubspec.yaml @@ -94,7 +94,6 @@ flutter: - assets/v2/uppercase_q.svg - assets/v2/uppercase_q.png - assets/v2/axe.svg - - assets/v2/uppercase_q_black_bg.png - assets/v2/swap_arrows_down_up.svg - assets/v2/swap_clock_counter_clockwise.svg - assets/v2/swap_qr_code.svg diff --git a/mobile-app/test/extensions.dart b/mobile-app/test/extensions.dart index d1a7b9608..02247ab87 100644 --- a/mobile-app/test/extensions.dart +++ b/mobile-app/test/extensions.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/misc.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:resonance_network_wallet/v2/theme/app_theme.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; extension Device on WidgetTester { Size get devicePixel { diff --git a/mobile-app/test/unit/scaffold_base_test.dart b/mobile-app/test/unit/scaffold_base_test.dart new file mode 100644 index 000000000..0bbd2345a --- /dev/null +++ b/mobile-app/test/unit/scaffold_base_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:resonance_network_wallet/providers/connectivity_provider.dart'; +import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; + +import '../extensions.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('shows Not Connected when offline', (tester) async { + await tester.pumpApp( + const ScaffoldBase(mainContent: Text('Hello')), + overrides: [isOnlineProvider.overrideWith((ref) => false)], + ); + + expect(find.text('Not Connected'), findsOneWidget); + }); + + testWidgets('hides Not Connected when online', (tester) async { + await tester.pumpApp( + const ScaffoldBase(mainContent: Text('Hello')), + overrides: [isOnlineProvider.overrideWith((ref) => true)], + ); + + expect(find.text('Not Connected'), findsNothing); + }); + + testWidgets('refreshable shows Not Connected when offline', (tester) async { + await tester.pumpApp( + ScaffoldBase.refreshable(onRefresh: () async {}, slivers: const [Text('Item')]), + overrides: [isOnlineProvider.overrideWith((ref) => false)], + ); + + expect(find.text('Not Connected'), findsOneWidget); + }); +} diff --git a/mobile-app/test/unit/shared_address_action_sheet_test.dart b/mobile-app/test/unit/shared_address_action_sheet_test.dart index 4a936e479..e74ca5b08 100644 --- a/mobile-app/test/unit/shared_address_action_sheet_test.dart +++ b/mobile-app/test/unit/shared_address_action_sheet_test.dart @@ -3,10 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:flutter_riverpod/misc.dart'; -import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; +import 'package:resonance_network_wallet/v2/components/shared_address_action_sheet.dart'; import 'package:resonance_network_wallet/providers/wallet_providers.dart'; import 'package:resonance_network_wallet/v2/screens/send/input_amount_screen.dart'; -import 'package:resonance_network_wallet/v2/theme/app_theme.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../fakes.dart'; diff --git a/quantus_sdk/assets/icons/caret_down.svg b/quantus_sdk/assets/icons/caret_down.svg new file mode 100644 index 000000000..54209fd36 --- /dev/null +++ b/quantus_sdk/assets/icons/caret_down.svg @@ -0,0 +1,3 @@ + + + diff --git a/quantus_sdk/assets/icons/chevron_left.svg b/quantus_sdk/assets/icons/chevron_left.svg new file mode 100644 index 000000000..d3333fcea --- /dev/null +++ b/quantus_sdk/assets/icons/chevron_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/quantus_sdk/assets/icons/chevron_right.svg b/quantus_sdk/assets/icons/chevron_right.svg new file mode 100644 index 000000000..270adaca4 --- /dev/null +++ b/quantus_sdk/assets/icons/chevron_right.svg @@ -0,0 +1,3 @@ + + + diff --git a/quantus_sdk/assets/icons/kebab.svg b/quantus_sdk/assets/icons/kebab.svg new file mode 100644 index 000000000..577f77595 --- /dev/null +++ b/quantus_sdk/assets/icons/kebab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/quantus_sdk/assets/icons/lock.svg b/quantus_sdk/assets/icons/lock.svg new file mode 100644 index 000000000..c4d6426f1 --- /dev/null +++ b/quantus_sdk/assets/icons/lock.svg @@ -0,0 +1,4 @@ + + + + diff --git a/quantus_sdk/assets/icons/plus.svg b/quantus_sdk/assets/icons/plus.svg new file mode 100644 index 000000000..9d342250e --- /dev/null +++ b/quantus_sdk/assets/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/quantus_sdk/assets/icons/swap_vertical.svg b/quantus_sdk/assets/icons/swap_vertical.svg new file mode 100644 index 000000000..9ffa683ce --- /dev/null +++ b/quantus_sdk/assets/icons/swap_vertical.svg @@ -0,0 +1,4 @@ + + + + diff --git a/mobile-app/assets/v2/uppercase_q_black_bg.png b/quantus_sdk/assets/v2/uppercase_q_black_bg.png similarity index 100% rename from mobile-app/assets/v2/uppercase_q_black_bg.png rename to quantus_sdk/assets/v2/uppercase_q_black_bg.png diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 1054bb4f1..37ba92a1a 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -14,8 +14,11 @@ export 'src/extensions/color_extensions.dart'; export 'src/extensions/context_extension.dart'; export 'src/extensions/decimal_input_filter.dart'; export 'src/extensions/keypair_extensions.dart'; +export 'src/extensions/media_query_data_extension.dart'; export 'src/extensions/string_extensions.dart'; +export 'src/extensions/toaster_extensions.dart'; // UI-related exports +export 'src/ui/index.dart'; export 'src/models/account.dart'; export 'src/models/base_account.dart'; export 'src/models/high_security_data.dart'; diff --git a/quantus_sdk/lib/src/chain/decoded_call.dart b/quantus_sdk/lib/src/chain/decoded_call.dart index fe9488d42..3b37de342 100644 --- a/quantus_sdk/lib/src/chain/decoded_call.dart +++ b/quantus_sdk/lib/src/chain/decoded_call.dart @@ -186,4 +186,31 @@ class DecodedCall { final words = call.split('_').where((w) => w.isNotEmpty).join(' '); return words[0].toUpperCase() + words.substring(1); } + + /// Every ss58 address this call names, nested calls included, first-seen order. + /// + /// Renderers watch checkphrases for this list in the host app, then pass the + /// resolved strings into the presentational tree. + List get addresses { + final seen = {}; + final out = []; + void walk(List fields) { + for (final field in fields) { + switch (field) { + case ValueField(:final value, kind: ValueKind.address): + if (seen.add(value)) out.add(value); + case FieldGroup(:final items): + walk(items); + case NestedCallField(:final call): + walk(call.fields); + case AmountField(): + case ValueField(): + break; + } + } + } + + walk(fields); + return out; + } } diff --git a/mobile-app/lib/shared/extensions/media_query_data_extension.dart b/quantus_sdk/lib/src/extensions/media_query_data_extension.dart similarity index 100% rename from mobile-app/lib/shared/extensions/media_query_data_extension.dart rename to quantus_sdk/lib/src/extensions/media_query_data_extension.dart diff --git a/mobile-app/lib/shared/extensions/toaster_extensions.dart b/quantus_sdk/lib/src/extensions/toaster_extensions.dart similarity index 89% rename from mobile-app/lib/shared/extensions/toaster_extensions.dart rename to quantus_sdk/lib/src/extensions/toaster_extensions.dart index db4e3d7e5..d27614ed2 100644 --- a/mobile-app/lib/shared/extensions/toaster_extensions.dart +++ b/quantus_sdk/lib/src/extensions/toaster_extensions.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/components/toaster_helper.dart' as th; +import 'package:quantus_sdk/src/ui/components/toaster_helper.dart' as th; extension ToasterExtensions on BuildContext { Future showSuccessToaster({required String message}) async { diff --git a/cold-wallet-app/lib/components/animated_ur_qr.dart b/quantus_sdk/lib/src/ui/components/animated_ur_qr.dart similarity index 74% rename from cold-wallet-app/lib/components/animated_ur_qr.dart rename to quantus_sdk/lib/src/ui/components/animated_ur_qr.dart index d3fc00fb7..81a441500 100644 --- a/cold-wallet-app/lib/components/animated_ur_qr.dart +++ b/quantus_sdk/lib/src/ui/components/animated_ur_qr.dart @@ -16,7 +16,8 @@ class AnimatedUrQr extends StatefulWidget { final bool paused; final double size; - const AnimatedUrQr({super.key, required this.parts, required this.fps, this.paused = false, this.size = 280}); + const AnimatedUrQr({super.key, required this.parts, this.fps = 15, this.paused = false, this.size = 280}) + : assert(fps > 0, 'AnimatedUrQr fps must be positive'); @override State createState() => _AnimatedUrQrState(); @@ -34,16 +35,19 @@ class _AnimatedUrQrState extends State { _restartTimer(); } - List _buildPainters() => widget.parts - .map( - (part) => QrPainter.withQr( - qr: QrCode.fromData(data: part, errorCorrectLevel: QrErrorCorrectLevel.L), - gapless: true, - eyeStyle: const QrEyeStyle(eyeShape: QrEyeShape.square, color: Colors.black), - dataModuleStyle: const QrDataModuleStyle(dataModuleShape: QrDataModuleShape.square, color: Colors.black), - ), - ) - .toList(); + List _buildPainters() { + assert(widget.parts.isNotEmpty, 'AnimatedUrQr requires at least one UR part'); + return widget.parts + .map( + (part) => QrPainter.withQr( + qr: QrCode.fromData(data: part, errorCorrectLevel: QrErrorCorrectLevel.L), + gapless: true, + eyeStyle: const QrEyeStyle(eyeShape: QrEyeShape.square, color: Colors.black), + dataModuleStyle: const QrDataModuleStyle(dataModuleShape: QrDataModuleShape.square, color: Colors.black), + ), + ) + .toList(); + } @override void didUpdateWidget(AnimatedUrQr oldWidget) { diff --git a/cold-wallet-app/lib/components/back_button.dart b/quantus_sdk/lib/src/ui/components/back_button.dart similarity index 59% rename from cold-wallet-app/lib/components/back_button.dart rename to quantus_sdk/lib/src/ui/components/back_button.dart index c92deba44..56eb4602d 100644 --- a/cold-wallet-app/lib/components/back_button.dart +++ b/quantus_sdk/lib/src/ui/components/back_button.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:quantus_cold_wallet/components/glass_button_base.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class AppBackButton extends StatelessWidget { final VoidCallback? onTap; @@ -20,12 +18,7 @@ class AppBackButton extends StatelessWidget { buttonWidth: containerSize, borderRadius: BorderRadius.circular(containerSize / 2), padding: const EdgeInsets.all(2), - child: SvgPicture.asset( - 'assets/v2/caret_left.svg', - width: iconSize, - height: iconSize, - colorFilter: ColorFilter.mode(context.colors.textPrimary, BlendMode.srcIn), - ), + child: QuantusIcon(QuantusIcons.chevronLeft, size: iconSize, color: context.colorsV3.textContent), ), ); } diff --git a/cold-wallet-app/lib/components/base_background.dart b/quantus_sdk/lib/src/ui/components/base_background.dart similarity index 63% rename from cold-wallet-app/lib/components/base_background.dart rename to quantus_sdk/lib/src/ui/components/base_background.dart index b17c84a0b..34ce414c1 100644 --- a/cold-wallet-app/lib/components/base_background.dart +++ b/quantus_sdk/lib/src/ui/components/base_background.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class BaseBackground extends StatelessWidget { final Widget child; @@ -8,6 +8,6 @@ class BaseBackground extends StatelessWidget { @override Widget build(BuildContext context) { - return Container(color: context.colors.background, child: child); + return Container(color: context.colorsV3.bgVoid, child: child); } } diff --git a/quantus_sdk/lib/src/ui/components/bottom_sheet_container.dart b/quantus_sdk/lib/src/ui/components/bottom_sheet_container.dart new file mode 100644 index 000000000..e03fa72ea --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/bottom_sheet_container.dart @@ -0,0 +1,69 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Shared v3 sheet shell: handle, title, content slot. +/// +/// Presentational only. Overlay presentation is [BottomSheetContainer.show]. +class BottomSheetContainer extends StatelessWidget { + final String title; + final Widget child; + final double? height; + + const BottomSheetContainer({super.key, required this.title, required this.child, this.height}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + + return Container( + width: double.infinity, + height: height, + padding: const EdgeInsets.fromLTRB(24, 16, 24, 32), + decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.lgBorder), + child: Column( + mainAxisSize: height != null ? MainAxisSize.max : MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: Container( + width: 36, + height: 4, + decoration: BoxDecoration(color: colors.bgSurface2, borderRadius: BorderRadius.circular(2)), + ), + ), + const SizedBox(height: 16), + Text(title, style: text.headingRow.copyWith(color: colors.textContent)), + const SizedBox(height: 16), + if (height != null) Expanded(child: child) else Flexible(child: SingleChildScrollView(child: child)), + ], + ), + ); + } + + static Future show(BuildContext context, {required WidgetBuilder builder, RouteSettings? routeSettings}) { + return showModalBottomSheet( + context: context, + routeSettings: routeSettings, + backgroundColor: Colors.transparent, + isScrollControlled: true, + enableDrag: true, + isDismissible: true, + constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width), + builder: (ctx) { + final maxSheetHeight = MediaQuery.sizeOf(ctx).height * 0.85; + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 2, sigmaY: 2), + child: Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: maxSheetHeight), + child: builder(ctx), + ), + ), + ); + }, + ); + } +} diff --git a/mobile-app/lib/v2/components/confirm_action_sheet.dart b/quantus_sdk/lib/src/ui/components/confirm_action_sheet.dart similarity index 85% rename from mobile-app/lib/v2/components/confirm_action_sheet.dart rename to quantus_sdk/lib/src/ui/components/confirm_action_sheet.dart index 6760137de..6dd570cfc 100644 --- a/mobile-app/lib/v2/components/confirm_action_sheet.dart +++ b/quantus_sdk/lib/src/ui/components/confirm_action_sheet.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/components/bottom_sheet_container.dart'; -import 'package:resonance_network_wallet/v2/components/quantus_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:quantus_sdk/quantus_sdk.dart'; /// Generic "are you sure?" confirmation sheet. Resolves to true when the user /// taps the confirm action, false on cancel/dismiss. @@ -53,7 +50,6 @@ class _ConfirmActionSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 16), Text(message, style: text.paragraph?.copyWith(color: colors.textSecondary)), const SizedBox(height: 24), QuantusButton.simple( diff --git a/quantus_sdk/lib/src/ui/components/decoded_call_view.dart b/quantus_sdk/lib/src/ui/components/decoded_call_view.dart new file mode 100644 index 000000000..9063f28ab --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/decoded_call_view.dart @@ -0,0 +1,415 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Density for [DecodedCallView] until design picks one layout. +enum DecodedCallLayout { + /// Signing review: label above value, wrap, no ellipsis. + stacked, + + /// Sheet/list: label left, value right, tighter. + compact, +} + +/// True when [summary] already puts this exact field on screen, so listing it +/// again would only add noise. Matches by identity, never by label or value. +bool coveredBySummary(CallField field, TransferSummary summary) { + if (identical(field, summary.amountField)) return true; + return summary.recipient != null && identical(field, summary.recipientField); +} + +/// The transfer a call performs, or null when it dispatches another call. +/// +/// A wrapper inherits the summary of the call it carries; the amount belongs +/// on that inner call, where the nested box shows it, not on the wrapper. +TransferSummary? heroSummary(DecodedCall call) => call.isWrapper ? null : call.summary; + +/// Asset amounts stay in the payload's own raw units: decimals are per-asset +/// chain state an air-gapped signer cannot resolve. +String assetAmountText(BigInt amount, int assetId) => '$amount raw units of asset $assetId'; + +String? _resolvedPhrase(String address, String? Function(String address)? checkphraseOf) { + final phrase = checkphraseOf?.call(address); + if (phrase == null || phrase.isEmpty) return null; + return phrase; +} + +bool _isMonospace(ValueKind kind) => switch (kind) { + ValueKind.address || ValueKind.hash || ValueKind.bytes => true, + _ => false, +}; + +Widget _callDetailRow({ + required String label, + required String value, + required ValueKind kind, + required DecodedCallLayout layout, + String? note, + String? checkphrase, +}) { + final monospace = _isMonospace(kind); + if (layout == DecodedCallLayout.stacked) { + return DetailSummaryRow.stacked( + label: label, + value: value, + monospace: monospace, + note: note, + checkphrase: checkphrase, + ); + } + return DetailSummaryRow(label: label, value: value, monospace: monospace, note: note, checkphrase: checkphrase); +} + +/// Renders every parameter of a [DecodedCall], nested calls included. +/// +/// Presentational only. Amounts and checkphrases are already-resolved strings +/// from the host app. Compact lists every field under the runtime title. +/// Stacked is the signing reading order: action title, hero amount and +/// recipient, then every remaining parameter. +class DecodedCallView extends StatelessWidget { + final DecodedCall call; + + /// Nesting depth; nested calls indent and drop a level of visual weight. + final int depth; + + /// Formats a native token amount for the current locale and currency display. + final String Function(BigInt token) amountText; + + final DecodedCallLayout layout; + + /// Resolved checkphrase for an ss58 address, or null while loading / unknown. + final String? Function(String address)? checkphraseOf; + + /// Host-provided signer row. Stacked wrappers insert it after nested calls. + final Widget? signer; + + /// When false, the call title is omitted (the host already showed it). + final bool showTitle; + + /// Label for the stacked recipient row. Ignored in compact layout. + final String recipientLabel; + + const DecodedCallView({ + super.key, + required this.call, + required this.amountText, + this.layout = DecodedCallLayout.compact, + this.checkphraseOf, + this.depth = 0, + this.signer, + this.showTitle = true, + this.recipientLabel = 'To', + }); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + final stacked = layout == DecodedCallLayout.stacked; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showTitle) ...[ + Text( + stacked ? call.actionTitle : call.displayTitle, + style: text.headingRow.copyWith(color: depth == 0 ? colors.textContent : colors.textMuted), + ), + SizedBox(height: stacked ? 8 : 4), + ], + ..._children(), + ], + ); + } + + List _children() { + if (layout == DecodedCallLayout.stacked && depth == 0 && signer != null && call.isWrapper) { + return [ + for (final field in call.fields.whereType()) _field(field), + signer!, + for (final field in call.fields.where((f) => f is! NestedCallField)) _field(field), + ]; + } + return [..._body(), if (depth == 0 && signer != null) signer!]; + } + + List _body() { + if (layout != DecodedCallLayout.stacked) { + return [for (final field in call.fields) _field(field)]; + } + + final summary = heroSummary(call); + final recipient = summary?.recipient; + return [ + if (summary != null) _TransferAmount(summary: summary, large: depth == 0, amountText: amountText), + if (recipient != null) + _callDetailRow( + label: recipientLabel, + value: recipient, + kind: ValueKind.address, + checkphrase: _resolvedPhrase(recipient, checkphraseOf), + layout: layout, + ), + for (final field in call.fields) + if (summary == null || !coveredBySummary(field, summary)) _field(field), + ]; + } + + Widget _field(CallField field) => _CallFieldView( + field: field, + depth: depth, + layout: layout, + amountText: amountText, + checkphraseOf: checkphraseOf, + recipientLabel: recipientLabel, + ); +} + +class _TransferAmount extends StatelessWidget { + final TransferSummary summary; + final bool large; + final String Function(BigInt token) amountText; + + const _TransferAmount({required this.summary, required this.large, required this.amountText}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + + if (summary.assetId != null) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + assetAmountText(summary.amount, summary.assetId!), + style: text.headingRow.copyWith(color: colors.textContent), + ), + ); + } + + final style = large ? text.amountHero : text.amountInline; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: Alignment.centerLeft, + child: Text(amountText(summary.amount), style: style.copyWith(color: colors.textContent)), + ), + ); + } +} + +class _CallFieldView extends StatelessWidget { + final CallField field; + final int depth; + final DecodedCallLayout layout; + final String Function(BigInt token) amountText; + final String? Function(String address)? checkphraseOf; + final String recipientLabel; + + const _CallFieldView({ + required this.field, + required this.depth, + required this.layout, + required this.amountText, + required this.checkphraseOf, + required this.recipientLabel, + }); + + @override + Widget build(BuildContext context) { + return switch (field) { + ValueField(:final label, :final value, :final kind, :final note) => _callDetailRow( + label: label, + value: value, + kind: kind, + note: note, + checkphrase: kind == ValueKind.address ? _resolvedPhrase(value, checkphraseOf) : null, + layout: layout, + ), + AmountField(:final label, :final token, :final assetId, :final note) => _callDetailRow( + label: label, + value: assetId == null ? amountText(token) : assetAmountText(token, assetId), + kind: ValueKind.number, + note: _amountNote(note, assetId), + layout: layout, + ), + FieldGroup(:final label, :final items) => _FieldGroupView( + label: label, + items: items, + depth: depth, + layout: layout, + amountText: amountText, + checkphraseOf: checkphraseOf, + recipientLabel: recipientLabel, + ), + NestedCallField(:final label, :final call, :final note) => _NestedCallBox( + label: label, + call: call, + note: note, + depth: depth, + layout: layout, + amountText: amountText, + checkphraseOf: checkphraseOf, + recipientLabel: recipientLabel, + ), + }; + } + + String? _amountNote(String? note, int? assetId) { + if (assetId == null) return note; + return [ + note, + 'Asset decimals are not part of this payload, so the raw amount is shown.', + ].whereType().join(' '); + } +} + +class _FieldGroupView extends StatelessWidget { + final String label; + final List items; + final int depth; + final DecodedCallLayout layout; + final String Function(BigInt token) amountText; + final String? Function(String address)? checkphraseOf; + final String recipientLabel; + + const _FieldGroupView({ + required this.label, + required this.items, + required this.depth, + required this.layout, + required this.amountText, + required this.checkphraseOf, + required this.recipientLabel, + }); + + @override + Widget build(BuildContext context) { + final stacked = layout == DecodedCallLayout.stacked; + return Padding( + padding: EdgeInsets.only(top: stacked ? 12 : 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(stacked ? label.toUpperCase() : label, style: _labelStyle(context)), + Padding( + padding: const EdgeInsets.only(left: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final item in items) + _CallFieldView( + field: item, + depth: depth, + layout: layout, + amountText: amountText, + checkphraseOf: checkphraseOf, + recipientLabel: recipientLabel, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _NestedCallBox extends StatelessWidget { + final String label; + final DecodedCall call; + final String? note; + final int depth; + final DecodedCallLayout layout; + final String Function(BigInt token) amountText; + final String? Function(String address)? checkphraseOf; + final String recipientLabel; + + const _NestedCallBox({ + required this.label, + required this.call, + required this.note, + required this.depth, + required this.layout, + required this.amountText, + required this.checkphraseOf, + required this.recipientLabel, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final stacked = layout == DecodedCallLayout.stacked; + return Padding( + padding: EdgeInsets.only(top: stacked ? 14 : 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(stacked ? label.toUpperCase() : label, style: _labelStyle(context)), + const SizedBox(height: 8), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: stacked ? 8 : 10), + decoration: BoxDecoration( + color: colors.bgSurface2, + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.borderHairline), + ), + child: DecodedCallView( + call: call, + depth: depth + 1, + layout: layout, + amountText: amountText, + checkphraseOf: checkphraseOf, + recipientLabel: recipientLabel, + ), + ), + if (note != null) ...[const SizedBox(height: 4), Text(note!, style: _noteStyle(context))], + ], + ), + ); + } +} + +TextStyle _labelStyle(BuildContext context) { + return context.themeTextV3.labelMonogram.copyWith(fontSize: 10, letterSpacing: 1, color: context.colorsV3.textMuted); +} + +TextStyle _noteStyle(BuildContext context) { + return context.themeTextV3.caption.copyWith(color: context.colorsV3.textMuted); +} + +/// The one-line-plus-subtitle form of a decoded call, for list rows and sheets. +/// +/// A transfer keeps reading as an amount and a recipient; anything else names +/// the call rather than pretending to be a zero-value transfer. +class DecodedCallHeadline { + /// Primary line: formatted amount for transfers, otherwise the call name. + final String primary; + + /// Shortened recipient address when this call moves value, else null. + final String? recipient; + + /// Subtitle for calls that move no value (the owning pallet), else null. + final String? palletSubtitle; + + const DecodedCallHeadline({required this.primary, this.recipient, this.palletSubtitle}); + + /// Either the recipient or the pallet, whichever this call has. + String? get secondary => recipient ?? palletSubtitle; + + /// [amountText] formats a native token amount for the current locale and + /// currency display; injected so this stays independent of Riverpod. + static DecodedCallHeadline of(DecodedCall call, {required String Function(BigInt token) amountText}) { + final summary = call.summary; + if (summary == null) { + return DecodedCallHeadline( + primary: call.call.isEmpty ? call.pallet : call.humanCall, + palletSubtitle: call.call.isEmpty ? null : call.pallet, + ); + } + return DecodedCallHeadline( + primary: summary.assetId == null ? amountText(summary.amount) : '${summary.amount} (asset ${summary.assetId})', + recipient: summary.recipient == null ? null : AddressFormattingService.formatAddress(summary.recipient!), + ); + } +} diff --git a/quantus_sdk/lib/src/ui/components/detail_summary_row.dart b/quantus_sdk/lib/src/ui/components/detail_summary_row.dart new file mode 100644 index 000000000..67d2cb396 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/detail_summary_row.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Density for [DetailSummaryRow] until design picks one layout. +enum DetailSummaryLayout { + /// Sheet/list: label left, value right. + compact, + + /// Signing review: label above value, wrap, no ellipsis. + stacked, +} + +/// Label/value row used in review screens, detail sheets, and signing. +class DetailSummaryRow extends StatelessWidget { + final String label; + final String? value; + final Widget? valueWidget; + final int labelFlex; + final int valueFlex; + final EdgeInsetsGeometry padding; + final TextStyle? labelStyle; + final TextStyle? valueStyle; + final DetailSummaryLayout layout; + + /// Monospace value, for addresses, hashes and byte blobs. + final bool monospace; + + final Color? valueColor; + + /// Caveat shown under the value. + final String? note; + + /// Resolved checkphrase for an address value. + /// + /// Compact paints it under the value; stacked uses a second labeled row. + final String? checkphrase; + + const DetailSummaryRow({ + super.key, + required this.label, + this.value, + this.valueWidget, + this.labelFlex = 2, + this.valueFlex = 3, + this.padding = const EdgeInsets.symmetric(vertical: 8), + this.labelStyle, + this.valueStyle, + this.layout = DetailSummaryLayout.compact, + this.monospace = false, + this.valueColor, + this.note, + this.checkphrase, + }) : assert(value != null || valueWidget != null), + assert(checkphrase == null || valueWidget == null); + + factory DetailSummaryRow.review({ + Key? key, + required String label, + String? value, + Widget? valueWidget, + int valueFlex = 3, + TextStyle? valueStyle, + }) { + return DetailSummaryRow( + key: key, + label: label, + value: value, + valueWidget: valueWidget, + valueFlex: valueFlex, + valueStyle: valueStyle, + padding: EdgeInsets.zero, + ); + } + + /// Label-above-value row. Values wrap rather than ellipsize so a signer can + /// read a full address or hash. + const DetailSummaryRow.stacked({ + super.key, + required this.label, + required String this.value, + this.monospace = false, + this.valueColor, + this.note, + this.checkphrase, + }) : valueWidget = null, + labelFlex = 2, + valueFlex = 3, + labelStyle = null, + valueStyle = null, + layout = DetailSummaryLayout.stacked, + padding = const EdgeInsets.symmetric(vertical: 10); + + @override + Widget build(BuildContext context) { + return layout == DetailSummaryLayout.stacked ? _stacked(context) : _compact(context); + } + + Widget _compact(BuildContext context) { + final text = context.themeTextV3; + final colors = context.colorsV3; + final effectiveLabelStyle = labelStyle ?? _labelStyle(text, colors); + final effectiveValueStyle = + valueStyle ?? (monospace ? text.dataAddress : text.body).copyWith(color: valueColor ?? colors.textContent); + + return Padding( + padding: padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: labelFlex, + child: Text(label, style: effectiveLabelStyle), + ), + const SizedBox(width: 8), + Expanded( + flex: valueFlex, + child: Align( + alignment: Alignment.centerRight, + child: valueWidget ?? _compactValue(effectiveValueStyle, text, colors), + ), + ), + ], + ), + if (note != null) ...[const SizedBox(height: 4), Text(note!, style: _noteStyle(text, colors))], + ], + ), + ); + } + + Widget _compactValue(TextStyle valueStyle, AppTextThemeV3 text, AppColorsV3 colors) { + final valueText = Text(value!, style: valueStyle, textAlign: TextAlign.right, softWrap: true); + if (checkphrase == null) return valueText; + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + valueText, + Text( + checkphrase!, + style: text.caption.copyWith(color: colors.semanticLilac), + textAlign: TextAlign.right, + ), + ], + ); + } + + Widget _stacked(BuildContext context) { + final text = context.themeTextV3; + final colors = context.colorsV3; + final effectiveValueStyle = (monospace ? text.dataAddressLarge : text.body).copyWith( + color: valueColor ?? colors.textContent, + ); + + return Padding( + padding: padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(label.toUpperCase(), style: _labelStyle(text, colors)), + const SizedBox(height: 6), + Text(value!, style: effectiveValueStyle), + if (note != null) ...[const SizedBox(height: 4), Text(note!, style: _noteStyle(text, colors))], + if (checkphrase != null) ...[ + const SizedBox(height: 10), + Text('$label checkphrase'.toUpperCase(), style: _labelStyle(text, colors)), + const SizedBox(height: 6), + Text(checkphrase!, style: effectiveValueStyle.copyWith(color: colors.semanticLilac)), + ], + ], + ), + ); + } + + TextStyle _labelStyle(AppTextThemeV3 text, AppColorsV3 colors) { + return text.labelMonogram.copyWith(fontSize: 10, letterSpacing: 1, color: colors.textMuted); + } + + TextStyle _noteStyle(AppTextThemeV3 text, AppColorsV3 colors) { + return text.caption.copyWith(color: colors.textMuted); + } +} diff --git a/cold-wallet-app/lib/components/glass_button_base.dart b/quantus_sdk/lib/src/ui/components/glass_button_base.dart similarity index 100% rename from cold-wallet-app/lib/components/glass_button_base.dart rename to quantus_sdk/lib/src/ui/components/glass_button_base.dart diff --git a/quantus_sdk/lib/src/ui/components/index.dart b/quantus_sdk/lib/src/ui/components/index.dart new file mode 100644 index 000000000..8eadbf415 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/index.dart @@ -0,0 +1,27 @@ +export 'quantus_button.dart'; +export 'skeleton.dart'; +export 'quantus_text_field.dart'; +export 'quantus_badge.dart'; +export 'quantus_data_row.dart'; +export 'quantus_step_row.dart'; +export 'quantus_banner.dart'; +export 'quantus_dialog.dart'; +export 'quantus_action_sheet.dart'; +export 'loader.dart'; +export 'quantus_icon.dart'; +export 'quantus_icon_button.dart'; +export 'glass_button_base.dart'; +export 'decoded_call_view.dart'; +export 'v2_app_bar.dart'; +export 'bottom_sheet_container.dart'; +export 'scaffold_base.dart'; +export 'scaffold_base_bottom_content.dart'; +export 'menu_divider.dart'; +export 'confirm_action_sheet.dart'; +export 'base_background.dart'; +export 'detail_summary_row.dart'; +export 'quantus_qr.dart'; +export 'segmented_controls.dart'; +export 'back_button.dart'; +export 'animated_ur_qr.dart'; +export 'mnemonic_grid.dart'; diff --git a/cold-wallet-app/lib/components/loader.dart b/quantus_sdk/lib/src/ui/components/loader.dart similarity index 72% rename from cold-wallet-app/lib/components/loader.dart rename to quantus_sdk/lib/src/ui/components/loader.dart index 654ec4cd3..b055cb3bb 100644 --- a/cold-wallet-app/lib/components/loader.dart +++ b/quantus_sdk/lib/src/ui/components/loader.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/shared/context_extensions.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class Loader extends StatelessWidget { final Color? color; @@ -15,7 +14,7 @@ class Loader extends StatelessWidget { return SizedBox( height: effectiveSize, width: effectiveSize, - child: CircularProgressIndicator(strokeWidth: 2, color: color ?? context.colors.textSecondary), + child: CircularProgressIndicator(strokeWidth: 2, color: color ?? context.colorsV3.textMuted), ); } } diff --git a/mobile-app/lib/v2/components/menu_divider.dart b/quantus_sdk/lib/src/ui/components/menu_divider.dart similarity index 71% rename from mobile-app/lib/v2/components/menu_divider.dart rename to quantus_sdk/lib/src/ui/components/menu_divider.dart index 1d1ef50e7..3479fc7c8 100644 --- a/mobile-app/lib/v2/components/menu_divider.dart +++ b/quantus_sdk/lib/src/ui/components/menu_divider.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; /// Thin hairline divider used between rows in account/menu lists. class MenuDivider extends StatelessWidget { const MenuDivider({super.key}); @override - Widget build(BuildContext context) => Divider(color: context.colors.toasterBackground, height: 1); + Widget build(BuildContext context) => Divider(color: context.colorsV3.borderHairline, height: 1); } diff --git a/mobile-app/lib/features/components/mnemonic_grid.dart b/quantus_sdk/lib/src/ui/components/mnemonic_grid.dart similarity index 81% rename from mobile-app/lib/features/components/mnemonic_grid.dart rename to quantus_sdk/lib/src/ui/components/mnemonic_grid.dart index f376727c1..ef1fbce59 100644 --- a/mobile-app/lib/features/components/mnemonic_grid.dart +++ b/quantus_sdk/lib/src/ui/components/mnemonic_grid.dart @@ -1,9 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_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:quantus_sdk/quantus_sdk.dart'; class MnemonicGrid extends StatelessWidget { final List words; @@ -50,15 +48,18 @@ class MnemonicGrid extends StatelessWidget { final double blur = isRevealed ? 0 : 5; final effectiveWord = isRevealed ? word : 'blurred'; + final colors = context.colorsV3; + final text = context.themeTextV3; + return Container( padding: padding, - decoration: BoxDecoration(color: context.colors.surfaceDeep, borderRadius: BorderRadius.circular(14)), + decoration: BoxDecoration(color: colors.bgSurface, borderRadius: context.radiusV3.mdBorder), child: Row( children: [ Text( '$index', textAlign: TextAlign.left, - style: context.themeText.detail?.copyWith(color: context.colors.textTertiary), + style: text.caption.copyWith(color: colors.textMuted), ), const SizedBox(width: 8), ImageFiltered( @@ -66,7 +67,7 @@ class MnemonicGrid extends StatelessWidget { child: Text( effectiveWord, textAlign: TextAlign.left, - style: context.themeText.detail?.copyWith(color: context.colors.checksum), + style: text.caption.copyWith(color: colors.semanticLilac), ), ), ], diff --git a/quantus_sdk/lib/src/ui/components/quantus_action_sheet.dart b/quantus_sdk/lib/src/ui/components/quantus_action_sheet.dart new file mode 100644 index 000000000..e756607e4 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_action_sheet.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// One row in a [QuantusActionSheet]. +/// +/// Presentational only. Callers pass an already-resolved [label]. +class QuantusActionSheetItem { + final String label; + final VoidCallback onTap; + final bool isDestructive; + + const QuantusActionSheetItem({required this.label, required this.onTap, this.isDestructive = false}); +} + +/// Shared v3 kebab/action sheet: item list, ember destructive last, divider above it. +/// +/// Presentational only. Overlay presentation is [showQuantusActionSheet]. +class QuantusActionSheet extends StatelessWidget { + final List items; + + const QuantusActionSheet({super.key, required this.items}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: colors.bgSurface, + borderRadius: context.radiusV3.lgBorder, + border: Border.all(color: colors.borderEmphasis, width: 1), + ), + clipBehavior: Clip.antiAlias, + child: Material( + color: Colors.transparent, + child: SafeArea( + top: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: _ActionSheetItems(items: items), + ), + ), + ), + ); + } +} + +/// Shows a [QuantusActionSheet] as a modal bottom sheet. +/// +/// Tapping an item pops the sheet, then runs that item's [QuantusActionSheetItem.onTap]. +/// Barrier tap or drag-down dismisses without running an action. +Future showQuantusActionSheet(BuildContext context, {required List items}) { + return BottomSheetContainer.show( + context, + builder: (sheetContext) { + return QuantusActionSheet( + items: [ + for (final item in items) + QuantusActionSheetItem( + label: item.label, + isDestructive: item.isDestructive, + onTap: () { + Navigator.pop(sheetContext); + item.onTap(); + }, + ), + ], + ); + }, + ); +} + +class _ActionSheetItems extends StatelessWidget { + final List items; + + const _ActionSheetItems({required this.items}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (var i = 0; i < items.length; i++) ...[ + if (_isFirstDestructive(i)) const _ActionSheetDivider(), + _ActionSheetRow(item: items[i]), + ], + ], + ); + } + + bool _isFirstDestructive(int index) { + if (!items[index].isDestructive) return false; + return !items.take(index).any((item) => item.isDestructive); + } +} + +class _ActionSheetRow extends StatelessWidget { + final QuantusActionSheetItem item; + + const _ActionSheetRow({required this.item}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final color = item.isDestructive ? colors.semanticEmber : colors.textContent; + + return InkWell( + onTap: item.onTap, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Text(item.label, style: context.themeTextV3.bodyEmphasis.copyWith(color: color)), + ), + ); + } +} + +class _ActionSheetDivider extends StatelessWidget { + const _ActionSheetDivider(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Divider(height: 1, thickness: 1, color: context.colorsV3.borderHairline), + ); + } +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_badge.dart b/quantus_sdk/lib/src/ui/components/quantus_badge.dart new file mode 100644 index 000000000..26a056d49 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_badge.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Status-grammar tones for [QuantusBadge]. +/// +/// Glacier waits, sage settles, ember fails, sand warns. Lilac is checkphrase +/// only. Flare is reserved for action emphasis. +enum BadgeTone { neutral, sage, sand, ember, glacier, lilac, flare } + +/// Stroke-only status badge: mono uppercase, tracked, tone on stroke and text. +/// +/// Presentational only. Callers pass an already-resolved [label]. +class QuantusBadge extends StatelessWidget { + final String label; + final BadgeTone tone; + + const QuantusBadge({super.key, required this.label, this.tone = BadgeTone.neutral}); + + @override + Widget build(BuildContext context) { + final color = _toneColor(context.colorsV3, tone); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + borderRadius: context.radiusV3.xsBorder, + border: Border.all(color: color, width: 1), + ), + child: Text(label.toUpperCase(), style: _labelStyle(context, color), maxLines: 1, softWrap: false), + ); + } + + /// Figma Badge type: Geist Mono Medium 10, tracking 0.8, from Label / Monogram. + TextStyle _labelStyle(BuildContext context, Color color) { + return context.themeTextV3.labelMonogram.copyWith(fontSize: 10, letterSpacing: 0.8, color: color); + } +} + +Color _toneColor(AppColorsV3 colors, BadgeTone tone) { + return switch (tone) { + BadgeTone.neutral => colors.textMuted, + BadgeTone.sage => colors.semanticSage, + BadgeTone.sand => colors.semanticSand, + BadgeTone.ember => colors.semanticEmber, + BadgeTone.glacier => colors.semanticGlacier, + BadgeTone.lilac => colors.semanticLilac, + BadgeTone.flare => colors.accentFlare, + }; +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_banner.dart b/quantus_sdk/lib/src/ui/components/quantus_banner.dart new file mode 100644 index 000000000..10656df18 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_banner.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Status grammar for [QuantusBanner]. +/// +/// Sage settles, sand warns, ember fails, glacier waits. +enum BannerTone { sage, sand, ember, glacier } + +/// Shared v3 status banner: tone gradient fill, 10% stroke, 40px icon circle. +/// +/// Presentational only. Callers pass an already-resolved [message]. +class QuantusBanner extends StatelessWidget { + final BannerTone tone; + final String message; + final Widget? leading; + + const QuantusBanner({super.key, required this.tone, required this.message, this.leading}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final foreground = _toneColor(colors, tone); + // Dark stop is tone mixed toward void so we do not add colors absent from the Figma table. + final fillEnd = Color.lerp(foreground, colors.bgVoid, 0.75)!; + + return Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 60), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [foreground.useOpacity(0.25), fillEnd.useOpacity(0.25)], + ), + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: foreground.useOpacity(0.10), width: 1), + ), + child: Row( + children: [ + _BannerIcon( + foreground: foreground, + child: leading ?? _DefaultBannerGlyph(foreground: foreground), + ), + const SizedBox(width: 12), + Expanded( + child: Text(message, style: context.themeTextV3.caption.copyWith(color: foreground, height: 1.5)), + ), + ], + ), + ); + } +} + +class _BannerIcon extends StatelessWidget { + final Color foreground; + final Widget child; + + const _BannerIcon({required this.foreground, required this.child}); + + @override + Widget build(BuildContext context) { + return Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: foreground.useOpacity(0.25), width: 1), + ), + child: child, + ); + } +} + +class _DefaultBannerGlyph extends StatelessWidget { + final Color foreground; + + const _DefaultBannerGlyph({required this.foreground}); + + @override + Widget build(BuildContext context) { + return Text('!', style: context.themeTextV3.body.copyWith(color: foreground, height: 1)); + } +} + +Color _toneColor(AppColorsV3 colors, BannerTone tone) { + return switch (tone) { + BannerTone.sage => colors.semanticSage, + BannerTone.sand => colors.semanticSand, + BannerTone.ember => colors.semanticEmber, + BannerTone.glacier => colors.semanticGlacier, + }; +} diff --git a/mobile-app/lib/v2/components/quantus_button.dart b/quantus_sdk/lib/src/ui/components/quantus_button.dart similarity index 50% rename from mobile-app/lib/v2/components/quantus_button.dart rename to quantus_sdk/lib/src/ui/components/quantus_button.dart index bd1e5b630..2afd70d68 100644 --- a/mobile-app/lib/v2/components/quantus_button.dart +++ b/quantus_sdk/lib/src/ui/components/quantus_button.dart @@ -1,11 +1,22 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/v2/components/loader.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; -enum ButtonVariant { transparent, primary, secondary, danger, success, outline, glass, underline } +enum ButtonVariant { + transparent, + primary, + secondary, + danger, + outline, + glass, + underline, + + /// Not-yet-ready primary: surface fill and emphasis border. + staged, + + /// Escape route: no fill or border, muted label. + ghost, +} enum IconPlacement { leading, trailing, top } @@ -15,28 +26,25 @@ class QuantusButton extends StatelessWidget { final Widget? _icon; final IconPlacement _iconPlacement; final TextStyle? _textStyle; + final BorderRadius? borderRadius; final VoidCallback? onTap; final bool isLoading; final double? width; final EdgeInsets padding; final ButtonVariant variant; final bool isDisabled; - final double borderRadius; final bool centered; - static const double defaultBorderRadius = 14.0; - static const double buttonFontSize = 16.0; - const QuantusButton({ super.key, required Widget this.child, this.onTap, + this.borderRadius, this.isLoading = false, this.width = double.infinity, - this.padding = const EdgeInsets.symmetric(horizontal: 12, vertical: 12), + this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 16), this.variant = ButtonVariant.primary, this.isDisabled = false, - this.borderRadius = defaultBorderRadius, this.centered = true, }) : _label = null, _icon = null, @@ -50,13 +58,13 @@ class QuantusButton extends StatelessWidget { Widget? icon, IconPlacement iconPlacement = IconPlacement.trailing, this.onTap, + this.borderRadius, this.isLoading = false, this.width = double.infinity, - this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 12), + this.padding = const EdgeInsets.symmetric(horizontal: 40, vertical: 16), TextStyle? textStyle, this.variant = ButtonVariant.primary, this.isDisabled = false, - this.borderRadius = defaultBorderRadius, this.centered = true, }) : _label = label, _icon = icon, @@ -67,60 +75,15 @@ class QuantusButton extends StatelessWidget { @override Widget build(BuildContext context) { final bool disabled = onTap == null || isLoading || isDisabled; - final visibility = disabled ? 0.5 : 1.0; - final buttonContent = _buildContent(context, variant: variant, disabled: disabled); - final borderRadius = BorderRadius.circular(this.borderRadius); - final basicBorder = BorderSide(color: context.colors.borderButton, width: 1); - - Color? buttonDecorationColor; - LinearGradient? buttonDecorationGradient; - BorderSide borderSide = BorderSide.none; - - switch (variant) { - case ButtonVariant.primary: - buttonDecorationColor = context.colors.accentOrange; - break; - - case ButtonVariant.secondary: - buttonDecorationGradient = LinearGradient( - transform: const GradientRotation(90 * math.pi / 180), - colors: [context.colors.surfaceDeep, context.colors.sheetBackground], - stops: [0.0, 1.0], - ); - borderSide = basicBorder.copyWith(color: context.colors.borderButton.useOpacity(0.5)); - break; - - case ButtonVariant.danger: - buttonDecorationColor = context.colors.buttonDanger; - break; - - case ButtonVariant.success: - buttonDecorationColor = context.colors.success; - break; - - case ButtonVariant.transparent: - buttonDecorationColor = Colors.transparent; - break; - - case ButtonVariant.outline: - buttonDecorationColor = Colors.transparent; - borderSide = basicBorder; - break; - - case ButtonVariant.glass: - buttonDecorationColor = context.colors.surfaceGlass; - break; - - case ButtonVariant.underline: - buttonDecorationColor = Colors.transparent; - break; - } + final chrome = _chrome(context, disabled: disabled); + final buttonContent = _buildContent(context, textColor: chrome.textColor); + final borderRadius = this.borderRadius ?? context.radiusV3.mdBorder; if (variant == ButtonVariant.underline) { return InkWell( onTap: disabled ? null : onTap, child: Opacity( - opacity: visibility, + opacity: chrome.opacity, child: Container(width: width, padding: padding, child: buttonContent), ), ); @@ -129,14 +92,14 @@ class QuantusButton extends StatelessWidget { return InkWell( onTap: disabled ? null : onTap, child: Opacity( - opacity: visibility, + opacity: chrome.opacity, child: Container( width: width, padding: padding, decoration: ShapeDecoration( - color: disabled ? context.colors.sheetBackground : buttonDecorationColor, - gradient: disabled ? null : buttonDecorationGradient, - shape: RoundedRectangleBorder(borderRadius: borderRadius, side: disabled ? basicBorder : borderSide), + color: chrome.color, + gradient: chrome.gradient, + shape: RoundedRectangleBorder(borderRadius: borderRadius, side: chrome.borderSide), ), child: buttonContent, ), @@ -144,20 +107,76 @@ class QuantusButton extends StatelessWidget { ); } - Widget _buildContent(BuildContext context, {ButtonVariant variant = ButtonVariant.primary, bool disabled = false}) { - final textColor = switch (variant) { - ButtonVariant.primary => context.colors.background, - ButtonVariant.secondary => context.colors.textPrimary, - ButtonVariant.danger => context.colors.textPrimary, - ButtonVariant.transparent => context.colors.textPrimary, - ButtonVariant.success => context.colors.textPrimary, - ButtonVariant.outline => context.colors.textLabel, - ButtonVariant.glass => context.colors.textPrimary, - ButtonVariant.underline => context.colors.textMuted, + _ButtonChrome _chrome(BuildContext context, {required bool disabled}) { + final colors = context.colorsV3; + final basicBorder = BorderSide(color: colors.borderEmphasis, width: 1); + + if (disabled && (variant == ButtonVariant.primary || variant == ButtonVariant.staged)) { + return _ButtonChrome(color: colors.bgSurface2, textColor: colors.textMuted2); + } + + final enabled = switch (variant) { + ButtonVariant.primary => _ButtonChrome(color: colors.accentFlare, textColor: colors.textVoid), + ButtonVariant.staged => _ButtonChrome( + color: colors.bgSurface, + borderSide: basicBorder, + textColor: colors.textContent, + ), + ButtonVariant.ghost => _ButtonChrome( + color: Colors.transparent, + textColor: disabled ? colors.textMuted2 : colors.textMuted, + ), + ButtonVariant.secondary => _ButtonChrome( + gradient: LinearGradient( + transform: const GradientRotation(90 * math.pi / 180), + colors: [context.colors.surfaceDeep, context.colors.sheetBackground], + stops: [0.0, 1.0], + ), + borderSide: basicBorder.copyWith(color: context.colors.borderButton.useOpacity(0.5)), + textColor: colors.textContent, + ), + ButtonVariant.danger => _ButtonChrome(color: context.colors.buttonDanger, textColor: colors.textContent), + ButtonVariant.transparent => _ButtonChrome(color: Colors.transparent, textColor: colors.textContent), + ButtonVariant.outline => _ButtonChrome( + color: Colors.transparent, + borderSide: basicBorder, + textColor: colors.textContent, + ), + ButtonVariant.glass => _ButtonChrome(color: context.colors.surfaceGlass, textColor: colors.textContent), + ButtonVariant.underline => _ButtonChrome( + color: Colors.transparent, + opacity: disabled ? 0.5 : 1.0, + textColor: colors.textMuted, + ), }; + final usesLegacyDisabled = + disabled && + variant != ButtonVariant.primary && + variant != ButtonVariant.staged && + variant != ButtonVariant.ghost && + variant != ButtonVariant.underline; + if (usesLegacyDisabled) { + return _legacyDisabledChrome(context, basicBorder); + } + + return enabled; + } + + _ButtonChrome _legacyDisabledChrome(BuildContext context, BorderSide basicBorder) { + return _ButtonChrome( + color: context.colors.sheetBackground, + borderSide: basicBorder, + opacity: 0.5, + textColor: context.colors.textPrimary.useOpacity(0.5), + ); + } + + Widget _buildContent(BuildContext context, {required Color textColor}) { + final defaultTextStyle = context.themeTextV3.headingRow; + if (isLoading) { - final size = (_textStyle?.fontSize ?? buttonFontSize) + 6; + final size = (_textStyle?.fontSize ?? defaultTextStyle.fontSize!) + 6; return Center(child: Loader(size: size)); } @@ -165,12 +184,7 @@ class QuantusButton extends StatelessWidget { final effectiveTextStyle = variant == ButtonVariant.underline ? _underlineTextStyle(context, textColor) - : _textStyle ?? - context.themeText.paragraph!.copyWith( - fontSize: buttonFontSize, - color: disabled ? context.colors.textPrimary.useOpacity(0.5) : textColor, - fontWeight: FontWeight.w500, - ); + : _textStyle ?? defaultTextStyle.copyWith(color: textColor); Widget content; if (_iconPlacement == IconPlacement.top) { @@ -204,3 +218,19 @@ class QuantusButton extends StatelessWidget { return style.copyWith(decoration: TextDecoration.underline, decorationColor: style.color); } } + +class _ButtonChrome { + final Color? color; + final LinearGradient? gradient; + final BorderSide borderSide; + final double opacity; + final Color textColor; + + const _ButtonChrome({ + this.color, + this.gradient, + this.borderSide = BorderSide.none, + this.opacity = 1.0, + required this.textColor, + }); +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_data_row.dart b/quantus_sdk/lib/src/ui/components/quantus_data_row.dart new file mode 100644 index 000000000..b4b23e3e2 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_data_row.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Declared data row: mono label left, mono value right, hairline border. +/// +/// Figma Row / Spec. Presentational only. Callers pass already-resolved [label] and [value] strings. +/// Tappable rows pass [onTap] and an affordance [trailing] glyph. +class QuantusDataRow extends StatelessWidget { + final String label; + final String value; + final VoidCallback? onTap; + final Widget? trailing; + + const QuantusDataRow({super.key, required this.label, required this.value, this.onTap, this.trailing}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + + final row = Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 46), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13), + decoration: BoxDecoration( + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: colors.borderHairline, width: 1), + ), + child: Row( + children: [ + Text(label.toUpperCase(), style: _labelStyle(text, colors), maxLines: 1, softWrap: false), + const SizedBox(width: 12), + Expanded( + child: Text( + value, + style: text.dataAddress.copyWith(color: colors.textContent), + textAlign: TextAlign.right, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (trailing != null) ...[const SizedBox(width: 12), trailing!], + ], + ), + ); + + if (onTap == null) return row; + + return GestureDetector(onTap: onTap, behavior: HitTestBehavior.opaque, child: row); + } + + /// Figma Row / Spec label: Geist Mono Medium 10, tracking 1, from Label / Monogram. + TextStyle _labelStyle(AppTextThemeV3 text, AppColorsV3 colors) { + return text.labelMonogram.copyWith(fontSize: 10, letterSpacing: 1, color: colors.textMuted); + } +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_dialog.dart b/quantus_sdk/lib/src/ui/components/quantus_dialog.dart new file mode 100644 index 000000000..19cffbb11 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_dialog.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Shared v3 centered dialog: title, plain-consequence body, primary CTA, Cancel. +/// +/// Presentational only. Callers pass already-resolved [title], [body], +/// [actionLabel], and [cancelLabel] strings. Overlay presentation is +/// [showQuantusDialog]. +class QuantusDialog extends StatelessWidget { + final String title; + final String body; + final String actionLabel; + final String cancelLabel; + final bool isDestructive; + final VoidCallback? onAction; + final VoidCallback? onCancel; + + const QuantusDialog({ + super.key, + required this.title, + required this.body, + required this.actionLabel, + this.cancelLabel = 'Cancel', + this.isDestructive = false, + this.onAction, + this.onCancel, + }); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + + return Container( + width: double.infinity, + constraints: const BoxConstraints(maxWidth: 326), + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: colors.bgSurface, + borderRadius: context.radiusV3.lgBorder, + border: Border.all(color: colors.borderEmphasis, width: 1), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text(title, style: text.headingRow.copyWith(color: colors.textContent)), + const SizedBox(height: 10), + Text(body, style: text.body.copyWith(color: colors.textMuted, height: 1.55)), + const SizedBox(height: 18), + QuantusButton.simple( + label: actionLabel, + variant: isDestructive ? ButtonVariant.danger : ButtonVariant.primary, + onTap: onAction, + ), + const SizedBox(height: 12), + QuantusButton.simple(label: cancelLabel, variant: ButtonVariant.secondary, onTap: onCancel), + ], + ), + ); + } +} + +/// Shows a centered [QuantusDialog]. +/// +/// Returns `true` when the primary action is tapped, `false` when Cancel is +/// tapped or the barrier is dismissed. +Future showQuantusDialog( + BuildContext context, { + required String title, + required String body, + required String actionLabel, + String cancelLabel = 'Cancel', + bool isDestructive = false, + bool barrierDismissible = true, +}) async { + final confirmed = await showDialog( + context: context, + barrierDismissible: barrierDismissible, + builder: (dialogContext) { + return Dialog( + backgroundColor: Colors.transparent, + elevation: 0, + insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + child: QuantusDialog( + title: title, + body: body, + actionLabel: actionLabel, + cancelLabel: cancelLabel, + isDestructive: isDestructive, + onAction: () => Navigator.pop(dialogContext, true), + onCancel: () => Navigator.pop(dialogContext, false), + ), + ); + }, + ); + return confirmed ?? false; +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_icon.dart b/quantus_sdk/lib/src/ui/components/quantus_icon.dart new file mode 100644 index 000000000..abe8ef031 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_icon.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Glyphs from the v3 Figma interim icon set (Phosphor Light replacement pending). +enum QuantusIcons { + kebab('assets/icons/kebab.svg'), + chevronRight('assets/icons/chevron_right.svg'), + chevronLeft('assets/icons/chevron_left.svg'), + caretDown('assets/icons/caret_down.svg'), + plus('assets/icons/plus.svg'), + swapVertical('assets/icons/swap_vertical.svg'), + lock('assets/icons/lock.svg'); + + const QuantusIcons(this.assetPath); + + /// Package-relative SVG path for this glyph. + final String assetPath; +} + +/// Tinted 20px Figma glyph. Pass [color] to override [AppColorsV3.textMuted]. +class QuantusIcon extends StatelessWidget { + final QuantusIcons icon; + final double size; + final Color? color; + final String? semanticLabel; + + const QuantusIcon(this.icon, {super.key, this.size = 20, this.color, this.semanticLabel}); + + @override + Widget build(BuildContext context) { + final Color resolvedColor = color ?? context.colorsV3.textMuted; + + return SizedBox( + width: size, + height: size, + child: SvgPicture.asset( + icon.assetPath, + package: 'quantus_sdk', + width: size, + height: size, + fit: BoxFit.contain, + colorFilter: ColorFilter.mode(resolvedColor, BlendMode.srcIn), + excludeFromSemantics: semanticLabel == null, + semanticsLabel: semanticLabel, + ), + ); + } +} diff --git a/mobile-app/lib/v2/components/quantus_icon_button.dart b/quantus_sdk/lib/src/ui/components/quantus_icon_button.dart similarity index 96% rename from mobile-app/lib/v2/components/quantus_icon_button.dart rename to quantus_sdk/lib/src/ui/components/quantus_icon_button.dart index 3126d72ab..4772ce0b9 100644 --- a/mobile-app/lib/v2/components/quantus_icon_button.dart +++ b/quantus_sdk/lib/src/ui/components/quantus_icon_button.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/v2/components/glass_button_base.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; enum IconButtonShape { rounded, circular } diff --git a/mobile-app/lib/v2/components/quantus_qr.dart b/quantus_sdk/lib/src/ui/components/quantus_qr.dart similarity index 92% rename from mobile-app/lib/v2/components/quantus_qr.dart rename to quantus_sdk/lib/src/ui/components/quantus_qr.dart index 65c96b87a..69e82c2a0 100644 --- a/mobile-app/lib/v2/components/quantus_qr.dart +++ b/quantus_sdk/lib/src/ui/components/quantus_qr.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class QuantusQr extends StatelessWidget { final String accountId; @@ -22,7 +22,7 @@ class QuantusQr extends StatelessWidget { child: QrImageView( data: accountId, errorCorrectionLevel: QrErrorCorrectLevel.M, - embeddedImage: const AssetImage('assets/v2/uppercase_q_black_bg.png'), + embeddedImage: const AssetImage('assets/v2/uppercase_q_black_bg.png', package: 'quantus_sdk'), embeddedImageStyle: QrEmbeddedImageStyle(size: Size(qrLogoSize, qrLogoSize)), version: QrVersions.auto, size: qrSize, diff --git a/quantus_sdk/lib/src/ui/components/quantus_step_row.dart b/quantus_sdk/lib/src/ui/components/quantus_step_row.dart new file mode 100644 index 000000000..eb7d63b95 --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_step_row.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Tracking-timeline states for [QuantusStepRow]. +/// +/// Done settles, active is in flight, pending has not started, ended stopped. +enum StepRowState { done, active, pending, ended } + +/// Figma Block / Step Row. Presentational only. Callers pass already-resolved [label] and [substate]. +/// +/// Takes the available width. Parent owns [state]; Active spins the flare mark. +class QuantusStepRow extends StatefulWidget { + final StepRowState state; + final String label; + final String? substate; + + const QuantusStepRow({super.key, required this.state, required this.label, this.substate}); + + @override + State createState() => _QuantusStepRowState(); +} + +class _QuantusStepRowState extends State with SingleTickerProviderStateMixin { + static const _spinDuration = Duration(seconds: 1); + + late final AnimationController _spin; + + bool get _isActive => widget.state == StepRowState.active; + + @override + void initState() { + super.initState(); + _spin = AnimationController(vsync: this, duration: _spinDuration); + if (_isActive) _spin.repeat(); + } + + @override + void didUpdateWidget(QuantusStepRow oldWidget) { + super.didUpdateWidget(oldWidget); + if (_isActive) { + if (!_spin.isAnimating) _spin.repeat(); + } else if (_spin.isAnimating) { + _spin + ..stop() + ..reset(); + } + } + + @override + void dispose() { + _spin.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Row( + children: [ + _StepStatusMark(state: widget.state, turns: _spin), + const SizedBox(width: 14), + Expanded( + child: _StepCopy(state: widget.state, label: widget.label, substate: widget.substate), + ), + ], + ), + ), + ); + } +} + +class _StepStatusMark extends StatelessWidget { + static const _slot = 22.0; + + final StepRowState state; + final Animation turns; + + const _StepStatusMark({required this.state, required this.turns}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final mark = switch (state) { + StepRowState.pending => _PendingDot(color: colors.bgSurface2), + StepRowState.done => _Glyph(text: '✓', color: colors.semanticSage), + StepRowState.active => RotationTransition( + turns: turns, + child: _Glyph(text: '◌', color: colors.accentFlare), + ), + StepRowState.ended => _Glyph(text: '⊘', color: colors.semanticSand), + }; + + return SizedBox( + width: _slot, + height: _slot, + child: Center(child: mark), + ); + } +} + +class _Glyph extends StatelessWidget { + final String text; + final Color color; + + const _Glyph({required this.text, required this.color}); + + @override + Widget build(BuildContext context) { + return Text( + text, + style: TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 13, + fontWeight: FontWeight.w400, + height: 1, + color: color, + ), + ); + } +} + +class _PendingDot extends StatelessWidget { + final Color color; + + const _PendingDot({required this.color}); + + @override + Widget build(BuildContext context) { + return Container( + width: 5, + height: 5, + decoration: BoxDecoration(color: color, shape: BoxShape.circle), + ); + } +} + +class _StepCopy extends StatelessWidget { + final StepRowState state; + final String label; + final String? substate; + + const _StepCopy({required this.state, required this.label, this.substate}); + + bool get _showSubstate { + return (state == StepRowState.active || state == StepRowState.ended) && (substate?.isNotEmpty ?? false); + } + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final text = context.themeTextV3; + final emphasized = state == StepRowState.active || state == StepRowState.ended; + final labelStyle = (emphasized ? text.bodyEmphasis : text.body).copyWith( + color: emphasized ? colors.textContent : colors.textMuted2, + ); + final substateText = substate; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text(label, style: labelStyle, maxLines: 1, overflow: TextOverflow.ellipsis), + if (_showSubstate && substateText != null) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + substateText.toUpperCase(), + style: _substateStyle(text, colors), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } + + /// Figma substate microlabel: Geist Mono Regular 10, tracking 0.8, from Label / Monogram. + TextStyle _substateStyle(AppTextThemeV3 text, AppColorsV3 colors) { + return text.labelMonogram.copyWith( + fontSize: 10, + fontWeight: FontWeight.w400, + letterSpacing: 0.8, + color: colors.textMuted2, + ); + } +} diff --git a/quantus_sdk/lib/src/ui/components/quantus_text_field.dart b/quantus_sdk/lib/src/ui/components/quantus_text_field.dart new file mode 100644 index 000000000..e6d0650fc --- /dev/null +++ b/quantus_sdk/lib/src/ui/components/quantus_text_field.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +/// Shared v3 text field: default, focus, and inline error chrome. +/// +/// Presentational only. Callers pass already-resolved [hint] and [error] strings. +class QuantusTextField extends StatefulWidget { + final TextEditingController controller; + final String? hint; + final String? error; + final bool showClearButton; + final FocusNode? focusNode; + final ValueChanged? onChanged; + final bool enabled; + final bool obscureText; + final bool enableSuggestions; + final TextInputType? keyboardType; + final TextInputAction? textInputAction; + final TextCapitalization textCapitalization; + final bool autocorrect; + final ValueChanged? onSubmitted; + final Widget? trailing; + + const QuantusTextField({ + super.key, + required this.controller, + this.hint, + this.error, + this.showClearButton = false, + this.focusNode, + this.onChanged, + this.enabled = true, + this.obscureText = false, + this.enableSuggestions = true, + this.keyboardType, + this.textInputAction, + this.textCapitalization = TextCapitalization.none, + this.autocorrect = true, + this.onSubmitted, + this.trailing, + }); + + @override + State createState() => _QuantusTextFieldState(); +} + +class _QuantusTextFieldState extends State { + late FocusNode _focusNode; + late bool _ownsFocusNode; + + bool get _hasError => widget.error != null; + + @override + void initState() { + super.initState(); + _ownsFocusNode = widget.focusNode == null; + _focusNode = widget.focusNode ?? FocusNode(); + _focusNode.addListener(_rebuild); + widget.controller.addListener(_rebuild); + } + + @override + void didUpdateWidget(QuantusTextField oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.controller != widget.controller) { + oldWidget.controller.removeListener(_rebuild); + widget.controller.addListener(_rebuild); + } + if (oldWidget.focusNode != widget.focusNode) { + _focusNode.removeListener(_rebuild); + if (_ownsFocusNode) { + _focusNode.dispose(); + } + _ownsFocusNode = widget.focusNode == null; + _focusNode = widget.focusNode ?? FocusNode(); + _focusNode.addListener(_rebuild); + } + } + + @override + void dispose() { + widget.controller.removeListener(_rebuild); + _focusNode.removeListener(_rebuild); + if (_ownsFocusNode) { + _focusNode.dispose(); + } + super.dispose(); + } + + void _rebuild() => setState(() {}); + + Color _borderColor(AppColorsV3 colors) { + if (_hasError) return colors.semanticEmber.useOpacity(0.55); + if (_focusNode.hasFocus) return colors.accentFlare; + return colors.borderEmphasis; + } + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + final textStyle = context.themeTextV3.dataAddressLarge.copyWith(color: colors.textContent); + final hintStyle = context.themeTextV3.dataAddressLarge.copyWith(color: colors.textMuted); + final showClear = widget.showClearButton && widget.controller.text.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 60), + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: colors.bgSurface, + borderRadius: context.radiusV3.mdBorder, + border: Border.all(color: _borderColor(colors), width: 1), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: widget.controller, + focusNode: _focusNode, + enabled: widget.enabled, + obscureText: widget.obscureText, + enableSuggestions: widget.enableSuggestions, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + textCapitalization: widget.textCapitalization, + autocorrect: widget.autocorrect, + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + cursorColor: colors.accentFlare, + style: textStyle, + decoration: InputDecoration.collapsed(hintText: widget.hint, hintStyle: hintStyle), + ), + ), + if (showClear) ...[ + const SizedBox(width: 8), + _QuantusTextFieldClearButton(onTap: widget.controller.clear), + ], + if (widget.trailing != null) ...[const SizedBox(width: 8), widget.trailing!], + ], + ), + ), + if (widget.error != null) ...[ + const SizedBox(height: 8), + Text(widget.error!, style: context.themeTextV3.caption.copyWith(color: colors.semanticEmber)), + ], + ], + ); + } +} + +class _QuantusTextFieldClearButton extends StatelessWidget { + final VoidCallback onTap; + + const _QuantusTextFieldClearButton({required this.onTap}); + + @override + Widget build(BuildContext context) { + final colors = context.colorsV3; + const size = 20.0; + + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: Container( + width: size, + height: size, + alignment: Alignment.center, + decoration: BoxDecoration( + color: colors.textMuted, + border: Border.all(color: colors.borderHairline, width: 0.5), + borderRadius: BorderRadius.circular(16), + ), + child: Icon(Icons.close, size: 12, color: colors.textVoid), + ), + ); + } +} diff --git a/cold-wallet-app/lib/components/scaffold_base.dart b/quantus_sdk/lib/src/ui/components/scaffold_base.dart similarity index 90% rename from cold-wallet-app/lib/components/scaffold_base.dart rename to quantus_sdk/lib/src/ui/components/scaffold_base.dart index a3201458d..b931f557e 100644 --- a/cold-wallet-app/lib/components/scaffold_base.dart +++ b/quantus_sdk/lib/src/ui/components/scaffold_base.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/components/base_background.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class ScaffoldBase extends StatelessWidget { final Widget? mainContent; final Widget? bottomContent; final List? slivers; final Widget? appBar; + final Widget? networkBanner; final ScrollController? scrollController; final ScrollPhysics? scrollPhysics; final RefreshCallback? onRefresh; @@ -19,6 +19,7 @@ class ScaffoldBase extends StatelessWidget { const ScaffoldBase({ super.key, this.appBar, + this.networkBanner, this.padding = defaultPadding, this.backgroundWidget, this.bottomContent, @@ -32,6 +33,7 @@ class ScaffoldBase extends StatelessWidget { const ScaffoldBase.refreshable({ super.key, this.appBar, + this.networkBanner, this.padding = defaultPadding, this.backgroundWidget, this.scrollController, @@ -43,10 +45,11 @@ class ScaffoldBase extends StatelessWidget { @override Widget build(BuildContext context) { - final colors = context.colors; + final colors = context.colorsV3; Widget bodyContent = Column( children: [ + ?networkBanner, if (appBar != null) Padding(padding: padding, child: appBar!), Expanded(child: _buildChild(colors)), ], @@ -63,13 +66,13 @@ class ScaffoldBase extends StatelessWidget { return Scaffold(body: scaffoldBody); } - Widget _buildChild(AppColorsV2 colors) { + Widget _buildChild(AppColorsV3 colors) { // Scrollable with refresh (CustomScrollView with slivers) if (onRefresh != null && slivers != null) { return RefreshIndicator( onRefresh: onRefresh!, - color: colors.textPrimary, - backgroundColor: colors.surface, + color: colors.textContent, + backgroundColor: colors.bgSurface, child: Column( children: [ Expanded( diff --git a/cold-wallet-app/lib/components/scaffold_base_bottom_content.dart b/quantus_sdk/lib/src/ui/components/scaffold_base_bottom_content.dart similarity index 73% rename from cold-wallet-app/lib/components/scaffold_base_bottom_content.dart rename to quantus_sdk/lib/src/ui/components/scaffold_base_bottom_content.dart index 801332f0c..2484f9e31 100644 --- a/cold-wallet-app/lib/components/scaffold_base_bottom_content.dart +++ b/quantus_sdk/lib/src/ui/components/scaffold_base_bottom_content.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class ScaffoldBaseBottomContent extends StatelessWidget { final Widget child; @@ -13,7 +13,7 @@ class ScaffoldBaseBottomContent extends StatelessWidget { return Container( padding: padding, decoration: BoxDecoration( - border: Border(top: BorderSide(color: context.colors.surfaceDeep, width: 1)), + border: Border(top: BorderSide(color: context.colorsV3.borderHairline, width: 1)), ), child: child, ); diff --git a/mobile-app/lib/v2/components/segmented_controls.dart b/quantus_sdk/lib/src/ui/components/segmented_controls.dart similarity index 96% rename from mobile-app/lib/v2/components/segmented_controls.dart rename to quantus_sdk/lib/src/ui/components/segmented_controls.dart index 7e0041ffc..81804da76 100644 --- a/mobile-app/lib/v2/components/segmented_controls.dart +++ b/quantus_sdk/lib/src/ui/components/segmented_controls.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class SegmentedControls extends StatelessWidget { final List> items; diff --git a/mobile-app/lib/features/components/skeleton.dart b/quantus_sdk/lib/src/ui/components/skeleton.dart similarity index 62% rename from mobile-app/lib/features/components/skeleton.dart rename to quantus_sdk/lib/src/ui/components/skeleton.dart index de9b72eed..e398a0779 100644 --- a/mobile-app/lib/features/components/skeleton.dart +++ b/quantus_sdk/lib/src/ui/components/skeleton.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; -/// A skeleton widget with shimmer animation for loading states +/// Shimmering loading bone. Compose rows and screens from sized instances. class Skeleton extends StatefulWidget { final double? width; final double? height; @@ -13,7 +12,7 @@ class Skeleton extends StatefulWidget { const Skeleton({super.key, this.width, this.height = 16, this.borderRadius, this.duration = defaultDuration}); - /// Creates a circular skeleton (useful for avatars) + /// Circular bone for avatars and icon placeholders. Skeleton.circular({super.key, required double size, this.duration = defaultDuration}) : width = size, height = size, @@ -46,7 +45,8 @@ class _SkeletonState extends State with SingleTickerProviderStateMixin @override Widget build(BuildContext context) { - final borderRadius = widget.borderRadius ?? BorderRadius.circular(4); + final colors = context.colorsV3; + final borderRadius = widget.borderRadius ?? context.radiusV3.xsBorder; return AnimatedBuilder( animation: _animation, @@ -54,7 +54,7 @@ class _SkeletonState extends State with SingleTickerProviderStateMixin return Container( width: widget.width, height: widget.height, - decoration: BoxDecoration(borderRadius: borderRadius, color: context.colors.skeletonBase), + decoration: BoxDecoration(borderRadius: borderRadius, color: colors.bgSurface), child: Container( width: widget.width, height: widget.height, @@ -64,9 +64,9 @@ class _SkeletonState extends State with SingleTickerProviderStateMixin begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ - context.colors.skeletonHighlightA.useOpacity(0.2), - context.colors.skeletonHighlightB.useOpacity(0.2), - context.colors.skeletonHighlightA.useOpacity(0.2), + colors.bgVoid.useOpacity(0.2), + colors.textMuted2.useOpacity(0.2), + colors.bgVoid.useOpacity(0.2), ], stops: const [0.0, 0.5, 1.0], transform: _SlideGradientTransform(_animation.value), @@ -89,37 +89,3 @@ class _SlideGradientTransform extends GradientTransform { return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); } } - -class TxItemSkeleton extends StatelessWidget { - const TxItemSkeleton({super.key}); - - @override - Widget build(BuildContext context) { - const double txItemHeight = 32.0; - const double txItemDetailHeight = 12.0; - - return const Row( - children: [ - Skeleton(width: txItemHeight, height: txItemHeight), - SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Skeleton(width: 64, height: txItemDetailHeight), - SizedBox(height: 6), - Skeleton(width: 52, height: txItemDetailHeight), - ], - ), - Spacer(), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Skeleton(width: 100, height: txItemDetailHeight), - SizedBox(height: 6), - Skeleton(width: 88, height: txItemDetailHeight), - ], - ), - ], - ); - } -} diff --git a/mobile-app/lib/v2/components/toaster.dart b/quantus_sdk/lib/src/ui/components/toaster.dart similarity index 82% rename from mobile-app/lib/v2/components/toaster.dart rename to quantus_sdk/lib/src/ui/components/toaster.dart index 2e9e290df..4b0e54baf 100644 --- a/mobile-app/lib/v2/components/toaster.dart +++ b/quantus_sdk/lib/src/ui/components/toaster.dart @@ -1,7 +1,5 @@ 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'; -import 'package:resonance_network_wallet/shared/extensions/media_query_data_extension.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class Toaster extends StatelessWidget { final String message; diff --git a/mobile-app/lib/v2/components/toaster_helper.dart b/quantus_sdk/lib/src/ui/components/toaster_helper.dart similarity index 94% rename from mobile-app/lib/v2/components/toaster_helper.dart rename to quantus_sdk/lib/src/ui/components/toaster_helper.dart index f877689f0..625ade4b6 100644 --- a/mobile-app/lib/v2/components/toaster_helper.dart +++ b/quantus_sdk/lib/src/ui/components/toaster_helper.dart @@ -1,8 +1,8 @@ import 'package:flash/flash.dart'; import 'package:flash/flash_helper.dart'; import 'package:flutter/material.dart'; -import 'package:resonance_network_wallet/v2/components/toaster.dart'; -import 'package:resonance_network_wallet/v2/theme/app_colors.dart'; +import 'package:quantus_sdk/src/ui/components/toaster.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; Future showToaster( BuildContext context, { diff --git a/cold-wallet-app/lib/components/v2_app_bar.dart b/quantus_sdk/lib/src/ui/components/v2_app_bar.dart similarity index 70% rename from cold-wallet-app/lib/components/v2_app_bar.dart rename to quantus_sdk/lib/src/ui/components/v2_app_bar.dart index be416600c..35de52882 100644 --- a/cold-wallet-app/lib/components/v2_app_bar.dart +++ b/quantus_sdk/lib/src/ui/components/v2_app_bar.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:quantus_cold_wallet/components/back_button.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; class V2AppBar extends StatelessWidget { final String title; @@ -21,8 +19,8 @@ class V2AppBar extends StatelessWidget { @override Widget build(BuildContext context) { - final colors = context.colors; - final text = context.themeText; + final colors = context.colorsV3; + final text = context.themeTextV3; Widget leftWidget = leading ?? (showBackButton ? const AppBackButton() : const SizedBox(width: 24)); Widget rightWidget = trailing ?? const SizedBox(width: 24); @@ -33,7 +31,7 @@ class V2AppBar extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ leftWidget, - Text(title, style: text.smallTitle?.copyWith(color: colors.textPrimary, fontSize: 20)), + Text(title, style: text.titleScreen.copyWith(color: colors.textContent)), rightWidget, ], ), diff --git a/quantus_sdk/lib/src/ui/index.dart b/quantus_sdk/lib/src/ui/index.dart new file mode 100644 index 000000000..c92b15cd2 --- /dev/null +++ b/quantus_sdk/lib/src/ui/index.dart @@ -0,0 +1,2 @@ +export 'components/index.dart'; +export 'themes/index.dart'; diff --git a/mobile-app/lib/v2/theme/app_colors.dart b/quantus_sdk/lib/src/ui/themes/app_colors.dart similarity index 72% rename from mobile-app/lib/v2/theme/app_colors.dart rename to quantus_sdk/lib/src/ui/themes/app_colors.dart index f1e3cae09..38326aaa3 100644 --- a/mobile-app/lib/v2/theme/app_colors.dart +++ b/quantus_sdk/lib/src/ui/themes/app_colors.dart @@ -372,3 +372,166 @@ class AppColorsV2 extends ThemeExtension { extension AppColorsV2Extension on BuildContext { AppColorsV2 get colors => Theme.of(this).extension()!; } + +/// Quantus App v3 color tokens, mapped 1:1 from Figma SYSTEM / Color Tokens. +/// +/// Do not add colors that are not in the Figma table. Derive alpha with [Color.useOpacity] +/// at the call site when a widget needs a tint of an existing token. +@immutable +class AppColorsV3 extends ThemeExtension { + /// Screen background. Every screen base. Nothing else. + final Color bgVoid; + + /// Anything on the screen: cards, sheets, rows. This is elevation. + final Color bgSurface; + + /// Anything on a surface: inputs in cards, skeletons, disabled CTAs. + final Color bgSurface2; + + /// Default text. Titles, amounts, body. Warm, not pure white. + final Color textContent; + + /// Void text. Used for text on accent color. + final Color textVoid; + + /// Secondary. Darkest gray legal on small text (AA floor). + final Color textMuted; + + /// Tertiary. Large or decorative only. Never on small labels. + final Color textMuted2; + + /// Rare deliberate emphasis. Using it often means something is wrong. + final Color textWhite; + + /// Actions and CTAs only. Never selection, identity, or data. + final Color accentFlare; + + /// Settles. Success, confirmed, guaranteed minimum, funds safe. + final Color semanticSage; + + /// Warns. Expired, attention, advisory. Not an error. + final Color semanticSand; + + /// Fails. Errors, destructive actions. Large text only on dark. + final Color semanticEmber; + + /// Waits. Pending, suspended, reversible, in-between. + final Color semanticGlacier; + + /// Checkphrases and recovery/mnemonic words. Nothing else wears lilac. + final Color semanticLilac; + + /// Default border. All cards, rows, dividers. White at 7%. + final Color borderHairline; + + /// Selected or focused containers. The loudest border allowed. White at 12%. + final Color borderEmphasis; + + const AppColorsV3({ + required this.bgVoid, + required this.bgSurface, + required this.bgSurface2, + required this.textContent, + required this.textVoid, + required this.textMuted, + required this.textMuted2, + required this.textWhite, + required this.accentFlare, + required this.semanticSage, + required this.semanticSand, + required this.semanticEmber, + required this.semanticGlacier, + required this.semanticLilac, + required this.borderHairline, + required this.borderEmphasis, + }); + + /// Dark palette from Figma SYSTEM / Color Tokens (Table). + /// + /// Border alphas match [Color.useOpacity]: 7% → `0x12`, 12% → `0x1F`. + const AppColorsV3.dark() + : this( + bgVoid: const Color(0xFF0E0E0E), + bgSurface: const Color(0xFF181818), + bgSurface2: const Color(0xFF222222), + textContent: const Color(0xFFE8E6E0), + textVoid: const Color(0xFF0E0E0E), + textMuted: const Color(0xFF8A8784), + textMuted2: const Color(0xFF6B6966), + textWhite: const Color(0xFFFFFFFF), + accentFlare: const Color(0xFFFF6B35), + semanticSage: const Color(0xFF6DBF8A), + semanticSand: const Color(0xFFD9BC8A), + semanticEmber: const Color(0xFFC94F3A), + semanticGlacier: const Color(0xFFA8D8E8), + semanticLilac: const Color(0xFFC4B1FC), + borderHairline: const Color(0x12FFFFFF), + borderEmphasis: const Color(0x1FFFFFFF), + ); + + @override + AppColorsV3 copyWith({ + Color? bgVoid, + Color? bgSurface, + Color? bgSurface2, + Color? textContent, + Color? textVoid, + Color? textMuted, + Color? textMuted2, + Color? textWhite, + Color? accentFlare, + Color? semanticSage, + Color? semanticSand, + Color? semanticEmber, + Color? semanticGlacier, + Color? semanticLilac, + Color? borderHairline, + Color? borderEmphasis, + }) { + return AppColorsV3( + bgVoid: bgVoid ?? this.bgVoid, + bgSurface: bgSurface ?? this.bgSurface, + bgSurface2: bgSurface2 ?? this.bgSurface2, + textContent: textContent ?? this.textContent, + textVoid: textVoid ?? this.textVoid, + textMuted: textMuted ?? this.textMuted, + textMuted2: textMuted2 ?? this.textMuted2, + textWhite: textWhite ?? this.textWhite, + accentFlare: accentFlare ?? this.accentFlare, + semanticSage: semanticSage ?? this.semanticSage, + semanticSand: semanticSand ?? this.semanticSand, + semanticEmber: semanticEmber ?? this.semanticEmber, + semanticGlacier: semanticGlacier ?? this.semanticGlacier, + semanticLilac: semanticLilac ?? this.semanticLilac, + borderHairline: borderHairline ?? this.borderHairline, + borderEmphasis: borderEmphasis ?? this.borderEmphasis, + ); + } + + @override + AppColorsV3 lerp(AppColorsV3? other, double t) { + if (other is! AppColorsV3) return this; + return AppColorsV3( + bgVoid: Color.lerp(bgVoid, other.bgVoid, t) ?? bgVoid, + bgSurface: Color.lerp(bgSurface, other.bgSurface, t) ?? bgSurface, + bgSurface2: Color.lerp(bgSurface2, other.bgSurface2, t) ?? bgSurface2, + textContent: Color.lerp(textContent, other.textContent, t) ?? textContent, + textVoid: Color.lerp(textVoid, other.textVoid, t) ?? textVoid, + textMuted: Color.lerp(textMuted, other.textMuted, t) ?? textMuted, + textMuted2: Color.lerp(textMuted2, other.textMuted2, t) ?? textMuted2, + textWhite: Color.lerp(textWhite, other.textWhite, t) ?? textWhite, + accentFlare: Color.lerp(accentFlare, other.accentFlare, t) ?? accentFlare, + semanticSage: Color.lerp(semanticSage, other.semanticSage, t) ?? semanticSage, + semanticSand: Color.lerp(semanticSand, other.semanticSand, t) ?? semanticSand, + semanticEmber: Color.lerp(semanticEmber, other.semanticEmber, t) ?? semanticEmber, + semanticGlacier: Color.lerp(semanticGlacier, other.semanticGlacier, t) ?? semanticGlacier, + semanticLilac: Color.lerp(semanticLilac, other.semanticLilac, t) ?? semanticLilac, + borderHairline: Color.lerp(borderHairline, other.borderHairline, t) ?? borderHairline, + borderEmphasis: Color.lerp(borderEmphasis, other.borderEmphasis, t) ?? borderEmphasis, + ); + } +} + +extension AppColorsV3Extension on BuildContext { + AppColorsV3 get colorsV3 => Theme.of(this).extension()!; +} diff --git a/quantus_sdk/lib/src/ui/themes/app_radius.dart b/quantus_sdk/lib/src/ui/themes/app_radius.dart new file mode 100644 index 000000000..cb873eef7 --- /dev/null +++ b/quantus_sdk/lib/src/ui/themes/app_radius.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +/// Quantus App v3 radius tokens, mapped 1:1 from Figma SYSTEM / Radius & Rules. +/// +/// Mobile-app law. Web surfaces stay zero-radius. +@immutable +class AppRadiusV3 extends ThemeExtension { + /// Tags, micro elements. Figma r-xs. + final double xs; + + /// Chips, small tiles. Figma r-sm. + final double sm; + + /// Buttons, inputs, cards, rows. Figma r-md. + final double md; + + /// Bottom sheets, modals. Figma r-lg. + final double lg; + + /// Full-round CTAs (height/2). Figma pill. + final double pill; + + const AppRadiusV3({required this.xs, required this.sm, required this.md, required this.lg, required this.pill}); + + /// Mobile radius scale from Figma SYSTEM / Radius Tokens. + const AppRadiusV3.standard() : this(xs: 4, sm: 8, md: 14, lg: 32, pill: 36); + + BorderRadius get xsBorder => BorderRadius.circular(xs); + BorderRadius get smBorder => BorderRadius.circular(sm); + BorderRadius get mdBorder => BorderRadius.circular(md); + BorderRadius get lgBorder => BorderRadius.circular(lg); + BorderRadius get pillBorder => BorderRadius.circular(pill); + + @override + AppRadiusV3 copyWith({double? xs, double? sm, double? md, double? lg, double? pill}) { + return AppRadiusV3( + xs: xs ?? this.xs, + sm: sm ?? this.sm, + md: md ?? this.md, + lg: lg ?? this.lg, + pill: pill ?? this.pill, + ); + } + + @override + AppRadiusV3 lerp(AppRadiusV3? other, double t) { + if (other is! AppRadiusV3) return this; + double l(double a, double b) => a + (b - a) * t; + return AppRadiusV3( + xs: l(xs, other.xs), + sm: l(sm, other.sm), + md: l(md, other.md), + lg: l(lg, other.lg), + pill: l(pill, other.pill), + ); + } +} + +extension AppRadiusV3Extension on BuildContext { + AppRadiusV3 get radiusV3 => Theme.of(this).extension()!; +} diff --git a/cold-wallet-app/lib/theme/app_spacing.dart b/quantus_sdk/lib/src/ui/themes/app_spacing.dart similarity index 100% rename from cold-wallet-app/lib/theme/app_spacing.dart rename to quantus_sdk/lib/src/ui/themes/app_spacing.dart diff --git a/cold-wallet-app/lib/theme/app_text_styles.dart b/quantus_sdk/lib/src/ui/themes/app_text_styles.dart similarity index 53% rename from cold-wallet-app/lib/theme/app_text_styles.dart rename to quantus_sdk/lib/src/ui/themes/app_text_styles.dart index 441eb64c4..5d5bd3478 100644 --- a/cold-wallet-app/lib/theme/app_text_styles.dart +++ b/quantus_sdk/lib/src/ui/themes/app_text_styles.dart @@ -249,3 +249,290 @@ class AppTextTheme extends ThemeExtension { extension AppTextThemeExtension on BuildContext { AppTextTheme get themeText => Theme.of(this).extension()!; } + +/// Quantus App v3 text styles, mapped 1:1 from Figma SYSTEM / Text Styles. +/// +/// Do not add styles that are not in the Figma table. Apply color at the call +/// site via [TextStyle.copyWith] using [AppColorsV3] tokens. +@immutable +class AppTextThemeV3 extends ThemeExtension { + /// POS charge display. Tabular figures. + final TextStyle displayCharge; + + /// Home balance. Tabular figures. + final TextStyle displayBalance; + + /// Terminal state titles. + final TextStyle titleSuccess; + + /// Onboarding and hero headers. + final TextStyle titleHero; + + /// Review-scale amounts. Tabular. + final TextStyle amountHero; + + /// Nav titles. 18px merged up here. + final TextStyle titleScreen; + + /// Inline numerics. Tabular. + final TextStyle amountInline; + + /// Row amounts. Tabular. + final TextStyle amountRow; + + /// Row and list headings. + final TextStyle headingRow; + + /// Row titles, action labels. + final TextStyle bodyLarge; + + /// Emphasized body. + final TextStyle bodyEmphasis; + + /// Default body text. + final TextStyle body; + + /// Supporting text. + final TextStyle caption; + + /// Badges and chips. + final TextStyle labelChip; + + /// Truncated addresses. Mono, always. + final TextStyle dataAddress; + + /// Full addresses on verification surfaces. + final TextStyle dataAddressLarge; + + /// Hidden balances. + final TextStyle dataMasked; + + /// Dossier data labels. + final TextStyle labelData; + + /// Account initials. + final TextStyle labelMonogram; + + const AppTextThemeV3({ + required this.displayCharge, + required this.displayBalance, + required this.titleSuccess, + required this.titleHero, + required this.amountHero, + required this.titleScreen, + required this.amountInline, + required this.amountRow, + required this.headingRow, + required this.bodyLarge, + required this.bodyEmphasis, + required this.body, + required this.caption, + required this.labelChip, + required this.dataAddress, + required this.dataAddressLarge, + required this.dataMasked, + required this.labelData, + required this.labelMonogram, + }); + + /// Typography from Figma SYSTEM / Text Styles. + const AppTextThemeV3.standard() + : this( + displayCharge: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 80, + fontWeight: FontWeight.w400, + height: 1.0, + fontFeatures: [_tabularFigures], + ), + displayBalance: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 40, + fontWeight: FontWeight.w400, + height: 1.0, + fontFeatures: [_tabularFigures], + ), + titleSuccess: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 32, + fontWeight: FontWeight.w400, + height: 1.0, + ), + titleHero: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 28, + fontWeight: FontWeight.w500, + height: 1.0, + ), + amountHero: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 28, + fontWeight: FontWeight.w400, + height: 1.0, + fontFeatures: [_tabularFigures], + ), + titleScreen: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 20, + fontWeight: FontWeight.w500, + height: 1.0, + ), + amountInline: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 20, + fontWeight: FontWeight.w400, + height: 1.0, + fontFeatures: [_tabularFigures], + ), + amountRow: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 18, + fontWeight: FontWeight.w400, + height: 1.0, + fontFeatures: [_tabularFigures], + ), + headingRow: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.0, + ), + bodyLarge: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 16, + fontWeight: FontWeight.w400, + height: 1.0, + ), + bodyEmphasis: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1.0, + ), + body: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.0, + ), + caption: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 1.0, + ), + labelChip: const TextStyle( + fontFamily: AppTextTheme.fontFamily, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1.0, + ), + dataAddress: const TextStyle( + fontFamily: AppTextTheme.fontFamilySecondary, + fontSize: 12, + fontWeight: FontWeight.w400, + height: 1.0, + ), + dataAddressLarge: const TextStyle( + fontFamily: AppTextTheme.fontFamilySecondary, + fontSize: 14, + fontWeight: FontWeight.w400, + height: 1.0, + ), + dataMasked: const TextStyle( + fontFamily: AppTextTheme.fontFamilySecondary, + fontSize: 16, + fontWeight: FontWeight.w400, + height: 1.0, + ), + labelData: const TextStyle( + fontFamily: AppTextTheme.fontFamilySecondary, + fontSize: 14, + fontWeight: FontWeight.w500, + height: 1.0, + ), + labelMonogram: const TextStyle( + fontFamily: AppTextTheme.fontFamilySecondary, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1.0, + ), + ); + + static const _tabularFigures = FontFeature.tabularFigures(); + + @override + AppTextThemeV3 copyWith({ + TextStyle? displayCharge, + TextStyle? displayBalance, + TextStyle? titleSuccess, + TextStyle? titleHero, + TextStyle? amountHero, + TextStyle? titleScreen, + TextStyle? amountInline, + TextStyle? amountRow, + TextStyle? headingRow, + TextStyle? bodyLarge, + TextStyle? bodyEmphasis, + TextStyle? body, + TextStyle? caption, + TextStyle? labelChip, + TextStyle? dataAddress, + TextStyle? dataAddressLarge, + TextStyle? dataMasked, + TextStyle? labelData, + TextStyle? labelMonogram, + }) { + return AppTextThemeV3( + displayCharge: displayCharge ?? this.displayCharge, + displayBalance: displayBalance ?? this.displayBalance, + titleSuccess: titleSuccess ?? this.titleSuccess, + titleHero: titleHero ?? this.titleHero, + amountHero: amountHero ?? this.amountHero, + titleScreen: titleScreen ?? this.titleScreen, + amountInline: amountInline ?? this.amountInline, + amountRow: amountRow ?? this.amountRow, + headingRow: headingRow ?? this.headingRow, + bodyLarge: bodyLarge ?? this.bodyLarge, + bodyEmphasis: bodyEmphasis ?? this.bodyEmphasis, + body: body ?? this.body, + caption: caption ?? this.caption, + labelChip: labelChip ?? this.labelChip, + dataAddress: dataAddress ?? this.dataAddress, + dataAddressLarge: dataAddressLarge ?? this.dataAddressLarge, + dataMasked: dataMasked ?? this.dataMasked, + labelData: labelData ?? this.labelData, + labelMonogram: labelMonogram ?? this.labelMonogram, + ); + } + + @override + AppTextThemeV3 lerp(AppTextThemeV3? other, double t) { + if (other is! AppTextThemeV3) return this; + return AppTextThemeV3( + displayCharge: TextStyle.lerp(displayCharge, other.displayCharge, t) ?? displayCharge, + displayBalance: TextStyle.lerp(displayBalance, other.displayBalance, t) ?? displayBalance, + titleSuccess: TextStyle.lerp(titleSuccess, other.titleSuccess, t) ?? titleSuccess, + titleHero: TextStyle.lerp(titleHero, other.titleHero, t) ?? titleHero, + amountHero: TextStyle.lerp(amountHero, other.amountHero, t) ?? amountHero, + titleScreen: TextStyle.lerp(titleScreen, other.titleScreen, t) ?? titleScreen, + amountInline: TextStyle.lerp(amountInline, other.amountInline, t) ?? amountInline, + amountRow: TextStyle.lerp(amountRow, other.amountRow, t) ?? amountRow, + headingRow: TextStyle.lerp(headingRow, other.headingRow, t) ?? headingRow, + bodyLarge: TextStyle.lerp(bodyLarge, other.bodyLarge, t) ?? bodyLarge, + bodyEmphasis: TextStyle.lerp(bodyEmphasis, other.bodyEmphasis, t) ?? bodyEmphasis, + body: TextStyle.lerp(body, other.body, t) ?? body, + caption: TextStyle.lerp(caption, other.caption, t) ?? caption, + labelChip: TextStyle.lerp(labelChip, other.labelChip, t) ?? labelChip, + dataAddress: TextStyle.lerp(dataAddress, other.dataAddress, t) ?? dataAddress, + dataAddressLarge: TextStyle.lerp(dataAddressLarge, other.dataAddressLarge, t) ?? dataAddressLarge, + dataMasked: TextStyle.lerp(dataMasked, other.dataMasked, t) ?? dataMasked, + labelData: TextStyle.lerp(labelData, other.labelData, t) ?? labelData, + labelMonogram: TextStyle.lerp(labelMonogram, other.labelMonogram, t) ?? labelMonogram, + ); + } +} + +extension AppTextThemeV3Extension on BuildContext { + AppTextThemeV3 get themeTextV3 => Theme.of(this).extension()!; +} diff --git a/cold-wallet-app/lib/theme/app_theme.dart b/quantus_sdk/lib/src/ui/themes/app_theme.dart similarity index 76% rename from cold-wallet-app/lib/theme/app_theme.dart rename to quantus_sdk/lib/src/ui/themes/app_theme.dart index 1977373ef..388d46753 100644 --- a/cold-wallet-app/lib/theme/app_theme.dart +++ b/quantus_sdk/lib/src/ui/themes/app_theme.dart @@ -1,15 +1,12 @@ import 'package:flutter/material.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:quantus_cold_wallet/shared/context_extensions.dart'; -import 'package:quantus_cold_wallet/theme/app_colors.dart'; -import 'package:quantus_cold_wallet/theme/app_spacing.dart'; -import 'package:quantus_cold_wallet/theme/app_text_styles.dart'; class AppTheme { static ThemeData darkTheme(BuildContext context) { final isTablet = context.isTablet; final colors = const AppColorsV2.dark(); final text = isTablet ? const AppTextTheme.iPad() : const AppTextTheme.defaultTheme(); + const radius = AppRadiusV3.standard(); return ThemeData( scaffoldBackgroundColor: colors.background, @@ -19,11 +16,11 @@ class AppTheme { elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( foregroundColor: Colors.white, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), + shape: RoundedRectangleBorder(borderRadius: radius.mdBorder), ), ), outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14))), + style: OutlinedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: radius.mdBorder)), ), textButtonTheme: TextButtonThemeData(style: TextButton.styleFrom(foregroundColor: colors.textPrimary)), textSelectionTheme: TextSelectionThemeData( @@ -41,7 +38,7 @@ class AppTheme { filled: true, fillColor: Colors.transparent, ), - extensions: [colors, text, isTablet ? const AppSizeTheme.iPad() : const AppSizeTheme.defaultTheme()], + extensions: [colors, const AppColorsV3.dark(), text, const AppTextThemeV3.standard(), radius], ); } } diff --git a/quantus_sdk/lib/src/ui/themes/index.dart b/quantus_sdk/lib/src/ui/themes/index.dart new file mode 100644 index 000000000..06b3d68e9 --- /dev/null +++ b/quantus_sdk/lib/src/ui/themes/index.dart @@ -0,0 +1,5 @@ +export 'app_theme.dart'; +export 'app_colors.dart'; +export 'app_radius.dart'; +export 'app_spacing.dart'; +export 'app_text_styles.dart'; diff --git a/quantus_sdk/pubspec.lock b/quantus_sdk/pubspec.lock index 9ad456d4e..c5dfc127b 100644 --- a/quantus_sdk/pubspec.lock +++ b/quantus_sdk/pubspec.lock @@ -347,6 +347,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + url: "https://pub.dev" + source: hosted + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -560,6 +568,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -697,7 +713,7 @@ packages: source: hosted version: "3.0.2" qr_flutter: - dependency: "direct dev" + dependency: "direct main" description: name: qr_flutter sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" @@ -956,6 +972,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" + url: "https://pub.dev" + source: hosted + version: "1.2.3" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" + url: "https://pub.dev" + source: hosted + version: "1.3.0" vector_math: dependency: transitive description: diff --git a/quantus_sdk/pubspec.yaml b/quantus_sdk/pubspec.yaml index 002de93c2..2e8a091b4 100644 --- a/quantus_sdk/pubspec.yaml +++ b/quantus_sdk/pubspec.yaml @@ -42,6 +42,9 @@ dependencies: url: https://github.com/Quantus-Network/qp-human-checkphrase.git ref: v2.2.0 path: dart + + qr_flutter: ^4.1.0 + flutter_svg: ^2.3.0 dev_dependencies: flutter_test: @@ -49,7 +52,7 @@ dev_dependencies: integration_test: sdk: flutter flutter_lints: ^6.0.0 - qr_flutter: ^4.1.0 + # DO NOT UPDATE polkadart_cli - must match polkadart 0.7.x. See note above. polkadart_cli: ^0.7.1 @@ -57,3 +60,8 @@ polkadart: output_dir: lib/generated chains: planck: wss://a1-planck.quantus.cat + +flutter: + assets: + - assets/v2/uppercase_q_black_bg.png + - assets/icons/ diff --git a/quantus_sdk/test/chain/decoded_call_test.dart b/quantus_sdk/test/chain/decoded_call_test.dart new file mode 100644 index 000000000..06241fb3e --- /dev/null +++ b/quantus_sdk/test/chain/decoded_call_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + test('addresses lists unique ss58 values from nested calls and groups', () { + final inner = DecodedCall( + pallet: 'Balances', + call: 'transfer_allow_death', + fields: [ + const ValueField('Destination', 'qzDest', kind: ValueKind.address), + AmountField('Amount', BigInt.one), + ], + ); + final call = DecodedCall( + pallet: 'Utility', + call: 'batch', + fields: [ + FieldGroup('Calls', [NestedCallField('Call 0', inner)]), + const ValueField('Origin', 'qzDest', kind: ValueKind.address), + const ValueField('Other', 'qzOther', kind: ValueKind.address), + const ValueField('Hash', '0xabc', kind: ValueKind.hash), + ], + ); + + expect(call.addresses, ['qzDest', 'qzOther']); + }); + + test('addresses is empty when no address fields are present', () { + const call = DecodedCall( + pallet: 'System', + call: 'remark', + fields: [ValueField('Remark', 'hello', kind: ValueKind.text)], + ); + + expect(call.addresses, isEmpty); + }); +} diff --git a/quantus_sdk/test/ui/animated_ur_qr_test.dart b/quantus_sdk/test/ui/animated_ur_qr_test.dart new file mode 100644 index 000000000..c2bc268c8 --- /dev/null +++ b/quantus_sdk/test/ui/animated_ur_qr_test.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const parts = ['ur:bytes/1-3/first', 'ur:bytes/2-3/second', 'ur:bytes/3-3/third']; + + Future pumpQr(WidgetTester tester, {required List parts, int fps = 10, bool paused = false}) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: AnimatedUrQr(parts: parts, fps: fps, paused: paused), + ), + ), + ); + } + + Finder qrPaintFinder() => find.descendant(of: find.byType(AnimatedUrQr), matching: find.byType(CustomPaint)); + + QrPainter painterOf(WidgetTester tester) { + final paint = tester.widget(qrPaintFinder()); + return paint.painter! as QrPainter; + } + + testWidgets('paints a precomputed QR matrix instead of encoding in build', (tester) async { + await pumpQr(tester, parts: parts); + + expect(qrPaintFinder(), findsOneWidget); + expect(find.byType(QrImageView), findsNothing); + expect(painterOf(tester), isA()); + }); + + testWidgets('cycles to the next precomputed frame on each tick', (tester) async { + await pumpQr(tester, parts: parts, fps: 10); + + final first = painterOf(tester); + await tester.pump(const Duration(milliseconds: 100)); + final second = painterOf(tester); + expect(identical(first, second), isFalse); + + await tester.pump(const Duration(milliseconds: 100)); + final third = painterOf(tester); + expect(identical(second, third), isFalse); + + await tester.pump(const Duration(milliseconds: 100)); + expect(identical(painterOf(tester), first), isTrue); + }); + + testWidgets('does not advance while paused', (tester) async { + await pumpQr(tester, parts: parts, fps: 10, paused: true); + + final first = painterOf(tester); + await tester.pump(const Duration(milliseconds: 500)); + expect(identical(painterOf(tester), first), isTrue); + }); + + testWidgets('restarts the timer when fps changes', (tester) async { + var fps = 1; + late VoidCallback setFps; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (context, setState) { + setFps = () => setState(() => fps = 10); + return AnimatedUrQr(parts: parts, fps: fps); + }, + ), + ), + ), + ); + + final first = painterOf(tester); + await tester.pump(const Duration(milliseconds: 500)); + expect(identical(painterOf(tester), first), isTrue, reason: '1 fps should not tick in 500ms'); + + setFps(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + expect(identical(painterOf(tester), first), isFalse); + }); + + testWidgets('starts animating when parts grow from one to many', (tester) async { + var currentParts = const ['ur:bytes/only']; + late VoidCallback growParts; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (context, setState) { + growParts = () => setState(() => currentParts = parts); + return AnimatedUrQr(parts: currentParts, fps: 10); + }, + ), + ), + ), + ); + + await tester.pump(const Duration(milliseconds: 200)); + + growParts(); + await tester.pump(); + final afterUpdate = painterOf(tester); + await tester.pump(const Duration(milliseconds: 100)); + expect(identical(painterOf(tester), afterUpdate), isFalse); + }); + + testWidgets('defaults fps to 15 and paused to false', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: AnimatedUrQr(parts: parts)), + ), + ); + + final qr = tester.widget(find.byType(AnimatedUrQr)); + expect(qr.fps, 15); + expect(qr.paused, isFalse); + }); + + testWidgets('rejects an empty parts list', (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold(body: AnimatedUrQr(parts: [])), + ), + ); + expect(tester.takeException(), isAssertionError); + }); + + test('rejects a non-positive fps', () { + expect(() => AnimatedUrQr(parts: parts, fps: 0), throwsA(isA())); + }); +} diff --git a/quantus_sdk/test/ui/bottom_sheet_container_test.dart b/quantus_sdk/test/ui/bottom_sheet_container_test.dart new file mode 100644 index 000000000..493364c2b --- /dev/null +++ b/quantus_sdk/test/ui/bottom_sheet_container_test.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpSheet(WidgetTester tester, Widget sheet) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: sheet), + ), + ), + ), + ); + await tester.pump(); + } + + BoxDecoration decorationAround(WidgetTester tester, String label) { + final container = tester.widget( + find.ancestor(of: find.text(label), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + Container sheetContainer(WidgetTester tester, String label) { + return tester.widget(find.ancestor(of: find.text(label), matching: find.byType(Container)).first); + } + + Container? handleContainer(WidgetTester tester) { + for (final container in tester.widgetList(find.byType(Container))) { + if (container.constraints?.maxWidth == 36 && container.constraints?.maxHeight == 4) { + return container; + } + } + return null; + } + + BottomSheetContainer sheet() { + return const BottomSheetContainer(title: 'Sheet Title', child: Text('Slot')); + } + + testWidgets('shows title and content slot', (tester) async { + await pumpSheet(tester, sheet()); + + expect(find.text('Sheet Title'), findsOneWidget); + expect(find.text('Slot'), findsOneWidget); + }); + + testWidgets('omits the close icon', (tester) async { + await pumpSheet(tester, sheet()); + + expect(find.byIcon(Icons.close), findsNothing); + }); + + testWidgets('title uses heading row and content color', (tester) async { + await pumpSheet(tester, sheet()); + + final title = tester.widget(find.text('Sheet Title')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.headingRow.fontSize); + expect(title.style?.fontWeight, text.headingRow.fontWeight); + }); + + testWidgets('paints surface fill without a border', (tester) async { + await pumpSheet(tester, sheet()); + + final decoration = decorationAround(tester, 'Sheet Title'); + expect(decoration.color, colors.bgSurface); + expect(decoration.border, isNull); + }); + + testWidgets('uses v3 lg radius', (tester) async { + await pumpSheet(tester, sheet()); + + expect(decorationAround(tester, 'Sheet Title').borderRadius, const AppRadiusV3.standard().lgBorder); + }); + + testWidgets('pads 24/16-32', (tester) async { + await pumpSheet(tester, sheet()); + + expect(sheetContainer(tester, 'Sheet Title').padding, const EdgeInsets.fromLTRB(24, 16, 24, 32)); + }); + + testWidgets('handle is 36 by 4 in surface-2', (tester) async { + await pumpSheet(tester, sheet()); + + final handle = handleContainer(tester); + expect(handle, isNotNull); + final decoration = handle!.decoration! as BoxDecoration; + expect(decoration.color, colors.bgSurface2); + }); + + testWidgets('takes the full available width', (tester) async { + await pumpSheet(tester, sheet()); + + expect( + tester.widgetList(find.byType(ConstrainedBox)).any((box) => box.constraints.maxWidth == 362), + isFalse, + ); + expect(tester.getSize(find.byType(BottomSheetContainer)).width, tester.getSize(find.byType(Scaffold)).width); + }); +} diff --git a/quantus_sdk/test/ui/decoded_call_view_test.dart b/quantus_sdk/test/ui/decoded_call_view_test.dart new file mode 100644 index 000000000..f48f02916 --- /dev/null +++ b/quantus_sdk/test/ui/decoded_call_view_test.dart @@ -0,0 +1,213 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + const dest = ValueField('Destination', 'qzDest', kind: ValueKind.address); + final amount = AmountField('Amount', BigInt.from(1000)); + final transfer = DecodedCall( + pallet: 'Balances', + call: 'transfer_allow_death', + fields: [dest, amount], + summary: TransferSummary(amount: amount.token, recipient: dest.value, amountField: amount, recipientField: dest), + ); + + Future pumpCall(WidgetTester tester, Widget view, {Size size = const Size(375, 667)}) async { + await tester.pumpWidget( + MediaQuery( + data: MediaQueryData(size: size), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: SingleChildScrollView(child: view)), + ), + ), + ), + ); + await tester.pump(); + } + + DecodedCallView view({ + required DecodedCall call, + DecodedCallLayout layout = DecodedCallLayout.compact, + String? Function(String address)? checkphraseOf, + int depth = 0, + }) { + return DecodedCallView( + call: call, + layout: layout, + amountText: (token) => '$token QNT', + checkphraseOf: checkphraseOf, + depth: depth, + ); + } + + testWidgets('renders pallet title, fields, and formatted native amount', (tester) async { + await pumpCall(tester, view(call: transfer)); + + expect(find.text('Balances · transfer_allow_death'), findsOneWidget); + expect(find.text('Destination'), findsOneWidget); + expect(find.text('qzDest'), findsOneWidget); + expect(find.text('1000 QNT'), findsOneWidget); + }); + + testWidgets('stacked layout uppercases labels and wraps checkphrase in lilac', (tester) async { + await pumpCall( + tester, + view(call: transfer, layout: DecodedCallLayout.stacked, checkphraseOf: (address) => 'alpha bravo charlie'), + ); + + expect(find.text('TO'), findsOneWidget); + expect(find.text('TO CHECKPHRASE'), findsOneWidget); + final phrase = tester.widget(find.text('alpha bravo charlie')); + expect(phrase.style?.color, colors.semanticLilac); + expect(find.ancestor(of: find.text('TO'), matching: find.byType(DetailSummaryRow)), findsOneWidget); + }); + + testWidgets('compact layout keeps title-case labels and right-aligns values', (tester) async { + await pumpCall(tester, view(call: transfer)); + + expect(find.text('Destination'), findsOneWidget); + expect(find.text('DESTINATION'), findsNothing); + final value = tester.widget(find.text('qzDest')); + expect(value.textAlign, TextAlign.right); + expect(value.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(find.ancestor(of: find.text('Destination'), matching: find.byType(DetailSummaryRow)), findsOneWidget); + }); + + testWidgets('nested call uses surface2 fill, hairline, and v3 md radius', (tester) async { + const nested = DecodedCall(pallet: 'Balances', call: 'transfer_allow_death', fields: [dest]); + const outer = DecodedCall(pallet: 'Multisig', call: 'as_multi', fields: [NestedCallField('Call', nested)]); + + await pumpCall(tester, view(call: outer)); + + expect(find.text('Multisig · as_multi'), findsOneWidget); + expect(find.text('Balances · transfer_allow_death'), findsOneWidget); + + final nestedTitle = tester.widget(find.text('Balances · transfer_allow_death')); + expect(nestedTitle.style?.color, colors.textMuted); + expect(nestedTitle.style?.fontSize, text.headingRow.fontSize); + + final container = tester.widget( + find.ancestor(of: find.text('Balances · transfer_allow_death'), matching: find.byType(Container)).first, + ); + final decoration = container.decoration! as BoxDecoration; + expect(decoration.color, colors.bgSurface2); + expect(decoration.borderRadius, const AppRadiusV3.standard().mdBorder); + final border = decoration.border! as Border; + expect(border.top.color, colors.borderHairline); + }); + + testWidgets('asset amounts show raw units and the decimals caveat', (tester) async { + final call = DecodedCall( + pallet: 'Assets', + call: 'transfer', + fields: [AmountField('Amount', BigInt.from(99), assetId: 7)], + ); + + await pumpCall(tester, view(call: call)); + + expect(find.text('99 raw units of asset 7'), findsOneWidget); + expect(find.text('Asset decimals are not part of this payload, so the raw amount is shown.'), findsOneWidget); + }); + + testWidgets('hash fields use the mono data style', (tester) async { + const call = DecodedCall( + pallet: 'Preimage', + call: 'note_preimage', + fields: [ValueField('Hash', '0xdead', kind: ValueKind.hash)], + ); + + await pumpCall(tester, view(call: call)); + + final hash = tester.widget(find.text('0xdead')); + expect(hash.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(hash.style?.color, colors.textContent); + }); + + testWidgets('stacked transfer headlines as SEND and leads with amount and To once', (tester) async { + await pumpCall(tester, view(call: transfer, layout: DecodedCallLayout.stacked)); + + expect(find.text('SEND'), findsOneWidget); + expect(find.text('Balances · transfer_allow_death'), findsNothing); + expect(find.text('1000 QNT'), findsOneWidget); + expect(find.text('TO'), findsOneWidget); + expect(find.text('qzDest'), findsOneWidget); + expect(find.text('AMOUNT'), findsNothing); + expect(find.text('DESTINATION'), findsNothing); + }); + + testWidgets('stacked force_transfer keeps Source when it equals the recipient', (tester) async { + const source = ValueField('Source', 'qzDest', kind: ValueKind.address); + final call = DecodedCall( + pallet: 'Balances', + call: 'force_transfer', + fields: [source, dest, amount], + summary: TransferSummary(amount: amount.token, recipient: dest.value, amountField: amount, recipientField: dest), + ); + + await pumpCall(tester, view(call: call, layout: DecodedCallLayout.stacked)); + + expect(find.text('SOURCE'), findsOneWidget); + expect(find.text('DESTINATION'), findsNothing); + expect(find.text('TO'), findsOneWidget); + }); + + testWidgets('stacked wrapper names itself and puts the hero on the inner call', (tester) async { + final outer = DecodedCall( + pallet: 'Multisig', + call: 'approve', + fields: [ + NestedCallField('You are approving', transfer), + const ValueField('Proposal id', '12', kind: ValueKind.number), + ], + summary: transfer.summary, + ); + + await pumpCall(tester, view(call: outer, layout: DecodedCallLayout.stacked)); + + expect(find.text('MULTISIG APPROVE'), findsOneWidget); + expect(find.text('SEND'), findsOneWidget); + expect(find.text('1000 QNT'), findsOneWidget); + expect(find.text('YOU ARE APPROVING'), findsOneWidget); + expect(find.text('PROPOSAL ID'), findsOneWidget); + }); + + testWidgets('stacked wrapper with signer leads with the nested call', (tester) async { + final outer = DecodedCall( + pallet: 'Multisig', + call: 'approve', + fields: [ + NestedCallField('You are approving', transfer), + const ValueField('Proposal id', '12', kind: ValueKind.number), + ], + summary: transfer.summary, + ); + + await pumpCall( + tester, + DecodedCallView( + call: outer, + layout: DecodedCallLayout.stacked, + amountText: (token) => '$token QNT', + showTitle: false, + signer: const Text('SIGNER'), + ), + ); + + expect(find.text('MULTISIG APPROVE'), findsNothing); + expect(find.text('SEND'), findsOneWidget); + expect(find.text('SIGNER'), findsOneWidget); + + final nestedTop = tester.getTopLeft(find.text('YOU ARE APPROVING')).dy; + final signerTop = tester.getTopLeft(find.text('SIGNER')).dy; + final proposalTop = tester.getTopLeft(find.text('PROPOSAL ID')).dy; + expect(nestedTop, lessThan(signerTop)); + expect(signerTop, lessThan(proposalTop)); + }); +} diff --git a/quantus_sdk/test/ui/detail_summary_row_test.dart b/quantus_sdk/test/ui/detail_summary_row_test.dart new file mode 100644 index 000000000..a4232f430 --- /dev/null +++ b/quantus_sdk/test/ui/detail_summary_row_test.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpRow(WidgetTester tester, Widget row) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: row), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('uses v3 muted label and content value tokens', (tester) async { + await pumpRow(tester, const DetailSummaryRow(label: 'Network fee', value: '1.00 QNT')); + + final label = tester.widget(find.text('Network fee')); + expect(label.style?.color, colors.textMuted); + expect(label.style?.fontSize, 10); + expect(label.style?.letterSpacing, 1); + expect(label.style?.fontFamily, AppTextTheme.fontFamilySecondary); + + final value = tester.widget(find.text('1.00 QNT')); + expect(value.style?.color, colors.textContent); + expect(value.style?.fontSize, text.body.fontSize); + expect(value.textAlign, TextAlign.right); + }); + + testWidgets('stacked uppercases the label and paints a wrapping value', (tester) async { + await pumpRow(tester, const DetailSummaryRow.stacked(label: 'Genesis hash', value: '0xabc', monospace: true)); + + expect(find.text('GENESIS HASH'), findsOneWidget); + expect(find.text('Genesis hash'), findsNothing); + + final value = tester.widget(find.text('0xabc')); + expect(value.textAlign, isNull); + expect(value.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(value.style?.fontSize, text.dataAddressLarge.fontSize); + expect(value.style?.color, colors.textContent); + }); + + testWidgets('stacked applies valueColor and shows a note', (tester) async { + await pumpRow( + tester, + DetailSummaryRow.stacked( + label: 'Signing as checkphrase', + value: 'alpha bravo', + valueColor: colors.semanticLilac, + note: 'Verify out loud.', + ), + ); + + final value = tester.widget(find.text('alpha bravo')); + expect(value.style?.color, colors.semanticLilac); + expect(find.text('Verify out loud.'), findsOneWidget); + }); + + testWidgets('stacked checkphrase is a second labeled row in lilac', (tester) async { + await pumpRow( + tester, + const DetailSummaryRow.stacked(label: 'To', value: 'qzDest', monospace: true, checkphrase: 'alpha bravo'), + ); + + expect(find.text('TO'), findsOneWidget); + expect(find.text('TO CHECKPHRASE'), findsOneWidget); + final phrase = tester.widget(find.text('alpha bravo')); + expect(phrase.style?.color, colors.semanticLilac); + expect(phrase.style?.fontSize, text.dataAddressLarge.fontSize); + }); + + testWidgets('compact checkphrase sits under the value without a second label', (tester) async { + await pumpRow( + tester, + const DetailSummaryRow(label: 'Destination', value: 'qzDest', monospace: true, checkphrase: 'alpha bravo'), + ); + + expect(find.text('DESTINATION CHECKPHRASE'), findsNothing); + expect(find.text('Destination checkphrase'), findsNothing); + final phrase = tester.widget(find.text('alpha bravo')); + expect(phrase.style?.color, colors.semanticLilac); + expect(phrase.style?.fontSize, text.caption.fontSize); + expect(phrase.textAlign, TextAlign.right); + + final value = tester.widget(find.text('qzDest')); + expect(value.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(value.textAlign, TextAlign.right); + }); + + testWidgets('compact shows a note under the row', (tester) async { + await pumpRow( + tester, + const DetailSummaryRow(label: 'Amount', value: '99 raw', note: 'Asset decimals are not in this payload.'), + ); + + expect(find.text('Asset decimals are not in this payload.'), findsOneWidget); + }); +} diff --git a/quantus_sdk/test/ui/loader_test.dart b/quantus_sdk/test/ui/loader_test.dart new file mode 100644 index 000000000..9cf4cc579 --- /dev/null +++ b/quantus_sdk/test/ui/loader_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpLoader(WidgetTester tester, Widget loader, {Size size = const Size(375, 667)}) async { + await tester.pumpWidget( + MediaQuery( + data: MediaQueryData(size: size), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: loader), + ), + ), + ), + ); + await tester.pump(); + } + + CircularProgressIndicator indicator(WidgetTester tester) { + return tester.widget(find.byType(CircularProgressIndicator)); + } + + testWidgets('defaults to v3 textMuted', (tester) async { + await pumpLoader(tester, const Loader()); + + expect(indicator(tester).color, colors.textMuted); + expect(tester.getSize(find.byType(Loader)), const Size(16, 16)); + }); + + testWidgets('uses the explicit color when provided', (tester) async { + await pumpLoader(tester, Loader(color: colors.accentFlare, size: 20)); + + expect(indicator(tester).color, colors.accentFlare); + expect(tester.getSize(find.byType(Loader)), const Size(20, 20)); + }); +} diff --git a/quantus_sdk/test/ui/menu_divider_test.dart b/quantus_sdk/test/ui/menu_divider_test.dart new file mode 100644 index 000000000..a949ef455 --- /dev/null +++ b/quantus_sdk/test/ui/menu_divider_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpDivider(WidgetTester tester) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: const Scaffold(body: MenuDivider()), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('paints v3 borderHairline', (tester) async { + await pumpDivider(tester); + + final divider = tester.widget(find.byType(Divider)); + expect(divider.color, colors.borderHairline); + expect(divider.height, 1); + }); +} diff --git a/quantus_sdk/test/ui/mnemonic_grid_test.dart b/quantus_sdk/test/ui/mnemonic_grid_test.dart new file mode 100644 index 000000000..0760b2cee --- /dev/null +++ b/quantus_sdk/test/ui/mnemonic_grid_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + const radius = AppRadiusV3.standard(); + + testWidgets('uses v3 surface, muted index, and lilac word tokens', (tester) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: const Scaffold(body: MnemonicGrid(words: ['abandon', 'ability', 'able'], isRevealed: true)), + ), + ), + ), + ); + await tester.pump(); + + final index = tester.widget(find.text('1')); + expect(index.style?.color, colors.textMuted); + expect(index.style?.fontSize, text.caption.fontSize); + + final word = tester.widget(find.text('abandon')); + expect(word.style?.color, colors.semanticLilac); + expect(word.style?.fontSize, text.caption.fontSize); + + final container = tester.widget( + find.ancestor(of: find.text('abandon'), matching: find.byType(Container)).first, + ); + final decoration = container.decoration! as BoxDecoration; + expect(decoration.color, colors.bgSurface); + expect(decoration.borderRadius, radius.mdBorder); + }); +} diff --git a/quantus_sdk/test/ui/quantus_action_sheet_test.dart b/quantus_sdk/test/ui/quantus_action_sheet_test.dart new file mode 100644 index 000000000..b9b4ba7f6 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_action_sheet_test.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpSheet(WidgetTester tester, Widget sheet) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: sheet), + ), + ), + ), + ); + await tester.pump(); + } + + BoxDecoration decorationAround(WidgetTester tester, String text) { + final container = tester.widget( + find.ancestor(of: find.text(text), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + QuantusActionSheet accountSheet({VoidCallback? onCopy, VoidCallback? onRemove}) { + return QuantusActionSheet( + items: [ + QuantusActionSheetItem(label: 'Copy Address', onTap: onCopy ?? () {}), + QuantusActionSheetItem(label: 'Edit Account', onTap: () {}), + QuantusActionSheetItem(label: 'View Recovery Phrase', onTap: () {}), + QuantusActionSheetItem(label: 'Remove Account', isDestructive: true, onTap: onRemove ?? () {}), + ], + ); + } + + testWidgets('shows item labels', (tester) async { + await pumpSheet(tester, accountSheet()); + + expect(find.text('Copy Address'), findsOneWidget); + expect(find.text('Edit Account'), findsOneWidget); + expect(find.text('View Recovery Phrase'), findsOneWidget); + expect(find.text('Remove Account'), findsOneWidget); + }); + + testWidgets('tapping an item invokes its callback', (tester) async { + var copied = false; + await pumpSheet(tester, accountSheet(onCopy: () => copied = true)); + + await tester.tap(find.text('Copy Address')); + expect(copied, isTrue); + }); + + testWidgets('destructive label uses ember', (tester) async { + await pumpSheet(tester, accountSheet()); + + final destructive = tester.widget(find.text('Remove Account')); + expect(destructive.style?.color, colors.semanticEmber); + + final regular = tester.widget(find.text('Copy Address')); + expect(regular.style?.color, colors.textContent); + }); + + testWidgets('inserts a divider above the first destructive item', (tester) async { + await pumpSheet(tester, accountSheet()); + + expect(find.byType(Divider), findsOneWidget); + }); + + testWidgets('omits the divider when no item is destructive', (tester) async { + await pumpSheet( + tester, + QuantusActionSheet( + items: [ + QuantusActionSheetItem(label: 'Rename Wallet', onTap: () {}), + QuantusActionSheetItem(label: 'Add Account', onTap: () {}), + ], + ), + ); + + expect(find.byType(Divider), findsNothing); + }); + + testWidgets('paints surface fill and emphasis border', (tester) async { + await pumpSheet(tester, accountSheet()); + + final decoration = decorationAround(tester, 'Copy Address'); + expect(decoration.color, colors.bgSurface); + final border = decoration.border! as Border; + expect(border.top.color, colors.borderEmphasis); + expect(border.top.width, 1); + }); + + testWidgets('uses v3 lg radius', (tester) async { + await pumpSheet(tester, accountSheet()); + + expect(decorationAround(tester, 'Copy Address').borderRadius, const AppRadiusV3.standard().lgBorder); + }); + + testWidgets('showQuantusActionSheet pops then runs the tapped action', (tester) async { + var copied = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold( + body: Builder( + builder: (context) { + return TextButton( + onPressed: () { + showQuantusActionSheet( + context, + items: [QuantusActionSheetItem(label: 'Copy Address', onTap: () => copied = true)], + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ), + ), + ), + ); + await tester.pump(); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + expect(find.text('Copy Address'), findsOneWidget); + + await tester.tap(find.text('Copy Address')); + await tester.pumpAndSettle(); + + expect(copied, isTrue); + expect(find.text('Copy Address'), findsNothing); + }); + + testWidgets('dismissing the sheet does not run actions', (tester) async { + var copied = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold( + body: Builder( + builder: (context) { + return TextButton( + onPressed: () { + showQuantusActionSheet( + context, + items: [QuantusActionSheetItem(label: 'Copy Address', onTap: () => copied = true)], + ); + }, + child: const Text('Open'), + ); + }, + ), + ), + ), + ), + ), + ); + await tester.pump(); + + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + expect(find.text('Copy Address'), findsOneWidget); + + await tester.tapAt(const Offset(8, 8)); + await tester.pumpAndSettle(); + + expect(copied, isFalse); + expect(find.text('Copy Address'), findsNothing); + }); +} diff --git a/quantus_sdk/test/ui/quantus_badge_test.dart b/quantus_sdk/test/ui/quantus_badge_test.dart new file mode 100644 index 000000000..63cf895ec --- /dev/null +++ b/quantus_sdk/test/ui/quantus_badge_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpBadge(WidgetTester tester, Widget badge) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: badge), + ), + ), + ), + ); + await tester.pump(); + } + + BoxDecoration decorationAround(WidgetTester tester, String text) { + final container = tester.widget( + find.ancestor(of: find.text(text), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + testWidgets('renders label in uppercase', (tester) async { + await pumpBadge(tester, const QuantusBadge(label: 'Pending')); + + expect(find.text('PENDING'), findsOneWidget); + expect(find.text('Pending'), findsNothing); + }); + + testWidgets('paints stroke and text with the tone token', (tester) async { + final cases = <(BadgeTone, Color)>[ + (BadgeTone.neutral, colors.textMuted), + (BadgeTone.sage, colors.semanticSage), + (BadgeTone.sand, colors.semanticSand), + (BadgeTone.ember, colors.semanticEmber), + (BadgeTone.glacier, colors.semanticGlacier), + (BadgeTone.lilac, colors.semanticLilac), + (BadgeTone.flare, colors.accentFlare), + ]; + + for (final (tone, color) in cases) { + await pumpBadge(tester, QuantusBadge(label: 'Badge', tone: tone)); + + final text = tester.widget(find.text('BADGE')); + expect(text.style?.color, color, reason: '$tone text'); + + final border = decorationAround(tester, 'BADGE').border! as Border; + expect(border.top.color, color, reason: '$tone stroke'); + expect(border.top.width, 1); + } + }); + + testWidgets('uses v3 xs radius', (tester) async { + await pumpBadge(tester, const QuantusBadge(label: 'Badge')); + + expect(decorationAround(tester, 'BADGE').borderRadius, const AppRadiusV3.standard().xsBorder); + }); +} diff --git a/quantus_sdk/test/ui/quantus_banner_test.dart b/quantus_sdk/test/ui/quantus_banner_test.dart new file mode 100644 index 000000000..ae1aa3670 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_banner_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpBanner(WidgetTester tester, Widget banner) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: banner), + ), + ), + ), + ); + await tester.pump(); + } + + BoxDecoration decorationAround(WidgetTester tester, String text) { + final container = tester.widget( + find.ancestor(of: find.text(text), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + testWidgets('shows message and default glyph', (tester) async { + await pumpBanner( + tester, + const QuantusBanner(tone: BannerTone.sage, message: 'Your funds are safe. Nothing left your wallet.'), + ); + + expect(find.text('Your funds are safe. Nothing left your wallet.'), findsOneWidget); + expect(find.text('!'), findsOneWidget); + }); + + testWidgets('custom leading replaces the default glyph', (tester) async { + await pumpBanner( + tester, + const QuantusBanner(tone: BannerTone.sand, message: 'The deposit window ended.', leading: Icon(Icons.schedule)), + ); + + expect(find.byIcon(Icons.schedule), findsOneWidget); + expect(find.text('!'), findsNothing); + }); + + testWidgets('paints 10 percent tone stroke and a vertical fill', (tester) async { + final cases = <(BannerTone, Color)>[ + (BannerTone.sage, colors.semanticSage), + (BannerTone.sand, colors.semanticSand), + (BannerTone.ember, colors.semanticEmber), + (BannerTone.glacier, colors.semanticGlacier), + ]; + + for (final (tone, color) in cases) { + await pumpBanner(tester, QuantusBanner(tone: tone, message: 'Status')); + + final border = decorationAround(tester, 'Status').border! as Border; + expect(border.top.color, color.useOpacity(0.10), reason: '$tone stroke'); + expect(decorationAround(tester, 'Status').gradient, isA(), reason: '$tone fill'); + } + }); + + testWidgets('uses v3 md radius', (tester) async { + await pumpBanner(tester, const QuantusBanner(tone: BannerTone.glacier, message: 'Waiting')); + + expect(decorationAround(tester, 'Waiting').borderRadius, const AppRadiusV3.standard().mdBorder); + }); +} diff --git a/quantus_sdk/test/ui/quantus_data_row_test.dart b/quantus_sdk/test/ui/quantus_data_row_test.dart new file mode 100644 index 000000000..67a575187 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_data_row_test.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpRow(WidgetTester tester, Widget row) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: row), + ), + ), + ), + ); + await tester.pump(); + } + + BoxDecoration decorationAround(WidgetTester tester, String text) { + final container = tester.widget( + find.ancestor(of: find.text(text), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + testWidgets('renders uppercase label and value', (tester) async { + await pumpRow(tester, const QuantusDataRow(label: 'Hash', value: '0xa5f3c2...e3a6c8f1')); + + expect(find.text('HASH'), findsOneWidget); + expect(find.text('Hash'), findsNothing); + expect(find.text('0xa5f3c2...e3a6c8f1'), findsOneWidget); + }); + + testWidgets('paints hairline border and v3 md radius', (tester) async { + await pumpRow(tester, const QuantusDataRow(label: 'Hash', value: '0xabc')); + + final decoration = decorationAround(tester, 'HASH'); + final border = decoration.border! as Border; + expect(border.top.color, colors.borderHairline); + expect(border.top.width, 1); + expect(decoration.borderRadius, const AppRadiusV3.standard().mdBorder); + }); + + testWidgets('uses muted label and content value tokens', (tester) async { + await pumpRow(tester, const QuantusDataRow(label: 'Hash', value: '0xabc')); + + final label = tester.widget(find.text('HASH')); + expect(label.style?.color, colors.textMuted); + expect(label.style?.fontSize, 10); + expect(label.style?.letterSpacing, 1); + expect(label.style?.fontFamily, AppTextTheme.fontFamilySecondary); + + final value = tester.widget(find.text('0xabc')); + expect(value.style?.color, colors.textContent); + expect(value.style?.fontSize, text.dataAddress.fontSize); + expect(value.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(value.textAlign, TextAlign.right); + }); + + testWidgets('tapping invokes onTap', (tester) async { + var tapped = false; + await pumpRow(tester, QuantusDataRow(label: 'Hash', value: '0xabc', onTap: () => tapped = true)); + + await tester.tap(find.text('HASH')); + expect(tapped, isTrue); + }); + + testWidgets('shows trailing glyph when provided', (tester) async { + await pumpRow(tester, const QuantusDataRow(label: 'Hash', value: '0xabc', trailing: Icon(Icons.chevron_right))); + + expect(find.byIcon(Icons.chevron_right), findsOneWidget); + }); +} diff --git a/quantus_sdk/test/ui/quantus_dialog_test.dart b/quantus_sdk/test/ui/quantus_dialog_test.dart new file mode 100644 index 000000000..c64ef42ac --- /dev/null +++ b/quantus_sdk/test/ui/quantus_dialog_test.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpDialog(WidgetTester tester, Widget dialog) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: dialog), + ), + ), + ), + ); + await tester.pump(); + } + + Future pumpShownDialog(WidgetTester tester, {required Future Function(bool result) onResult}) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold( + body: Builder( + builder: (context) { + return TextButton( + onPressed: () async { + final result = await showQuantusDialog( + context, + title: 'Dialog title?', + body: 'Consequences.', + actionLabel: 'Label', + ); + await onResult(result); + }, + child: const Text('Open'), + ); + }, + ), + ), + ), + ), + ), + ); + await tester.pump(); + await tester.tap(find.text('Open')); + await tester.pumpAndSettle(); + } + + BoxDecoration decorationAround(WidgetTester tester, String text) { + final container = tester.widget( + find.ancestor(of: find.text(text), matching: find.byType(Container)).first, + ); + return container.decoration! as BoxDecoration; + } + + QuantusButton actionButton(WidgetTester tester, String label) { + return tester.widget(find.ancestor(of: find.text(label), matching: find.byType(QuantusButton))); + } + + testWidgets('shows title, body, action, and Cancel', (tester) async { + await pumpDialog( + tester, + QuantusDialog( + title: 'Dialog title?', + body: 'One or two sentences stating consequences plainly. Nothing has been sent.', + actionLabel: 'Label', + onAction: () {}, + onCancel: () {}, + ), + ); + + expect(find.text('Dialog title?'), findsOneWidget); + expect(find.text('One or two sentences stating consequences plainly. Nothing has been sent.'), findsOneWidget); + expect(find.text('Label'), findsOneWidget); + expect(find.text('Cancel'), findsOneWidget); + }); + + testWidgets('action uses primary variant by default', (tester) async { + await pumpDialog( + tester, + QuantusDialog( + title: 'Dialog title?', + body: 'Consequences.', + actionLabel: 'Label', + onAction: () {}, + onCancel: () {}, + ), + ); + + expect(actionButton(tester, 'Label').variant, ButtonVariant.primary); + }); + + testWidgets('action uses danger variant when destructive', (tester) async { + await pumpDialog( + tester, + QuantusDialog( + title: 'Dialog title?', + body: 'Consequences.', + actionLabel: 'Label', + isDestructive: true, + onAction: () {}, + onCancel: () {}, + ), + ); + + expect(actionButton(tester, 'Label').variant, ButtonVariant.danger); + expect(actionButton(tester, 'Cancel').variant, ButtonVariant.secondary); + }); + + testWidgets('stacks Cancel below the primary action', (tester) async { + await pumpDialog( + tester, + QuantusDialog( + title: 'Dialog title?', + body: 'Consequences.', + actionLabel: 'Label', + onAction: () {}, + onCancel: () {}, + ), + ); + + expect(tester.getTopLeft(find.text('Cancel')).dy, greaterThan(tester.getTopLeft(find.text('Label')).dy)); + }); + + testWidgets('tapping the action invokes the callback', (tester) async { + var tapped = false; + await pumpDialog( + tester, + QuantusDialog(title: 'Dialog title?', body: 'Consequences.', actionLabel: 'Label', onAction: () => tapped = true), + ); + + await tester.tap(find.text('Label')); + expect(tapped, isTrue); + }); + + testWidgets('tapping Cancel invokes the cancel callback', (tester) async { + var cancelled = false; + await pumpDialog( + tester, + QuantusDialog( + title: 'Dialog title?', + body: 'Consequences.', + actionLabel: 'Label', + onAction: () {}, + onCancel: () => cancelled = true, + ), + ); + + await tester.tap(find.text('Cancel')); + expect(cancelled, isTrue); + }); + + testWidgets('paints surface fill and emphasis border', (tester) async { + await pumpDialog( + tester, + QuantusDialog(title: 'Dialog title?', body: 'Consequences.', actionLabel: 'Label', onAction: () {}), + ); + + final decoration = decorationAround(tester, 'Dialog title?'); + expect(decoration.color, colors.bgSurface); + final border = decoration.border! as Border; + expect(border.top.color, colors.borderEmphasis); + expect(border.top.width, 1); + }); + + testWidgets('uses v3 lg radius', (tester) async { + await pumpDialog( + tester, + QuantusDialog(title: 'Dialog title?', body: 'Consequences.', actionLabel: 'Label', onAction: () {}), + ); + + expect(decorationAround(tester, 'Dialog title?').borderRadius, const AppRadiusV3.standard().lgBorder); + }); + + testWidgets('showQuantusDialog returns true when the action is tapped', (tester) async { + bool? result; + await pumpShownDialog(tester, onResult: (confirmed) async => result = confirmed); + + await tester.tap(find.text('Label')); + await tester.pumpAndSettle(); + + expect(result, isTrue); + }); + + testWidgets('showQuantusDialog returns false when Cancel is tapped', (tester) async { + bool? result; + await pumpShownDialog(tester, onResult: (confirmed) async => result = confirmed); + + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); + + expect(result, isFalse); + }); +} diff --git a/quantus_sdk/test/ui/quantus_icon_test.dart b/quantus_sdk/test/ui/quantus_icon_test.dart new file mode 100644 index 000000000..e832a6d82 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_icon_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpIcon(WidgetTester tester, Widget icon) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: icon), + ), + ), + ), + ); + await tester.pump(); + } + + SvgPicture svgOf(WidgetTester tester) => tester.widget(find.byType(SvgPicture)); + + SvgAssetLoader loaderOf(WidgetTester tester) => svgOf(tester).bytesLoader as SvgAssetLoader; + + testWidgets('loads every glyph from the SDK package', (tester) async { + for (final icon in QuantusIcons.values) { + await pumpIcon(tester, QuantusIcon(icon)); + + final loader = loaderOf(tester); + expect(loader.assetName, icon.assetPath, reason: '$icon asset'); + expect(loader.packageName, 'quantus_sdk', reason: '$icon package'); + } + }); + + testWidgets('defaults to 20px muted tint', (tester) async { + await pumpIcon(tester, const QuantusIcon(QuantusIcons.plus)); + + final svg = svgOf(tester); + expect(svg.width, 20); + expect(svg.height, 20); + expect(svg.colorFilter, ColorFilter.mode(colors.textMuted, BlendMode.srcIn)); + expect(tester.getSize(find.byType(QuantusIcon)), const Size(20, 20)); + }); + + testWidgets('honors size and color overrides', (tester) async { + await pumpIcon(tester, QuantusIcon(QuantusIcons.lock, size: 16, color: colors.accentFlare)); + + final svg = svgOf(tester); + expect(svg.width, 16); + expect(svg.height, 16); + expect(svg.colorFilter, ColorFilter.mode(colors.accentFlare, BlendMode.srcIn)); + expect(tester.getSize(find.byType(QuantusIcon)), const Size(16, 16)); + }); +} diff --git a/quantus_sdk/test/ui/quantus_step_row_test.dart b/quantus_sdk/test/ui/quantus_step_row_test.dart new file mode 100644 index 000000000..d1f51d5c0 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_step_row_test.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pumpRow(WidgetTester tester, Widget row) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: row), + ), + ), + ), + ); + await tester.pump(); + } + + Finder pendingDot() { + return find.byWidgetPredicate((widget) { + if (widget is! Container) return false; + final decoration = widget.decoration; + return decoration is BoxDecoration && + decoration.color == colors.bgSurface2 && + decoration.shape == BoxShape.circle; + }); + } + + testWidgets('done paints sage check and muted label', (tester) async { + await pumpRow(tester, const QuantusStepRow(state: StepRowState.done, label: 'Preparing')); + + expect(find.text('Preparing'), findsOneWidget); + expect(find.text('✓'), findsOneWidget); + expect(find.text('◌'), findsNothing); + + final mark = tester.widget(find.text('✓')); + expect(mark.style?.color, colors.semanticSage); + expect(mark.style?.fontSize, 13); + expect(mark.style?.fontFamily, AppTextTheme.fontFamily); + + final label = tester.widget(find.text('Preparing')); + expect(label.style?.color, colors.textMuted2); + expect(label.style?.fontSize, text.body.fontSize); + expect(label.style?.fontWeight, text.body.fontWeight); + }); + + testWidgets('active paints spinning flare mark, content label, and uppercase substate', (tester) async { + await pumpRow( + tester, + const QuantusStepRow(state: StepRowState.active, label: 'Building privacy proof', substate: 'substate · detail'), + ); + + expect(find.text('Building privacy proof'), findsOneWidget); + expect(find.text('◌'), findsOneWidget); + expect(find.descendant(of: find.byType(QuantusStepRow), matching: find.byType(RotationTransition)), findsOneWidget); + expect(find.text('SUBSTATE · DETAIL'), findsOneWidget); + expect(find.text('substate · detail'), findsNothing); + + final mark = tester.widget(find.text('◌')); + expect(mark.style?.color, colors.accentFlare); + expect(mark.style?.fontSize, 13); + + final label = tester.widget(find.text('Building privacy proof')); + expect(label.style?.color, colors.textContent); + expect(label.style?.fontWeight, text.bodyEmphasis.fontWeight); + + final substate = tester.widget(find.text('SUBSTATE · DETAIL')); + expect(substate.style?.color, colors.textMuted2); + expect(substate.style?.fontSize, 10); + expect(substate.style?.letterSpacing, 0.8); + expect(substate.style?.fontFamily, AppTextTheme.fontFamilySecondary); + expect(substate.style?.fontWeight, FontWeight.w400); + }); + + testWidgets('pending paints muted dot and hides substate', (tester) async { + await pumpRow( + tester, + const QuantusStepRow(state: StepRowState.pending, label: 'Submitting to network', substate: 'hidden'), + ); + + expect(find.text('Submitting to network'), findsOneWidget); + expect(find.text('✓'), findsNothing); + expect(find.text('HIDDEN'), findsNothing); + expect(pendingDot(), findsOneWidget); + expect(tester.getSize(pendingDot()), const Size(5, 5)); + + final label = tester.widget(find.text('Submitting to network')); + expect(label.style?.color, colors.textMuted2); + }); + + testWidgets('ended paints sand mark, content label, and substate', (tester) async { + await pumpRow(tester, const QuantusStepRow(state: StepRowState.ended, label: 'Cancelled', substate: 'user abort')); + + expect(find.text('⊘'), findsOneWidget); + expect(find.text('Cancelled'), findsOneWidget); + expect(find.text('USER ABORT'), findsOneWidget); + expect(find.descendant(of: find.byType(QuantusStepRow), matching: find.byType(RotationTransition)), findsNothing); + + final mark = tester.widget(find.text('⊘')); + expect(mark.style?.color, colors.semanticSand); + + final label = tester.widget(find.text('Cancelled')); + expect(label.style?.color, colors.textContent); + expect(label.style?.fontWeight, text.bodyEmphasis.fontWeight); + }); + + testWidgets('expands to the available width', (tester) async { + await pumpRow( + tester, + const SizedBox( + width: 300, + child: QuantusStepRow(state: StepRowState.done, label: 'Preparing'), + ), + ); + + expect(tester.getSize(find.byType(QuantusStepRow)).width, 300); + }); + + testWidgets('rebuilding with a new state replaces the mark', (tester) async { + await pumpRow(tester, const QuantusStepRow(state: StepRowState.pending, label: 'Preparing')); + expect(pendingDot(), findsOneWidget); + + await pumpRow(tester, const QuantusStepRow(state: StepRowState.active, label: 'Preparing')); + expect(find.text('◌'), findsOneWidget); + expect(find.descendant(of: find.byType(QuantusStepRow), matching: find.byType(RotationTransition)), findsOneWidget); + expect(pendingDot(), findsNothing); + }); +} diff --git a/quantus_sdk/test/ui/quantus_text_field_test.dart b/quantus_sdk/test/ui/quantus_text_field_test.dart new file mode 100644 index 000000000..d21895171 --- /dev/null +++ b/quantus_sdk/test/ui/quantus_text_field_test.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + Future pumpField(WidgetTester tester, Widget field) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: field), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('hides error copy and clear button by default', (tester) async { + final controller = TextEditingController(text: 'Value'); + addTearDown(controller.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, hint: 'Hint')); + + expect(find.text('Value'), findsOneWidget); + expect(find.byIcon(Icons.close), findsNothing); + }); + + testWidgets('shows inline error message', (tester) async { + final controller = TextEditingController(); + addTearDown(controller.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, error: 'Fix this')); + + expect(find.text('Fix this'), findsOneWidget); + }); + + testWidgets('clear button empties the controller', (tester) async { + final controller = TextEditingController(text: 'Hello'); + addTearDown(controller.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, showClearButton: true)); + + expect(find.byIcon(Icons.close), findsOneWidget); + await tester.tap(find.byIcon(Icons.close)); + await tester.pump(); + + expect(controller.text, isEmpty); + expect(find.byIcon(Icons.close), findsNothing); + }); + + testWidgets('requestFocus rebuilds without throwing', (tester) async { + final controller = TextEditingController(); + final focusNode = FocusNode(); + addTearDown(controller.dispose); + addTearDown(focusNode.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, focusNode: focusNode)); + + expect(focusNode.hasFocus, isFalse); + focusNode.requestFocus(); + await tester.pump(); + expect(focusNode.hasFocus, isTrue); + }); + + testWidgets('obscureText and enableSuggestions are forwarded', (tester) async { + final controller = TextEditingController(text: 'secret'); + addTearDown(controller.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, obscureText: true, enableSuggestions: false)); + + final field = tester.widget(find.byType(TextField)); + expect(field.obscureText, isTrue); + expect(field.enableSuggestions, isFalse); + }); + + testWidgets('onSubmitted fires on IME done', (tester) async { + final controller = TextEditingController(text: 'hello'); + addTearDown(controller.dispose); + String? submitted; + + await pumpField(tester, QuantusTextField(controller: controller, onSubmitted: (value) => submitted = value)); + + await tester.showKeyboard(find.byType(TextField)); + await tester.testTextInput.receiveAction(TextInputAction.done); + expect(submitted, 'hello'); + }); + + testWidgets('trailing widget is shown', (tester) async { + final controller = TextEditingController(); + addTearDown(controller.dispose); + + await pumpField(tester, QuantusTextField(controller: controller, trailing: const Icon(Icons.visibility_outlined))); + + expect(find.byIcon(Icons.visibility_outlined), findsOneWidget); + expect(find.byIcon(Icons.close), findsNothing); + }); +} diff --git a/quantus_sdk/test/ui/scaffold_base_test.dart b/quantus_sdk/test/ui/scaffold_base_test.dart new file mode 100644 index 000000000..3ba18c67c --- /dev/null +++ b/quantus_sdk/test/ui/scaffold_base_test.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pump(WidgetTester tester, Widget child) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp(theme: AppTheme.darkTheme(context), home: child), + ), + ), + ); + await tester.pump(); + } + + testWidgets('BaseBackground fills with v3 void', (tester) async { + await pump(tester, const ScaffoldBase(mainContent: Text('Hello'))); + + final container = tester.widget( + find.descendant(of: find.byType(BaseBackground), matching: find.byType(Container)).first, + ); + expect(container.color, colors.bgVoid); + }); + + testWidgets('bottom content uses v3 hairline divider', (tester) async { + await pump( + tester, + const ScaffoldBase( + mainContent: Text('Hello'), + bottomContent: ScaffoldBaseBottomContent(child: Text('Continue')), + ), + ); + + final container = tester.widget( + find.descendant(of: find.byType(ScaffoldBaseBottomContent), matching: find.byType(Container)).first, + ); + final decoration = container.decoration! as BoxDecoration; + expect((decoration.border! as Border).top.color, colors.borderHairline); + }); + + testWidgets('refresh indicator uses v3 content and surface tokens', (tester) async { + await pump(tester, ScaffoldBase.refreshable(onRefresh: () async {}, slivers: const [Text('Item')])); + + final indicator = tester.widget(find.byType(RefreshIndicator)); + expect(indicator.color, colors.textContent); + expect(indicator.backgroundColor, colors.bgSurface); + }); +} diff --git a/quantus_sdk/test/ui/skeleton_test.dart b/quantus_sdk/test/ui/skeleton_test.dart new file mode 100644 index 000000000..42cc1ed83 --- /dev/null +++ b/quantus_sdk/test/ui/skeleton_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + + Future pumpSkeleton(WidgetTester tester, Widget skeleton) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: skeleton), + ), + ), + ), + ); + await tester.pump(); + } + + List decorations(WidgetTester tester) { + return tester + .widgetList(find.descendant(of: find.byType(Skeleton), matching: find.byType(Container))) + .map((container) => container.decoration! as BoxDecoration) + .toList(); + } + + testWidgets('paints v3 surface fill and default height', (tester) async { + await pumpSkeleton(tester, const Skeleton(width: 180)); + + expect(tester.getSize(find.byType(Skeleton)), const Size(180, 16)); + + final outer = decorations(tester).first; + expect(outer.color, colors.bgSurface); + expect(outer.borderRadius, const AppRadiusV3.standard().xsBorder); + }); + + testWidgets('circular constructor is a square pill of the given size', (tester) async { + await pumpSkeleton(tester, Skeleton.circular(size: 40)); + + expect(tester.getSize(find.byType(Skeleton)), const Size(40, 40)); + expect(decorations(tester).first.borderRadius, BorderRadius.circular(40)); + }); + + testWidgets('shimmer highlights use v3 tokens', (tester) async { + await pumpSkeleton(tester, const Skeleton(width: 110, height: 10)); + + final gradient = decorations(tester)[1].gradient! as LinearGradient; + expect(gradient.colors, [ + colors.bgVoid.useOpacity(0.2), + colors.textMuted2.useOpacity(0.2), + colors.bgVoid.useOpacity(0.2), + ]); + }); +} diff --git a/quantus_sdk/test/ui/v2_app_bar_test.dart b/quantus_sdk/test/ui/v2_app_bar_test.dart new file mode 100644 index 000000000..106125379 --- /dev/null +++ b/quantus_sdk/test/ui/v2_app_bar_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quantus_sdk/quantus_sdk.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const colors = AppColorsV3.dark(); + const text = AppTextThemeV3.standard(); + + Future pump(WidgetTester tester, Widget child) async { + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(size: Size(375, 667)), + child: Builder( + builder: (context) => MaterialApp( + theme: AppTheme.darkTheme(context), + home: Scaffold(body: child), + ), + ), + ), + ); + await tester.pump(); + } + + testWidgets('title uses v3 screen title and content color', (tester) async { + await pump(tester, const V2AppBar(title: 'Settings', showBackButton: false)); + + final title = tester.widget(find.text('Settings')); + expect(title.style?.color, colors.textContent); + expect(title.style?.fontSize, text.titleScreen.fontSize); + expect(title.style?.fontWeight, text.titleScreen.fontWeight); + expect(title.style?.height, text.titleScreen.height); + }); + + testWidgets('default back button uses v3 chevron and content color', (tester) async { + await pump(tester, const AppBackButton()); + + expect(find.byType(QuantusIcon), findsOneWidget); + final icon = tester.widget(find.byType(QuantusIcon)); + expect(icon.icon, QuantusIcons.chevronLeft); + expect(icon.color, colors.textContent); + + final svg = tester.widget(find.byType(SvgPicture)); + expect(svg.colorFilter, ColorFilter.mode(colors.textContent, BlendMode.srcIn)); + }); +}