From a0a5d02212f31ef0d77a46692c5174d4e8e21eeb Mon Sep 17 00:00:00 2001 From: poka Date: Thu, 2 Dec 2021 07:23:12 +0100 Subject: [PATCH] Improve performance of history explorer --- lib/main.dart | 99 +++++----- lib/models/cesium_plus.dart | 61 ++++-- lib/models/search.dart | 29 ++- lib/models/wallets_profiles.dart | 16 +- lib/screens/history.dart | 36 ++-- lib/screens/home.dart | 4 +- lib/screens/myWallets/choose_chest.dart | 17 +- .../onBoarding/13_congratulations.dart | 7 +- lib/screens/wallet_view.dart | 179 +++++++++--------- pubspec.lock | 7 + pubspec.yaml | 3 +- 11 files changed, 263 insertions(+), 195 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 664dcd6..3377e3a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,6 +35,8 @@ import 'package:gecko/screens/home.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:gecko/screens/myWallets/wallets_home.dart'; +import 'package:gecko/screens/search.dart'; +import 'package:gecko/screens/search_result.dart'; import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; @@ -139,55 +141,58 @@ class Gecko extends StatelessWidget { // HistoryProvider _historyProvider = Provider.of(context); // HistoryProvider('').snackNode(context); return MultiProvider( - providers: [ - // Provider(create: (context) => HistoryProvider()), - ChangeNotifierProvider(create: (_) => HomeProvider()), - ChangeNotifierProvider(create: (_) => WalletsProfilesProvider('')), - ChangeNotifierProvider(create: (_) => MyWalletsProvider()), - ChangeNotifierProvider(create: (_) => ChestProvider()), - ChangeNotifierProvider(create: (_) => GenerateWalletsProvider()), - ChangeNotifierProvider(create: (_) => WalletOptionsProvider()), - ChangeNotifierProvider(create: (_) => ChangePinProvider()), - ChangeNotifierProvider(create: (_) => SearchProvider()), - ChangeNotifierProvider(create: (_) => CesiumPlusProvider()) - ], - child: GraphQLProvider( - client: _client, - child: MaterialApp( - builder: (context, widget) => ResponsiveWrapper.builder( - BouncingScrollWrapper.builder(context, widget), - maxWidth: 1200, - minWidth: 480, - defaultScale: true, - breakpoints: [ - const ResponsiveBreakpoint.resize(480, name: MOBILE), - const ResponsiveBreakpoint.autoScale(800, name: TABLET), - const ResponsiveBreakpoint.resize(1000, name: DESKTOP), - ], - background: Container(color: backgroundColor)), - title: 'Ğecko', - theme: ThemeData( - appBarTheme: const AppBarTheme( - color: Color(0xffFFD58D), - foregroundColor: Color(0xFF000000), - ), - primaryColor: const Color(0xffFFD58D), - textTheme: const TextTheme( - bodyText1: TextStyle(), - bodyText2: TextStyle(), - ).apply( - bodyColor: const Color(0xFF000000), - ), - colorScheme: ColorScheme.fromSwatch() - .copyWith(secondary: Colors.grey[850]), + providers: [ + // Provider(create: (context) => HistoryProvider()), + ChangeNotifierProvider(create: (_) => HomeProvider()), + ChangeNotifierProvider(create: (_) => WalletsProfilesProvider('')), + ChangeNotifierProvider(create: (_) => MyWalletsProvider()), + ChangeNotifierProvider(create: (_) => ChestProvider()), + ChangeNotifierProvider(create: (_) => GenerateWalletsProvider()), + ChangeNotifierProvider(create: (_) => WalletOptionsProvider()), + ChangeNotifierProvider(create: (_) => ChangePinProvider()), + ChangeNotifierProvider(create: (_) => SearchProvider()), + ChangeNotifierProvider(create: (_) => CesiumPlusProvider()) + ], + child: GraphQLProvider( + client: _client, + child: MaterialApp( + builder: (context, widget) => ResponsiveWrapper.builder( + BouncingScrollWrapper.builder(context, widget), + maxWidth: 1200, + minWidth: 480, + defaultScale: true, + breakpoints: [ + const ResponsiveBreakpoint.resize(480, name: MOBILE), + const ResponsiveBreakpoint.autoScale(800, name: TABLET), + const ResponsiveBreakpoint.resize(1000, name: DESKTOP), + ], + background: Container(color: backgroundColor)), + title: 'Ğecko', + theme: ThemeData( + appBarTheme: const AppBarTheme( + color: Color(0xffFFD58D), + foregroundColor: Color(0xFF000000), ), - home: const HomeScreen(), - initialRoute: "/", - routes: { - '/mywallets': (context) => WalletsHome(), - }, + primaryColor: const Color(0xffFFD58D), + textTheme: const TextTheme( + bodyText1: TextStyle(), + bodyText2: TextStyle(), + ).apply( + bodyColor: const Color(0xFF000000), + ), + colorScheme: + ColorScheme.fromSwatch().copyWith(secondary: Colors.grey[850]), ), - )); + home: const HomeScreen(), + initialRoute: "/", + routes: { + '/mywallets': (context) => WalletsHome(), + '/search': (context) => const SearchScreen(), + '/searchResult': (context) => const SearchResultScreen(), + }, + ), + ), + ); } } diff --git a/lib/models/cesium_plus.dart b/lib/models/cesium_plus.dart index ca5adbf..e2659c1 100644 --- a/lib/models/cesium_plus.dart +++ b/lib/models/cesium_plus.dart @@ -1,16 +1,19 @@ import 'dart:convert'; import 'dart:io'; +import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gecko/globals.dart'; -import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; +// import 'package:http/http.dart' as http; class CesiumPlusProvider with ChangeNotifier { TextEditingController cesiumName = TextEditingController(); Image defaultAvatar(double size) => Image.asset(('assets/icon_user.png'), height: size); + CancelToken avatarCancelToken = CancelToken(); + Future _buildQuery(_pubkey) async { var queryGetAvatar = json.encode({ "query": { @@ -66,18 +69,34 @@ class CesiumPlusProvider with ChangeNotifier { } List queryOptions = await _buildQuery(_pubkey); - final response = await http.post((Uri.parse(queryOptions[0])), - body: queryOptions[1], headers: queryOptions[2]); - final responseJson = json.decode(response.body); - if (responseJson['hits']['hits'].toString() == '[]') { + + var dio = Dio(); + Response response; + try { + response = await dio.post( + queryOptions[0], + data: queryOptions[1], + options: Options( + headers: queryOptions[2], + sendTimeout: 3000, + receiveTimeout: 5000, + ), + ); + // response = await http.post((Uri.parse(queryOptions[0])), + // body: queryOptions[1], headers: queryOptions[2]); + } catch (e) { + log.e(e); + } + + if (response.data['hits']['hits'].toString() == '[]') { return ''; } final bool _nameExist = - responseJson['hits']['hits'][0]['_source'].containsKey("title"); + response.data['hits']['hits'][0]['_source'].containsKey("title"); if (!_nameExist) { return ''; } - _name = responseJson['hits']['hits'][0]['_source']['title']; + _name = response.data['hits']['hits'][0]['_source']['title']; g1WalletsBox.get(_pubkey).csName = _name; @@ -88,27 +107,39 @@ class CesiumPlusProvider with ChangeNotifier { if (g1WalletsBox.get(_pubkey).avatar != null) { return g1WalletsBox.get(_pubkey).avatar; } + var dio = Dio(); - log.d(_pubkey); + // log.d(_pubkey); List queryOptions = await _buildQuery(_pubkey); - http.Response response; + Response response; try { - response = await http.post((Uri.parse(queryOptions[0])), - body: queryOptions[1], headers: queryOptions[2]); + response = await dio + .post(queryOptions[0], + data: queryOptions[1], + options: Options( + headers: queryOptions[2], + sendTimeout: 4000, + receiveTimeout: 15000, + ), + cancelToken: avatarCancelToken) + .timeout( + const Duration(seconds: 15), + ); + // response = await http.post((Uri.parse(queryOptions[0])), + // body: queryOptions[1], headers: queryOptions[2]); } catch (e) { log.e(e); } - final responseJson = json.decode(response.body); - if (responseJson['hits']['hits'].toString() == '[]' || - !responseJson['hits']['hits'][0]['_source'].containsKey("avatar")) { + if (response.data['hits']['hits'].toString() == '[]' || + !response.data['hits']['hits'][0]['_source'].containsKey("avatar")) { return defaultAvatar(size); } final _avatar = - responseJson['hits']['hits'][0]['_source']['avatar']['_content']; + response.data['hits']['hits'][0]['_source']['avatar']['_content']; var avatarFile = File('${(await getTemporaryDirectory()).path}/avatar_$_pubkey.png'); diff --git a/lib/models/search.dart b/lib/models/search.dart index d1262ed..6c4f7b3 100644 --- a/lib/models/search.dart +++ b/lib/models/search.dart @@ -1,9 +1,8 @@ -import 'dart:convert'; +import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/models/g1_wallets_list.dart'; -import 'package:http/http.dart' as http; class SearchProvider with ChangeNotifier { TextEditingController searchController = TextEditingController(); @@ -21,10 +20,26 @@ class SearchProvider with ChangeNotifier { if (cacheTime + cacheDuring <= searchTime) { g1WalletsBox.clear(); - final url = Uri.parse('https://g1-stats.axiom-team.fr/data/forbes.json'); - final response = await http.get(url); + // final url = Uri.parse('https://g1-stats.axiom-team.fr/data/forbes.json'); + // final response = await http.get(url); - List _listWallets = _parseG1Wallets(response.body); + var dio = Dio(); + Response response; + try { + response = await dio.get( + 'https://g1-stats.axiom-team.fr/data/forbes.json', + options: Options( + sendTimeout: 5000, + receiveTimeout: 10000, + ), + ); + // response = await http.post((Uri.parse(queryOptions[0])), + // body: queryOptions[1], headers: queryOptions[2]); + } catch (e) { + log.e(e); + } + + List _listWallets = _parseG1Wallets(response.data); Map _mapWallets = { for (var e in _listWallets) e.pubkey: e }; @@ -49,8 +64,8 @@ class SearchProvider with ChangeNotifier { } } -List _parseG1Wallets(String responseBody) { - final parsed = jsonDecode(responseBody).cast>(); +List _parseG1Wallets(var responseBody) { + final parsed = responseBody.cast>(); return parsed .map((json) => G1WalletsList.fromJson(json)) diff --git a/lib/models/wallets_profiles.dart b/lib/models/wallets_profiles.dart index 2e55128..b1e0322 100644 --- a/lib/models/wallets_profiles.dart +++ b/lib/models/wallets_profiles.dart @@ -30,6 +30,8 @@ class WalletsProfilesProvider with ChangeNotifier { TextEditingController payAmount = TextEditingController(); TextEditingController payComment = TextEditingController(); num balance; + int nRepositories = 10; + int nPage = 1; Future scan(context) async { await Permission.camera.request(); @@ -193,11 +195,21 @@ class WalletsProfilesProvider with ChangeNotifier { (result.data['txsHistoryBc']['both']['edges'] as List); pageInfo = result.data['txsHistoryBc']['both']['pageInfo']; - fetchMoreCursor = pageInfo['endCursor']; + if (fetchMoreCursor == null) nPage = 1; + + if (nPage == 1) { + nRepositories = 30; + } else if (nPage == 2) { + nRepositories = 100; + } + log.d(nPage); + log.d(nRepositories); + nPage++; + if (fetchMoreCursor != null) { opts = FetchMoreOptions( - variables: {'cursor': fetchMoreCursor}, + variables: {'cursor': fetchMoreCursor, 'number': nRepositories}, updateQuery: (previousResultData, fetchMoreResultData) { final List repos = [ ...previousResultData['txsHistoryBc']['both']['edges'] diff --git a/lib/screens/history.dart b/lib/screens/history.dart index 27a5b01..d8a3fe1 100644 --- a/lib/screens/history.dart +++ b/lib/screens/history.dart @@ -1,7 +1,6 @@ import 'package:flutter/services.dart'; import 'package:gecko/globals.dart'; import 'package:gecko/models/cesium_plus.dart'; -import 'package:gecko/models/home.dart'; import 'package:gecko/models/queries.dart'; import 'package:gecko/models/wallets_profiles.dart'; import 'package:flutter/material.dart'; @@ -18,7 +17,6 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { HistoryScreen({@required this.pubkey, this.avatar, this.username, Key key}) : super(key: key); final ScrollController scrollController = ScrollController(); - final nRepositories = 20; final double avatarsSize = 80; final String pubkey; final String username; @@ -53,14 +51,14 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { ), body: Column(children: [ headerProfileView(context, _historyProvider, _cesiumPlusProvider), - historyQuery(context, _historyProvider, _cesiumPlusProvider), + historyQuery(context, _cesiumPlusProvider), ])); } - Widget historyQuery(context, WalletsProfilesProvider _historyProvider2, - CesiumPlusProvider _cesiumPlusProvider) { + Widget historyQuery(context, CesiumPlusProvider _cesiumPlusProvider) { WalletsProfilesProvider _historyProvider = Provider.of(context, listen: true); + return Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -71,13 +69,11 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { document: gql(getHistory), variables: { 'pubkey': pubkey, - 'number': nRepositories, + 'number': 10, 'cursor': null }, ), builder: (QueryResult result, {fetchMore, refetch}) { - // log.d(result.data); - if (result.isLoading && result.data == null) { return const Center( child: CircularProgressIndicator(), @@ -110,7 +106,10 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { .removeDecimalZero(result.data['balance']['amount'] / 100); } - opts = _historyProvider.checkQueryResult(result, opts, pubkey); + if (result.isNotLoading) { + // log.d(result.data); + opts = _historyProvider.checkQueryResult(result, opts, pubkey); + } // Build history list return NotificationListener( @@ -175,8 +174,6 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { Widget getTransactionTile( BuildContext context, WalletsProfilesProvider _historyProvider) { - HomeProvider _homeProvider = - Provider.of(context, listen: false); CesiumPlusProvider _cesiumPlusProvider = Provider.of(context, listen: false); int keyID = 0; @@ -358,15 +355,14 @@ class HistoryScreen extends StatelessWidget with ChangeNotifier { dense: false, isThreeLine: false, onTap: () { - if (_historyProvider.isPubkey(context, repository[2])) { - _homeProvider.currentIndex = 0; - Navigator.push( - context, - MaterialPageRoute(builder: (context) { - return WalletViewScreen(pubkey: repository[2]); - }), - ); - } + _historyProvider.nPage = 1; + // _cesiumPlusProvider.avatarCancelToken.cancel('cancelled'); + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + return WalletViewScreen(pubkey: repository[2]); + }), + ); // Navigator.pop(context); }), ), diff --git a/lib/screens/home.dart b/lib/screens/home.dart index da1f1eb..c79723e 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -235,7 +235,7 @@ Widget geckHome(context) { ), const SizedBox(height: 12), const Text( - "Rechercher un\nportfeuille", + "Rechercher un\nportefeuille", textAlign: TextAlign.center, style: TextStyle( color: Colors.white, @@ -473,7 +473,7 @@ Widget welcomeHome(context) { ); }, child: const Text( - 'Créer un portfeuille', + 'Créer un portefeuille', style: TextStyle(fontSize: 24, fontWeight: FontWeight.w600), ), diff --git a/lib/screens/myWallets/choose_chest.dart b/lib/screens/myWallets/choose_chest.dart index 6377d2e..aafb524 100644 --- a/lib/screens/myWallets/choose_chest.dart +++ b/lib/screens/myWallets/choose_chest.dart @@ -113,13 +113,16 @@ class _ChooseChestState extends State { WalletData defaultWallet = _myWalletProvider.getDefaultWallet(currentChest); _myWalletProvider.rebuildWidget(); - Navigator.pushAndRemoveUntil(context, - MaterialPageRoute(builder: (context) { - return UnlockingWallet( - wallet: defaultWallet, - action: "mywallets", - ); - }), ModalRoute.withName('/')); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) { + return UnlockingWallet( + wallet: defaultWallet, + action: "mywallets", + ); + }), + ModalRoute.withName('/'), + ); }, child: Text( 'Ouvrir ce coffre', diff --git a/lib/screens/onBoarding/13_congratulations.dart b/lib/screens/onBoarding/13_congratulations.dart index ac73743..ae2ec6f 100644 --- a/lib/screens/onBoarding/13_congratulations.dart +++ b/lib/screens/onBoarding/13_congratulations.dart @@ -46,13 +46,10 @@ class OnboardingStepFiveteen extends StatelessWidget { onPrimary: Colors.white, // foreground ), onPressed: () { - Navigator.popUntil( - context, - ModalRoute.withName('/'), - ); - Navigator.pushNamed( + Navigator.pushNamedAndRemoveUntil( context, '/mywallets', + ModalRoute.withName('/'), ); }, child: const Text("Accéder à mes portefeuilles", diff --git a/lib/screens/wallet_view.dart b/lib/screens/wallet_view.dart index d5046b9..f77e21f 100644 --- a/lib/screens/wallet_view.dart +++ b/lib/screens/wallet_view.dart @@ -65,9 +65,9 @@ class WalletViewScreen extends StatelessWidget { page: HistoryScreen( pubkey: pubkey, username: username ?? - g1WalletsBox.get(pubkey).username, + g1WalletsBox.get(pubkey)?.username, avatar: avatar ?? - g1WalletsBox.get(pubkey).avatar, + g1WalletsBox.get(pubkey)?.avatar, ), isFast: false), ); @@ -98,8 +98,7 @@ class WalletViewScreen extends StatelessWidget { image: AssetImage('assets/copy_key.png'), height: 90)), onTap: () { - Clipboard.setData( - ClipboardData(text: _historyProvider.pubkey)); + Clipboard.setData(ClipboardData(text: pubkey)); _historyProvider.snackCopyKey(context); }), ), @@ -201,101 +200,103 @@ class WalletViewScreen extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(left: 30, right: 40), child: Row(children: [ - Column(crossAxisAlignment: CrossAxisAlignment.start, children: < - Widget>[ - Row(children: [ - GestureDetector( - key: const Key('copyPubkey'), - onTap: () { - Clipboard.setData(ClipboardData(text: pubkey)); - _historyProvider.snackCopyKey(context); - }, - child: Text( - _historyProvider.getShortPubkey(pubkey), - style: const TextStyle( - fontSize: 30, - fontWeight: FontWeight.w800, - ), - ), - ), - ]), - const SizedBox(height: 10), - if (username == null && g1WalletsBox.get(pubkey).username == null) - Query( - options: QueryOptions( - document: gql(getId), - variables: { - 'pubkey': _historyProvider.pubkey, - }, - ), - builder: (QueryResult result, - {VoidCallback refetch, FetchMore fetchMore}) { - if (result.isLoading || result.hasException) { - return const Text('...'); - } else if (result.data['idty'] == null || - result.data['idty']['username'] == null) { - g1WalletsBox.get(pubkey).username = ''; - return const Text(''); - } else { - g1WalletsBox.get(pubkey).username = - result?.data['idty']['username'] ?? ''; - return SizedBox( - width: 230, - child: Text( - result?.data['idty']['username'] ?? '', - style: const TextStyle( - fontSize: 27, - color: Color(0xff814C00), - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + GestureDetector( + key: const Key('copyPubkey'), + onTap: () { + Clipboard.setData(ClipboardData(text: pubkey)); + _historyProvider.snackCopyKey(context); + }, + child: Text( + _historyProvider.getShortPubkey(pubkey), + style: const TextStyle( + fontSize: 30, + fontWeight: FontWeight.w800, ), - ); - } - }, - ), - if (username == null && g1WalletsBox.get(pubkey).username != null) - SizedBox( - width: 230, - child: Text( - g1WalletsBox.get(pubkey).username, - style: const TextStyle( - fontSize: 27, - color: Color(0xff814C00), + ), ), - ), - ), - if (username != null) - SizedBox( - width: 230, - child: Text( - username, - style: const TextStyle( - fontSize: 27, - color: Color(0xff814C00), + ]), + const SizedBox(height: 10), + if (username == null && + g1WalletsBox.get(pubkey)?.username == null) + Query( + options: QueryOptions( + document: gql(getId), + variables: { + 'pubkey': pubkey, + }, + ), + builder: (QueryResult result, + {VoidCallback refetch, FetchMore fetchMore}) { + if (result.isLoading || result.hasException) { + return const Text('...'); + } else if (result.data['idty'] == null || + result.data['idty']['username'] == null) { + g1WalletsBox.get(pubkey)?.username = ''; + return const Text(''); + } else { + g1WalletsBox.get(pubkey)?.username = + result?.data['idty']['username'] ?? ''; + return SizedBox( + width: 230, + child: Text( + result?.data['idty']['username'] ?? '', + style: const TextStyle( + fontSize: 27, + color: Color(0xff814C00), + ), + ), + ); + } + }, ), - ), - ), - const SizedBox(height: 25), - FutureBuilder( - future: _cesiumPlusProvider.getName(_historyProvider.pubkey), - initialData: '...', - builder: (context, snapshot) { - return SizedBox( + if (username == null && + g1WalletsBox.get(pubkey)?.username != null) + SizedBox( width: 230, child: Text( - snapshot.data ?? '-', - style: - const TextStyle(fontSize: 18, color: Colors.black), + g1WalletsBox.get(pubkey)?.username, + style: const TextStyle( + fontSize: 27, + color: Color(0xff814C00), + ), ), - ); - }), - const SizedBox(height: 30), - ]), + ), + if (username != null) + SizedBox( + width: 230, + child: Text( + username, + style: const TextStyle( + fontSize: 27, + color: Color(0xff814C00), + ), + ), + ), + const SizedBox(height: 25), + FutureBuilder( + future: _cesiumPlusProvider.getName(pubkey), + initialData: '...', + builder: (context, snapshot) { + return SizedBox( + width: 230, + child: Text( + snapshot.data ?? '-', + style: const TextStyle( + fontSize: 18, color: Colors.black), + ), + ); + }), + const SizedBox(height: 30), + ]), const Spacer(), Column(children: [ if (avatar == null) FutureBuilder( - future: _cesiumPlusProvider.getAvatar( - _historyProvider.pubkey, _avatarSize), + future: _cesiumPlusProvider.getAvatar(pubkey, _avatarSize), builder: (BuildContext context, AsyncSnapshot _avatar) { if (_avatar.connectionState != ConnectionState.done) { diff --git a/pubspec.lock b/pubspec.lock index b646b7f..d5101a8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -267,6 +267,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.6" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.4" dubp: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 89b6d3d..fdcfd77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: Pay with G1. # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.0.3+10 +version: 0.0.3+12 environment: sdk: ">=2.7.0 <3.0.0" @@ -57,6 +57,7 @@ dependencies: unorm_dart: ^0.2.0 xml: ^5.3.0 pull_to_refresh: ^2.0.0 + dio: ^4.0.4 flutter_icons: android: "ic_launcher"