diff --git a/app/lib/main.dart b/app/lib/main.dart index 470e2a26b..c3152351a 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:encointer_wallet/service/substrate_api/core/dart_api.dart'; import 'package:ew_storage/ew_storage.dart'; import 'package:ew_http/ew_http.dart'; import 'package:flutter/cupertino.dart'; @@ -37,6 +38,7 @@ Future main({AppConfig? appConfig, AppSettings? settings}) async { providers: [ RepositoryProvider(create: (context) => EwHttp()), RepositoryProvider(create: (context) => appConfig ?? const AppConfig()), + RepositoryProvider(create: (context) => SubstrateDartApi()), ], child: MultiProvider( providers: [ diff --git a/app/lib/modules/account/view/create_pin_view.dart b/app/lib/modules/account/view/create_pin_view.dart index 7749db897..4410e9322 100644 --- a/app/lib/modules/account/view/create_pin_view.dart +++ b/app/lib/modules/account/view/create_pin_view.dart @@ -11,7 +11,7 @@ import 'package:encointer_wallet/common/components/loading/centered_activity_ind import 'package:encointer_wallet/theme/theme.dart'; import 'package:encointer_wallet/common/components/gradient_elements.dart'; import 'package:encointer_wallet/page-encointer/common/community_chooser_on_map.dart'; -import 'package:encointer_wallet/page-encointer/home_page.dart'; +import 'package:encointer_wallet/presentation/home/views/home_page.dart'; import 'package:encointer_wallet/service/substrate_api/api.dart'; import 'package:encointer_wallet/store/app.dart'; import 'package:encointer_wallet/utils/format.dart'; diff --git a/app/lib/modules/login/logic/login_store.dart b/app/lib/modules/login/logic/login_store.dart index ebc09e810..88568ce37 100644 --- a/app/lib/modules/login/logic/login_store.dart +++ b/app/lib/modules/login/logic/login_store.dart @@ -6,7 +6,7 @@ import 'package:mobx/mobx.dart'; import 'package:provider/provider.dart'; import 'package:encointer_wallet/service/log/log_service.dart'; -import 'package:encointer_wallet/page-encointer/home_page.dart'; +import 'package:encointer_wallet/presentation/home/views/home_page.dart'; import 'package:encointer_wallet/utils/snack_bar.dart'; import 'package:encointer_wallet/store/app.dart'; import 'package:encointer_wallet/utils/alerts/app_alert.dart'; diff --git a/app/lib/modules/splash/view/splash_view.dart b/app/lib/modules/splash/view/splash_view.dart index 2d0551792..79a2eee0c 100644 --- a/app/lib/modules/splash/view/splash_view.dart +++ b/app/lib/modules/splash/view/splash_view.dart @@ -1,15 +1,9 @@ -import 'package:ew_http/ew_http.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:encointer_wallet/service/init_web_api/init_web_api.dart'; import 'package:encointer_wallet/modules/modules.dart'; -import 'package:encointer_wallet/config.dart'; -import 'package:encointer_wallet/utils/repository_provider.dart'; import 'package:encointer_wallet/gen/assets.gen.dart'; -import 'package:encointer_wallet/service/log/log_service.dart'; -import 'package:encointer_wallet/service/substrate_api/api.dart'; -import 'package:encointer_wallet/service/substrate_api/core/dart_api.dart'; -import 'package:encointer_wallet/service/substrate_api/core/js_api.dart'; import 'package:encointer_wallet/common/components/logo/encointer_logo.dart'; import 'package:encointer_wallet/store/app.dart'; @@ -61,19 +55,3 @@ class _SplashViewState extends State { ); } } - -/// Initialize an the webApi instance. -/// -/// Currently, `store.init()` must be called before it is passed into the api -/// due to some cyclic dependencies between webApi <> AppStore. -Future initWebApi(BuildContext context, AppStore store) async { - final js = await DefaultAssetBundle.of(context).loadString(Assets.jsServiceEncointer.dist.main); - final ewHttp = RepositoryProvider.of(context); - final appConfig = RepositoryProvider.of(context); - webApi = Api.create(store, JSApi(), SubstrateDartApi(), ewHttp, js, isIntegrationTest: appConfig.isIntegrationTest); - - await webApi.init().timeout( - const Duration(seconds: 20), - onTimeout: () => Log.d('webApi.init() has run into a timeout. We might be offline.'), - ); -} diff --git a/app/lib/page/page.dart b/app/lib/page/page.dart new file mode 100644 index 000000000..fe47d8512 --- /dev/null +++ b/app/lib/page/page.dart @@ -0,0 +1,5 @@ +///TODO(Azamat): Add other exports here +export 'assets/index.dart'; +export 'profile/contacts/contacts_page.dart'; +export 'profile/index.dart'; +export 'qr_scan/qr_scan_page.dart'; diff --git a/app/lib/presentation/home/store/home_page_store.dart b/app/lib/presentation/home/store/home_page_store.dart new file mode 100644 index 000000000..cab95757b --- /dev/null +++ b/app/lib/presentation/home/store/home_page_store.dart @@ -0,0 +1,98 @@ +import 'package:flutter/cupertino.dart'; +import 'package:ew_http/ew_http.dart'; +import 'package:mobx/mobx.dart'; +import 'package:provider/provider.dart'; +import 'package:timezone/timezone.dart' as tz; + +import 'package:encointer_wallet/service/service.dart'; +import 'package:encointer_wallet/config.dart'; +import 'package:encointer_wallet/modules/settings/logic/app_settings_store.dart'; +import 'package:encointer_wallet/store/app.dart'; +import 'package:encointer_wallet/utils/utils.dart'; + +part 'home_page_store.g.dart'; + +const _logTarget = 'HomePageStore'; +// ignore: library_private_types_in_public_api +class HomePageStore = _HomePageStoreBase with _$HomePageStore; + +abstract class _HomePageStoreBase with Store, WidgetsBindingObserver { + _HomePageStoreBase(this.appStore, this.buildContext) { + _init(); + } + + @observable + late AppStore appStore; + + @observable + late BuildContext buildContext; + + @action + void _init() { + Log.d(_logTarget, '_init'); + WidgetsBinding.instance.addObserver(this); + if (!RepositoryProvider.of(buildContext).isIntegrationTest) NotificationPlugin.init(buildContext); + } + + @action + Future postFrameCallbacks() async { + Log.d(_logTarget, 'postFrameCallbacks'); + final encointer = buildContext.read().encointer; + final cid = encointer.community?.cid.toFmtString(); + await initialDeepLinks(buildContext); + await NotificationHandler.fetchMessagesAndScheduleNotifications( + tz.local, + NotificationPlugin.scheduleNotification, + langCode: Localizations.localeOf(buildContext).languageCode, + cid: cid, + ewHttp: RepositoryProvider.of(buildContext), + devMode: buildContext.read().developerMode, + ); + + // Should never be null, we either come from the splash screen, and hence we had + // enough time to connect to the blockchain or we already have a populated store. + // + // Hence, can only be null if someone uses the app for the first time and is offline. + if (encointer.nextRegisteringPhaseStart != null && + encointer.currentCeremonyIndex != null && + encointer.ceremonyCycleDuration != null) { + await CeremonyNotifications.scheduleRegisteringStartsReminders( + encointer.nextRegisteringPhaseStart!, + encointer.currentCeremonyIndex!, + encointer.ceremonyCycleDuration!, + I18n.of(buildContext)!.translationsForLocale().encointer, + cid: cid, + ); + + await CeremonyNotifications.scheduleLastDayOfRegisteringReminders( + encointer.assigningPhaseStart!, + encointer.currentCeremonyIndex!, + encointer.ceremonyCycleDuration!, + I18n.of(buildContext)!.translationsForLocale().encointer, + cid: cid, + ); + } + } + + @action + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { + Log.d(_logTarget, 'Change lifecycle to $state'); + + if (state == AppLifecycleState.resumed) { + final connected = await webApi.isConnected(); + Log.d(_logTarget, 'webApi.isConnected() = $connected'); + if (!connected) { + /// initialize webApi again if it's failed after + /// user closes the app and reopens it + await initWebApi(buildContext, appStore); + } + } + super.didChangeAppLifecycleState(state); + } + + @action + void dispose() { + WidgetsBinding.instance.removeObserver(this); + } +} diff --git a/app/lib/presentation/home/store/home_page_store.g.dart b/app/lib/presentation/home/store/home_page_store.g.dart new file mode 100644 index 000000000..fd15c8a3e --- /dev/null +++ b/app/lib/presentation/home/store/home_page_store.g.dart @@ -0,0 +1,86 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'home_page_store.dart'; + +// ************************************************************************** +// StoreGenerator +// ************************************************************************** + +// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers + +mixin _$HomePageStore on _HomePageStoreBase, Store { + late final _$appStoreAtom = Atom(name: '_HomePageStoreBase.appStore', context: context); + + @override + AppStore get appStore { + _$appStoreAtom.reportRead(); + return super.appStore; + } + + @override + set appStore(AppStore value) { + _$appStoreAtom.reportWrite(value, super.appStore, () { + super.appStore = value; + }); + } + + late final _$buildContextAtom = Atom(name: '_HomePageStoreBase.buildContext', context: context); + + @override + BuildContext get buildContext { + _$buildContextAtom.reportRead(); + return super.buildContext; + } + + @override + set buildContext(BuildContext value) { + _$buildContextAtom.reportWrite(value, super.buildContext, () { + super.buildContext = value; + }); + } + + late final _$postFrameCallbacksAsyncAction = AsyncAction('_HomePageStoreBase.postFrameCallbacks', context: context); + + @override + Future postFrameCallbacks() { + return _$postFrameCallbacksAsyncAction.run(() => super.postFrameCallbacks()); + } + + late final _$didChangeAppLifecycleStateAsyncAction = + AsyncAction('_HomePageStoreBase.didChangeAppLifecycleState', context: context); + + @override + Future didChangeAppLifecycleState(AppLifecycleState state) { + return _$didChangeAppLifecycleStateAsyncAction.run(() => super.didChangeAppLifecycleState(state)); + } + + late final _$_HomePageStoreBaseActionController = ActionController(name: '_HomePageStoreBase', context: context); + + @override + void _init() { + final _$actionInfo = _$_HomePageStoreBaseActionController.startAction(name: '_HomePageStoreBase._init'); + try { + return super._init(); + } finally { + _$_HomePageStoreBaseActionController.endAction(_$actionInfo); + } + } + + @override + void dispose() { + final _$actionInfo = _$_HomePageStoreBaseActionController.startAction(name: '_HomePageStoreBase.dispose'); + try { + return super.dispose(); + } finally { + _$_HomePageStoreBaseActionController.endAction(_$actionInfo); + } + } + + @override + String toString() { + return ''' +appStore: ${appStore}, +buildContext: ${buildContext} + '''; + } +} diff --git a/app/lib/page-encointer/home_page.dart b/app/lib/presentation/home/views/home_page.dart similarity index 60% rename from app/lib/page-encointer/home_page.dart rename to app/lib/presentation/home/views/home_page.dart index 387e5017f..b661eef14 100644 --- a/app/lib/page-encointer/home_page.dart +++ b/app/lib/presentation/home/views/home_page.dart @@ -1,23 +1,12 @@ -import 'package:ew_http/ew_http.dart'; import 'package:flutter/material.dart'; import 'package:iconsax/iconsax.dart'; import 'package:provider/provider.dart'; -import 'package:timezone/timezone.dart' as tz; import 'package:encointer_wallet/theme/theme.dart'; -import 'package:encointer_wallet/config.dart'; -import 'package:encointer_wallet/utils/repository_provider.dart'; -import 'package:encointer_wallet/modules/settings/logic/app_settings_store.dart'; +import 'package:encointer_wallet/presentation/home/store/home_page_store.dart'; import 'package:encointer_wallet/page-encointer/bazaar/0_main/bazaar_main.dart'; -import 'package:encointer_wallet/page/assets/index.dart'; -import 'package:encointer_wallet/page/profile/contacts/contacts_page.dart'; -import 'package:encointer_wallet/page/profile/index.dart'; -import 'package:encointer_wallet/page/qr_scan/qr_scan_page.dart'; -import 'package:encointer_wallet/service/deep_link/deep_link.dart'; -import 'package:encointer_wallet/service/meetup/meetup.dart'; -import 'package:encointer_wallet/service/notification/lib/notification.dart'; +import 'package:encointer_wallet/page/page.dart'; import 'package:encointer_wallet/store/app.dart'; -import 'package:encointer_wallet/utils/translations/index.dart'; class EncointerHomePage extends StatefulWidget { const EncointerHomePage({super.key}); @@ -31,124 +20,38 @@ class EncointerHomePage extends StatefulWidget { class _EncointerHomePageState extends State { final PageController _pageController = PageController(); + late final HomePageStore _store; + late List _tabList; int _tabIndex = 0; @override void initState() { - if (!RepositoryProvider.of(context).isIntegrationTest) NotificationPlugin.init(context); - final encointer = context.read().encointer; - final cid = encointer.community?.cid.toFmtString(); + _store = HomePageStore(context.read(), context); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { - await initialDeepLinks(context); - await NotificationHandler.fetchMessagesAndScheduleNotifications( - tz.local, - NotificationPlugin.scheduleNotification, - langCode: Localizations.localeOf(context).languageCode, - cid: cid, - ewHttp: RepositoryProvider.of(context), - devMode: context.read().developerMode, - ); - - // Should never be null, we either come from the splash screen, and hence we had - // enough time to connect to the blockchain or we already have a populated store. - // - // Hence, can only be null if someone uses the app for the first time and is offline. - if (encointer.nextRegisteringPhaseStart != null && - encointer.currentCeremonyIndex != null && - encointer.ceremonyCycleDuration != null) { - await CeremonyNotifications.scheduleRegisteringStartsReminders( - encointer.nextRegisteringPhaseStart!, - encointer.currentCeremonyIndex!, - encointer.ceremonyCycleDuration!, - I18n.of(context)!.translationsForLocale().encointer, - cid: cid, - ); - - await CeremonyNotifications.scheduleLastDayOfRegisteringReminders( - encointer.assigningPhaseStart!, - encointer.currentCeremonyIndex!, - encointer.ceremonyCycleDuration!, - I18n.of(context)!.translationsForLocale().encointer, - cid: cid, - ); - } + await _store.postFrameCallbacks(); }); super.initState(); } - List _navBarItems(int activeItem) { - return _tabList - .map( - (tabData) => BottomNavigationBarItem( - icon: _tabList[activeItem] == tabData - ? ShaderMask( - blendMode: BlendMode.srcIn, - shaderCallback: (bounds) => AppColors.primaryGradient(context).createShader( - Rect.fromLTWH(0, 0, bounds.width, bounds.height), - ), - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon( - tabData.iconData, - key: Key(tabData.key.name), - ), - Container( - height: 4, - width: 16, - decoration: const BoxDecoration( - border: Border( - bottom: BorderSide(width: 2), - ), - ), - ) - ]), - ) - : Icon( - tabData.iconData, - key: Key(tabData.key.name), - color: tabData.key == TabKey.scan ? context.colorScheme.onSurface : AppColors.encointerGrey, - ), - label: '', - ), - ) - .toList(); + @override + void dispose() { + _store.dispose(); + super.dispose(); } @override Widget build(BuildContext context) { - final store = context.watch(); - _tabList = [ - TabData( - TabKey.wallet, - Iconsax.home_2, - ), - if (context.select((store) => store.settings.enableBazaar)) - TabData( - TabKey.bazaar, - Iconsax.shop, - ), // dart collection if - TabData( - TabKey.scan, - Iconsax.scan_barcode, - ), - TabData( - TabKey.contacts, - Iconsax.profile_2user, - ), - TabData( - TabKey.profile, - Iconsax.profile_circle, - ), - ]; - + _getTabList(); return Scaffold( backgroundColor: Colors.white, body: PageView( physics: const NeverScrollableScrollPhysics(), controller: _pageController, children: [ - AssetsView(store), - if (context.select((store) => store.settings.enableBazaar)) const BazaarMain(), + AssetsView(_store.appStore), + if (context.select((store) => _store.appStore.settings.enableBazaar)) const BazaarMain(), /// empty widget here because when qr code is clicked, we navigate to [ScanPage] const SizedBox(), @@ -181,6 +84,69 @@ class _EncointerHomePageState extends State { ), ); } + + void _getTabList() { + _tabList = [ + TabData( + TabKey.wallet, + Iconsax.home_2, + ), + if (context.select((store) => _store.appStore.settings.enableBazaar)) + TabData( + TabKey.bazaar, + Iconsax.shop, + ), // dart collection if + TabData( + TabKey.scan, + Iconsax.scan_barcode, + ), + TabData( + TabKey.contacts, + Iconsax.profile_2user, + ), + TabData( + TabKey.profile, + Iconsax.profile_circle, + ), + ]; + } + + List _navBarItems(int activeItem) { + return _tabList + .map( + (tabData) => BottomNavigationBarItem( + icon: _tabList[activeItem] == tabData + ? ShaderMask( + blendMode: BlendMode.srcIn, + shaderCallback: (bounds) => AppColors.primaryGradient(context).createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height), + ), + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon( + tabData.iconData, + key: Key(tabData.key.name), + ), + Container( + height: 4, + width: 16, + decoration: const BoxDecoration( + border: Border( + bottom: BorderSide(width: 2), + ), + ), + ) + ]), + ) + : Icon( + tabData.iconData, + key: Key(tabData.key.name), + color: tabData.key == TabKey.scan ? context.colorScheme.onSurface : AppColors.encointerGrey, + ), + label: '', + ), + ) + .toList(); + } } class TabData { diff --git a/app/lib/router/app_router.dart b/app/lib/router/app_router.dart index f9a5f8846..56dffcda5 100644 --- a/app/lib/router/app_router.dart +++ b/app/lib/router/app_router.dart @@ -11,7 +11,7 @@ import 'package:encointer_wallet/store/account/types/account_data.dart'; import 'package:encointer_wallet/utils/repository_provider.dart'; import 'package:encointer_wallet/page-encointer/bazaar/0_main/bazaar_main.dart'; import 'package:encointer_wallet/page-encointer/common/community_chooser_on_map.dart'; -import 'package:encointer_wallet/page-encointer/home_page.dart'; +import 'package:encointer_wallet/presentation/home/views/home_page.dart'; import 'package:encointer_wallet/page/assets/receive/receive_page.dart'; import 'package:encointer_wallet/page/assets/transfer/detail_page.dart'; import 'package:encointer_wallet/page/assets/transfer/payment_confirmation_page/index.dart'; diff --git a/app/lib/service/init_web_api/init_web_api.dart b/app/lib/service/init_web_api/init_web_api.dart new file mode 100644 index 000000000..52ca9dd18 --- /dev/null +++ b/app/lib/service/init_web_api/init_web_api.dart @@ -0,0 +1,27 @@ +import 'package:encointer_wallet/config.dart'; +import 'package:encointer_wallet/gen/assets.gen.dart'; +import 'package:encointer_wallet/service/log/log_service.dart'; +import 'package:encointer_wallet/service/substrate_api/api.dart'; +import 'package:encointer_wallet/service/substrate_api/core/dart_api.dart'; +import 'package:encointer_wallet/service/substrate_api/core/js_api.dart'; +import 'package:encointer_wallet/store/app.dart'; +import 'package:encointer_wallet/utils/repository_provider.dart'; +import 'package:ew_http/ew_http.dart'; +import 'package:flutter/material.dart'; + +/// Initialize an the webApi instance. +/// +/// Currently, `store.init()` must be called before it is passed into the api +/// due to some cyclic dependencies between webApi <> AppStore. +Future initWebApi(BuildContext context, AppStore store) async { + final js = await DefaultAssetBundle.of(context).loadString(Assets.jsServiceEncointer.dist.main); + final ewHttp = RepositoryProvider.of(context); + final appConfig = RepositoryProvider.of(context); + final dartApi = RepositoryProvider.of(context); + webApi = Api.create(store, JSApi(), dartApi, ewHttp, js, isIntegrationTest: appConfig.isIntegrationTest); + + await webApi.init().timeout( + const Duration(seconds: 20), + onTimeout: () => Log.d('webApi.init() has run into a timeout. We might be offline.'), + ); +} diff --git a/app/lib/service/service.dart b/app/lib/service/service.dart new file mode 100644 index 000000000..024781ba6 --- /dev/null +++ b/app/lib/service/service.dart @@ -0,0 +1,7 @@ +///TODO(Azamat): Add other exports here +export 'init_web_api/init_web_api.dart'; +export 'log/log_service.dart'; +export 'meetup/notification_handler.dart'; +export 'notification/lib/notification.dart'; +export 'substrate_api/api.dart'; +export 'deep_link/deep_link.dart'; diff --git a/app/lib/service/substrate_api/api.dart b/app/lib/service/substrate_api/api.dart index 5397dbe34..9ce50148a 100644 --- a/app/lib/service/substrate_api/api.dart +++ b/app/lib/service/substrate_api/api.dart @@ -143,7 +143,7 @@ class Api { Future connectNodeAll() async { final nodes = store.settings.endpointList.map((e) => e.value).toList(); final configs = store.settings.endpointList.map((e) => e.overrideConfig).toList(); - Log.d('configs: $configs', 'Api'); + Log.d('connectNodeAll: configs = $configs', 'Api'); // do connect final res = await evalJavascript('settings.connectAll(${jsonEncode(nodes)}, ${jsonEncode(configs)})'); diff --git a/app/lib/utils/utils.dart b/app/lib/utils/utils.dart new file mode 100644 index 000000000..7e1e26a30 --- /dev/null +++ b/app/lib/utils/utils.dart @@ -0,0 +1,3 @@ +///TODO(Azamat): Add other exports here +export 'repository_provider.dart'; +export 'translations/index.dart';