Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ 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';
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/v2/components/quantus_button.dart';
import 'package:resonance_network_wallet/v2/screens/send/input_amount_screen.dart';
Expand Down Expand Up @@ -215,8 +217,11 @@ class _SharedAddressActionSheetState extends State<SharedAddressActionSheet> {

// Helper function to show the receive sheet
void showSharedAddressActionSheet(BuildContext context, String address) {
if (context.peekTopRouteName == sharedAccountSheetRouteSettings.name) Navigator.pop(context);

Comment thread
cursor[bot] marked this conversation as resolved.
showModalBottomSheet(
context: context,
routeSettings: sharedAccountSheetRouteSettings,
backgroundColor: Colors.transparent,
isScrollControlled: true,
constraints: BoxConstraints(
Expand Down
5 changes: 5 additions & 0 deletions mobile-app/lib/routes.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import 'package:flutter/material.dart';

const RouteSettings transactionDetailSheetRouteSettings = RouteSettings(name: 'transaction_detail_sheet');
const RouteSettings inputAmountScreenRouteSettings = RouteSettings(name: 'input_amount_screen');
const RouteSettings sharedAccountSheetRouteSettings = RouteSettings(name: 'shared_account_sheet');
14 changes: 11 additions & 3 deletions mobile-app/lib/services/pos_service.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:quantus_sdk/quantus_sdk.dart';

class PosPaymentRequest {
final String paymentUrl;
final String refId;
Expand All @@ -7,6 +9,11 @@ class PosPaymentRequest {
}

class PosService {
final NumberFormattingService _formattingService;

PosService({NumberFormattingService? formattingService})
: _formattingService = formattingService ?? NumberFormattingService();

String generateRefId() {
final now = DateTime.now().millisecondsSinceEpoch;
return now.toRadixString(36).toUpperCase();
Expand All @@ -17,9 +24,10 @@ class PosService {
return uri.toString();
}

PosPaymentRequest createPaymentRequest({required String accountId, required String amount}) {
PosPaymentRequest createPaymentRequest({required String accountId, required BigInt amountPlanck}) {
final refId = generateRefId();
final url = buildPaymentUrl(accountId: accountId, amount: amount, refId: refId);
return PosPaymentRequest(paymentUrl: url, refId: refId, amount: amount);
final wireAmount = _formattingService.formatWireAmount(amountPlanck);
final url = buildPaymentUrl(accountId: accountId, amount: wireAmount, refId: refId);
return PosPaymentRequest(paymentUrl: url, refId: refId, amount: wireAmount);
}
}
14 changes: 14 additions & 0 deletions mobile-app/lib/shared/extensions/current_route_extensions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:flutter/material.dart';

extension CurrentRouteExtensions on BuildContext {
String? get peekTopRouteName {
String? topRouteName;

Navigator.popUntil(this, (route) {
topRouteName = route.settings.name;
return true;
});

return topRouteName;
}
}
3 changes: 2 additions & 1 deletion mobile-app/lib/v2/components/bottom_sheet_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@ class BottomSheetContainer extends StatelessWidget {
);
}

static Future<T?> show<T>(BuildContext context, {required WidgetBuilder builder}) {
static Future<T?> show<T>(BuildContext context, {required WidgetBuilder builder, RouteSettings? routeSettings}) {
return showModalBottomSheet<T>(
context: context,
routeSettings: routeSettings,
backgroundColor: Colors.transparent,
isScrollControlled: true,
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:quantus_sdk/quantus_sdk.dart';
import 'package:resonance_network_wallet/features/components/dotted_border.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/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/shared/utils/open_external_url.dart';
import 'package:resonance_network_wallet/v2/components/amount_display_with_conversion.dart';
Expand All @@ -12,9 +14,12 @@ 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);

BottomSheetContainer.show(
context,
builder: (_) => _TransactionDetailSheet(tx: tx, activeAccountId: activeAccountId),
routeSettings: transactionDetailSheetRouteSettings,
);
}

Expand Down
19 changes: 14 additions & 5 deletions mobile-app/lib/v2/screens/home/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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:resonance_network_wallet/providers/remote_config_provider.dart';
import 'package:resonance_network_wallet/routes.dart';
import 'package:resonance_network_wallet/shared/extensions/current_route_extensions.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';
Expand Down Expand Up @@ -68,23 +70,30 @@ class _HomeScreenState extends ConsumerState<HomeScreen> {
final active = ref.read(activeAccountProvider).value;
if (active == null) return;
ref.read(transactionIntentProvider.notifier).state = null;

showTransactionDetailSheet(context, transaction, active.account.accountId);
}

void _onPaymentIntent(PaymentIntent? _, PaymentIntent? payment) {
if (payment == null || !mounted) return;
ref.read(paymentIntentProvider.notifier).state = null;
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => InputAmountScreen(recipientAddress: payment.to, initialAmount: payment.amount, isPayMode: true),
),

final pageRoute = MaterialPageRoute(
builder: (_) => InputAmountScreen(recipientAddress: payment.to, initialAmount: payment.amount, isPayMode: true),
settings: inputAmountScreenRouteSettings,
);

if (context.peekTopRouteName == inputAmountScreenRouteSettings.name) {
Navigator.pushReplacement(context, pageRoute);
} else {
Navigator.push(context, pageRoute);
}
}

void _onSharedIntent(String? _, String? shared) {
if (shared == null || !mounted) return;
ref.read(sharedAccountIntentProvider.notifier).state = null;

showSharedAddressActionSheet(context, shared);
}

Expand Down
3 changes: 1 addition & 2 deletions mobile-app/lib/v2/screens/pos/pos_amount_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ class _PosAmountScreenState extends ConsumerState<PosAmountScreen> {

void _onCharge() {
if (_amount <= BigInt.zero) return;
final quanString = _amountInputLogic.formatQuanAmount(_amount);
Navigator.push(context, MaterialPageRoute(builder: (_) => PosQrScreen(amount: quanString)));
Navigator.push(context, MaterialPageRoute(builder: (_) => PosQrScreen(amountPlanck: _amount)));
}

Future<void> _toggleFlip() async {
Expand Down
31 changes: 17 additions & 14 deletions mobile-app/lib/v2/screens/pos/pos_qr_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,14 @@ import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';

class PosQrScreen extends ConsumerStatefulWidget {
final String amount;
const PosQrScreen({super.key, required this.amount});
final BigInt amountPlanck;
const PosQrScreen({super.key, required this.amountPlanck});

@override
ConsumerState<PosQrScreen> createState() => _PosQrScreenState();
}

class _PosQrScreenState extends ConsumerState<PosQrScreen> {
final _posService = PosService();
PosPaymentRequest? _request;

final _txWatch = TxWatchService();
Expand All @@ -51,13 +50,11 @@ class _PosQrScreenState extends ConsumerState<PosQrScreen> {
}

void _startWatching() {
final formattingService = ref.watch(numberFormattingServiceProvider);
final active = ref.read(activeAccountProvider).value;
if (active == null) return;

final expectedPlanck = formattingService.parseAmount(widget.amount);
if (expectedPlanck == null) {
print('[PosQr] ERROR: failed to parse amount "${widget.amount}"');
if (widget.amountPlanck <= BigInt.zero) {
print('[PosQr] ERROR: invalid amount planck ${widget.amountPlanck}');
if (mounted) setState(() => _watchError = 'Invalid amount. Tap to retry.');
return;
}
Expand All @@ -67,15 +64,15 @@ class _PosQrScreenState extends ConsumerState<PosQrScreen> {
_watchError = null;
});

print('[PosQr] watching address=${active.account.accountId} expected=$expectedPlanck planck');
print('[PosQr] watching address=${active.account.accountId} expected=${widget.amountPlanck} planck');
_txWatch.watch(
address: active.account.accountId,
onTransfer: (tx) {
print('[PosQr] onTransfer from=${tx.from} amount=${tx.amount} hash=${tx.txHash}');
if (_isPaid) return;
final received = BigInt.tryParse(tx.amount);
if (received != expectedPlanck) {
print('[PosQr] amount mismatch (received=$received expected=$expectedPlanck), ignoring');
if (received != widget.amountPlanck) {
print('[PosQr] amount mismatch (received=$received expected=${widget.amountPlanck}), ignoring');
return;
}

Expand All @@ -84,7 +81,7 @@ class _PosQrScreenState extends ConsumerState<PosQrScreen> {
tempId: 'pending_recv_${DateTime.now().millisecondsSinceEpoch}',
from: tx.from,
to: active.account.accountId,
amount: expectedPlanck,
amount: widget.amountPlanck,
timestamp: DateTime.now(),
transactionState: TransactionState.pending,
isReversible: false,
Expand Down Expand Up @@ -163,8 +160,12 @@ class _PosQrScreenState extends ConsumerState<PosQrScreen> {
final text = context.themeText;
final accountAsync = ref.watch(activeAccountProvider);
final formattingService = ref.watch(numberFormattingServiceProvider);
final planck = formattingService.parseAmount(widget.amount) ?? BigInt.zero;
final display = ref.watch(txAmountDisplayProvider)(planck, withSignPrefix: false, isSend: false, quanDecimals: 4);
final display = ref.watch(txAmountDisplayProvider)(
widget.amountPlanck,
withSignPrefix: false,
isSend: false,
quanDecimals: 4,
);

return ScaffoldBase(
appBar: V2AppBar(title: _isPaid ? 'Payment Received' : 'Scan to Pay'),
Expand All @@ -175,7 +176,9 @@ class _PosQrScreenState extends ConsumerState<PosQrScreen> {
),
data: (active) {
if (active == null) return const Center(child: Text('No active account'));
_request ??= _posService.createPaymentRequest(accountId: active.account.accountId, amount: widget.amount);
_request ??= PosService(
formattingService: formattingService,
).createPaymentRequest(accountId: active.account.accountId, amountPlanck: widget.amountPlanck);
if (_isPaid) return _buildPaidContent(colors, text, display.primaryAmount);
return _buildQrContent(_request!, colors, text, display);
},
Expand Down
64 changes: 32 additions & 32 deletions mobile-app/lib/v2/screens/send/input_amount_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,14 @@ class _InputAmountScreenState extends ConsumerState<InputAmountScreen> {
super.initState();
assert(widget.recipientAddress.trim().isNotEmpty, 'InputAmountScreen requires a recipient');
_amountFocus.addListener(_onAmountFocusChanged);
if (widget.initialAmount != null) {
final isFlipped = ref.read(isCurrencyFlippedProvider);
if (!isFlipped) {
_amount = _amountInputLogic.parseQuanAmount(widget.initialAmount!);
_amountController.text = widget.initialAmount!;
} else {
final parsed = _amountInputLogic.parseQuanAmount(widget.initialAmount!);
if (parsed > BigInt.zero) {
_amount = parsed;
_amountController.text = _amountInputLogic.quanToFiatString(parsed);
}
if (widget.initialAmount != null && widget.initialAmount!.isNotEmpty) {
final formattingService = ref.read(numberFormattingServiceProvider);
final planck = widget.isPayMode
? formattingService.parseWireAmount(widget.initialAmount!) ?? BigInt.zero
: _amountInputLogic.parseQuanAmount(widget.initialAmount!);
if (planck > BigInt.zero) {
_amount = planck;
_amountController.text = _amountInputLogic.formatQuanAmount(planck);
}
}
if (widget.recipientChecksum != null) {
Expand Down Expand Up @@ -121,7 +118,7 @@ class _InputAmountScreenState extends ConsumerState<InputAmountScreen> {
}

void _onAmountChanged(String _) {
final isFlipped = ref.read(isCurrencyFlippedProvider);
final isFlipped = widget.isPayMode ? false : ref.read(isCurrencyFlippedProvider);
try {
setState(() => _amount = _amountInputLogic.onAmountChanged(value: _amountController.text, isFlipped: isFlipped));
} on InvalidNumberInputException catch (e, stack) {
Expand Down Expand Up @@ -342,7 +339,8 @@ class _InputAmountScreenState extends ConsumerState<InputAmountScreen> {
}

Widget _amountCenter(AppColorsV2 colors, AppTextTheme text) {
final isFlipped = ref.watch(isCurrencyFlippedProvider);
final isPayMode = widget.isPayMode;
final isFlipped = isPayMode ? false : ref.watch(isCurrencyFlippedProvider);
final selectedFiat = ref.watch(selectedFiatCurrencyProvider);
final localeConfig = ref.watch(localeNumberConfigProvider);
final display = ref.watch(txAmountDisplayProvider)(
Expand Down Expand Up @@ -398,26 +396,28 @@ class _InputAmountScreenState extends ConsumerState<InputAmountScreen> {
children: primaryRowChildren,
),
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'≈ ${display.secondaryAmount}',
style: text.paragraph?.copyWith(
color: colors.textTertiary,
fontFamily: AppTextTheme.fontFamilySecondary,
if (!isPayMode) ...[
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'≈ ${display.secondaryAmount}',
style: text.paragraph?.copyWith(
color: colors.textTertiary,
fontFamily: AppTextTheme.fontFamilySecondary,
),
),
),
const SizedBox(width: 8),
QuantusIconButton.circular(
icon: Icons.swap_vert,
onTap: _toggleFlip,
isActive: display.isFlipped,
size: IconButtonSize.small,
),
],
),
const SizedBox(width: 8),
QuantusIconButton.circular(
icon: Icons.swap_vert,
onTap: _toggleFlip,
isActive: display.isFlipped,
size: IconButtonSize.small,
),
],
),
],
],
),
);
Expand Down
2 changes: 2 additions & 0 deletions mobile-app/lib/v2/screens/send/select_recipient_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:resonance_network_wallet/features/components/skeleton.dart';
import 'package:resonance_network_wallet/providers/account_providers.dart';
import 'package:resonance_network_wallet/providers/route_intent_providers.dart';
import 'package:resonance_network_wallet/providers/wallet_providers.dart';
import 'package:resonance_network_wallet/routes.dart';
import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart';
import 'package:resonance_network_wallet/v2/components/loader.dart';
import 'package:resonance_network_wallet/v2/components/qr_scanner_page.dart';
Expand Down Expand Up @@ -150,6 +151,7 @@ class _SelectRecipientScreenState extends ConsumerState<SelectRecipientScreen> {
Navigator.push<bool>(
context,
MaterialPageRoute(
settings: inputAmountScreenRouteSettings,
builder: (_) => InputAmountScreen(
recipientAddress: address,
recipientChecksum: _recipientChecksum,
Expand Down
Loading
Loading