Skip to content

Commit

Permalink
fix: remove NumberPad callbacks (#24)
Browse files Browse the repository at this point in the history
* replace callbacks with `ValueNotifier` and handle some input edge cases

* use `NumberPad` without defining callbacks

* fix tests now that deposit input/output default to integers

* change tests to reflect removal of callbacks in `NumberPad`

* refactor number validation util

* refactor shake animation

* refactor number pad to take only `onKeyPressed()`

* add `Currency` class

* add `CurrencyDropdown`

* add `Payin` and `Payout`

* refactor `SendPage`

* refactor `DepositPage` and `WithdrawPage`

* add `auto_size_text`

* update tests

* refactor test

* set `shouldAnimate` based on transaction type

* add `bottomSheetTheme`

* support btc symbol in number format
  • Loading branch information
ethan-tbd authored Feb 23, 2024
1 parent 2549954 commit bca9e7e
Show file tree
Hide file tree
Showing 25 changed files with 881 additions and 593 deletions.
55 changes: 55 additions & 0 deletions lib/features/currency/currency.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:intl/intl.dart';

class Currency {
final double exchangeRate;
final String label;
final IconData icon;

Currency({
required this.exchangeRate,
required this.label,
required this.icon,
});

static String formatFromString(String amount, {String? currency}) {
final parsedAmount = double.tryParse(amount) ?? 0.0;

if (currency == 'BTC') {
return NumberFormat.currency(
symbol: '₿',
decimalDigits: amount.contains('.') ? 8 : 0,
).format(parsedAmount);
}

return NumberFormat.simpleCurrency(
name: currency,
decimalDigits: amount.contains('.') ? 2 : 0,
).format(parsedAmount);
}

static String formatFromDouble(double amount, {String? currency}) {
if (currency == 'BTC') {
return NumberFormat.currency(
symbol: '₿',
decimalDigits: amount % 1 == 0 ? 0 : 8,
).format(amount);
}

return NumberFormat.simpleCurrency(
name: currency,
decimalDigits: amount % 1 == 0 ? 0 : 2,
).format(amount);
}
}

final _defaultList = [
Currency(exchangeRate: 1, label: 'USD', icon: Icons.attach_money),
Currency(exchangeRate: 17, label: 'MXN', icon: Icons.attach_money),
Currency(exchangeRate: 0.000024, label: 'BTC', icon: Icons.currency_bitcoin),
];

final currencyProvider = Provider<List<Currency>>((ref) {
return _defaultList;
});
89 changes: 0 additions & 89 deletions lib/features/currency/currency_converter.dart

This file was deleted.

43 changes: 43 additions & 0 deletions lib/features/currency/currency_dropdown.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:didpay/features/currency/currency.dart';
import 'package:didpay/features/currency/currency_modal.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class CurrencyDropdown extends HookConsumerWidget {
final ValueNotifier<Currency?> selectedCurrency;

const CurrencyDropdown({
required this.selectedCurrency,
super.key,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final currencies = ref.watch(currencyProvider);

return Directionality(
textDirection: TextDirection.rtl,
child: ElevatedButton.icon(
icon: const Icon(Icons.keyboard_arrow_down),
label: Text(
selectedCurrency.value?.label.toString() ?? '',
style: Theme.of(context).textTheme.headlineMedium,
),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.only(left: Grid.xxs),
backgroundColor: Colors.transparent,
elevation: 0,
),
onPressed: () {
CurrencyModal.show(
context,
(value) => selectedCurrency.value =
currencies.firstWhere((c) => c.label == value),
currencies,
selectedCurrency.value?.label.toString() ?? '');
},
),
);
}
}
23 changes: 9 additions & 14 deletions lib/features/currency/currency_modal.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import 'package:didpay/features/currency/currency.dart';
import 'package:flutter/material.dart';
import 'package:didpay/shared/theme/grid.dart';

class CurrencyModal {
static Future<dynamic> show(
BuildContext context,
Function(String) onPressed,
List<Map<String, Object>> supportedCurrencyList,
String selectedCurrency) {
static Future<dynamic> show(BuildContext context, Function(String) onPressed,
List<Currency> currencies, String selectedCurrency) {
return showModalBottomSheet(
useSafeArea: true,
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return SafeArea(
child: SizedBox(
height: supportedCurrencyList.length * 80,
height: currencies.length * 80,
child: Column(children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: Grid.xs),
Expand All @@ -25,19 +23,16 @@ class CurrencyModal {
)),
Expanded(
child: ListView(
children:
supportedCurrencyList.map((Map<String, Object> map) {
IconData icon = map['icon'] as IconData;
String label = map['label'].toString();
children: currencies.map((Currency c) {
return (ListTile(
onTap: () {
onPressed(label);
onPressed(c.label);
Navigator.pop(context);
},
leading: Icon(icon),
title: Text(label,
leading: Icon(c.icon),
title: Text(c.label,
style: Theme.of(context).textTheme.titleMedium),
trailing: (selectedCurrency == label)
trailing: (selectedCurrency == c.label)
? const Icon(Icons.check)
: null,
));
Expand Down
132 changes: 132 additions & 0 deletions lib/features/currency/payin.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:didpay/features/currency/currency.dart';
import 'package:didpay/features/currency/currency_dropdown.dart';
import 'package:didpay/features/home/transaction.dart';
import 'package:didpay/l10n/app_localizations.dart';
import 'package:didpay/shared/shake_animated_text.dart';
import 'package:didpay/shared/theme/grid.dart';
import 'package:didpay/shared/utils/number_validation_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

class PayinKeyPress {
final int count;
final String key;

PayinKeyPress(this.count, this.key);
}

class Payin extends HookWidget {
final String transactionType;
final ValueNotifier<String> amount;
final ValueNotifier<PayinKeyPress> keyPress;
final ValueNotifier<Currency?> currency;

const Payin({
required this.transactionType,
required this.amount,
required this.keyPress,
required this.currency,
super.key,
});

@override
Widget build(BuildContext context) {
final shouldAnimate = useState(false);

final formattedAmount = transactionType == Type.deposit
? Currency.formatFromString(amount.value,
currency: currency.value?.label)
: Currency.formatFromString(amount.value);

useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) => amount.value = '0');
return;
}, [currency.value]);

useEffect(() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final current = amount.value;
final key = keyPress.value.key;
if (key == '') return;

shouldAnimate.value = (key == '<')
? !NumberValidationUtil.isValidDelete(current)
: (transactionType == Type.deposit
? !NumberValidationUtil.isValidInput(
current,
key,
currency: currency.value?.label,
)
: !NumberValidationUtil.isValidInput(current, key));
if (shouldAnimate.value) return;

if (key == '<') {
amount.value = (current.length > 1)
? current.substring(0, current.length - 1)
: '0';
} else {
amount.value = (current == '0' && key == '.')
? '$current$key'
: (current == '0')
? key
: '$current$key';
}
});

return;
}, [keyPress.value]);

return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ShakeAnimatedWidget(
shouldAnimate: shouldAnimate,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
Flexible(
child: AutoSizeText(
formattedAmount,
style: Theme.of(context).textTheme.displayMedium,
maxFontSize:
Theme.of(context).textTheme.displayMedium?.fontSize ??
45.0,
minFontSize:
Theme.of(context).textTheme.bodyLarge?.fontSize ??
16.0,
maxLines: 1,
),
),
const SizedBox(width: Grid.half),
transactionType == Type.deposit
? CurrencyDropdown(selectedCurrency: currency)
: Padding(
padding:
const EdgeInsets.symmetric(horizontal: Grid.xxs),
child: Text(
Loc.of(context).usd,
style: Theme.of(context).textTheme.headlineMedium,
),
),
],
),
],
),
),
const SizedBox(height: Grid.xs),
Text(
transactionType == Type.deposit
? Loc.of(context).youDeposit
: Loc.of(context).youWithdraw,
style: Theme.of(context).textTheme.bodyLarge,
)
],
);
}
}
Loading

0 comments on commit bca9e7e

Please sign in to comment.