diff --git a/lib/models/plugins.dart b/lib/models/plugins.dart index 26088a991..eac094368 100644 --- a/lib/models/plugins.dart +++ b/lib/models/plugins.dart @@ -134,6 +134,18 @@ class MoonpayPlugin extends DepositPlugin { : null; } +class TransakPlugin extends DepositPlugin { + TransakPlugin({name, isActive, widgetUrl}) : super(name, isActive, widgetUrl); + + static TransakPlugin fromJson(dynamic json) => json != null + ? TransakPlugin( + name: json['name'], + widgetUrl: json['widgetUrl'], + isActive: json["isActive"] || false, + ) + : null; +} + class CarbonPlugin extends DepositPlugin { CarbonPlugin({name, isActive, widgetUrl}) : super(name, isActive, widgetUrl); @@ -187,6 +199,8 @@ class RampPlugin extends DepositPlugin { class Plugins { @JsonKey(name: 'moonpay', fromJson: _moonpayFromJson, toJson: _moonpayToJson, includeIfNull: false) MoonpayPlugin moonpay; + @JsonKey(name: 'transak', fromJson: _transakFromJson, toJson: _transakToJson, includeIfNull: false) + TransakPlugin transak; @JsonKey(name: 'carbon', fromJson: _carbonFromJson, toJson: _carbonToJson, includeIfNull: false) CarbonPlugin carbon; @JsonKey(name: 'wyre', fromJson: _wyreFromJson, toJson: _wyreToJson, includeIfNull: false) @@ -204,7 +218,7 @@ class Plugins { @JsonKey(name: 'inviteBonus', fromJson: _inviteBonusFromJson, toJson: _inviteBonusToJson, includeIfNull: false) InviteBonusPlugin inviteBonus; - Plugins({this.moonpay, this.carbon, this.wyre, this.coindirect, this.ramp, this.joinBonus, this.walletBanner, this.backupBonus, this.inviteBonus}); + Plugins({this.moonpay, this.transak, this.carbon, this.wyre, this.coindirect, this.ramp, this.joinBonus, this.walletBanner, this.backupBonus, this.inviteBonus}); static Map getServicesMap (dynamic json) { if (json.containsKey('onramp')) { @@ -225,6 +239,7 @@ class Plugins { dynamic services= Plugins.getServicesMap(json); return Plugins( moonpay: MoonpayPlugin.fromJson(services["moonpay"]), + transak: TransakPlugin.fromJson(services["transak"]), carbon: CarbonPlugin.fromJson(services["carbon"]), wyre: WyrePlugin.fromJson(services["wyre"]), coindirect: CoindirectPlugin.fromJson(services["coindirect"]), @@ -280,9 +295,15 @@ class Plugins { static MoonpayPlugin _moonpayFromJson(Map json) => json == null ? null : MoonpayPlugin.fromJson(json); + static TransakPlugin _transakFromJson(Map json) => + json == null ? null : TransakPlugin.fromJson(json); + static Map _moonpayToJson(MoonpayPlugin moonpay) => moonpay != null ? moonpay.toJson() : null; + static Map _transakToJson(TransakPlugin transak) => + transak != null ? transak.toJson() : null; + static CarbonPlugin _carbonFromJson(Map json) => json == null ? null : CarbonPlugin.fromJson(json); @@ -291,6 +312,9 @@ class Plugins { List getDepositPlugins() { List depositPlugins = []; + if (this.transak != null && this.transak.isActive) { + depositPlugins.add(this.transak); + } if (this.moonpay != null && this.moonpay.isActive) { depositPlugins.add(this.moonpay); } diff --git a/lib/models/plugins.g.dart b/lib/models/plugins.g.dart index d6673eb8c..c4c7f13e1 100644 --- a/lib/models/plugins.g.dart +++ b/lib/models/plugins.g.dart @@ -16,6 +16,7 @@ Map _$PluginsToJson(Plugins instance) { } writeNotNull('moonpay', Plugins._moonpayToJson(instance.moonpay)); + writeNotNull('transak', Plugins._transakToJson(instance.transak)); writeNotNull('carbon', Plugins._carbonToJson(instance.carbon)); writeNotNull('wyre', Plugins._wyreToJson(instance.wyre)); writeNotNull('coindirect', Plugins._coindirectToJson(instance.coindirect)); diff --git a/lib/redux/actions/cash_wallet_actions.dart b/lib/redux/actions/cash_wallet_actions.dart index 555513fec..0c8df5088 100644 --- a/lib/redux/actions/cash_wallet_actions.dart +++ b/lib/redux/actions/cash_wallet_actions.dart @@ -19,6 +19,7 @@ import 'package:supervecina/redux/actions/user_actions.dart'; import 'package:supervecina/utils/addresses.dart'; import 'package:supervecina/redux/state/store.dart'; import 'package:supervecina/utils/constans.dart'; +import 'package:supervecina/utils/firebase.dart'; import 'package:supervecina/utils/forks.dart'; import 'package:supervecina/utils/format.dart'; import 'package:http/http.dart'; @@ -252,15 +253,22 @@ ThunkAction enablePushNotifications() { }, }); + void switchOnPush(message) { + String communityAddress = communityAddressFromNotification(message); + if (communityAddress != null && communityAddress.isNotEmpty) { + // store.dispatch(switchCommunityCall(communityAddress)); + } + } + firebaseMessaging.configure( onMessage: (Map message) async { - logger.info('onMessage called: $message'); + switchOnPush(message); }, onResume: (Map message) async { - logger.info('onResume called: $message'); + switchOnPush(message); }, onLaunch: (Map message) async { - logger.info('onLaunch called: $message'); + switchOnPush(message); }, ); } catch (e) { @@ -300,7 +308,7 @@ ThunkAction segmentIdentifyCall(Map traits) { final logger = await AppFactory().getLogger('action'); try { UserState userState = store.state.userState; - String fullPhoneNumber = store.state.userState.normalizedPhoneNumber ?? '';// formatPhoneNumber(store.state.userState.phoneNumber, store.state.userState.countryCode); + String fullPhoneNumber = store.state.userState.normalizedPhoneNumber ?? ''; logger.info('Identify - $fullPhoneNumber'); traits = traits ?? new Map(); DateTime installedAt = userState.installedAt; diff --git a/lib/redux/actions/user_actions.dart b/lib/redux/actions/user_actions.dart index 59fda4b42..a38625a22 100644 --- a/lib/redux/actions/user_actions.dart +++ b/lib/redux/actions/user_actions.dart @@ -310,33 +310,26 @@ ThunkAction syncContactsCall(List contacts) { String countryCode = store.state.userState.countryCode; String isoCode = store.state.userState.isoCode; for (Contact contact in contacts) { - if (isoCode == null) { - List uniquePhone = contact.phones - .map((Item phone) => formatPhoneNumber( - phone.value, store.state.userState.countryCode)) - .toSet() - .toList(); - for (String phone in uniquePhone) { - if (!syncedContacts.contains(phone)) { - newPhones.add(phone); - } - } - } else { - Future> phones = Future.wait(contact.phones.map((Item phone) async { + Future> phones = Future.wait(contact.phones.map((Item phone) async { + String value = clearNotNumbersAndPlusSymbol(phone.value); + try { + Map response = await phoneNumberUtil.parse(value); + return response['e164']; + } catch (e) { String phoneNum = formatPhoneNumber(phone.value, countryCode); bool isValid = await PhoneService.isValid(phoneNum, isoCode); if (isValid) { String ph = await PhoneService.getNormalizedPhoneNumber(phoneNum, isoCode); return ph; } - return phoneNum; - })); - List result = await phones; - result = result.toSet().toList(); - for (String phone in result) { - if (!syncedContacts.contains(phone)) { - newPhones.add(phone); - } + return ''; + } + })); + List result = await phones; + result = result.toSet().toList()..removeWhere((element) => element == ''); + for (String phone in result) { + if (!syncedContacts.contains(phone)) { + newPhones.add(phone); } } } diff --git a/lib/screens/cash_home/deposit_webview.dart b/lib/screens/cash_home/deposit_webview.dart deleted file mode 100644 index e3d4a00f8..000000000 --- a/lib/screens/cash_home/deposit_webview.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'dart:async'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:supervecina/models/app_state.dart'; -import 'package:supervecina/models/plugins.dart'; -import 'package:supervecina/models/views/drawer.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:flutter_redux/flutter_redux.dart'; - -class DepositWebView extends StatefulWidget { - final DepositPlugin depositPlugin; - - DepositWebView({this.depositPlugin}); - - @override - _DepositWebViewState createState() => _DepositWebViewState(); -} - -class _DepositWebViewState extends State { - final Completer _controller = - Completer(); - - TextEditingController address = new TextEditingController(); - - @override - void initState() { - super.initState(); - address.value = - address.value.copyWith(text: widget.depositPlugin.widgetUrl); - } - - @override - Widget build(BuildContext context) { - return new StoreConnector( - distinct: true, - converter: DrawerViewModel.fromStore, - builder: (_, viewModel) { - return Scaffold( - body: Builder(builder: (BuildContext context) { - dynamic depositPlugin = widget.depositPlugin; - dynamic url = depositPlugin.generateUrl( - walletAddress: viewModel.walletAddress); - return Container( - constraints: BoxConstraints.expand(), - child: Stack( - children: [ - Padding( - padding: EdgeInsets.only(top: 150), - child: WebView( - initialUrl: url, - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: - (WebViewController webViewController) { - _controller.complete(webViewController); - })), - Positioned( - top: 0, - right: 0, - left: 0, - child: Container( - height: 150, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: - Theme.of(context).primaryColor.withAlpha(20), - blurRadius: 5.0, - spreadRadius: 0.0, - offset: Offset( - 0.0, - 3.0, - ), - ) - ], - color: Color(0xFFF5F5F5), - ), - width: MediaQuery.of(context).size.width, - child: Padding( - padding: EdgeInsets.only(bottom: 20), - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text('Top up', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 20, - fontWeight: FontWeight.w800)) - ]), - Positioned( - top: 60, - left: 20, - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: SvgPicture.asset( - 'assets/images/arrow.svg'), - )), - ], - ), - ), - ), - ) - ], - ), - ); - }), - ); - }); - } -} diff --git a/lib/screens/cash_home/prize.dart b/lib/screens/cash_home/prize.dart index b48e215a0..72a364378 100644 --- a/lib/screens/cash_home/prize.dart +++ b/lib/screens/cash_home/prize.dart @@ -9,7 +9,6 @@ import 'package:supervecina/models/app_state.dart'; import 'package:supervecina/models/draw_info.dart'; import 'package:supervecina/models/views/prize.dart'; import 'package:supervecina/redux/state/store.dart'; -import 'package:supervecina/screens/cash_home/deposit_webview.dart'; import 'package:supervecina/screens/cash_home/webview_page.dart'; import 'package:supervecina/utils/format.dart'; import 'package:supervecina/widgets/main_scaffold.dart'; @@ -361,13 +360,20 @@ class _PrizeScreenState extends State { ), InkWell( onTap: () { + dynamic depositPlugin = + depositPlugins[0]; + dynamic url = + depositPlugin.generateUrl(); Navigator.push( context, MaterialPageRoute( builder: (context) => - DepositWebView( - depositPlugin: - depositPlugins[0]), + WebViewPage( + pageArgs: + WebViewPageArguments( + url: url, + title: + 'Top up')), fullscreenDialog: true), ); Segment.track( diff --git a/lib/screens/cash_home/webview_page.dart b/lib/screens/cash_home/webview_page.dart index db0eeb4d4..bb6d5e8e0 100644 --- a/lib/screens/cash_home/webview_page.dart +++ b/lib/screens/cash_home/webview_page.dart @@ -1,7 +1,7 @@ -import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:webview_flutter/webview_flutter.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; +import 'package:supervecina/widgets/my_app_bar.dart'; class WebViewPageArguments { final String url; @@ -21,82 +21,63 @@ class WebViewPage extends StatefulWidget { } class _WebViewPageState extends State { - final Completer _controller = - Completer(); - @override Widget build(BuildContext context) { final WebViewPageArguments webPageArgs = this.widget.pageArgs; return Scaffold( body: Builder(builder: (BuildContext context) { - return Container( - constraints: BoxConstraints.expand(), - child: Stack( - children: [ - Padding( - padding: EdgeInsets.only(top: 150), - child: WebView( - initialUrl: webPageArgs.url, - javascriptMode: JavascriptMode.unrestricted, - onWebViewCreated: (WebViewController webViewController) { - _controller.complete(webViewController); - })), - Positioned( - top: 0, - right: 0, - left: 0, - child: Container( - height: 150, - decoration: BoxDecoration( - boxShadow: [ - BoxShadow( - color: Theme.of(context).primaryColor.withAlpha(20), - blurRadius: 5.0, - spreadRadius: 0.0, - offset: Offset( - 0.0, - 3.0, - ), - ) + return WebviewScaffold( + url: webPageArgs.url, + appBar: MyAppBar( + child: Container( + height: 120, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Theme.of(context).primaryColor.withAlpha(20), + blurRadius: 5.0, + spreadRadius: 0.0, + offset: Offset( + 0.0, + 3.0, + ), + ) + ], + color: Color(0xFFF5F5F5), + ), + width: MediaQuery.of(context).size.width, + child: Padding( + padding: EdgeInsets.only(bottom: 20), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text(webPageArgs.title, + style: TextStyle( + color: Theme.of(context).primaryColor, + fontSize: 20, + fontWeight: FontWeight.w800)) + ]), + webPageArgs.withBack + ? Positioned( + top: 60, + left: 20, + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: + SvgPicture.asset('assets/images/arrow.svg'), + )) + : SizedBox.shrink(), ], - color: Color(0xFFF5F5F5), - ), - width: MediaQuery.of(context).size.width, - child: Padding( - padding: EdgeInsets.only(bottom: 20), - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text(webPageArgs.title, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 20, - fontWeight: FontWeight.w800)) - ]), - webPageArgs.withBack - ? Positioned( - top: 60, - left: 20, - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: SvgPicture.asset( - 'assets/images/arrow.svg'), - )) - : SizedBox.shrink(), - ], - ), ), ), - ) - ], - ), - ); + ), + )); }), ); } diff --git a/lib/screens/pro_mode/pro_drawer.dart b/lib/screens/pro_mode/pro_drawer.dart index d9471bafd..6db780854 100644 --- a/lib/screens/pro_mode/pro_drawer.dart +++ b/lib/screens/pro_mode/pro_drawer.dart @@ -8,7 +8,7 @@ import 'package:supervecina/models/app_state.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:supervecina/models/views/drawer.dart'; import 'package:supervecina/screens/backup/show_mnemonic.dart'; -import 'package:supervecina/screens/cash_home/deposit_webview.dart'; +import 'package:supervecina/screens/cash_home/webview_page.dart'; import 'package:supervecina/screens/misc/settings.dart'; import 'package:supervecina/utils/forks.dart'; import 'package:supervecina/utils/format.dart'; @@ -88,11 +88,12 @@ class _DrawerWidgetState extends State { ), ), onTap: () { + dynamic url = depositPlugins[0].generateUrl(); Navigator.push( context, MaterialPageRoute( - builder: (context) => - DepositWebView(depositPlugin: depositPlugins[0]), + builder: (context) => WebViewPage( + pageArgs: WebViewPageArguments(url: url, title: 'Top up')), fullscreenDialog: true), ); Segment.track(eventName: 'User clicked on top up'); diff --git a/lib/screens/send/contact_tile.dart b/lib/screens/send/contact_tile.dart new file mode 100644 index 000000000..61610c0e6 --- /dev/null +++ b/lib/screens/send/contact_tile.dart @@ -0,0 +1,50 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; + +class ContactTile extends StatelessWidget { + final Uint8List avatar; + final String displayName; + final String phoneNumber; + final Function onTap; + final Widget trailing; + const ContactTile( + {Key key, + this.avatar, + this.displayName, + this.phoneNumber, + this.onTap, + this.trailing}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Slidable( + actionPane: SlidableDrawerActionPane(), + actionExtentRatio: 0.25, + child: Container( + decoration: new BoxDecoration( + border: Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), + child: ListTile( + contentPadding: + EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), + leading: CircleAvatar( + backgroundColor: Color(0xFFE0E0E0), + radius: 25, + backgroundImage: avatar != null && avatar.isNotEmpty + ? MemoryImage(avatar) + : new AssetImage('assets/images/anom.png'), + ), + title: Text( + displayName, + style: + TextStyle(fontSize: 15, color: Theme.of(context).primaryColor), + ), + trailing: trailing, + onTap: onTap, + ), + ), + ); + } +} diff --git a/lib/screens/send/contacts_list.dart b/lib/screens/send/contacts_list.dart index 815761799..0c81b4fe4 100644 --- a/lib/screens/send/contacts_list.dart +++ b/lib/screens/send/contacts_list.dart @@ -2,23 +2,21 @@ import 'dart:core'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_segment/flutter_segment.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:contacts_service/contacts_service.dart'; import 'package:supervecina/generated/i18n.dart'; import 'package:supervecina/models/app_state.dart'; -import 'package:supervecina/models/transactions/transaction.dart'; -import 'package:supervecina/models/transactions/transfer.dart'; import 'package:supervecina/models/views/contacts.dart'; +import 'package:supervecina/screens/send/contact_tile.dart'; +import 'package:supervecina/screens/send/recent_contacts.dart'; import 'package:supervecina/utils/barcode.dart'; -import 'package:supervecina/screens/send/send_amount.dart'; -import 'package:supervecina/screens/send/send_amount_arguments.dart'; -import 'package:supervecina/services.dart'; +import 'package:supervecina/utils/contacts.dart'; import 'package:supervecina/utils/format.dart'; import 'package:supervecina/utils/phone.dart'; -import 'package:supervecina/utils/transaction_row.dart'; +import 'package:supervecina/utils/send.dart'; import 'package:supervecina/widgets/main_scaffold.dart'; import "package:ethereum_address/ethereum_address.dart"; -import 'dart:math' as math; +import 'package:supervecina/widgets/preloader.dart'; +import 'package:supervecina/widgets/silver_app_bar.dart'; class ContactsList extends StatefulWidget { final List contacts; @@ -31,10 +29,8 @@ class ContactsList extends StatefulWidget { class _ContactsListState extends State { List userList = []; List filteredUsers = []; - bool showFooter = true; - bool hasSynced = false; TextEditingController searchController = TextEditingController(); - bool isPreloading = false; + List _contacts; @override Widget build(BuildContext context) { @@ -45,34 +41,38 @@ class _ContactsListState extends State { }, converter: ContactsViewModel.fromStore, builder: (_, viewModel) { - return MainScaffold( - automaticallyImplyLeading: false, - title: I18n.of(context).send_to, - sliverList: _buildPageList(viewModel), - ); + return _contacts != null + ? MainScaffold( + automaticallyImplyLeading: false, + title: I18n.of(context).send_to, + sliverList: _buildPageList(context, viewModel), + ) + : Center( + child: Preloader(), + ); }); } - loadContacts(List contacts) async { - if (this.mounted) { + Future refreshContacts() async { + List contacts = await ContactController.getContacts() + ..toList(); + if (mounted) { setState(() { - isPreloading = true; + _contacts = contacts; }); } - for (var contact in contacts) { - userList.add(contact); - } - userList.sort((a, b) => - a.displayName.toLowerCase().compareTo(b.displayName.toLowerCase())); filterList(); searchController.addListener(() { filterList(); }); - if (this.mounted) { - setState(() { - isPreloading = false; + for (final contact in contacts) { + ContactsService.getAvatar(contact).then((avatar) { + if (avatar == null) return; + if (mounted) { + setState(() => contact.avatar = avatar); + } }); } } @@ -80,20 +80,12 @@ class _ContactsListState extends State { @override void initState() { super.initState(); - loadContacts(widget.contacts); - } - - void _onFocusChange(hasFocus) { - if (mounted) { - setState(() { - showFooter = !hasFocus; - }); - } + refreshContacts(); } filterList() { List users = []; - users.addAll(userList); + users.addAll(_contacts); if (searchController.text.isNotEmpty) { users.retainWhere((user) => user.displayName .toLowerCase() @@ -111,7 +103,7 @@ class _ContactsListState extends State { return SliverPersistentHeader( pinned: true, floating: true, - delegate: _SliverAppBarDelegate( + delegate: SliverAppBarDelegate( minHeight: 40.0, maxHeight: 40.0, child: Container( @@ -126,130 +118,45 @@ class _ContactsListState extends State { ); } - sendToContact(BuildContext context, Contact user, ContactsViewModel viewModel) async { - String phoneNumber = await PhoneService.getNormalizedPhoneNumber(formatPhoneNumber(user.phones.first.value, viewModel.countryCode), viewModel.isoCode); - Map wallet = await api.getWalletByPhoneNumber(phoneNumber); - String accountAddress = (wallet != null) ? wallet["walletAddress"] : null; - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - erc20Token: viewModel.isProMode ? viewModel.daiToken : null, - sendType: viewModel.isProMode - ? SendType.ETHEREUM_ADDRESS - : accountAddress != null - ? SendType.FUSE_ADDRESS - : SendType.CONTACT, - name: user.displayName, - accountAddress: accountAddress, - avatar: user.avatar != null && user.avatar.isNotEmpty - ? MemoryImage(user.avatar) - : new AssetImage('assets/images/anom.png'), - phoneNumber: phoneNumber)))); - } - - listBody(context, ContactsViewModel viewModel, List group) { + listBody(BuildContext context, ContactsViewModel viewModel, List group) { List listItems = List(); for (Contact user in group) { - dynamic component = Slidable( - actionPane: SlidableDrawerActionPane(), - actionExtentRatio: 0.25, - child: Container( - decoration: new BoxDecoration( - border: - Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), - child: ListTile( - contentPadding: - EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), - leading: CircleAvatar( - backgroundColor: Color(0xFFE0E0E0), - radius: 25, - backgroundImage: user.avatar != null && user.avatar.isNotEmpty - ? MemoryImage(user.avatar) - : new AssetImage('assets/images/anom.png'), - ), - title: Text( - user.displayName, - style: TextStyle( - fontSize: 15, color: Theme.of(context).primaryColor), - ), + Iterable phones = user.phones.map((e) => Item(label: e.label, value: clearNotNumbersAndPlusSymbol(e.value))).toSet().toList(); + for (Item phone in phones) { + listItems.add(ContactTile( + avatar: user.avatar, + displayName: user.displayName, + phoneNumber: phone.value, onTap: () { - sendToContact(context, user, viewModel); + sendToContact(context, viewModel, user.displayName, phone.value, avatar: user.avatar); }, - ), - ), - ); - - listItems.add(component); + trailing: Text( + phone.value, + style: TextStyle( + fontSize: 13, color: Theme.of(context).primaryColor), + ))); + } } return SliverList( delegate: SliverChildListDelegate(listItems), ); } - Widget sendToAcccountAddress( - ContactsViewModel viewModel, String accountAddress) { - Widget component = Slidable( - actionPane: SlidableDrawerActionPane(), - actionExtentRatio: 0.25, - child: Container( - decoration: new BoxDecoration( - border: Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), - child: ListTile( - contentPadding: - EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), - leading: CircleAvatar( - backgroundColor: Color(0xFFE0E0E0), - radius: 25, - backgroundImage: new AssetImage('assets/images/anom.png'), - ), - title: Text( - formatAddress(accountAddress), - style: TextStyle(fontSize: 16), - ), - trailing: InkWell( - child: Text( - I18n.of(context).next_button, - style: TextStyle(color: Color(0xFF0377FF)), - ), - onTap: () { - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - erc20Token: viewModel.isProMode - ? viewModel.daiToken - : null, - sendType: viewModel.isProMode - ? SendType.ETHEREUM_ADDRESS - : SendType.PASTED_ADDRESS, - accountAddress: accountAddress, - name: formatAddress(accountAddress), - avatar: new AssetImage( - 'assets/images/anom.png'))))); - }, - ), - //subtitle: Text("user.company" ?? ""), - onTap: () { - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - erc20Token: viewModel.isProMode - ? viewModel.daiToken - : null, - sendType: viewModel.isProMode - ? SendType.ETHEREUM_ADDRESS - : SendType.PASTED_ADDRESS, - accountAddress: accountAddress, - name: formatAddress(accountAddress), - avatar: - new AssetImage('assets/images/anom.png'))))); - }), + Widget sendToAcccountAddress(BuildContext context, ContactsViewModel viewModel, String accountAddress) { + Widget component = ContactTile( + displayName: formatAddress(accountAddress), + onTap: () { + sendToPastedAddress(context, viewModel, accountAddress); + }, + trailing: InkWell( + child: Text( + I18n.of(context).next_button, + style: TextStyle(color: Color(0xFF0377FF)), + ), + onTap: () { + sendToPastedAddress(context, viewModel, accountAddress); + }, ), ); return SliverList( @@ -257,116 +164,16 @@ class _ContactsListState extends State { ); } - Widget recentContacts(int numToShow, ContactsViewModel viewModel) { - List listItems = List(); - final sorted = - new List.from(viewModel.transactions.list.toSet().toList()) - .where((t) => t.type == 'SEND' && t.isConfirmed()) - .toList() - ..sort((a, b) => a.blockNumber != null && b.blockNumber != null - ? b.blockNumber?.compareTo(a.blockNumber) - : b.status.compareTo(a.status)); - - Map uniqueValues = {}; - for (var item in sorted) { - final Contact contact = getContact(item, viewModel.reverseContacts, - viewModel.contacts, viewModel.countryCode); - var a = contact != null - ? contact.displayName - : deducePhoneNumber(item, viewModel.reverseContacts, - businesses: viewModel.businesses); - uniqueValues[a] = item; - } - - dynamic uniqueList = uniqueValues.values.toList().length > numToShow - ? uniqueValues.values.toList().sublist(0, numToShow) - : uniqueValues.values.toList(); - for (int i = 0; i < uniqueList.length; i++) { - final Transfer transfer = uniqueList[i]; - final Contact contact = getContact(transfer, viewModel.reverseContacts, - viewModel.contacts, viewModel.countryCode); - final String displatName = contact != null - ? contact.displayName - : deducePhoneNumber(transfer, viewModel.reverseContacts, - businesses: viewModel.businesses); - dynamic image = getContactImage(transfer, contact, viewModel.businesses); - listItems.add( - Slidable( - actionPane: SlidableDrawerActionPane(), - actionExtentRatio: 0.25, - // secondaryActions: [ - // IconSlideAction( - // iconWidget: Icon(Icons.star), - // onTap: () {}, - // ), - // IconSlideAction( - // iconWidget: Icon(Icons.more_horiz), - // onTap: () {}, - // ), - // ], - child: Container( - decoration: new BoxDecoration( - border: - Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), - child: ListTile( - contentPadding: - EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), - leading: CircleAvatar( - backgroundColor: Color(0xFFE0E0E0), - radius: 25, - backgroundImage: image, - ), - title: Text( - displatName, - style: TextStyle(fontSize: 16), - ), - onTap: () { - if (contact == null) { - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - sendType: SendType.FUSE_ADDRESS, - accountAddress: transfer.to, - name: displatName, - avatar: new AssetImage( - 'assets/images/anom.png'))))); - } else { - sendToContact(context, contact, viewModel); - } - }, - ), - ), - ), - ); - } - - if (listItems.isNotEmpty) { - listItems.insert( - 0, - Container( - padding: EdgeInsets.only(left: 15, top: 15, bottom: 8), - child: Text(I18n.of(context).recent, - style: TextStyle( - color: Color(0xFF979797), - fontSize: 12.0, - fontWeight: FontWeight.normal)))); - } - return SliverList( - delegate: SliverChildListDelegate(listItems), - ); - } - - List _buildPageList(ContactsViewModel viewModel) { + List _buildPageList(context, ContactsViewModel viewModel) { List listItems = List(); listItems.add(searchPanel()); if (searchController.text.isEmpty && !viewModel.isProMode) { - listItems.add(recentContacts(3, viewModel)); + listItems.add(RecentContacts()); } else if (isValidEthereumAddress(searchController.text)) { - listItems.add(sendToAcccountAddress(viewModel, searchController.text)); + listItems.add( + sendToAcccountAddress(context, viewModel, searchController.text)); } Map> groups = new Map>(); @@ -392,7 +199,7 @@ class _ContactsListState extends State { searchPanel() { return SliverPersistentHeader( pinned: true, - delegate: _SliverAppBarDelegate( + delegate: SliverAppBarDelegate( minHeight: 80.0, maxHeight: 100.0, child: Container( @@ -409,9 +216,7 @@ class _ContactsListState extends State { Expanded( child: Padding( padding: EdgeInsets.only(right: 20), - child: FocusScope( - onFocusChange: (showFooter) => _onFocusChange(showFooter), - child: TextFormField( + child: TextFormField( controller: searchController, style: TextStyle(fontSize: 18, color: Colors.black), decoration: InputDecoration( @@ -430,7 +235,6 @@ class _ContactsListState extends State { labelText: I18n.of(context).search, ), ), - ), ), ), Container( @@ -456,30 +260,3 @@ class _ContactsListState extends State { ); } } - -class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { - _SliverAppBarDelegate({ - @required this.minHeight, - @required this.maxHeight, - @required this.child, - }); - final double minHeight; - final double maxHeight; - final Widget child; - @override - double get minExtent => minHeight; - @override - double get maxExtent => math.max(maxHeight, minHeight); - @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { - return new SizedBox.expand(child: child); - } - - @override - bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { - return maxHeight != oldDelegate.maxHeight || - minHeight != oldDelegate.minHeight || - child != oldDelegate.child; - } -} diff --git a/lib/screens/send/recent_contacts.dart b/lib/screens/send/recent_contacts.dart new file mode 100644 index 000000000..7c0ee3f9f --- /dev/null +++ b/lib/screens/send/recent_contacts.dart @@ -0,0 +1,114 @@ +import 'package:contacts_service/contacts_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:supervecina/generated/i18n.dart'; +import 'package:supervecina/models/app_state.dart'; +import 'package:supervecina/models/transactions/transaction.dart'; +import 'package:supervecina/models/transactions/transfer.dart'; +import 'package:supervecina/models/views/contacts.dart'; +import 'package:supervecina/screens/send/send_amount.dart'; +import 'package:supervecina/screens/send/send_amount_arguments.dart'; +import 'package:supervecina/utils/send.dart'; +import 'package:supervecina/utils/transaction_row.dart'; + +class RecentContacts extends StatelessWidget { + final int numToShow; + const RecentContacts({Key key, this.numToShow = 3}) : super(key: key); + @override + Widget build(BuildContext context) { + return new StoreConnector( + converter: ContactsViewModel.fromStore, + builder: (_, viewModel) { + List listItems = List(); + Map uniqueValues = {}; + final List sorted = new List.from(viewModel.transactions.list.toSet().toList()) + .where((t) => t.type == 'SEND' && t.isConfirmed()) + .toList(); + + for (Transaction item in sorted) { + final Contact contact = getContact(item, viewModel.reverseContacts, + viewModel.contacts, viewModel.countryCode); + var a = contact != null + ? contact.displayName + : deducePhoneNumber(item, viewModel.reverseContacts, + businesses: viewModel.businesses); + uniqueValues[a] = item; + } + List test = uniqueValues.values.toList().reversed.toList(); + List uniqueList = test.length > numToShow + ? test.sublist(0, numToShow) + : test; + + for (int i = 0; i < uniqueList.length; i++) { + final Transfer transfer = uniqueList[i]; + final Contact contact = getContact( + transfer, + viewModel.reverseContacts, + viewModel.contacts, + viewModel.countryCode); + final String displatName = contact != null + ? contact.displayName + : deducePhoneNumber(transfer, viewModel.reverseContacts, + businesses: viewModel.businesses); + dynamic image = getContactImage(transfer, contact, viewModel.businesses); + listItems.add( + Slidable( + actionPane: SlidableDrawerActionPane(), + actionExtentRatio: 0.25, + child: Container( + decoration: new BoxDecoration( + border: Border( + bottom: BorderSide(color: const Color(0xFFDCDCDC)))), + child: ListTile( + contentPadding: + EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), + leading: CircleAvatar( + backgroundColor: Color(0xFFE0E0E0), + radius: 25, + backgroundImage: image, + ), + title: Text( + displatName, + style: TextStyle(fontSize: 16), + ), + onTap: () { + if (contact == null) { + Navigator.push( + context, + new MaterialPageRoute( + builder: (context) => SendAmountScreen( + pageArgs: SendAmountArguments( + sendType: SendType.FUSE_ADDRESS, + accountAddress: transfer.to, + name: displatName, + avatar: new AssetImage( + 'assets/images/anom.png'))))); + } else { + sendToContact(context, viewModel, displatName, '',avatar: contact.avatar, address: transfer.to); + } + }, + ), + ), + ), + ); + } + + if (listItems.isNotEmpty) { + listItems.insert( + 0, + Container( + padding: EdgeInsets.only(left: 15, top: 15, bottom: 8), + child: Text(I18n.of(context).recent, + style: TextStyle( + color: Color(0xFF979797), + fontSize: 12.0, + fontWeight: FontWeight.normal)))); + } + return SliverList( + delegate: SliverChildListDelegate(listItems), + ); + }, + ); + } +} diff --git a/lib/screens/send/send_contact.dart b/lib/screens/send/send_contact.dart index f25a85e99..8b19f7615 100644 --- a/lib/screens/send/send_contact.dart +++ b/lib/screens/send/send_contact.dart @@ -6,22 +6,19 @@ import 'package:contacts_service/contacts_service.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:supervecina/generated/i18n.dart'; import 'package:supervecina/models/app_state.dart'; -import 'package:supervecina/models/transactions/transaction.dart'; -import 'package:supervecina/models/transactions/transfer.dart'; import 'package:supervecina/models/views/contacts.dart'; +import 'package:supervecina/screens/send/recent_contacts.dart'; import 'package:supervecina/utils/barcode.dart'; import 'package:supervecina/screens/send/enable_contacts.dart'; import 'package:supervecina/screens/send/send_amount.dart'; import 'package:supervecina/screens/send/send_amount_arguments.dart'; -import 'package:supervecina/services.dart'; import 'package:supervecina/utils/contacts.dart'; import 'package:supervecina/utils/format.dart'; -import 'package:supervecina/utils/phone.dart'; -import 'package:supervecina/utils/transaction_row.dart'; +import 'package:supervecina/utils/send.dart'; import 'package:supervecina/widgets/main_scaffold.dart'; import "package:ethereum_address/ethereum_address.dart"; -import 'dart:math' as math; import 'package:redux/redux.dart'; +import 'package:supervecina/widgets/silver_app_bar.dart'; class SendToContactScreen extends StatefulWidget { @override @@ -102,7 +99,7 @@ class _SendToContactScreenState extends State { return SliverPersistentHeader( pinned: true, floating: true, - delegate: _SliverAppBarDelegate( + delegate: SliverAppBarDelegate( minHeight: 40.0, maxHeight: 40.0, child: Container( @@ -117,50 +114,48 @@ class _SendToContactScreenState extends State { ); } - listBody(viewModel, List group) { + listBody(ContactsViewModel viewModel, List group) { List listItems = List(); for (Contact user in group) { - dynamic component = Slidable( - actionPane: SlidableDrawerActionPane(), - actionExtentRatio: 0.25, - // secondaryActions: [ - // IconSlideAction( - // iconWidget: Icon(Icons.star), - // onTap: () {}, - // ), - // IconSlideAction( - // iconWidget: Icon(Icons.more_horiz), - // onTap: () {}, - // ), - // ], - child: Container( - decoration: new BoxDecoration( - border: - Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), - child: ListTile( - contentPadding: - EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), - leading: CircleAvatar( - backgroundColor: Color(0xFFE0E0E0), - radius: 25, - backgroundImage: user.avatar != null && user.avatar.isNotEmpty - ? MemoryImage(user.avatar) - : new AssetImage('assets/images/anom.png'), - ), - title: Text( - user.displayName, - style: TextStyle( - fontSize: 15, color: Theme.of(context).primaryColor), + for (Item phone in user.phones.toList()) { + dynamic component = Slidable( + actionPane: SlidableDrawerActionPane(), + actionExtentRatio: 0.25, + child: Container( + decoration: new BoxDecoration( + border: + Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), + child: ListTile( + contentPadding: + EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), + leading: CircleAvatar( + backgroundColor: Color(0xFFE0E0E0), + radius: 25, + backgroundImage: user.avatar != null && user.avatar.isNotEmpty + ? MemoryImage(user.avatar) + : new AssetImage('assets/images/anom.png'), + ), + title: Text( + user.displayName, + style: TextStyle( + fontSize: 15, color: Theme.of(context).primaryColor), + ), + subtitle: Text( + phone.value, + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.secondary), + ), + onTap: () { + sendToContact(context, viewModel, user.displayName, phone.value, + avatar: user.avatar); + }, ), - onTap: () async { - sendToContact(context, user, viewModel); - }, ), - ), - ); - - listItems.add(component); + ); + listItems.add(component); + } } return SliverList( delegate: SliverChildListDelegate(listItems), @@ -244,95 +239,6 @@ class _SendToContactScreenState extends State { ); } - Widget recentContacts(numToShow, ContactsViewModel viewModel) { - List listItems = List(); - final sorted = - new List.from(viewModel.transactions.list.toSet().toList()) - .where((t) => t.type == 'SEND' && t.isConfirmed()) - .toList() - ..sort((a, b) => a.blockNumber != null && b.blockNumber != null - ? b.blockNumber?.compareTo(a.blockNumber) - : b.status.compareTo(a.status)); - Map uniqueValues = {}; - for (var item in sorted) { - final Contact contact = getContact(item, viewModel.reverseContacts, - viewModel.contacts, viewModel.countryCode); - var a = contact != null - ? contact.displayName - : deducePhoneNumber(item, viewModel.reverseContacts, - businesses: viewModel.businesses); - uniqueValues[a] = item; - } - - dynamic uniqueList = uniqueValues.values.toList().length > numToShow - ? uniqueValues.values.toList().sublist(0, numToShow) - : uniqueValues.values.toList(); - for (int i = 0; i < uniqueList.length; i++) { - final Transfer transfer = uniqueList[i]; - final Contact contact = getContact(transfer, viewModel.reverseContacts, - viewModel.contacts, viewModel.countryCode); - final String displatName = contact != null - ? contact.displayName - : deducePhoneNumber(transfer, viewModel.reverseContacts, - businesses: viewModel.businesses); - dynamic image = getContactImage(transfer, contact, viewModel.businesses); - listItems.add( - Slidable( - actionPane: SlidableDrawerActionPane(), - actionExtentRatio: 0.25, - child: Container( - decoration: new BoxDecoration( - border: - Border(bottom: BorderSide(color: const Color(0xFFDCDCDC)))), - child: ListTile( - contentPadding: - EdgeInsets.only(top: 5, bottom: 5, left: 16, right: 16), - leading: CircleAvatar( - backgroundColor: Color(0xFFE0E0E0), - radius: 25, - backgroundImage: image, - ), - title: Text( - displatName, - style: TextStyle(fontSize: 16), - ), - onTap: () { - if (contact == null) { - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - sendType: SendType.FUSE_ADDRESS, - accountAddress: transfer.to, - name: displatName, - avatar: new AssetImage( - 'assets/images/anom.png'))))); - } else { - sendToContact(context, contact, viewModel); - } - }, - ), - ), - ), - ); - } - if (listItems.isNotEmpty) { - listItems.insert( - 0, - Container( - padding: EdgeInsets.only(left: 15, top: 15, bottom: 8), - child: Text(I18n.of(context).recent, - style: TextStyle( - color: Color(0xFF979797), - fontSize: 12.0, - fontWeight: FontWeight.normal)))); - } - return SliverList( - delegate: SliverChildListDelegate(listItems), - ); - } - List _buildPageList(viewModel) { List listItems = List(); @@ -340,7 +246,7 @@ class _SendToContactScreenState extends State { if (searchController.text.isEmpty) { if (hasSynced) { - listItems.add(recentContacts(3, viewModel)); + listItems.add(RecentContacts()); } } else if (isValidEthereumAddress(searchController.text)) { listItems.add(sendToAcccountAddress(searchController.text, viewModel)); @@ -369,7 +275,7 @@ class _SendToContactScreenState extends State { searchPanel() { return SliverPersistentHeader( pinned: true, - delegate: _SliverAppBarDelegate( + delegate: SliverAppBarDelegate( minHeight: 80.0, maxHeight: 100.0, child: Container( @@ -433,29 +339,6 @@ class _SendToContactScreenState extends State { ); } - sendToContact(BuildContext context, Contact user, ContactsViewModel viewModel) async { - String phoneNumber = await PhoneService.getNormalizedPhoneNumber(formatPhoneNumber(user.phones.first.value, viewModel.countryCode), viewModel.isoCode); - Map wallet = await api.getWalletByPhoneNumber(phoneNumber); - String accountAddress = (wallet != null) ? wallet["walletAddress"] : null; - Navigator.push( - context, - new MaterialPageRoute( - builder: (context) => SendAmountScreen( - pageArgs: SendAmountArguments( - erc20Token: viewModel.isProMode ? viewModel.daiToken : null, - sendType: viewModel.isProMode - ? SendType.ETHEREUM_ADDRESS - : accountAddress != null - ? SendType.FUSE_ADDRESS - : SendType.CONTACT, - name: user.displayName, - accountAddress: accountAddress, - avatar: user.avatar != null && user.avatar.isNotEmpty - ? MemoryImage(user.avatar) - : new AssetImage('assets/images/anom.png'), - phoneNumber: phoneNumber)))); - } - onInit(Store store) { Segment.screen(screenName: '/send-to-contact-screen'); loadContacts(store.state.userState?.contacts ?? [], store.state.userState.isContactsSynced); @@ -562,30 +445,3 @@ class _SendToContactScreenState extends State { }); } } - -class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { - _SliverAppBarDelegate({ - @required this.minHeight, - @required this.maxHeight, - @required this.child, - }); - final double minHeight; - final double maxHeight; - final Widget child; - @override - double get minExtent => minHeight; - @override - double get maxExtent => math.max(maxHeight, minHeight); - @override - Widget build( - BuildContext context, double shrinkOffset, bool overlapsContent) { - return new SizedBox.expand(child: child); - } - - @override - bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { - return maxHeight != oldDelegate.maxHeight || - minHeight != oldDelegate.minHeight || - child != oldDelegate.child; - } -} diff --git a/lib/screens/send/send_review.dart b/lib/screens/send/send_review.dart index a6c10ed3f..10ed51f6d 100644 --- a/lib/screens/send/send_review.dart +++ b/lib/screens/send/send_review.dart @@ -9,6 +9,7 @@ import 'package:supervecina/widgets/main_scaffold.dart'; import 'package:supervecina/widgets/primary_button.dart'; import 'package:supervecina/models/app_state.dart'; import 'package:flutter_redux/flutter_redux.dart'; + class SendReviewScreen extends StatefulWidget { final SendAmountArguments pageArgs; SendReviewScreen({this.pageArgs}); @@ -57,7 +58,7 @@ class _SendReviewScreenState extends State } else { viewModel.sendToErc20Token(args.erc20Token, args.accountAddress, args.amount, sendSuccessCallback, sendFailureCallback); } - }else { + } else { if (args.accountAddress == null || args.accountAddress == '' && args.phoneNumber != null) { viewModel.sendToContact( @@ -189,6 +190,13 @@ class _SendReviewScreenState extends State args.name, style: TextStyle(fontSize: 18), ), + args.phoneNumber == null || + args.phoneNumber.isEmpty + ? SizedBox.shrink() + : Text( + args.phoneNumber, + style: TextStyle(fontSize: 13), + ), args.accountAddress == null || args.accountAddress.isEmpty ? SizedBox.shrink() diff --git a/lib/services.dart b/lib/services.dart index d47cb1718..3b4864352 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -1,6 +1,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart'; +import 'package:phone_number/phone_number.dart'; import 'package:wallet_core/wallet_core.dart'; final Client client = new Client(); @@ -20,3 +21,5 @@ final TokensApi tokenAPI = new TokensApi( etherscanApiKey: DotEnv().env['ETHERSCAN_API_KEY'], amberdataApiKey: DotEnv().env['AMBERDATA_API_KEY'], amberdataBaseUri: DotEnv().env['AMBERDATA_BASE_URL']); + +final PhoneNumber phoneNumberUtil = new PhoneNumber(); diff --git a/lib/utils/contacts.dart b/lib/utils/contacts.dart index 39d18cabd..aa554e9e9 100644 --- a/lib/utils/contacts.dart +++ b/lib/utils/contacts.dart @@ -26,8 +26,7 @@ class Contacts { } static Future> getContacts() async { - Iterable contacts = (await ContactsService.getContacts( - withThumbnails: true)) + Iterable contacts = (await ContactsService.getContacts(withThumbnails: false)) .where((i) => i.displayName != null && i.displayName != "" && i.phones.length > 0) .toList(); diff --git a/lib/utils/firebase.dart b/lib/utils/firebase.dart new file mode 100644 index 000000000..aa4c55d88 --- /dev/null +++ b/lib/utils/firebase.dart @@ -0,0 +1,5 @@ +String communityAddressFromNotification(Map message) { + final dynamic data = message['data'] ?? message; + final String communityAddress = data['communityAddress']; + return communityAddress; +} diff --git a/lib/utils/phone.dart b/lib/utils/phone.dart index 6e0913b05..37f5d001c 100644 --- a/lib/utils/phone.dart +++ b/lib/utils/phone.dart @@ -44,3 +44,7 @@ String formatPhoneNumber(String phoneNumber, String myCountryCode) { String removeUnicodes(String value) { return value.replaceAll(new RegExp(r"[^\s\w]"), ''); } + +String clearNotNumbersAndPlusSymbol (String phoneNumber) { + return phoneNumber.replaceAll(new RegExp('(-| |\\(0\\)|\\(0|\\(|\\))'), ''); +} diff --git a/lib/utils/send.dart b/lib/utils/send.dart new file mode 100644 index 000000000..0a76f9330 --- /dev/null +++ b/lib/utils/send.dart @@ -0,0 +1,71 @@ +import 'dart:typed_data'; +import 'package:flutter/material.dart'; +import 'package:supervecina/models/views/contacts.dart'; +import 'package:supervecina/screens/send/send_amount.dart'; +import 'package:supervecina/screens/send/send_amount_arguments.dart'; +import 'package:supervecina/services.dart'; +import 'package:supervecina/utils/format.dart'; +import 'package:supervecina/utils/phone.dart'; + +void navigateToSendAmountScreen(BuildContext context, ContactsViewModel viewModel, + String accountAddress, String displayName, String phoneNumber, + {Uint8List avatar}) { + Navigator.push( + context, + new MaterialPageRoute( + builder: (context) => SendAmountScreen( + pageArgs: SendAmountArguments( + erc20Token: viewModel.isProMode ? viewModel.daiToken : null, + sendType: viewModel.isProMode + ? SendType.ETHEREUM_ADDRESS + : accountAddress != null + ? SendType.FUSE_ADDRESS + : SendType.CONTACT, + name: displayName, + accountAddress: accountAddress, + avatar: avatar != null && avatar.isNotEmpty + ? MemoryImage(avatar) + : new AssetImage('assets/images/anom.png'), + phoneNumber: phoneNumber)))); +} + +void sendToContact(BuildContext context, ContactsViewModel viewModel, + String displayName, String phone, + {Uint8List avatar, String address}) async { + if (address != null && address.isNotEmpty) { + navigateToSendAmountScreen(context, viewModel, address, displayName, null, avatar: avatar); + } + try { + Map response = await phoneNumberUtil.parse(phone); + String phoneNumber = response['e164']; + Map wallet = await api.getWalletByPhoneNumber(response['e164']); + String accountAddress = (wallet != null) ? wallet["walletAddress"] : null; + navigateToSendAmountScreen(context, viewModel, accountAddress, displayName, phoneNumber, + avatar: avatar); + } catch (e) { + String formatted = formatPhoneNumber(phone, viewModel.countryCode); + bool isValid = await PhoneService.isValid(formatted, viewModel.isoCode); + if (isValid) { + Map wallet = await api.getWalletByPhoneNumber(formatted); + String accountAddress = (wallet != null) ? wallet["walletAddress"] : null; + navigateToSendAmountScreen(context, viewModel, accountAddress, displayName, formatted, + avatar: avatar); + } + } +} + +void sendToPastedAddress( + BuildContext context, ContactsViewModel viewModel, accountAddress) { + Navigator.push( + context, + new MaterialPageRoute( + builder: (context) => SendAmountScreen( + pageArgs: SendAmountArguments( + erc20Token: viewModel.isProMode ? viewModel.daiToken : null, + sendType: viewModel.isProMode + ? SendType.ETHEREUM_ADDRESS + : SendType.PASTED_ADDRESS, + accountAddress: accountAddress, + name: formatAddress(accountAddress), + avatar: new AssetImage('assets/images/anom.png'))))); +} diff --git a/lib/utils/transaction_row.dart b/lib/utils/transaction_row.dart index d7bd36981..8808a3e85 100644 --- a/lib/utils/transaction_row.dart +++ b/lib/utils/transaction_row.dart @@ -20,8 +20,7 @@ String deduceSign(Transfer transfer) { } } -Contact getContact(Transfer transfer, Map reverseContacts, - List contacts, String countryCode) { +Contact getContact(Transfer transfer, Map reverseContacts, List contacts, String countryCode) { String accountAddress = transfer.type == 'SEND' ? transfer.to : transfer.from; if (accountAddress == null) { return null; @@ -31,6 +30,9 @@ Contact getContact(Transfer transfer, Map reverseContacts, if (contacts == null) return null; for (Contact contact in contacts) { for (Item contactPhoneNumber in contact.phones.toList()) { + if (clearNotNumbersAndPlusSymbol(contactPhoneNumber.value) == phoneNumber) { + return contact; + } if (formatPhoneNumber(contactPhoneNumber.value, countryCode) == phoneNumber) { return contact; diff --git a/lib/widgets/deposit_dai_popup.dart b/lib/widgets/deposit_dai_popup.dart index b601486de..5c3f7f09e 100644 --- a/lib/widgets/deposit_dai_popup.dart +++ b/lib/widgets/deposit_dai_popup.dart @@ -4,7 +4,7 @@ import 'package:flutter_segment/flutter_segment.dart'; import 'package:supervecina/generated/i18n.dart'; import 'package:supervecina/models/community.dart'; import 'package:supervecina/models/plugins.dart'; -import 'package:supervecina/screens/cash_home/deposit_webview.dart'; +import 'package:supervecina/screens/cash_home/webview_page.dart'; import 'package:supervecina/screens/pro_routes.gr.dart'; import 'package:supervecina/screens/routes.gr.dart'; import 'package:supervecina/utils/addresses.dart'; @@ -104,14 +104,14 @@ class DepositDaiDialogState extends State onTap: () { if (viewModel.isProMode) { ProRouter.navigator.pushNamed( - ProRouter.proModeHomeScreen, - arguments: ProModeScaffoldArguments( - tabIndex: 3)); + ProRouter.proModeHomeScreen, + arguments: ProModeScaffoldArguments( + tabIndex: 3)); } else { Router.navigator.pushNamed( - Router.cashHomeScreen, - arguments: CashModeScaffoldArguments( - tabIndex: 3)); + Router.cashHomeScreen, + arguments: CashModeScaffoldArguments( + tabIndex: 3)); } }, ), @@ -141,11 +141,13 @@ class DepositDaiDialogState extends State ), ), onTap: () { + dynamic url = depositPlugins[0].generateUrl(); Navigator.push( context, MaterialPageRoute( - builder: (context) => DepositWebView( - depositPlugin: depositPlugins[0]), + builder: (context) => WebViewPage( + pageArgs: WebViewPageArguments( + url: url, title: 'Top up')), fullscreenDialog: true), ); Segment.track( diff --git a/lib/widgets/drawer.dart b/lib/widgets/drawer.dart index 373094805..a23b0be00 100644 --- a/lib/widgets/drawer.dart +++ b/lib/widgets/drawer.dart @@ -8,8 +8,8 @@ import 'package:supervecina/models/app_state.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:supervecina/models/views/drawer.dart'; import 'package:supervecina/screens/backup/show_mnemonic.dart'; -import 'package:supervecina/screens/cash_home/deposit_webview.dart'; import 'package:supervecina/screens/cash_home/switch_commmunity.dart'; +import 'package:supervecina/screens/cash_home/webview_page.dart'; import 'package:supervecina/screens/misc/settings.dart'; import 'package:supervecina/utils/forks.dart'; import 'package:supervecina/utils/format.dart'; @@ -89,11 +89,12 @@ class _DrawerWidgetState extends State { ), ), onTap: () { + dynamic url = depositPlugins[0].generateUrl(); Navigator.push( context, MaterialPageRoute( - builder: (context) => - DepositWebView(depositPlugin: depositPlugins[0]), + builder: (context) => WebViewPage( + pageArgs: WebViewPageArguments(url: url, title: 'Top up')), fullscreenDialog: true), ); Segment.track(eventName: 'User clicked on top up'); diff --git a/lib/widgets/silver_app_bar.dart b/lib/widgets/silver_app_bar.dart new file mode 100644 index 000000000..61882895b --- /dev/null +++ b/lib/widgets/silver_app_bar.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +class SliverAppBarDelegate extends SliverPersistentHeaderDelegate { + SliverAppBarDelegate({ + @required this.minHeight, + @required this.maxHeight, + @required this.child, + }); + final double minHeight; + final double maxHeight; + final Widget child; + @override + double get minExtent => minHeight; + @override + double get maxExtent => math.max(maxHeight, minHeight); + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + return new SizedBox.expand(child: child); + } + + @override + bool shouldRebuild(SliverAppBarDelegate oldDelegate) { + return maxHeight != oldDelegate.maxHeight || + minHeight != oldDelegate.minHeight || + child != oldDelegate.child; + } +} diff --git a/pubspec.lock b/pubspec.lock index 199592f62..4b6f5f3de 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -455,6 +455,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_webview_plugin: + dependency: "direct main" + description: + name: flutter_webview_plugin + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.11" glob: dependency: transitive description: @@ -735,6 +742,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.0" + phone_number: + dependency: "direct main" + description: + name: phone_number + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2+2" pin_input_text_field: dependency: "direct main" description: @@ -1125,13 +1139,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.5" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.19+9" xml: dependency: transitive description: @@ -1148,4 +1155,4 @@ packages: version: "2.2.0" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.6 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index b01bd38ce..efa63fea0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,9 @@ description: A new Flutter project. environment: sdk: ">=2.2.2 <3.0.0" dependencies: + phone_number: ^0.6.2+2 libphonenumber: ^1.0.1 + flutter_webview_plugin: ^0.3.11 cached_network_image: ^2.0.0 rate_my_app: ^0.6.0+2 auto_route: ^0.3.1 @@ -21,7 +23,6 @@ dependencies: flutter_secure_storage: ^3.3.2 flutter_redux: ^0.6.0 flutter_dotenv: ^2.1.0 - webview_flutter: ^0.3.19+9 redux_thunk: ^0.3.0 redux_logging: ^0.4.0 redux_persist: ^0.8.2