desktop exchange coin selection ui

This commit is contained in:
julian 2022-11-21 11:21:44 -06:00
parent 5c7cb8a3c5
commit d06c4862b1
3 changed files with 459 additions and 320 deletions

View file

@ -8,6 +8,8 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
@ -16,8 +18,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'package:stackwallet/utilities/util.dart';
class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget { class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget {
const FixedRateMarketPairCoinSelectionView({ const FixedRateMarketPairCoinSelectionView({
Key? key, Key? key,
@ -120,95 +120,106 @@ class _FixedRateMarketPairCoinSelectionViewState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( final isDesktop = Util.isDesktop;
backgroundColor: Theme.of(context).extension<StackColors>()!.background, return ConditionalParent(
appBar: AppBar( condition: !isDesktop,
leading: AppBarBackButton( builder: (child) {
onPressed: () async { return Scaffold(
if (FocusScope.of(context).hasFocus) { backgroundColor:
FocusScope.of(context).unfocus(); Theme.of(context).extension<StackColors>()!.background,
await Future<void>.delayed(const Duration(milliseconds: 50)); appBar: AppBar(
} leading: AppBarBackButton(
if (mounted) { onPressed: () async {
Navigator.of(context).pop(); if (FocusScope.of(context).hasFocus) {
} FocusScope.of(context).unfocus();
}, await Future<void>.delayed(const Duration(milliseconds: 50));
), }
title: Text( if (mounted) {
"Choose a coin to exchange", Navigator.of(context).pop();
style: STextStyles.pageTitleH2(context), }
), },
), ),
body: Padding( title: Text(
padding: const EdgeInsets.symmetric( "Choose a coin to exchange",
horizontal: 16, style: STextStyles.pageTitleH2(context),
), ),
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, body: Padding(
children: [ padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isDesktop)
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
), ),
child: TextField( child: TextField(
autocorrect: Util.isDesktop ? false : true, autocorrect: Util.isDesktop ? false : true,
enableSuggestions: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true,
controller: _searchController, controller: _searchController,
focusNode: _searchFocusNode, focusNode: _searchFocusNode,
onChanged: filter, onChanged: filter,
style: STextStyles.field(context), style: STextStyles.field(context),
decoration: standardInputDecoration( decoration: standardInputDecoration(
"Search", "Search",
_searchFocusNode, _searchFocusNode,
context, context,
).copyWith( ).copyWith(
prefixIcon: Padding( prefixIcon: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
vertical: 16, vertical: 16,
), ),
child: SvgPicture.asset( child: SvgPicture.asset(
Assets.svg.search, Assets.svg.search,
width: 16, width: 16,
height: 16, height: 16,
),
), ),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
), ),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
},
),
],
),
),
)
: null,
), ),
), ),
const SizedBox( ),
height: 10, const SizedBox(
), height: 10,
Text( ),
"Popular coins", Text(
style: STextStyles.smallMed12(context), "Popular coins",
), style: STextStyles.smallMed12(context),
const SizedBox( ),
height: 12, const SizedBox(
), height: 12,
Builder(builder: (context) { ),
Flexible(
child: Builder(builder: (context) {
final items = _markets final items = _markets
.where((e) => Coin.values .where((e) => Coin.values
.where((coin) => .where((coin) =>
@ -221,6 +232,7 @@ class _FixedRateMarketPairCoinSelectionViewState
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
child: ListView.builder( child: ListView.builder(
shrinkWrap: true, shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length, itemCount: items.length,
itemBuilder: (builderContext, index) { itemBuilder: (builderContext, index) {
final String ticker = final String ticker =
@ -282,84 +294,85 @@ class _FixedRateMarketPairCoinSelectionViewState
), ),
); );
}), }),
const SizedBox( ),
height: 20, const SizedBox(
), height: 20,
Text( ),
"All coins", Text(
style: STextStyles.smallMed12(context), "All coins",
), style: STextStyles.smallMed12(context),
const SizedBox( ),
height: 12, const SizedBox(
), height: 12,
Flexible( ),
child: RoundedWhiteContainer( Flexible(
padding: const EdgeInsets.all(0), child: RoundedWhiteContainer(
child: ListView.builder( padding: const EdgeInsets.all(0),
shrinkWrap: true, child: ListView.builder(
itemCount: _markets.length, shrinkWrap: true,
itemBuilder: (builderContext, index) { primary: isDesktop ? false : null,
final String ticker = itemCount: _markets.length,
isFrom ? _markets[index].from : _markets[index].to; itemBuilder: (builderContext, index) {
final String ticker =
isFrom ? _markets[index].from : _markets[index].to;
final tuple = _imageUrlAndNameFor(ticker); final tuple = _imageUrlAndNameFor(ticker);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
Navigator.of(context).pop(ticker); Navigator.of(context).pop(ticker);
}, },
child: RoundedWhiteContainer( child: RoundedWhiteContainer(
child: Row( child: Row(
children: [ children: [
SizedBox( SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
tuple.item1,
width: 24, width: 24,
height: 24, height: 24,
child: SvgPicture.network( placeholderBuilder: (_) =>
tuple.item1, const LoadingIndicator(),
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
), ),
const SizedBox( ),
width: 10, const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
), ),
Expanded( ),
child: Column( ],
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
tuple.item2,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
), ),
), ),
); ),
}, );
), },
), ),
), ),
], ),
), ],
), ),
); );
} }

View file

@ -6,6 +6,8 @@ import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/conditional_parent.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
@ -13,8 +15,6 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:stackwallet/utilities/util.dart';
class FloatingRateCurrencySelectionView extends StatefulWidget { class FloatingRateCurrencySelectionView extends StatefulWidget {
const FloatingRateCurrencySelectionView({ const FloatingRateCurrencySelectionView({
Key? key, Key? key,
@ -76,96 +76,109 @@ class _FloatingRateCurrencySelectionViewState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( final isDesktop = Util.isDesktop;
backgroundColor: Theme.of(context).extension<StackColors>()!.background, return ConditionalParent(
appBar: AppBar( condition: !isDesktop,
leading: AppBarBackButton( builder: (child) {
onPressed: () async { return Scaffold(
if (FocusScope.of(context).hasFocus) { backgroundColor:
FocusScope.of(context).unfocus(); Theme.of(context).extension<StackColors>()!.background,
await Future<void>.delayed(const Duration(milliseconds: 50)); appBar: AppBar(
} leading: AppBarBackButton(
if (mounted) { onPressed: () async {
Navigator.of(context).pop(); if (FocusScope.of(context).hasFocus) {
} FocusScope.of(context).unfocus();
}, await Future<void>.delayed(const Duration(milliseconds: 50));
), }
title: Text( if (mounted) {
"Choose a coin to exchange", Navigator.of(context).pop();
style: STextStyles.pageTitleH2(context), }
), },
), ),
body: Padding( title: Text(
padding: const EdgeInsets.symmetric( "Choose a coin to exchange",
horizontal: 16, style: STextStyles.pageTitleH2(context),
), ),
child: Column( ),
crossAxisAlignment: CrossAxisAlignment.start, body: Padding(
children: [ padding: const EdgeInsets.symmetric(
horizontal: 16,
),
child: child,
),
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max,
children: [
if (!isDesktop)
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
ClipRRect( ClipRRect(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(
Constants.size.circularBorderRadius, Constants.size.circularBorderRadius,
), ),
child: TextField( child: TextField(
autocorrect: Util.isDesktop ? false : true, autocorrect: !isDesktop,
enableSuggestions: Util.isDesktop ? false : true, enableSuggestions: !isDesktop,
controller: _searchController, controller: _searchController,
focusNode: _searchFocusNode, focusNode: _searchFocusNode,
onChanged: filter, onChanged: filter,
style: STextStyles.field(context), style: STextStyles.field(context),
decoration: standardInputDecoration( decoration: standardInputDecoration(
"Search", "Search",
_searchFocusNode, _searchFocusNode,
context, context,
).copyWith( desktopMed: isDesktop,
prefixIcon: Padding( ).copyWith(
padding: const EdgeInsets.symmetric( prefixIcon: Padding(
horizontal: 10, padding: const EdgeInsets.symmetric(
vertical: 16, horizontal: 10,
), vertical: 16,
child: SvgPicture.asset( ),
Assets.svg.search, child: SvgPicture.asset(
width: 16, Assets.svg.search,
height: 16, width: 16,
), height: 16,
), ),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
), ),
suffixIcon: _searchController.text.isNotEmpty
? Padding(
padding: const EdgeInsets.only(right: 0),
child: UnconstrainedBox(
child: Row(
children: [
TextFieldIconButton(
child: const XIcon(),
onTap: () async {
setState(() {
_searchController.text = "";
});
filter("");
},
),
],
),
),
)
: null,
), ),
), ),
const SizedBox( ),
height: 10, const SizedBox(
), height: 10,
Text( ),
"Popular coins", Text(
style: STextStyles.smallMed12(context), "Popular coins",
), style: STextStyles.smallMed12(context),
const SizedBox( ),
height: 12, const SizedBox(
), height: 12,
Builder(builder: (context) { ),
Flexible(
child: Builder(builder: (context) {
final items = _currencies final items = _currencies
.where((e) => Coin.values .where((e) => Coin.values
.where((coin) => .where((coin) =>
@ -177,6 +190,7 @@ class _FloatingRateCurrencySelectionViewState
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
child: ListView.builder( child: ListView.builder(
shrinkWrap: true, shrinkWrap: true,
primary: isDesktop ? false : null,
itemCount: items.length, itemCount: items.length,
itemBuilder: (builderContext, index) { itemBuilder: (builderContext, index) {
return Padding( return Padding(
@ -234,80 +248,81 @@ class _FloatingRateCurrencySelectionViewState
), ),
); );
}), }),
const SizedBox( ),
height: 20, const SizedBox(
), height: 20,
Text( ),
"All coins", Text(
style: STextStyles.smallMed12(context), "All coins",
), style: STextStyles.smallMed12(context),
const SizedBox( ),
height: 12, const SizedBox(
), height: 12,
Flexible( ),
child: RoundedWhiteContainer( Flexible(
padding: const EdgeInsets.all(0), child: RoundedWhiteContainer(
child: ListView.builder( padding: const EdgeInsets.all(0),
shrinkWrap: true, child: ListView.builder(
itemCount: _currencies.length, shrinkWrap: true,
itemBuilder: (builderContext, index) { primary: isDesktop ? false : null,
return Padding( itemCount: _currencies.length,
padding: const EdgeInsets.symmetric(vertical: 4), itemBuilder: (builderContext, index) {
child: GestureDetector( return Padding(
onTap: () { padding: const EdgeInsets.symmetric(vertical: 4),
Navigator.of(context).pop(_currencies[index]); child: GestureDetector(
}, onTap: () {
child: RoundedWhiteContainer( Navigator.of(context).pop(_currencies[index]);
child: Row( },
children: [ child: RoundedWhiteContainer(
SizedBox( child: Row(
children: [
SizedBox(
width: 24,
height: 24,
child: SvgPicture.network(
_currencies[index].image,
width: 24, width: 24,
height: 24, height: 24,
child: SvgPicture.network( placeholderBuilder: (_) =>
_currencies[index].image, const LoadingIndicator(),
width: 24,
height: 24,
placeholderBuilder: (_) =>
const LoadingIndicator(),
),
), ),
const SizedBox( ),
width: 10, const SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currencies[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_currencies[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
), ),
Expanded( ),
child: Column( ],
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_currencies[index].name,
style: STextStyles.largeMedium14(context),
),
const SizedBox(
height: 2,
),
Text(
_currencies[index].ticker.toUpperCase(),
style: STextStyles.smallMed12(context)
.copyWith(
color: Theme.of(context)
.extension<StackColors>()!
.textSubtitle1,
),
),
],
),
),
],
),
), ),
), ),
); ),
}, );
), },
), ),
), ),
], ),
), ],
), ),
); );
} }

View file

@ -39,6 +39,7 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart';
import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart';
import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/loading_indicator.dart';
import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_container.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
@ -410,13 +411,65 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
} }
}).toList(growable: false); }).toList(growable: false);
final result = await Navigator.of(context).push( final result = isDesktop
MaterialPageRoute<dynamic>( ? await showDialog<Currency?>(
builder: (_) => FloatingRateCurrencySelectionView( context: context,
currencies: tickers, builder: (context) {
), return DesktopDialog(
), maxHeight: 700,
); maxWidth: 580,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Choose a coin to exchange",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: FloatingRateCurrencySelectionView(
currencies: tickers,
),
),
),
],
),
),
),
],
),
);
})
: await Navigator.of(context).push(
MaterialPageRoute<dynamic>(
builder: (_) => FloatingRateCurrencySelectionView(
currencies: tickers,
),
),
);
if (mounted && result is Currency) { if (mounted && result is Currency) {
onSelected(result); onSelected(result);
@ -490,15 +543,73 @@ class _ExchangeFormState extends ConsumerState<ExchangeForm> {
.toList(growable: false); .toList(growable: false);
} }
final result = await Navigator.of(context).push( final result = isDesktop
MaterialPageRoute<dynamic>( ? await showDialog<String?>(
builder: (_) => FixedRateMarketPairCoinSelectionView( context: context,
markets: marketsThatPairWithExcludedTicker, builder: (context) {
currencies: ref.read(availableChangeNowCurrenciesProvider).currencies, return DesktopDialog(
isFrom: excludedTicker != fromTicker, maxHeight: 700,
), maxWidth: 580,
), child: Column(
); children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(
left: 32,
),
child: Text(
"Choose a coin to exchange",
style: STextStyles.desktopH3(context),
),
),
const DesktopDialogCloseButton(),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 32,
right: 32,
bottom: 32,
),
child: Row(
children: [
Expanded(
child: RoundedWhiteContainer(
padding: const EdgeInsets.all(16),
borderColor: Theme.of(context)
.extension<StackColors>()!
.background,
child: FixedRateMarketPairCoinSelectionView(
markets: marketsThatPairWithExcludedTicker,
currencies: ref
.read(
availableChangeNowCurrenciesProvider)
.currencies,
isFrom: excludedTicker != fromTicker,
),
),
),
],
),
),
),
],
),
);
})
: await Navigator.of(context).push(
MaterialPageRoute<dynamic>(
builder: (_) => FixedRateMarketPairCoinSelectionView(
markets: marketsThatPairWithExcludedTicker,
currencies:
ref.read(availableChangeNowCurrenciesProvider).currencies,
isFrom: excludedTicker != fromTicker,
),
),
);
if (mounted && result is String) { if (mounted && result is String) {
onSelected(result); onSelected(result);