feat: mobile amount unit selection ui

This commit is contained in:
julian 2023-05-29 16:50:21 -06:00
parent 1b1c61a3a3
commit 528bc7405c
6 changed files with 603 additions and 0 deletions

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_coin_units/manage_coin_units_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
import 'package:stackwallet/pages/stack_privacy_calls.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
@ -261,6 +262,41 @@ class AdvancedSettingsView extends StatelessWidget {
),
),
),
const SizedBox(
height: 8,
),
RoundedWhiteContainer(
padding: const EdgeInsets.all(0),
child: RawMaterialButton(
// splashColor: Theme.of(context).extension<StackColors>()!.highlight,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: () {
Navigator.of(context).pushNamed(
ManageCoinUnitsView.routeName,
);
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 20,
),
child: Row(
children: [
Text(
"Units",
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
),
),
),
],
),
),

View file

@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
class ChooseUnitSheet extends ConsumerStatefulWidget {
const ChooseUnitSheet({
Key? key,
required this.coin,
}) : super(key: key);
final Coin coin;
@override
ConsumerState<ChooseUnitSheet> createState() => _ChooseUnitSheetState();
}
class _ChooseUnitSheetState extends ConsumerState<ChooseUnitSheet> {
late AmountUnit _current;
late final List<AmountUnit> values;
@override
void initState() {
values = AmountUnit.valuesForCoin(widget.coin);
_current = ref.read(pAmountUnit(widget.coin));
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Theme.of(context).extension<StackColors>()!.popupBG,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 24,
right: 24,
top: 10,
bottom: 0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultBG,
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
width: 60,
height: 4,
),
),
const SizedBox(
height: 36,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Phrase length",
style: STextStyles.pageTitleH2(context),
textAlign: TextAlign.left,
),
const SizedBox(
height: 16,
),
for (int i = 0; i < values.length; i++)
Column(
children: [
GestureDetector(
onTap: () {
setState(() {
_current = values[i];
});
Navigator.of(context).pop(_current);
},
child: Container(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 20,
height: 20,
child: Radio(
activeColor: Theme.of(context)
.extension<StackColors>()!
.radioButtonIconEnabled,
value: values[i],
groupValue: _current,
onChanged: (x) {
setState(() {
_current = values[i];
});
Navigator.of(context).pop(_current);
},
),
),
const SizedBox(
width: 12,
),
Text(
values[i].unitForCoin(widget.coin),
style: STextStyles.titleBold12(context),
textAlign: TextAlign.left,
),
],
),
),
),
const SizedBox(
height: 16,
),
],
),
const SizedBox(
height: 8,
),
],
),
],
),
),
);
}
}

View file

@ -0,0 +1,251 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_coin_units/choose_unit_sheet.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/desktop/primary_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
class EditCoinUnitsView extends ConsumerStatefulWidget {
const EditCoinUnitsView({
Key? key,
required this.coin,
}) : super(key: key);
final Coin coin;
static const String routeName = "/editCoinUnitsView";
@override
ConsumerState<EditCoinUnitsView> createState() => _EditCoinUnitsViewState();
}
class _EditCoinUnitsViewState extends ConsumerState<EditCoinUnitsView> {
late final TextEditingController _decimalsController;
late final FocusNode _decimalsFocusNode;
late AmountUnit _currentUnit;
void onSave() {
final maxDecimals = int.tryParse(_decimalsController.text);
if (maxDecimals == null) {
// TODO show dialog error thing
return;
}
ref.read(prefsChangeNotifierProvider).updateAmountUnit(
coin: widget.coin,
amountUnit: _currentUnit,
);
ref.read(prefsChangeNotifierProvider).updateMaxDecimals(
coin: widget.coin,
maxDecimals: maxDecimals,
);
Navigator.of(context).pop();
}
Future<void> chooseUnit() async {
final chosenUnit = await showModalBottomSheet<AmountUnit?>(
backgroundColor: Colors.transparent,
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (_) {
return ChooseUnitSheet(
coin: widget.coin,
);
},
);
if (chosenUnit != null) {
setState(() {
_currentUnit = chosenUnit;
});
}
}
@override
void initState() {
_decimalsFocusNode = FocusNode();
_decimalsController = TextEditingController()
..text = ref.read(pMaxDecimals(widget.coin)).toString();
_currentUnit = ref.read(pAmountUnit(widget.coin));
super.initState();
}
@override
void dispose() {
_decimalsFocusNode.dispose();
_decimalsController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => DesktopDialog(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Edit ${widget.coin.prettyName} Units",
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
)
],
)),
child: ConditionalParent(
condition: !Util.isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Edit ${widget.coin.prettyName} units",
style: STextStyles.navBarTitle(context),
),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: child,
),
),
),
child: Column(
children: [
Stack(
children: [
TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
// controller: _lengthController,
readOnly: true,
textInputAction: TextInputAction.none,
),
Positioned.fill(
child: RawMaterialButton(
splashColor:
Theme.of(context).extension<StackColors>()!.highlight,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
),
onPressed: chooseUnit,
child: Padding(
padding: const EdgeInsets.only(
left: 12,
right: 17,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_currentUnit.unitForCoin(widget.coin),
style: STextStyles.itemSubtitle12(context),
),
SvgPicture.asset(
Assets.svg.chevronDown,
width: 14,
height: 6,
color: Theme.of(context)
.extension<StackColors>()!
.textFieldActiveSearchIconRight,
),
],
),
),
),
)
],
),
const SizedBox(
height: 8,
),
ClipRRect(
borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius,
),
child: TextField(
autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true,
key: const Key("addCustomNodeNodeNameFieldKey"),
controller: _decimalsController,
focusNode: _decimalsFocusNode,
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
style: STextStyles.field(context),
decoration: standardInputDecoration(
"Maximum precision",
_decimalsFocusNode,
context,
).copyWith(
suffixIcon: _decimalsController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
_decimalsController.text = "";
setState(() {});
},
),
],
),
),
)
: null,
),
),
),
const SizedBox(
height: 24,
),
if (!Util.isDesktop) const Spacer(),
PrimaryButton(
label: "Save",
buttonHeight: ButtonHeight.xl,
onPressed: onSave,
),
],
),
),
);
}
}

View file

@ -0,0 +1,125 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_coin_units/edit_coin_units_view.dart';
import 'package:stackwallet/providers/global/prefs_provider.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog.dart';
import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
class ManageCoinUnitsView extends ConsumerWidget {
const ManageCoinUnitsView({Key? key}) : super(key: key);
static const String routeName = "/manageCoinUnitsView";
@override
Widget build(BuildContext context, WidgetRef ref) {
bool showTestNet = ref.watch(
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
);
final _coins = Coin.values.where((e) => e != Coin.firoTestNet).toList();
List<Coin> coins = showTestNet
? _coins
: _coins.sublist(0, _coins.length - kTestNetCoinCount);
return ConditionalParent(
condition: Util.isDesktop,
builder: (child) => DesktopDialog(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Units",
style: STextStyles.desktopH3(context),
),
const DesktopDialogCloseButton(),
],
)
],
)),
child: ConditionalParent(
condition: !Util.isDesktop,
builder: (child) => Background(
child: Scaffold(
backgroundColor:
Theme.of(context).extension<StackColors>()!.background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
Navigator.of(context).pop();
},
),
title: Text(
"Units",
style: STextStyles.navBarTitle(context),
),
),
body: child,
),
),
child: ListView.separated(
itemCount: coins.length + 2,
separatorBuilder: (_, __) => const SizedBox(
height: 12,
),
itemBuilder: (_, index) {
if (index == 0) {
return const SizedBox(height: 0);
} else if (index > coins.length) {
return const SizedBox(height: 10);
}
final coin = coins[index - 1];
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: RoundedWhiteContainer(
onPressed: () {
Navigator.of(context).pushNamed(
EditCoinUnitsView.routeName,
arguments: coin,
);
},
child: Row(
children: [
SvgPicture.file(
File(
ref.watch(
coinIconProvider(coin),
),
),
width: 24,
height: 24,
),
const SizedBox(
width: 12,
),
Text(
"Edit ${coin.prettyName} units",
style: STextStyles.titleBold12(context),
),
],
),
),
);
},
),
),
);
}
}

View file

@ -62,6 +62,8 @@ import 'package:stackwallet/pages/send_view/token_send_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_coin_units/edit_coin_units_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_coin_units/manage_coin_units_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart';
import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart';
@ -627,6 +629,26 @@ class RouteGenerator {
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case ManageCoinUnitsView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => const ManageCoinUnitsView(),
settings: RouteSettings(name: settings.name));
case EditCoinUnitsView.routeName:
if (args is Coin) {
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,
builder: (_) => EditCoinUnitsView(
coin: args,
),
settings: RouteSettings(
name: settings.name,
),
);
}
return _routeError("${settings.name} invalid args: ${args.toString()}");
case CreateBackupInfoView.routeName:
return getRoute(
shouldUseMaterialRoute: useMaterialPageRoute,

View file

@ -20,6 +20,33 @@ enum AmountUnit {
const AmountUnit(this.shift);
final int shift;
static List<AmountUnit> valuesForCoin(Coin coin) {
switch (coin) {
case Coin.firo:
case Coin.litecoin:
case Coin.particl:
case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
case Coin.bitcoin:
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.eCash:
case Coin.epicCash:
return AmountUnit.values.sublist(0, 4);
case Coin.monero:
case Coin.wownero:
return AmountUnit.values.sublist(0, 5);
case Coin.ethereum:
return AmountUnit.values;
}
}
}
extension AmountUnitExt on AmountUnit {