diff --git a/lib/globals.dart b/lib/globals.dart index a588e35..f28b1c8 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gecko/models/chest_data.dart'; import 'package:gecko/models/g1_wallets_list.dart'; @@ -55,3 +56,18 @@ late int udValue; late DateTime startBlockchainTime; late int currentUdIndex; + + final Map monthsInYear = { + 1: "month1".tr(), + 2: "month2".tr(), + 3: "month3".tr(), + 4: "month4".tr(), + 5: "month5".tr(), + 6: "month6".tr(), + 7: "month7".tr(), + 8: "month8".tr(), + 9: "month9".tr(), + 10: "month10".tr(), + 11: "month11".tr(), + 12: "month12".tr() + }; diff --git a/lib/providers/duniter_indexer.dart b/lib/providers/duniter_indexer.dart index 9083cd7..3184de8 100644 --- a/lib/providers/duniter_indexer.dart +++ b/lib/providers/duniter_indexer.dart @@ -6,21 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/models/queries_indexer.dart'; -import 'package:gecko/models/widgets_keys.dart'; -import 'package:gecko/providers/cesium_plus.dart'; import 'package:gecko/providers/substrate_sdk.dart'; -import 'package:gecko/providers/wallets_profiles.dart'; -import 'package:gecko/screens/wallet_view.dart'; -import 'package:gecko/widgets/balance.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; -import 'package:provider/provider.dart'; class DuniterIndexer with ChangeNotifier { Map walletNameIndexer = {}; String? fetchMoreCursor; Map? pageInfo; - int nPage = 1; - int nRepositories = 20; List? transBC; List listIndexerEndpoints = []; bool isLoadingIndexer = false; @@ -91,7 +83,6 @@ class DuniterIndexer with ChangeNotifier { if (configBox.containsKey('customIndexer')) { return configBox.get('customIndexer'); - // listIndexerEndpoints.insert(0, configBox.get('customIndexer')); } if (configBox.containsKey('indexerEndpoint')) { @@ -153,123 +144,8 @@ class DuniterIndexer with ChangeNotifier { return indexerEndpoint; } - Widget searchIdentity(BuildContext context, String name) { - // WalletOptionsProvider _walletOptions = - // Provider.of(context, listen: false); - WalletsProfilesProvider walletsProfiles = - Provider.of(context, listen: false); - final duniterIndexer = Provider.of(context, listen: false); - if (indexerEndpoint == '') { - return const Text('Aucun résultat'); - } - - log.d(indexerEndpoint); - final httpLink = HttpLink( - '$indexerEndpoint/v1/graphql', - ); - - final client = ValueNotifier( - GraphQLClient( - cache: GraphQLCache( - store: HiveStore()), // GraphQLCache(store: HiveStore()) - link: httpLink, - ), - ); - return GraphQLProvider( - client: client, - child: Query( - options: QueryOptions( - document: gql( - searchAddressByNameQ), // this is the query string you just created - variables: { - 'name': name, - }, - // pollInterval: const Duration(seconds: 10), - ), - builder: (QueryResult result, - {VoidCallback? refetch, FetchMore? fetchMore}) { - if (result.hasException) { - return Text(result.exception.toString()); - } - - if (result.isLoading) { - return Text('loading'.tr()); - } - - final List identities = result.data?['search_identity'] ?? []; - - if (identities.isEmpty) { - return Text('noResult'.tr()); - } - - for (Map profile in identities) { - duniterIndexer.walletNameIndexer - .putIfAbsent(profile['pubkey'], () => profile['name']); - } - - double avatarSize = 55; - return Expanded( - child: ListView(children: [ - for (Map profile in identities) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: ListTile( - key: keySearchResult(profile['pubkey']), - horizontalTitleGap: 40, - contentPadding: const EdgeInsets.all(5), - leading: defaultAvatar(avatarSize), - title: Row(children: [ - Text(getShortPubkey(profile['pubkey']), - style: const TextStyle( - fontSize: 18, - fontFamily: 'Monospace', - fontWeight: FontWeight.w500), - textAlign: TextAlign.center), - ]), - trailing: SizedBox( - width: 110, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Balance( - address: profile['pubkey'], size: 16), - ]), - ]), - ), - subtitle: Row(children: [ - Text(profile['name'] ?? '', - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.w500), - textAlign: TextAlign.center), - ]), - dense: false, - isThreeLine: false, - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - walletsProfiles.address = profile['pubkey']; - return WalletViewScreen( - address: profile['pubkey'], - username: name, - avatar: - g1WalletsBox.get(profile['pubkey'])?.avatar, - ); - }), - ); - }), - ), - ]), - ); - }), - ); - } - List parseHistory(blockchainTX, pubkey) { - var transBC = []; + List transBC = []; int i = 0; for (final trans in blockchainTX) { @@ -291,32 +167,21 @@ class DuniterIndexer with ChangeNotifier { } transBC[i].add(amount); transBC[i].add(direction); - // transBC[i].add(''); //transaction comment i++; } return transBC; } - FetchMoreOptions? checkQueryResult(result, opts, pubkey) { + FetchMoreOptions? mergeQueryResult(result, opts, pubkey, nRepositories) { final List? blockchainTX = (result.data['transaction_connection']['edges'] as List?); - // final List mempoolTX = - // (result.data['txsHistoryMp']['receiving'] as List); pageInfo = result.data['transaction_connection']['pageInfo']; fetchMoreCursor = pageInfo!['endCursor']; - if (fetchMoreCursor == null) nPage = 1; - - log.d(fetchMoreCursor); - - if (nPage == 1) { - nRepositories = 40; - } else if (nPage == 2) { - nRepositories = 100; - } - // nRepositories = 10; - nPage++; + final hasNextPage = pageInfo!['hasNextPage']; + final hasPreviousPage = pageInfo!['hasPreviousPage']; + log.d('endCursor: $fetchMoreCursor $hasNextPage $hasPreviousPage'); if (fetchMoreCursor != null) { opts = FetchMoreOptions( @@ -329,18 +194,12 @@ class DuniterIndexer with ChangeNotifier { as List ]; - log.d('repos: $previousResultData'); - log.d('repos: $fetchMoreResultData'); - log.d('repos: $repos'); - fetchMoreResultData['transaction_connection']['edges'] = repos; return fetchMoreResultData; }, ); } - log.d( - "###### DEBUG H Parse blockchainTX list. Cursor: $fetchMoreCursor ######"); if (fetchMoreCursor != null) { transBC = parseHistory(blockchainTX, pubkey); } else { @@ -388,3 +247,86 @@ Future _execQuery( return await client.query(options); } + +Map computeHistoryView(repository, lastDateDelimiter, isDouble) { + bool isTody = false; + bool isYesterday = false; + bool isThisWeek = false; + bool isMigrationTime = false; + String? dateDelimiter; + DateTime now = DateTime.now(); + final bool isUdUnit = configBox.get('isUdUnit') ?? false; + + late double amount; + late String finalAmount; + DateTime date = repository[0]; + String dateForm; + bool isDelimiter = true; + + if ({4, 10, 11, 12}.contains(date.month)) { + dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 3)}."; + } else if ({1, 2, 7, 9}.contains(date.month)) { + dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 4)}."; + } else { + dateForm = "${date.day} ${monthsInYear[date.month]}"; + } + + final transactionDate = DateTime(date.year, date.month, date.day); + final todayDate = DateTime(now.year, now.month, now.day); + final yesterdayDate = DateTime(now.year, now.month, now.day - 1); + + if (transactionDate == todayDate && !isTody) { + dateDelimiter = lastDateDelimiter = "today".tr(); + isTody = true; + } else if (transactionDate == yesterdayDate && !isYesterday) { + dateDelimiter = lastDateDelimiter = "yesterday".tr(); + isYesterday = true; + } else if (weekNumber(date) == weekNumber(now) && + date.year == now.year && + transactionDate != yesterdayDate && + transactionDate != todayDate && + !isThisWeek) { + dateDelimiter = lastDateDelimiter = "thisWeek".tr(); + isThisWeek = true; + } else if (lastDateDelimiter != "${monthsInYear[date.month]} ${date.year}" && + transactionDate != todayDate && + transactionDate != yesterdayDate && + !(weekNumber(date) == weekNumber(now) && date.year == now.year)) { + if (date.year == now.year) { + dateDelimiter = lastDateDelimiter = monthsInYear[date.month]; + } else { + dateDelimiter = + lastDateDelimiter = "${monthsInYear[date.month]} ${date.year}"; + } + } else { + isDelimiter = false; + } + + amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1; + + if (isUdUnit) { + amount = round(amount / balanceRatio); + finalAmount = 'ud'.tr(args: ['$amount ']); + } else { + finalAmount = '$amount $currencyName'; + } + + if (date.compareTo(startBlockchainTime) < 0) { + isMigrationTime = true; + } else { + isMigrationTime = false; + } + + return { + 'finalAmount': finalAmount, + 'isMigrationTime': isMigrationTime, + 'dateDelimiter': dateDelimiter ?? '', + 'isDelimiter': isDelimiter, + 'dateForm': dateForm, + }; +} + +int weekNumber(DateTime date) { + int dayOfYear = int.parse(DateFormat("D").format(date)); + return ((dayOfYear - date.weekday + 10) / 7).floor(); +} diff --git a/lib/providers/search.dart b/lib/providers/search.dart index c7d9892..aed638a 100644 --- a/lib/providers/search.dart +++ b/lib/providers/search.dart @@ -7,6 +7,7 @@ class SearchProvider with ChangeNotifier { List searchResult = []; final cacheDuring = 20 * 60 * 1000; //First number is minutes int cacheTime = 0; + int resultLenght = 0; void reload() { notifyListeners(); diff --git a/lib/providers/substrate_sdk.dart b/lib/providers/substrate_sdk.dart index 837cdc9..b20e2cb 100644 --- a/lib/providers/substrate_sdk.dart +++ b/lib/providers/substrate_sdk.dart @@ -180,8 +180,6 @@ class SubstrateSdk with ChangeNotifier { } Future> getBalance(String address) async { - log.d('BALANCE: $address'); - if (!nodeConnected) { return { 'transferableBalance': 0, @@ -220,6 +218,8 @@ class SubstrateSdk with ChangeNotifier { }; // log.i(finalBalances); + log.d( + '${getShortPubkey(address)} --- BALANCE: ${finalBalances['transferableBalance']}'); return finalBalances; } diff --git a/lib/screens/activity.dart b/lib/screens/activity.dart index fc28b21..2e7fde6 100644 --- a/lib/screens/activity.dart +++ b/lib/screens/activity.dart @@ -5,29 +5,35 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/models/queries_indexer.dart'; import 'package:gecko/models/widgets_keys.dart'; -import 'package:gecko/providers/cesium_plus.dart'; import 'package:gecko/providers/duniter_indexer.dart'; -import 'package:gecko/providers/substrate_sdk.dart'; import 'package:flutter/material.dart'; -import 'package:gecko/screens/wallet_view.dart'; import 'package:gecko/widgets/bottom_app_bar.dart'; import 'package:gecko/widgets/header_profile.dart'; -import 'package:gecko/widgets/page_route_no_transition.dart'; +import 'package:gecko/widgets/transaction_tile.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:provider/provider.dart'; -class ActivityScreen extends StatelessWidget with ChangeNotifier { +class ActivityScreen extends StatefulWidget with ChangeNotifier { ActivityScreen({required this.address, required this.avatar, this.username}) : super(key: keyActivityScreen); - final ScrollController scrollController = ScrollController(); - final double avatarsSize = 80; final String address; final String? username; final Image avatar; + @override + State createState() => _ActivityScreenState(); +} + +class _ActivityScreenState extends State { + // @override + // void initState() { + // super.initState(); + // } + + final ScrollController scrollController = ScrollController(); + final double avatarsSize = 80; FetchMore? fetchMore; FetchMoreOptions? opts; - final GlobalKey _scaffoldKey = GlobalKey(); @override @@ -44,13 +50,15 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { ), bottomNavigationBar: const GeckoBottomAppBar(), body: Column(children: [ - HeaderProfile(address: address, username: username), + HeaderProfile(address: widget.address, username: widget.username), historyQuery(context), ])); } Widget historyQuery(context) { final duniterIndexer = Provider.of(context, listen: false); + int nPage = 1; + int nRepositories = 20; if (indexerEndpoint == '') { return Column(children: [ @@ -85,7 +93,7 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { options: QueryOptions( document: gql(getHistoryByAddressQ), variables: { - 'address': address, + 'address': widget.address, 'number': 20, 'cursor': null }, @@ -93,7 +101,9 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { builder: (QueryResult result, {fetchMore, refetch}) { if (result.isLoading && result.data == null) { return const Center( - child: CircularProgressIndicator(), + child: CircularProgressIndicator( + color: orangeC, + ), ); } @@ -119,8 +129,22 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { } if (result.isNotLoading) { - // log.d(result.data); - opts = duniterIndexer.checkQueryResult(result, opts, address); + if (duniterIndexer.fetchMoreCursor == null) nPage = 1; + + // log.d('nPage: $nPage'); + + if (nPage <= 3) { + nRepositories = 20; + } else if (nPage <= 6) { + nRepositories = 40; + } else if (nPage <= 12) { + nRepositories = 80; + } else { + nRepositories = 120; + } + nPage++; + opts = duniterIndexer.mergeQueryResult( + result, opts, widget.address, nRepositories); } // Build history list @@ -153,6 +177,11 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { Widget historyView(context, result) { final duniterIndexer = Provider.of(context, listen: false); + int keyID = 0; + const double avatarSize = 200; + String? lastDateDelimiter; + bool? isDouble; + bool isMigrationPassed = false; return duniterIndexer.transBC == null ? Column(children: [ @@ -163,7 +192,53 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { ) ]) : Column(children: [ - getTransactionTile(context, duniterIndexer), + Column( + children: duniterIndexer.transBC!.map((repository) { + final answer = + computeHistoryView(repository, lastDateDelimiter, isDouble); + isDouble = lastDateDelimiter == answer['dateDelimiter'] || + answer['dateDelimiter'] == ''; + lastDateDelimiter = answer['dateDelimiter']; + bool isMigrationTime = false; + if (answer['isMigrationTime'] && !isMigrationPassed) { + isMigrationPassed = true; + isMigrationTime = true; + } + + return Column(children: [ + if (isMigrationTime) + const Padding( + padding: EdgeInsets.symmetric(vertical: 30), + child: Text( + 'Début de la ĞDev', + style: TextStyle( + fontSize: 25, + color: Colors.blueAccent, + fontWeight: FontWeight.w500), + ), + ), + if (!isDouble!) + Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: Text( + answer['dateDelimiter'], + style: const TextStyle( + fontSize: 23, + color: orangeC, + fontWeight: FontWeight.w300), + ), + ), + TransactionTile( + widget: widget, + keyID: keyID, + avatarSize: avatarSize, + repository: repository, + dateForm: answer['dateForm'], + finalAmount: answer['finalAmount'], + duniterIndexer: duniterIndexer, + context: context), + ]); + }).toList()), if (result.isLoading && duniterIndexer.pageInfo!['hasPreviousPage']) Row( mainAxisAlignment: MainAxisAlignment.center, @@ -183,196 +258,4 @@ class ActivityScreen extends StatelessWidget with ChangeNotifier { ) ]); } - - Widget getTransactionTile( - BuildContext context, DuniterIndexer duniterIndexer) { - int keyID = 0; - String? dateDelimiter; - String? lastDateDelimiter; - const double avatarSize = 200; - - bool isTody = false; - bool isYesterday = false; - bool isThisWeek = false; - bool isMigrationTime = false; - bool isMigrationTimePassed = false; - - final Map monthsInYear = { - 1: "month1".tr(), - 2: "month2".tr(), - 3: "month3".tr(), - 4: "month4".tr(), - 5: "month5".tr(), - 6: "month6".tr(), - 7: "month7".tr(), - 8: "month8".tr(), - 9: "month9".tr(), - 10: "month10".tr(), - 11: "month11".tr(), - 12: "month12".tr() - }; - - return Column( - children: duniterIndexer.transBC!.map((repository) { - // log.d('bbbbbbbbbbbbbbbbbbbbbb: ' + repository.toString()); - - DateTime now = DateTime.now(); - DateTime date = repository[0]; - - String dateForm; - if ({4, 10, 11, 12}.contains(date.month)) { - dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 3)}."; - } else if ({1, 2, 7, 9}.contains(date.month)) { - dateForm = "${date.day} ${monthsInYear[date.month]!.substring(0, 4)}."; - } else { - dateForm = "${date.day} ${monthsInYear[date.month]}"; - } - - int weekNumber(DateTime date) { - int dayOfYear = int.parse(DateFormat("D").format(date)); - return ((dayOfYear - date.weekday + 10) / 7).floor(); - } - - final transactionDate = DateTime(date.year, date.month, date.day); - final todayDate = DateTime(now.year, now.month, now.day); - final yesterdayDate = DateTime(now.year, now.month, now.day - 1); - - if (transactionDate == todayDate && !isTody) { - dateDelimiter = lastDateDelimiter = "today".tr(); - isTody = true; - } else if (transactionDate == yesterdayDate && !isYesterday) { - dateDelimiter = lastDateDelimiter = "yesterday".tr(); - isYesterday = true; - } else if (weekNumber(date) == weekNumber(now) && - date.year == now.year && - lastDateDelimiter != "thisWeek".tr() && - transactionDate != yesterdayDate && - transactionDate != todayDate && - !isThisWeek) { - dateDelimiter = lastDateDelimiter = "thisWeek".tr(); - isThisWeek = true; - } else if (lastDateDelimiter != monthsInYear[date.month] && - lastDateDelimiter != "${monthsInYear[date.month]} ${date.year}" && - transactionDate != todayDate && - transactionDate != yesterdayDate && - !(weekNumber(date) == weekNumber(now) && date.year == now.year)) { - if (date.year == now.year) { - dateDelimiter = lastDateDelimiter = monthsInYear[date.month]; - } else { - dateDelimiter = - lastDateDelimiter = "${monthsInYear[date.month]} ${date.year}"; - } - } else { - dateDelimiter = null; - } - - final bool isUdUnit = configBox.get('isUdUnit') ?? false; - late double amount; - late String finalAmount; - amount = repository[4] == 'RECEIVED' ? repository[3] : repository[3] * -1; - - if (isUdUnit) { - amount = round(amount / balanceRatio); - finalAmount = 'ud'.tr(args: ['$amount ']); - } else { - finalAmount = '$amount $currencyName'; - } - - if (!isMigrationTimePassed && date.compareTo(startBlockchainTime) < 0) { - isMigrationTimePassed = true; - isMigrationTime = true; - } else { - isMigrationTime = false; - } - - return Column(children: [ - if (isMigrationTime) - const Padding( - padding: EdgeInsets.symmetric(vertical: 30), - child: Text( - 'Début de la ĞDev', - style: TextStyle( - fontSize: 25, - color: Colors.blueAccent, - fontWeight: FontWeight.w500), - ), - ), - if (dateDelimiter != null) - Padding( - padding: const EdgeInsets.symmetric(vertical: 30), - child: Text( - dateDelimiter!, - style: const TextStyle( - fontSize: 23, color: orangeC, fontWeight: FontWeight.w300), - ), - ), - Padding( - padding: const EdgeInsets.only(right: 0), - child: - // Row(children: [Column(children: [],)],) - ListTile( - key: keyTransaction(keyID++), - contentPadding: const EdgeInsets.only( - left: 20, right: 30, top: 15, bottom: 15), - leading: ClipOval( - child: defaultAvatar(avatarSize), - ), - title: Padding( - padding: const EdgeInsets.only(bottom: 5), - child: Text(getShortPubkey(repository[1]), - style: const TextStyle( - fontSize: 18, fontFamily: 'Monospace')), - ), - subtitle: RichText( - text: TextSpan( - style: TextStyle( - fontSize: 16, - color: Colors.grey[700], - ), - children: [ - TextSpan( - text: dateForm, - ), - if (repository[2] != '') - TextSpan( - text: ' · ', - style: TextStyle( - fontSize: 20, - color: Colors.grey[550], - ), - ), - TextSpan( - text: repository[2], - style: TextStyle( - fontStyle: FontStyle.italic, - color: Colors.grey[600], - ), - ), - ], - ), - ), - trailing: Text(finalAmount, - style: const TextStyle( - fontSize: 18, fontWeight: FontWeight.w500), - textAlign: TextAlign.justify), - dense: false, - isThreeLine: false, - onTap: () { - duniterIndexer.nPage = 1; - // _cesiumPlusProvider.avatarCancelToken.cancel('cancelled'); - Navigator.push( - context, - PageNoTransit(builder: (context) { - return WalletViewScreen( - address: repository[1], - username: username ?? '', - ); - }), - ); - // Navigator.pop(context); - }), - ), - ]); - }).toList()); - } } diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 64a368b..2f0a059 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -92,7 +92,6 @@ class _HomeScreenState extends State { // sub.nodeConnected = false; // } - // TODO: fix random bad network status on startup HomeProvider homeProvider = Provider.of(context, listen: false); Connectivity() diff --git a/lib/screens/myWallets/wallets_home.dart b/lib/screens/myWallets/wallets_home.dart index 0aded22..8d2e94e 100644 --- a/lib/screens/myWallets/wallets_home.dart +++ b/lib/screens/myWallets/wallets_home.dart @@ -58,8 +58,17 @@ class WalletsHome extends StatelessWidget { ModalRoute.withName('/'), ); }), - title: Text(currentChest.name!, - style: TextStyle(color: Colors.grey[850])), + title: Row( + children: [ + Image.asset( + 'assets/chests/${currentChest.imageName}', + height: 32, + ), + const SizedBox(width: 17), + Text(currentChest.name!, + style: TextStyle(color: Colors.grey[850])), + ], + ), backgroundColor: const Color(0xffFFD58D), ), bottomNavigationBar: myWalletProvider.lastFlyBy == '' diff --git a/lib/screens/my_contacts.dart b/lib/screens/my_contacts.dart index 0b59572..c3556fb 100644 --- a/lib/screens/my_contacts.dart +++ b/lib/screens/my_contacts.dart @@ -2,18 +2,11 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:gecko/globals.dart'; import 'package:flutter/material.dart'; -import 'package:gecko/models/wallet_data.dart'; -import 'package:gecko/models/widgets_keys.dart'; -import 'package:gecko/providers/cesium_plus.dart'; -import 'package:gecko/models/g1_wallets_list.dart'; import 'package:gecko/providers/duniter_indexer.dart'; -import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/screens/common_elements.dart'; -import 'package:gecko/screens/wallet_view.dart'; -import 'package:gecko/widgets/balance.dart'; import 'package:gecko/widgets/bottom_app_bar.dart'; -import 'package:gecko/widgets/name_by_address.dart'; +import 'package:gecko/widgets/contacts_list.dart'; import 'package:provider/provider.dart'; class ContactsScreen extends StatelessWidget { @@ -24,15 +17,9 @@ class ContactsScreen extends StatelessWidget { WalletsProfilesProvider walletsProfilesClass = Provider.of(context, listen: true); final duniterIndexer = Provider.of(context, listen: false); - double avatarSize = 55; - final myContacts = contactsBox.toMap().values.toList(); - // for (var element in myContacts) { - // log.d('yooo: ${element.pubkey} ${element.username}'); - // } - myContacts.sort((p1, p2) { return Comparable.compare(p1.username?.toLowerCase() ?? 'zz', p2.username?.toLowerCase() ?? 'zz'); @@ -52,85 +39,11 @@ class ContactsScreen extends StatelessWidget { bottomNavigationBar: const GeckoBottomAppBar(), body: SafeArea( child: Stack(children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 20), - if (myContacts.isEmpty) - Text('noContacts'.tr()) - else - Expanded( - child: ListView(children: [ - for (G1WalletsList g1Wallet in myContacts) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 5), - child: ListTile( - key: keySearchResult('keyID++'), - horizontalTitleGap: 40, - contentPadding: const EdgeInsets.all(5), - leading: defaultAvatar(avatarSize), - title: Row(children: [ - Text(getShortPubkey(g1Wallet.address), - style: const TextStyle( - fontSize: 18, - fontFamily: 'Monospace', - fontWeight: FontWeight.w500), - textAlign: TextAlign.center), - ]), - trailing: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 110, - child: Row( - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Balance( - address: - g1Wallet.address, - size: 16), - ]), - ]), - ), - ]), - subtitle: Row(children: [ - NameByAddress( - wallet: - WalletData(address: g1Wallet.address)) - ]), - dense: false, - isThreeLine: false, - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - walletsProfilesClass.address = - g1Wallet.address; - return WalletViewScreen( - address: g1Wallet.address, - username: - duniterIndexer.walletNameIndexer[ - g1Wallet.address] ?? - '', - avatar: g1WalletsBox - .get(g1Wallet.address) - ?.avatar, - ); - }), - ); - }), - ), - ]), - ) - ]), - ), + ContactsList( + myContacts: myContacts, + avatarSize: avatarSize, + walletsProfilesClass: walletsProfilesClass, + duniterIndexer: duniterIndexer), CommonElements().offlineInfo(context), ]), ), diff --git a/lib/screens/search.dart b/lib/screens/search.dart index 9749e1c..9f9b6e4 100644 --- a/lib/screens/search.dart +++ b/lib/screens/search.dart @@ -1,5 +1,7 @@ // ignore_for_file: use_build_context_synchronously +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/services.dart'; import 'package:gecko/globals.dart'; @@ -21,6 +23,8 @@ class SearchScreen extends StatefulWidget { class _SearchScreenState extends State { bool canPasteAddress = false; String pastedAddress = ''; + Timer? debounce; + final int debouneTime = 50; Future getClipBoard() async { final clipboard = await Clipboard.getData('text/plain'); @@ -88,10 +92,12 @@ class _SearchScreenState extends State { autofocus: true, maxLines: 1, textAlign: TextAlign.left, - onChanged: (v) async => { - await getClipBoard(), - setState(() {}), - searchProvider.reload() + onChanged: (v) => { + if (debounce?.isActive ?? false) {debounce!.cancel()}, + debounce = Timer(Duration(milliseconds: debouneTime), () { + getClipBoard(); + searchProvider.reload(); + }) }, decoration: InputDecoration( filled: true, @@ -99,6 +105,23 @@ class _SearchScreenState extends State { prefixIconConstraints: const BoxConstraints( minHeight: 32, ), + suffixIcon: searchProvider.searchController.text == '' + ? null + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 17), + child: IconButton( + onPressed: (() async => { + searchProvider.searchController.text = '', + await getClipBoard(), + searchProvider.reload(), + }), + icon: Icon( + Icons.close, + color: Colors.grey[600], + size: 30, + ), + ), + ), prefixIcon: const Padding( padding: EdgeInsets.symmetric(horizontal: 17), child: Image( diff --git a/lib/screens/search_result.dart b/lib/screens/search_result.dart index d2ecaaa..ee85ff6 100644 --- a/lib/screens/search_result.dart +++ b/lib/screens/search_result.dart @@ -2,19 +2,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:gecko/globals.dart'; import 'package:flutter/material.dart'; -import 'package:gecko/models/wallet_data.dart'; -import 'package:gecko/models/widgets_keys.dart'; -import 'package:gecko/providers/cesium_plus.dart'; -import 'package:gecko/models/g1_wallets_list.dart'; import 'package:gecko/providers/duniter_indexer.dart'; -import 'package:gecko/providers/substrate_sdk.dart'; import 'package:gecko/providers/wallets_profiles.dart'; import 'package:gecko/providers/search.dart'; import 'package:gecko/screens/common_elements.dart'; -import 'package:gecko/screens/wallet_view.dart'; -import 'package:gecko/widgets/balance.dart'; import 'package:gecko/widgets/bottom_app_bar.dart'; -import 'package:gecko/widgets/name_by_address.dart'; +import 'package:gecko/widgets/search_result_list.dart'; import 'package:provider/provider.dart'; class SearchResultScreen extends StatelessWidget { @@ -28,6 +21,13 @@ class SearchResultScreen extends StatelessWidget { final duniterIndexer = Provider.of(context, listen: false); double avatarSize = 55; + // List myContacts = contactsBox.toMap().values.toList(); + // myContacts = myContacts + // .where((map) => + // (map.username ?? '').contains(searchProvider.searchController.text)) + // .toList(); + + // final searchProvider.resultLenght.toString(); return Scaffold( backgroundColor: backgroundColor, @@ -48,126 +48,42 @@ class SearchResultScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 30), - RichText( - text: TextSpan( - style: TextStyle( - fontSize: 18, - color: Colors.grey[700], - ), - children: [ - TextSpan( - text: "resultsFor".tr(), - ), - TextSpan( - text: '"${searchProvider.searchController.text}"', - style: const TextStyle(fontStyle: FontStyle.italic), + Center( + child: Column( + children: [ + Text( + "resultsFor".tr(), + style: TextStyle(color: Colors.grey[600]), ), + Text( + '"${searchProvider.searchController.text}"', + style: const TextStyle( + fontStyle: FontStyle.italic, fontSize: 21), + ) ], ), ), + // const SizedBox(height: 40), + // Text( + // 'Dans mes contacts'.tr(args: [currencyName]), + // style: const TextStyle(fontSize: 20), + // ), + // ContactsList( + // myContacts: myContacts, + // avatarSize: avatarSize, + // walletsProfilesClass: walletsProfilesClass, + // duniterIndexer: duniterIndexer), const SizedBox(height: 40), Text( 'inBlockchainResult'.tr(args: [currencyName]), style: const TextStyle(fontSize: 20), ), const SizedBox(height: 20), - FutureBuilder( - future: searchProvider.searchAddress(), - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data?.isEmpty ?? true) { - return duniterIndexer.searchIdentity( - context, searchProvider.searchController.text); - // const Text('Aucun résultat'); - } else { - return Expanded( - child: ListView(children: [ - for (G1WalletsList g1Wallet - in snapshot.data ?? []) - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 5), - child: ListTile( - key: keySearchResult(g1Wallet.address), - horizontalTitleGap: 40, - contentPadding: const EdgeInsets.all(5), - leading: defaultAvatar(avatarSize), - title: Row(children: [ - Text(getShortPubkey(g1Wallet.address), - style: const TextStyle( - fontSize: 18, - fontFamily: 'Monospace', - fontWeight: FontWeight.w500), - textAlign: TextAlign.center), - ]), - trailing: Column( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - SizedBox( - width: 110, - child: Row( - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - Column( - mainAxisAlignment: - MainAxisAlignment - .center, - children: [ - Balance( - address: g1Wallet - .address, - size: 16), - ]), - ]), - ), - ]), - subtitle: Row(children: [ - NameByAddress( - wallet: WalletData( - address: g1Wallet.address), - ), - ]), - dense: false, - isThreeLine: false, - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - walletsProfilesClass.address = - g1Wallet.address; - return WalletViewScreen( - address: g1Wallet.address, - username: duniterIndexer - .walletNameIndexer[ - g1Wallet.address] ?? - '', - avatar: g1WalletsBox - .get(g1Wallet.address) - ?.avatar, - ); - }), - ); - }), - ), - ]), - ); - } - } - return const Center( - heightFactor: 5, - child: CircularProgressIndicator( - strokeWidth: 3, - backgroundColor: yellowC, - color: orangeC, - ), - ); - }, - ), - // Text( - // _searchProvider.searchResult.toString(), - // ) + SearchResult( + searchProvider: searchProvider, + duniterIndexer: duniterIndexer, + avatarSize: avatarSize, + walletsProfilesClass: walletsProfilesClass), ]), ), CommonElements().offlineInfo(context), diff --git a/lib/screens/wallet_view.dart b/lib/screens/wallet_view.dart index 0729235..998c153 100644 --- a/lib/screens/wallet_view.dart +++ b/lib/screens/wallet_view.dart @@ -162,8 +162,8 @@ class WalletViewScreen extends StatelessWidget { builder: (context, AsyncSnapshot> snapshot) { if (snapshot.data == null) return const SizedBox(); String duration = ''; - log.d('certDelay ${snapshot.data!['certDelay']}'); - log.d('certRenewable ${snapshot.data!['certRenewable']}'); + log.d( + '${getShortPubkey(address)} --- certDelay ${snapshot.data!['certDelay']} --- certRenewable ${snapshot.data!['certRenewable']}'); if (snapshot.data!['certDelay'] != null || snapshot.data!['certRenewable'] != null) { diff --git a/lib/widgets/contacts_list.dart b/lib/widgets/contacts_list.dart new file mode 100644 index 0000000..cc374d2 --- /dev/null +++ b/lib/widgets/contacts_list.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gecko/globals.dart'; +import 'package:gecko/models/g1_wallets_list.dart'; +import 'package:gecko/models/wallet_data.dart'; +import 'package:gecko/models/widgets_keys.dart'; +import 'package:gecko/providers/cesium_plus.dart'; +import 'package:gecko/providers/duniter_indexer.dart'; +import 'package:gecko/providers/substrate_sdk.dart'; +import 'package:gecko/providers/wallets_profiles.dart'; +import 'package:gecko/screens/wallet_view.dart'; +import 'package:gecko/widgets/balance.dart'; +import 'package:gecko/widgets/name_by_address.dart'; + +class ContactsList extends StatelessWidget { + const ContactsList({ + Key? key, + required this.myContacts, + required this.avatarSize, + required this.walletsProfilesClass, + required this.duniterIndexer, + }) : super(key: key); + + final List myContacts; + final double avatarSize; + final WalletsProfilesProvider walletsProfilesClass; + final DuniterIndexer duniterIndexer; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + if (myContacts.isEmpty) + Text('noContacts'.tr()) + else + Expanded( + child: ListView(children: [ + for (G1WalletsList g1Wallet in myContacts) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: ListTile( + key: keySearchResult('keyID++'), + horizontalTitleGap: 40, + contentPadding: const EdgeInsets.all(5), + leading: defaultAvatar(avatarSize), + title: Row(children: [ + Text(getShortPubkey(g1Wallet.address), + style: const TextStyle( + fontSize: 18, + fontFamily: 'Monospace', + fontWeight: FontWeight.w500), + textAlign: TextAlign.center), + ]), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 110, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Balance( + address: g1Wallet.address, + size: 16), + ]), + ]), + ), + ]), + subtitle: Row(children: [ + NameByAddress( + wallet: WalletData(address: g1Wallet.address)) + ]), + dense: false, + isThreeLine: false, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + walletsProfilesClass.address = g1Wallet.address; + return WalletViewScreen( + address: g1Wallet.address, + username: duniterIndexer.walletNameIndexer[ + g1Wallet.address] ?? + '', + avatar: g1WalletsBox + .get(g1Wallet.address) + ?.avatar, + ); + }), + ); + }), + ), + ]), + ) + ]), + ); + } +} diff --git a/lib/widgets/search_identity_query.dart b/lib/widgets/search_identity_query.dart new file mode 100644 index 0000000..c396d74 --- /dev/null +++ b/lib/widgets/search_identity_query.dart @@ -0,0 +1,137 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gecko/globals.dart'; +import 'package:gecko/models/queries_indexer.dart'; +import 'package:gecko/models/widgets_keys.dart'; +import 'package:gecko/providers/cesium_plus.dart'; +import 'package:gecko/providers/duniter_indexer.dart'; +import 'package:gecko/providers/search.dart'; +import 'package:gecko/providers/substrate_sdk.dart'; +import 'package:gecko/providers/wallets_profiles.dart'; +import 'package:gecko/screens/wallet_view.dart'; +import 'package:gecko/widgets/balance.dart'; +import 'package:graphql_flutter/graphql_flutter.dart'; +import 'package:provider/provider.dart'; + +class SearchIdentityQuery extends StatelessWidget { + const SearchIdentityQuery({Key? key, required this.name}) : super(key: key); + final String name; + + @override + Widget build(BuildContext context) { + WalletsProfilesProvider walletsProfiles = + Provider.of(context, listen: false); + final duniterIndexer = Provider.of(context, listen: false); + final searchProvider = Provider.of(context, listen: false); + if (indexerEndpoint == '') { + return const Text('Aucun résultat'); + } + + log.d(indexerEndpoint); + final httpLink = HttpLink( + '$indexerEndpoint/v1/graphql', + ); + + final client = ValueNotifier( + GraphQLClient( + cache: GraphQLCache( + store: HiveStore()), // GraphQLCache(store: HiveStore()) + link: httpLink, + ), + ); + return GraphQLProvider( + client: client, + child: Query( + options: QueryOptions( + document: gql( + searchAddressByNameQ), // this is the query string you just created + variables: { + 'name': name, + }, + // pollInterval: const Duration(seconds: 10), + ), + builder: (QueryResult result, + {VoidCallback? refetch, FetchMore? fetchMore}) { + if (result.hasException) { + return Text(result.exception.toString()); + } + + if (result.isLoading) { + return Text('loading'.tr()); + } + + final List identities = result.data?['search_identity'] ?? []; + + if (identities.isEmpty) { + return Text('noResult'.tr()); + } + + for (Map profile in identities) { + duniterIndexer.walletNameIndexer + .putIfAbsent(profile['pubkey'], () => profile['name']); + } + + searchProvider.resultLenght = identities.length; + // TODO: Find a way to reload a provider here, in Widget build... + + double avatarSize = 55; + return Expanded( + child: ListView(children: [ + for (Map profile in identities) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: ListTile( + key: keySearchResult(profile['pubkey']), + horizontalTitleGap: 40, + contentPadding: const EdgeInsets.all(5), + leading: defaultAvatar(avatarSize), + title: Row(children: [ + Text(getShortPubkey(profile['pubkey']), + style: const TextStyle( + fontSize: 18, + fontFamily: 'Monospace', + fontWeight: FontWeight.w500), + textAlign: TextAlign.center), + ]), + trailing: SizedBox( + width: 110, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Balance( + address: profile['pubkey'], size: 16), + ]), + ]), + ), + subtitle: Row(children: [ + Text(profile['name'] ?? '', + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.w500), + textAlign: TextAlign.center), + ]), + dense: false, + isThreeLine: false, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + walletsProfiles.address = profile['pubkey']; + return WalletViewScreen( + address: profile['pubkey'], + username: name, + avatar: + g1WalletsBox.get(profile['pubkey'])?.avatar, + ); + }), + ); + }), + ), + ]), + ); + }), + ); + } +} diff --git a/lib/widgets/search_result_list.dart b/lib/widgets/search_result_list.dart new file mode 100644 index 0000000..21305d7 --- /dev/null +++ b/lib/widgets/search_result_list.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:gecko/globals.dart'; +import 'package:gecko/models/g1_wallets_list.dart'; +import 'package:gecko/models/wallet_data.dart'; +import 'package:gecko/models/widgets_keys.dart'; +import 'package:gecko/providers/cesium_plus.dart'; +import 'package:gecko/providers/duniter_indexer.dart'; +import 'package:gecko/providers/search.dart'; +import 'package:gecko/providers/substrate_sdk.dart'; +import 'package:gecko/providers/wallets_profiles.dart'; +import 'package:gecko/screens/wallet_view.dart'; +import 'package:gecko/widgets/balance.dart'; +import 'package:gecko/widgets/name_by_address.dart'; +import 'package:gecko/widgets/search_identity_query.dart'; + +class SearchResult extends StatelessWidget { + const SearchResult({ + Key? key, + required this.searchProvider, + required this.duniterIndexer, + required this.avatarSize, + required this.walletsProfilesClass, + }) : super(key: key); + + final SearchProvider searchProvider; + final DuniterIndexer duniterIndexer; + final double avatarSize; + final WalletsProfilesProvider walletsProfilesClass; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: searchProvider.searchAddress(), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data?.isEmpty ?? true) { + return SearchIdentityQuery( + name: searchProvider.searchController.text); + // const Text('Aucun résultat'); + } else { + return Expanded( + child: ListView(children: [ + for (G1WalletsList g1Wallet in snapshot.data ?? []) + resultTile(g1Wallet, context), + ]), + ); + } + } + return const Center( + heightFactor: 5, + child: CircularProgressIndicator( + strokeWidth: 3, + backgroundColor: yellowC, + color: orangeC, + ), + ); + }, + ); + } + + Padding resultTile(G1WalletsList g1Wallet, BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5), + child: ListTile( + key: keySearchResult(g1Wallet.address), + horizontalTitleGap: 40, + contentPadding: const EdgeInsets.all(5), + leading: defaultAvatar(avatarSize), + title: Row(children: [ + Text(getShortPubkey(g1Wallet.address), + style: const TextStyle( + fontSize: 18, + fontFamily: 'Monospace', + fontWeight: FontWeight.w500), + textAlign: TextAlign.center), + ]), + trailing: + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + SizedBox( + width: 110, + child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Balance(address: g1Wallet.address, size: 16), + ]), + ]), + ), + ]), + subtitle: Row(children: [ + NameByAddress( + wallet: WalletData(address: g1Wallet.address), + ), + ]), + dense: false, + isThreeLine: false, + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + walletsProfilesClass.address = g1Wallet.address; + return WalletViewScreen( + address: g1Wallet.address, + username: + duniterIndexer.walletNameIndexer[g1Wallet.address] ?? '', + avatar: g1WalletsBox.get(g1Wallet.address)?.avatar, + ); + }), + ); + }), + ); + } +} diff --git a/lib/widgets/transaction_tile.dart b/lib/widgets/transaction_tile.dart new file mode 100644 index 0000000..7ffcbf3 --- /dev/null +++ b/lib/widgets/transaction_tile.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:gecko/models/widgets_keys.dart'; +import 'package:gecko/providers/cesium_plus.dart'; +import 'package:gecko/providers/duniter_indexer.dart'; +import 'package:gecko/providers/substrate_sdk.dart'; +import 'package:gecko/screens/activity.dart'; +import 'package:gecko/screens/wallet_view.dart'; +import 'package:gecko/widgets/page_route_no_transition.dart'; + +class TransactionTile extends StatelessWidget { + const TransactionTile({ + Key? key, + required this.widget, + required this.keyID, + required this.avatarSize, + required this.repository, + required this.dateForm, + required this.finalAmount, + required this.duniterIndexer, + required this.context, + }) : super(key: key); + + final ActivityScreen widget; + final int keyID; + final double avatarSize; + final List repository; + final String dateForm; + final String finalAmount; + final DuniterIndexer duniterIndexer; + final BuildContext context; + + @override + Widget build(BuildContext context) { + final newKey = keyID + 1; + return Padding( + padding: const EdgeInsets.only(right: 0), + child: ListTile( + key: keyTransaction(newKey), + contentPadding: + const EdgeInsets.only(left: 20, right: 30, top: 15, bottom: 15), + leading: ClipOval( + child: defaultAvatar(avatarSize), + ), + title: Padding( + padding: const EdgeInsets.only(bottom: 5), + child: Text(getShortPubkey(repository[1]), + style: const TextStyle(fontSize: 18, fontFamily: 'Monospace')), + ), + subtitle: RichText( + text: TextSpan( + style: TextStyle( + fontSize: 16, + color: Colors.grey[700], + ), + children: [ + TextSpan( + text: dateForm, + ), + if (repository[2] != '') + TextSpan( + text: ' · ', + style: TextStyle( + fontSize: 20, + color: Colors.grey[550], + ), + ), + TextSpan( + text: repository[2], + style: TextStyle( + fontStyle: FontStyle.italic, + color: Colors.grey[600], + ), + ), + ], + ), + ), + trailing: Text(finalAmount, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500), + textAlign: TextAlign.justify), + dense: false, + isThreeLine: false, + onTap: () { + Navigator.push( + context, + PageNoTransit(builder: (context) { + return WalletViewScreen( + address: repository[1], + username: widget.username ?? '', + ); + }), + ); + // Navigator.pop(context); + }), + ); + } +}