Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2d0ee9d
feat: update to new graphql
dewabisma May 5, 2026
5765020
fix: revert to parse string
dewabisma May 6, 2026
9775189
chore: formatting
dewabisma May 6, 2026
639aa0a
feat: finish mining reward screen
dewabisma May 6, 2026
5bda3a0
feat: finish proper loading state
dewabisma May 6, 2026
db03006
feat: update empty state in home
dewabisma May 6, 2026
68d122c
feat: big clean up
dewabisma May 6, 2026
504a962
wip: pos flow
dewabisma May 6, 2026
ce2bdc4
feat: finish pos screens, and formatting
dewabisma May 6, 2026
5453876
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 7, 2026
5379fe8
feat: update graphql wormhole to comply hasura
dewabisma May 7, 2026
58b4f1b
Merge branch 'beast/update-graphql' of https://github.com/Quantus-Net…
dewabisma May 7, 2026
f1776f8
feat: make planck as the highlighted mined stats and earned
dewabisma May 7, 2026
43b3dda
fix: query for transfer
dewabisma May 7, 2026
754b94b
Merge branch 'beast/update-graphql' of https://github.com/Quantus-Net…
dewabisma May 7, 2026
fdab10f
Merge branch 'beast/miner-reward-menu' of https://github.com/Quantus-…
dewabisma May 7, 2026
b20cad8
fix: import path
dewabisma May 7, 2026
f602a62
feat: make charge button big in bottom of screen
dewabisma May 7, 2026
3410530
Merge branch 'beast/cleanup-unused-files' of https://github.com/Quant…
dewabisma May 7, 2026
4a4336a
chore: formatting
dewabisma May 7, 2026
2da5314
feat: show current testnet details and redeem button
dewabisma May 8, 2026
a4ac898
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 8, 2026
760c7e9
feat: update skeleton widget with improved shimmer and colors
dewabisma May 8, 2026
6f1a510
revert: remove skeleton widget changes (moved to feat/skeleton-widget…
dewabisma May 8, 2026
f53a2b6
Merge branch 'beast/miner-reward-menu' into beast/cleanup-unused-files
dewabisma May 8, 2026
2db8553
Merge branch 'beast/miner-reward-menu' into beast/pos-mode-update
dewabisma May 8, 2026
5c40a0d
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 8, 2026
750c246
feat: change skeleton to new one
dewabisma May 8, 2026
f3a03e8
Merge branch 'feat/skeleton-widget-update' of https://github.com/Quan…
dewabisma May 8, 2026
7785fd1
chore: formatting
dewabisma May 8, 2026
20d310b
Merge branch 'beast/cleanup-unused-files' of https://github.com/Quant…
dewabisma May 8, 2026
b7d5009
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 11, 2026
74e4178
Merge branch 'main' of https://github.com/Quantus-Network/quantus-app…
dewabisma May 12, 2026
48046df
chore: formatting
dewabisma May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions mobile-app/assets/copy_icon.svg

This file was deleted.

7 changes: 0 additions & 7 deletions mobile-app/assets/lock_icon.svg

This file was deleted.

88 changes: 88 additions & 0 deletions mobile-app/lib/shared/utils/amount_input_logic.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import 'package:quantus_sdk/quantus_sdk.dart';
import 'package:resonance_network_wallet/models/fiat_currency.dart';
import 'package:resonance_network_wallet/services/exchange_rate_service.dart';

class ToggledInputResult {
final String text;
final BigInt amount;

ToggledInputResult({required this.text, required this.amount});

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is ToggledInputResult && runtimeType == other.runtimeType && text == other.text && amount == other.amount;

@override
int get hashCode => text.hashCode ^ amount.hashCode;
}

class AmountInputLogic {
final ExchangeRateService exchangeRateService;
final FiatCurrency selectedFiat;
final LocaleNumberConfig localeConfig;
final NumberFormattingService formattingService;

AmountInputLogic({
required this.exchangeRateService,
required this.selectedFiat,
required this.localeConfig,
required this.formattingService,
});

/// Converts a raw QUAN [BigInt] to a fiat input string using the current
/// exchange rate and selected fiat currency, formatted for the user's locale.
String quanToFiatString(BigInt quanAmount) {
if (quanAmount == BigInt.zero) return '';
final fiatValue = exchangeRateService.quanRawToFiat(quanAmount, selectedFiat, AppConstants.decimals);
final canonical = fiatValue.toStringAsFixed(selectedFiat.decimals);
return localeConfig.localize(canonical, addGroupingSeparators: false);
}

/// Parses a locale-formatted fiat input string and returns the equivalent
/// raw QUAN [BigInt] scaled by [AppConstants.decimals].
///
/// Throws [InvalidNumberInputException] when [fiatText] cannot be parsed.
BigInt fiatStringToQuan(String fiatText) {
if (fiatText.isEmpty) return BigInt.zero;
final fiatDecimal = localeConfig.parseDecimal(fiatText);
return exchangeRateService.fiatToQuanRaw(fiatDecimal, selectedFiat, AppConstants.decimals);
}

/// Parses a QUAN amount string.
BigInt parseQuanAmount(String text) {
if (text.isEmpty) return BigInt.zero;
return formattingService.parseAmount(text) ?? BigInt.zero;
}

/// Formats a QUAN amount for display in an input field.
String formatQuanAmount(BigInt amount) {
if (amount == BigInt.zero) return '';
return formattingService.formatBalance(amount, maxDecimals: AppConstants.decimals, addThousandsSeparators: false);
}

/// Returns the new input string and amount when toggling between QUAN and Fiat.
ToggledInputResult getToggledInput({required bool wasFlipped, required BigInt currentAmount}) {
if (wasFlipped) {
// Fiat -> QUAN: The user was looking at a fiat amount.
// We already have currentAmount which was calculated from that fiat amount.
final text = formatQuanAmount(currentAmount);
return ToggledInputResult(text: text, amount: currentAmount);
} else {
// QUAN -> Fiat: re-parse amount from the rounded fiat string so
// the displayed value and amount stay in sync.
final text = quanToFiatString(currentAmount);
final newAmount = currentAmount == BigInt.zero ? BigInt.zero : fiatStringToQuan(text);
return ToggledInputResult(text: text, amount: newAmount);
}
}

/// Handles amount change and returns the updated BigInt amount.
BigInt onAmountChanged({required String value, required bool isFlipped}) {
if (isFlipped) {
return fiatStringToQuan(value);
} else {
return parseQuanAmount(value);
}
}
}
35 changes: 35 additions & 0 deletions mobile-app/lib/v2/components/quantus_qr.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:resonance_network_wallet/v2/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),
),
);
}
}
127 changes: 56 additions & 71 deletions mobile-app/lib/v2/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,12 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
showSharedAddressActionSheet(context, shared);
});

final isPosMode = ref.watch(posModeProvider);
final balanceAsync = ref.watch(balanceProvider);
final accountAsync = ref.watch(activeAccountProvider);
final txAsync = ref.watch(activeAccountTransactionsProvider(TransactionFilter.all));
final colors = context.colors;
final text = context.themeText;

Widget screen = accountAsync.when(
return accountAsync.when(
loading: () => const ScaffoldBase(mainContent: Center(child: Loader())),
error: (e, _) => ScaffoldBase(
mainContent: Center(
Expand All @@ -111,46 +109,12 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
slivers: [
_buildContent(active, colors, text),
ActivitySection(txAsync: txAsync, activeAccount: active.account, onRetry: _refresh),
SizedBox(height: isPosMode ? 120 : 58),
const SizedBox(height: 58),
],
bottomContent: balanceAsync
.whenData(
(balance) => balance == BigInt.zero
? ScaffoldBaseBottomContent(
child: QuantusButton.simple(
label: 'Get Testnet Tokens ↗',
onTap: () => launchXPost(AppConstants.faucetUrl),
),
)
: null,
)
.value,
bottomContent: _buildBottomContent(),
);
},
);

if (!isPosMode) return screen;

return Stack(
children: [
screen,
Positioned(
left: 24,
right: 24,
bottom: MediaQuery.of(context).padding.bottom + 24,
child: Material(color: Colors.transparent, child: _buildPosButton(colors, text)),
),
],
);
}

Widget _buildPosButton(AppColorsV2 colors, AppTextTheme text) {
return QuantusButton.simple(
label: 'New Charge',
variant: ButtonVariant.success,
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PosAmountScreen())),
textStyle: text.smallTitle?.copyWith(fontWeight: FontWeight.w700, fontSize: 20, decoration: TextDecoration.none),
);
}

Widget _buildContent(DisplayAccount active, AppColorsV2 colors, AppTextTheme text) {
Expand All @@ -173,6 +137,33 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
);
}

Widget? _buildBottomContent() {
final enablePos = ref.watch(posModeProvider);
final balanceAsync = ref.watch(balanceProvider);

if (enablePos) {
return ScaffoldBaseBottomContent(
child: QuantusButton.simple(
label: 'Charge',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const PosAmountScreen())),
),
);
}

return balanceAsync
.whenData(
(balance) => balance == BigInt.zero
? ScaffoldBaseBottomContent(
child: QuantusButton.simple(
label: 'Get Testnet Tokens ↗',
onTap: () => launchXPost(AppConstants.faucetUrl),
),
)
: null,
)
.value;
}

Widget _buildTopBar() {
final isBalanceHidden = ref.watch(isBalanceHiddenProvider);

Expand Down Expand Up @@ -247,43 +238,37 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SelectRecipientScreen())),
);

if (!enableSwap) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(width: 151, child: receiveCard),
const SizedBox(width: 20),
SizedBox(width: 151, child: sendCard),
],
);
final swapCard = _actionCard(
iconAsset: 'assets/v2/action_swap.svg',
label: 'Swap',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen())),
);

final List<Widget> children = [];

children.add(receiveCard);
children.add(const SizedBox(width: 15));
children.add(sendCard);

if (enableSwap) {
children.add(const SizedBox(width: 15));
children.add(swapCard);
}

return Row(
children: [
Expanded(child: receiveCard),
const SizedBox(width: 15),
Expanded(child: sendCard),
const SizedBox(width: 15),
Expanded(
child: _actionCard(
iconAsset: 'assets/v2/action_swap.svg',
label: 'Swap',
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SwapScreen())),
),
),
],
);
return Row(children: children);
}

Widget _actionCard({required String iconAsset, required String label, required VoidCallback onTap}) {
return QuantusButton.simple(
label: label,
onTap: onTap,
icon: SvgPicture.asset(iconAsset, width: 24, height: 24),
iconPlacement: IconPlacement.top,
padding: const EdgeInsets.all(14),
variant: ButtonVariant.secondary,
textStyle: context.themeText.paragraph?.copyWith(color: context.colors.textPrimary.useOpacity(0.8)),
return Expanded(
child: QuantusButton.simple(
label: label,
onTap: onTap,
icon: SvgPicture.asset(iconAsset, width: 24, height: 24),
iconPlacement: IconPlacement.top,
padding: const EdgeInsets.all(14),
variant: ButtonVariant.secondary,
textStyle: context.themeText.paragraph?.copyWith(color: context.colors.textPrimary.useOpacity(0.8)),
),
);
}
}
Loading
Loading