Cw 09 implement picker screen with ability to search (#250)
* Added new picker screen with search bar and currency iconswownero
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 391 B |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
@ -1,176 +1,210 @@
|
||||
import 'dart:ui';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_utils.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/picker_item.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_background.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
|
||||
import 'currency_picker_widget.dart';
|
||||
|
||||
class CurrencyPicker extends StatefulWidget {
|
||||
CurrencyPicker({
|
||||
@required this.selectedAtIndex,
|
||||
@required this.items,
|
||||
@required this.title,
|
||||
@required this.onItemSelected,
|
||||
});
|
||||
CurrencyPicker(
|
||||
{@required this.selectedAtIndex,
|
||||
@required this.items,
|
||||
@required this.title,
|
||||
@required this.onItemSelected,
|
||||
this.isMoneroWallet = false,
|
||||
this.isConvertFrom = false});
|
||||
|
||||
final int selectedAtIndex;
|
||||
int selectedAtIndex;
|
||||
final List<CryptoCurrency> items;
|
||||
final String title;
|
||||
final Function(CryptoCurrency) onItemSelected;
|
||||
final bool isMoneroWallet;
|
||||
final bool isConvertFrom;
|
||||
|
||||
@override
|
||||
CurrencyPickerState createState() => CurrencyPickerState(
|
||||
selectedAtIndex,
|
||||
items,
|
||||
title,
|
||||
onItemSelected
|
||||
);
|
||||
CurrencyPickerState createState() => CurrencyPickerState(items);
|
||||
}
|
||||
|
||||
class CurrencyPickerState extends State<CurrencyPicker> {
|
||||
CurrencyPickerState(
|
||||
this.selectedAtIndex,
|
||||
this.items,
|
||||
this.title,
|
||||
this.onItemSelected): itemsCount = items.length;
|
||||
CurrencyPickerState(this.items)
|
||||
: isSearchBarActive = false,
|
||||
textFieldValue = '',
|
||||
subPickerItemsList = [],
|
||||
appBarTextStyle = TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
backgroundColor: Colors.transparent,
|
||||
color: Colors.white);
|
||||
|
||||
final int selectedAtIndex;
|
||||
final List<CryptoCurrency> items;
|
||||
final String title;
|
||||
final Function(CryptoCurrency) onItemSelected;
|
||||
@override
|
||||
void initState() {
|
||||
pickerItemsList = CryptoCurrency.all
|
||||
.map((CryptoCurrency cur) => PickerItem<CryptoCurrency>(cur,
|
||||
title: CurrencyUtils.titleForCurrency(cur),
|
||||
iconPath: CurrencyUtils.iconPathForCurrency(cur),
|
||||
tag: CurrencyUtils.tagForCurrency(cur),
|
||||
description: CurrencyUtils.descriptionForCurrency(cur)))
|
||||
.toList();
|
||||
cleanSubPickerItemsList();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final closeButton = Image.asset('assets/images/close.png',
|
||||
color: Palette.darkBlueCraiola,
|
||||
);
|
||||
final int crossAxisCount = 3;
|
||||
final int maxNumberItemsInAlert = 12;
|
||||
final int itemsCount;
|
||||
final double backgroundHeight = 280;
|
||||
final double thumbHeight = 72;
|
||||
ScrollController controller = ScrollController();
|
||||
double fromTop = 0;
|
||||
List<PickerItem<CryptoCurrency>> pickerItemsList;
|
||||
List<CryptoCurrency> items;
|
||||
bool isSearchBarActive;
|
||||
String textFieldValue;
|
||||
List<PickerItem<CryptoCurrency>> subPickerItemsList;
|
||||
TextStyle appBarTextStyle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
controller.addListener(() {
|
||||
fromTop = controller.hasClients
|
||||
? (controller.offset / controller.position.maxScrollExtent * (backgroundHeight - thumbHeight))
|
||||
: 0;
|
||||
setState(() {});
|
||||
void cleanSubPickerItemsList() {
|
||||
subPickerItemsList = pickerItemsList
|
||||
.where((element) => items.contains(element.original))
|
||||
.toList();
|
||||
}
|
||||
|
||||
void currencySearchBySubstring(
|
||||
String subString, List<PickerItem<CryptoCurrency>> list) {
|
||||
setState(() {
|
||||
if (subString.isNotEmpty) {
|
||||
subPickerItemsList = subPickerItemsList
|
||||
.where((element) =>
|
||||
element.title.contains(subString.toUpperCase()) ||
|
||||
element.description.contains(subString.toLowerCase()))
|
||||
.toList();
|
||||
} else {
|
||||
cleanSubPickerItemsList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertBackground(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 24, right: 24),
|
||||
child: Text(
|
||||
title,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.none,
|
||||
color: Colors.white
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 26.0, vertical: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
isSearchBarActive
|
||||
? Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
InkWell(
|
||||
child: Text(
|
||||
S.of(context).cancel,
|
||||
style: appBarTextStyle,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
isSearchBarActive = false;
|
||||
textFieldValue = '';
|
||||
cleanSubPickerItemsList();
|
||||
});
|
||||
}),
|
||||
Container(
|
||||
width: 100.0,
|
||||
child: CupertinoTextField(
|
||||
autofocus: true,
|
||||
placeholder:
|
||||
S.of(context).search + '...',
|
||||
placeholderStyle: appBarTextStyle,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent),
|
||||
cursorColor: Colors.white,
|
||||
cursorHeight: 23.0,
|
||||
style: appBarTextStyle,
|
||||
onChanged: (value) {
|
||||
this.textFieldValue = value;
|
||||
cleanSubPickerItemsList();
|
||||
currencySearchBySubstring(
|
||||
textFieldValue,
|
||||
subPickerItemsList);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
widget.title,
|
||||
style: appBarTextStyle,
|
||||
),
|
||||
IconButton(
|
||||
splashRadius: 23,
|
||||
icon: Icon(Icons.search, color: Colors.white),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isSearchBarActive = true;
|
||||
});
|
||||
},
|
||||
)
|
||||
]),
|
||||
),
|
||||
Expanded(
|
||||
flex: 12,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 26.0, vertical: 26.0),
|
||||
child: Container(
|
||||
child: CurrencyPickerWidget(
|
||||
crossAxisCount: 2,
|
||||
selectedAtIndex: widget.selectedAtIndex,
|
||||
itemsCount: subPickerItemsList.length,
|
||||
pickerItemsList: subPickerItemsList,
|
||||
pickListItem: (int index) {
|
||||
setState(() {
|
||||
widget.selectedAtIndex = index;
|
||||
});
|
||||
widget
|
||||
.onItemSelected(subPickerItemsList[index].original);
|
||||
if (widget.isConvertFrom &&
|
||||
!widget.isMoneroWallet &&
|
||||
(subPickerItemsList[index].original ==
|
||||
CryptoCurrency.xmr)) {
|
||||
} else {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: GestureDetector(
|
||||
onTap: () => null,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||
child: Container(
|
||||
height: 320,
|
||||
width: 300,
|
||||
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
GridView.count(
|
||||
padding: EdgeInsets.all(0),
|
||||
controller: controller,
|
||||
crossAxisCount: crossAxisCount,
|
||||
childAspectRatio: 1.25,
|
||||
crossAxisSpacing: 1,
|
||||
mainAxisSpacing: 1,
|
||||
children: List.generate(
|
||||
itemsCount
|
||||
+ getExtraEmptyTilesCount(crossAxisCount, itemsCount),
|
||||
(index) {
|
||||
|
||||
if (index >= itemsCount) {
|
||||
return Container(
|
||||
color: Theme.of(context).accentTextTheme.title.color,
|
||||
);
|
||||
}
|
||||
|
||||
final item = items[index];
|
||||
final isItemSelected = index == selectedAtIndex;
|
||||
|
||||
final color = isItemSelected
|
||||
? Theme.of(context).textTheme.body2.color
|
||||
: Theme.of(context).accentTextTheme.title.color;
|
||||
final textColor = isItemSelected
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context).primaryTextTheme.title.color;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (onItemSelected == null) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
onItemSelected(item);
|
||||
},
|
||||
child: Container(
|
||||
color: color,
|
||||
child: Center(
|
||||
child: Text(
|
||||
item.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w600,
|
||||
decoration: TextDecoration.none,
|
||||
color: textColor
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
),
|
||||
if (itemsCount > maxNumberItemsInAlert)
|
||||
CakeScrollbar(
|
||||
backgroundHeight: backgroundHeight,
|
||||
thumbHeight: thumbHeight,
|
||||
fromTop: fromTop
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Container(
|
||||
width: 42.0,
|
||||
alignment: Alignment.topCenter,
|
||||
child: FittedBox(
|
||||
child: FloatingActionButton(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close_outlined,
|
||||
color: Palette.darkBlueCraiola,
|
||||
size: 30.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
AlertCloseButton(image: closeButton)
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int getExtraEmptyTilesCount(int crossAxisCount, int itemsCount) {
|
||||
final int tilesInNewRowCount = itemsCount % crossAxisCount;
|
||||
return tilesInNewRowCount == 0 ? 0 : crossAxisCount - tilesInNewRowCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/palette.dart';
|
||||
|
||||
class PickerItemWidget extends StatelessWidget {
|
||||
const PickerItemWidget(
|
||||
{this.iconPath, this.title, this.isSelected, this.tag, this.onTap});
|
||||
|
||||
final String iconPath;
|
||||
final String title;
|
||||
final bool isSelected;
|
||||
final String tag;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
color: isSelected
|
||||
? Theme.of(context).textTheme.bodyText1.color
|
||||
: Theme.of(context).accentTextTheme.headline6.color,
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: Image.asset(
|
||||
iconPath,
|
||||
height: 32.0,
|
||||
width: 32.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? Palette.blueCraiola
|
||||
: Theme.of(context).primaryTextTheme.title.color,
|
||||
fontSize: 18.0,
|
||||
fontFamily: 'Lato',
|
||||
),
|
||||
),
|
||||
tag != null
|
||||
? Positioned(
|
||||
top: -20.0,
|
||||
right: 7.0,
|
||||
child: Container(
|
||||
width: 35.0,
|
||||
height: 18.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
tag,
|
||||
style: TextStyle(
|
||||
fontSize: 7.0,
|
||||
fontFamily: 'Lato',
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.body1
|
||||
.color),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
//border: Border.all(color: ),
|
||||
color: Theme.of(context)
|
||||
.textTheme
|
||||
.body1
|
||||
.decorationColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Container(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'picker_item.dart';
|
||||
import 'currency_picker_item_widget.dart';
|
||||
|
||||
class CurrencyPickerWidget extends StatelessWidget {
|
||||
const CurrencyPickerWidget({
|
||||
@required this.crossAxisCount,
|
||||
@required this.selectedAtIndex,
|
||||
@required this.itemsCount,
|
||||
@required this.pickerItemsList,
|
||||
@required this.pickListItem,
|
||||
});
|
||||
|
||||
final int crossAxisCount;
|
||||
final int selectedAtIndex;
|
||||
final int itemsCount;
|
||||
final Function pickListItem;
|
||||
final List<PickerItem<CryptoCurrency>> pickerItemsList;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).accentTextTheme.headline6.backgroundColor,
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(14.0),
|
||||
child: Scrollbar(
|
||||
showTrackOnHover: true,
|
||||
isAlwaysShown: true,
|
||||
thickness: 6.0,
|
||||
radius: Radius.circular(3),
|
||||
child: GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
crossAxisSpacing: 1,
|
||||
mainAxisExtent: constraints.maxHeight / 8,
|
||||
mainAxisSpacing: 1),
|
||||
itemCount: pickerItemsList.length,
|
||||
itemBuilder: (BuildContext ctx, index) {
|
||||
return PickerItemWidget(
|
||||
onTap: () {
|
||||
pickListItem(index);
|
||||
},
|
||||
title: pickerItemsList[index].title,
|
||||
iconPath: pickerItemsList[index].iconPath,
|
||||
isSelected: index == selectedAtIndex,
|
||||
tag: pickerItemsList[index].tag,
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class CurrencyUtils {
|
||||
static String tagForCurrency(CryptoCurrency cur) {
|
||||
switch (cur) {
|
||||
case CryptoCurrency.bnb:
|
||||
return 'BEP2';
|
||||
case CryptoCurrency.dai:
|
||||
return 'ETH';
|
||||
case CryptoCurrency.usdt:
|
||||
return 'OMNI';
|
||||
case CryptoCurrency.usdterc20:
|
||||
return 'ETH';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String iconPathForCurrency(CryptoCurrency cur) {
|
||||
switch (cur) {
|
||||
case CryptoCurrency.xmr:
|
||||
return 'assets/images/monero_icon.png';
|
||||
case CryptoCurrency.ada:
|
||||
return 'assets/images/ada_icon.png';
|
||||
case CryptoCurrency.bch:
|
||||
return 'assets/images/bch_icon.png';
|
||||
case CryptoCurrency.bnb:
|
||||
return 'assets/images/bnb_icon.png';
|
||||
case CryptoCurrency.btc:
|
||||
return 'assets/images/btc.png';
|
||||
case CryptoCurrency.dai:
|
||||
return 'assets/images/dai_icon.png';
|
||||
case CryptoCurrency.dash:
|
||||
return 'assets/images/dash_icon.png';
|
||||
case CryptoCurrency.eos:
|
||||
return 'assets/images/eos_icon.png';
|
||||
case CryptoCurrency.eth:
|
||||
return 'assets/images/eth_icon.png';
|
||||
case CryptoCurrency.ltc:
|
||||
return 'assets/images/litecoin-ltc_icon.png';
|
||||
case CryptoCurrency.trx:
|
||||
return 'assets/images/trx_icon.png';
|
||||
case CryptoCurrency.usdt:
|
||||
return 'assets/images/usdt_icon.png';
|
||||
case CryptoCurrency.usdterc20:
|
||||
return 'assets/images/usdterc20_icon.png';
|
||||
case CryptoCurrency.xlm:
|
||||
return 'assets/images/xlm_icon.png';
|
||||
case CryptoCurrency.xrp:
|
||||
return 'assets/images/xrp_icon.png';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String titleForCurrency(CryptoCurrency cur) {
|
||||
switch (cur) {
|
||||
case CryptoCurrency.bnb:
|
||||
return 'BNB';
|
||||
case CryptoCurrency.usdterc20:
|
||||
return 'USDT';
|
||||
default:
|
||||
return cur.title;
|
||||
}
|
||||
}
|
||||
|
||||
static String descriptionForCurrency(CryptoCurrency cur) {
|
||||
switch (cur) {
|
||||
case CryptoCurrency.xmr:
|
||||
return 'monero';
|
||||
case CryptoCurrency.ada:
|
||||
return 'cardano';
|
||||
case CryptoCurrency.bch:
|
||||
return 'bitcoin cash';
|
||||
case CryptoCurrency.bnb:
|
||||
return 'binance bep2';
|
||||
case CryptoCurrency.btc:
|
||||
return 'bitcoin';
|
||||
case CryptoCurrency.dai:
|
||||
return 'dai eth';
|
||||
case CryptoCurrency.eth:
|
||||
return 'ethereum';
|
||||
case CryptoCurrency.ltc:
|
||||
return 'litecoin';
|
||||
case CryptoCurrency.trx:
|
||||
return 'tron';
|
||||
case CryptoCurrency.usdt:
|
||||
return 'usdt omni';
|
||||
case CryptoCurrency.usdterc20:
|
||||
return 'tether ERC20 eth';
|
||||
case CryptoCurrency.xlm:
|
||||
return 'lumens';
|
||||
case CryptoCurrency.xrp:
|
||||
return 'ripple';
|
||||
default:
|
||||
return cur.title;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
class PickerItem<T> {
|
||||
PickerItem(this.original,
|
||||
{this.title, this.iconPath, this.tag, this.description});
|
||||
|
||||
final String title;
|
||||
final String iconPath;
|
||||
final String tag;
|
||||
final T original;
|
||||
final String description;
|
||||
}
|