diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 66d20500..258b3b96 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -12,38 +12,45 @@ PODS: - connectivity_plus (0.0.1): - Flutter - FlutterMacOS - - device_info_plus (0.0.1): - - Flutter - - Firebase/Analytics (10.27.0): + - Firebase/Analytics (10.28.0): - Firebase/Core - - Firebase/Auth (10.27.0): + - Firebase/Auth (10.28.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 10.28.0) + - Firebase/Core (10.28.0): - Firebase/CoreOnly - - FirebaseAuth (~> 10.27.0) - - Firebase/Core (10.27.0): + - FirebaseAnalytics (~> 10.28.0) + - Firebase/CoreOnly (10.28.0): + - FirebaseCore (= 10.28.0) + - Firebase/Crashlytics (10.28.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.27.0) - - Firebase/CoreOnly (10.27.0): - - FirebaseCore (= 10.27.0) - - Firebase/Crashlytics (10.27.0): + - FirebaseCrashlytics (~> 10.28.0) + - Firebase/RemoteConfig (10.28.0): - Firebase/CoreOnly - - FirebaseCrashlytics (~> 10.27.0) - - firebase_analytics (11.1.0): - - Firebase/Analytics (= 10.27.0) + - FirebaseRemoteConfig (~> 10.28.0) + - firebase_analytics (11.2.0): + - Firebase/Analytics (= 10.28.0) - firebase_core - Flutter - - firebase_auth (5.1.1): - - Firebase/Auth (= 10.27.0) + - firebase_auth (5.1.2): + - Firebase/Auth (= 10.28.0) - firebase_core - Flutter - - firebase_core (3.1.1): - - Firebase/CoreOnly (= 10.27.0) + - firebase_core (3.2.0): + - Firebase/CoreOnly (= 10.28.0) - Flutter - - firebase_crashlytics (4.0.2): - - Firebase/Crashlytics (= 10.27.0) + - firebase_crashlytics (4.0.3): + - Firebase/Crashlytics (= 10.28.0) - firebase_core - Flutter - - FirebaseAnalytics (10.27.0): - - FirebaseAnalytics/AdIdSupport (= 10.27.0) + - firebase_remote_config (5.0.3): + - Firebase/RemoteConfig (= 10.28.0) + - firebase_core + - Flutter + - FirebaseABTesting (10.28.0): + - FirebaseCore (~> 10.0) + - FirebaseAnalytics (10.28.0): + - FirebaseAnalytics/AdIdSupport (= 10.28.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -51,24 +58,24 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.27.0): + - FirebaseAnalytics/AdIdSupport (10.28.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.27.0) + - GoogleAppMeasurement (= 10.28.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - FirebaseAppCheckInterop (10.28.0) - - FirebaseAuth (10.27.0): + - FirebaseAuth (10.28.0): - FirebaseAppCheckInterop (~> 10.17) - FirebaseCore (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.8) - GoogleUtilities/Environment (~> 7.8) - GTMSessionFetcher/Core (< 4.0, >= 2.1) - RecaptchaInterop (~> 100.0) - - FirebaseCore (10.27.0): + - FirebaseCore (10.28.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) @@ -76,7 +83,7 @@ PODS: - FirebaseCore (~> 10.0) - FirebaseCoreInternal (10.28.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseCrashlytics (10.27.0): + - FirebaseCrashlytics (10.28.1): - FirebaseCore (~> 10.5) - FirebaseInstallations (~> 10.0) - FirebaseRemoteConfigInterop (~> 10.23) @@ -90,6 +97,14 @@ PODS: - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) + - FirebaseRemoteConfig (10.28.0): + - FirebaseABTesting (~> 10.0) + - FirebaseCore (~> 10.0) + - FirebaseInstallations (~> 10.0) + - FirebaseRemoteConfigInterop (~> 10.23) + - FirebaseSharedSwift (~> 10.0) + - GoogleUtilities/Environment (~> 7.8) + - "GoogleUtilities/NSData+zlib (~> 7.8)" - FirebaseRemoteConfigInterop (10.28.0) - FirebaseSessions (10.28.0): - FirebaseCore (~> 10.5) @@ -100,6 +115,7 @@ PODS: - GoogleUtilities/UserDefaults (~> 7.13) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesSwift (~> 2.1) + - FirebaseSharedSwift (10.28.0) - Flutter (1.0.0) - google_sign_in_ios (0.0.1): - AppAuth (>= 1.7.4) @@ -107,21 +123,21 @@ PODS: - FlutterMacOS - GoogleSignIn (~> 7.1) - GTMSessionFetcher (>= 3.4.0) - - GoogleAppMeasurement (10.27.0): - - GoogleAppMeasurement/AdIdSupport (= 10.27.0) + - GoogleAppMeasurement (10.28.0): + - GoogleAppMeasurement/AdIdSupport (= 10.28.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.27.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.27.0) + - GoogleAppMeasurement/AdIdSupport (10.28.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.28.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.27.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.28.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -202,11 +218,11 @@ DEPENDENCIES: - audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) + - firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`) - Flutter (from `Flutter`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - just_audio (from `.symlinks/plugins/just_audio/ios`) @@ -221,6 +237,7 @@ SPEC REPOS: trunk: - AppAuth - Firebase + - FirebaseABTesting - FirebaseAnalytics - FirebaseAppCheckInterop - FirebaseAuth @@ -229,8 +246,10 @@ SPEC REPOS: - FirebaseCoreInternal - FirebaseCrashlytics - FirebaseInstallations + - FirebaseRemoteConfig - FirebaseRemoteConfigInterop - FirebaseSessions + - FirebaseSharedSwift - GoogleAppMeasurement - GoogleDataTransport - GoogleSignIn @@ -249,8 +268,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/audio_session/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/darwin" - device_info_plus: - :path: ".symlinks/plugins/device_info_plus/ios" firebase_analytics: :path: ".symlinks/plugins/firebase_analytics/ios" firebase_auth: @@ -259,6 +276,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_core/ios" firebase_crashlytics: :path: ".symlinks/plugins/firebase_crashlytics/ios" + firebase_remote_config: + :path: ".symlinks/plugins/firebase_remote_config/ios" Flutter: :path: Flutter google_sign_in_ios: @@ -283,25 +302,28 @@ SPEC CHECKSUMS: audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 088d2483ebd1dc43f51d253d4a1c517d9a2e7207 connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db - device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d - Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 - firebase_analytics: cc6e3ca8f159facf99fb1ceb2d81bda9cc811556 - firebase_auth: cc4ea3f47dda7afc4c64f59540aa570aca60814d - firebase_core: f8d0424c45e0f1e596811085fc12c638d628457c - firebase_crashlytics: 39ca2155bac4fa2eec0aec9f0eb5e938a08bca23 - FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1 + Firebase: 5121c624121af81cbc81df3bda414b3c28c4f3c3 + firebase_analytics: e51d02e2da883fd058f2a4f151f6a80acdfe67aa + firebase_auth: e778ee89483b86fe4200d1f8e9a1c52aa5fb64a8 + firebase_core: a9d0180d5285527884d07a41eb4a9ec9ed12cdb6 + firebase_crashlytics: 12b2b1ecfc50f6c551c68e491ae156b2b7d41273 + firebase_remote_config: 5f92bfc62c3ef2c657bf3d703ffa4be29082280f + FirebaseABTesting: 589bc28c0ab3e5554336895a34aa262e24276665 + FirebaseAnalytics: 1e06fe7d246af7230b08d1d9cdca54a4624dd461 FirebaseAppCheckInterop: 5315f40293191bfec04b2cfab0215760e441540a - FirebaseAuth: 77a012b7e08042bf44d0db835ca2e86e6ca7bbd3 - FirebaseCore: a2b95ae4ce7c83ceecfbbbe3b6f1cddc7415a808 + FirebaseAuth: 3d872fbbfc4223edeb72769e488f325fa8b0a4a9 + FirebaseCore: 857dc1c6dd1255675047404d8466f7dfaac5d779 FirebaseCoreExtension: f63147b723e2a700fe0f34ec6fb7f358d6fe83e0 FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698 - FirebaseCrashlytics: 81ea6ec96519388687f6061beb838a8eec482293 + FirebaseCrashlytics: f51e12b93f8e1134bbed602ed22df33804d55ccf FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e + FirebaseRemoteConfig: f0879a8dccf4e8905716ed849569130efaeab3e2 FirebaseRemoteConfigInterop: 70d200c6956ef3b5c3592a95e824c1210682d785 FirebaseSessions: 20da8500ad66bb12622743e170459bf62a0768e8 + FirebaseSharedSwift: 48de4aec81a6b79bb30404e5e6db43ea74848fed Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 google_sign_in_ios: 07375bfbf2620bc93a602c0e27160d6afc6ead38 - GoogleAppMeasurement: f65fc137531af9ad647f1c0a42f3b6a4d3a98049 + GoogleAppMeasurement: 55a4a3c8000c1280d68bf4c084adbfef20c49db1 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 diff --git a/app/lib/app/presentation/cubit/app_cubit.dart b/app/lib/app/presentation/cubit/app_cubit.dart index 9445587e..855f1d26 100644 --- a/app/lib/app/presentation/cubit/app_cubit.dart +++ b/app/lib/app/presentation/cubit/app_cubit.dart @@ -14,10 +14,7 @@ class AppCubit extends Cubit { required this.setColorUseCase, required this.getAppVersionUseCase, }) : super( - AppState( - getInitialThemeUseCase.call, - getAppVersionUseCase.call, - ), + AppState(getInitialThemeUseCase.call, getAppVersionUseCase.call), ); final GetAppInitialThemeUseCase getInitialThemeUseCase; diff --git a/app/lib/app/presentation/cubit/remote_config_cubit.dart b/app/lib/app/presentation/cubit/remote_config_cubit.dart new file mode 100644 index 00000000..7ab9b4d8 --- /dev/null +++ b/app/lib/app/presentation/cubit/remote_config_cubit.dart @@ -0,0 +1,49 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:meta/meta.dart'; +import 'package:my_quran/core/core.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +part 'remote_config_state.dart'; + +class RemoteConfigCubit extends Cubit { + RemoteConfigCubit({ + required this.packageInfo, + required this.remoteConfig, + }) : super(const RemoteConfigState()); + + final PackageInfo packageInfo; + final MqRemoteConfig remoteConfig; + + Future init() async { + _emitNewState(); + remoteConfig.remoteConfig.onConfigUpdated.listen((event) async { + await remoteConfig.remoteConfig.activate(); + _emitNewState(); + }); + } + + void _emitNewState() { + final newState = state.copyWith( + appVersionStatus: _getAppVersionStatus, + isHatimEnable: _hatimIsEnable, + ); + emit(newState); + } + + bool get _hatimIsEnable => remoteConfig.hatimIsEnable; + + AppVersionStatus get _getAppVersionStatus { + final currentBuildNumber = int.parse(packageInfo.buildNumber); + final requiredBuildNumber = remoteConfig.requiredBuildNumber; + final recommendedBuildNumber = remoteConfig.recommendedBuildNumber; + if (currentBuildNumber < requiredBuildNumber) { + return YesRequiredVersion(requiredBuildNumber); + } else if (currentBuildNumber < recommendedBuildNumber) { + return YesRecommendedVersion(recommendedBuildNumber); + } else { + return const NoNewVersion(); + } + } +} diff --git a/app/lib/app/presentation/cubit/remote_config_state.dart b/app/lib/app/presentation/cubit/remote_config_state.dart new file mode 100644 index 00000000..d1a7c27d --- /dev/null +++ b/app/lib/app/presentation/cubit/remote_config_state.dart @@ -0,0 +1,56 @@ +part of 'remote_config_cubit.dart'; + +class RemoteConfigState extends Equatable { + const RemoteConfigState({ + this.appVersionStatus = const NoNewVersion(), + this.isHatimEnable = true, + this.deviceId, + }); + + final AppVersionStatus appVersionStatus; + final String? deviceId; + final bool isHatimEnable; + + @override + List get props => [ + appVersionStatus, + deviceId, + isHatimEnable, + ]; + + RemoteConfigState copyWith({ + AppVersionStatus? appVersionStatus, + String? deviceId, + bool? isHatimEnable, + }) { + return RemoteConfigState( + appVersionStatus: appVersionStatus ?? this.appVersionStatus, + isHatimEnable: isHatimEnable ?? this.isHatimEnable, + deviceId: deviceId ?? this.deviceId, + ); + } +} + +@immutable +abstract class AppVersionStatus { + const AppVersionStatus(); +} + +@immutable +final class NoNewVersion extends AppVersionStatus { + const NoNewVersion(); +} + +@immutable +final class YesRecommendedVersion extends AppVersionStatus { + const YesRecommendedVersion(this.buildNumber); + + final int buildNumber; +} + +@immutable +final class YesRequiredVersion extends AppVersionStatus { + const YesRequiredVersion(this.buildNumber); + + final int buildNumber; +} diff --git a/app/lib/app/presentation/presenation.dart b/app/lib/app/presentation/presenation.dart index 58eb425b..da80663c 100644 --- a/app/lib/app/presentation/presenation.dart +++ b/app/lib/app/presentation/presenation.dart @@ -1,3 +1,5 @@ export 'cubit/app_cubit.dart'; export 'cubit/auth_cubit.dart'; +export 'cubit/remote_config_cubit.dart'; export 'view/app_view.dart'; +export 'view/app_listener.dart'; diff --git a/app/lib/app/presentation/view/app_listener.dart b/app/lib/app/presentation/view/app_listener.dart new file mode 100644 index 00000000..9696abba --- /dev/null +++ b/app/lib/app/presentation/view/app_listener.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:my_quran/app/app.dart'; +import 'package:my_quran/core/core.dart'; +import 'package:my_quran/l10n/l10.dart'; + +class AppListener extends StatelessWidget { + const AppListener({ + required this.child, + required this.navigationKey, + super.key, + }); + + final Widget child; + final GlobalKey navigationKey; + + @override + Widget build(BuildContext context) { + return BlocListener( + listenWhen: (p, c) => p.appVersionStatus != c.appVersionStatus, + listener: (context, state) { + if (state.appVersionStatus is YesRecommendedVersion) { + _showUpdateDialog(context); + } else if (state.appVersionStatus is YesRequiredVersion) { + _showUpdateDialog(context, forceUpdate: true); + } + }, + child: child, + ); + } + + void _showUpdateDialog(BuildContext context, {bool forceUpdate = false}) { + final ctx = navigationKey.currentState!.overlay!.context; + showAdaptiveDialog( + context: ctx, + barrierDismissible: !forceUpdate, + builder: (ctx) { + return PopScope( + canPop: !forceUpdate, + child: AlertDialog.adaptive( + title: Text( + ctx.l10n.appUpdateAvailable, + style: Theme.of(context).textTheme.titleMedium, + ), + content: Text( + forceUpdate ? ctx.l10n.requiredVersionDescription : ctx.l10n.recommendedVersionDescription, + ), + actions: [ + if (!forceUpdate) + TextButton( + onPressed: () => Navigator.pop(ctx), + child: Text( + ctx.l10n.remindMeLater, + textAlign: TextAlign.center, + ), + ), + TextButton( + onPressed: () => AppLaunch.launchURL(_getPlatformAppStoreLink), + child: Text( + ctx.l10n.updateNow, + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + }, + ); + } + + String get _getPlatformAppStoreLink { + if (Platform.isAndroid) { + return 'https://play.google.com/store/apps/details?id=com.alee.my_quran&hl=en'; + } else { + return 'https://apps.apple.com/kg/app/my-quran/id1671645027'; + } + } +} diff --git a/app/lib/app/presentation/view/app_view.dart b/app/lib/app/presentation/view/app_view.dart index f1d8d123..b0a4a4e7 100644 --- a/app/lib/app/presentation/view/app_view.dart +++ b/app/lib/app/presentation/view/app_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:just_audio/just_audio.dart'; import 'package:loader_overlay/loader_overlay.dart'; @@ -78,26 +79,48 @@ class MyApp extends StatelessWidget { context.read(), )..init(), ), + BlocProvider( + create: (context) => RemoteConfigCubit( + packageInfo: context.read(), + remoteConfig: context.read(), + ), + ), ], child: const QuranApp(), ); } } -class QuranApp extends StatelessWidget { +class QuranApp extends StatefulWidget { const QuranApp({super.key}); + @override + State createState() => _QuranAppState(); +} + +class _QuranAppState extends State { + @override + void initState() { + super.initState(); + SchedulerBinding.instance.addPostFrameCallback((_) { + context.read().init(); + }); + } + @override Widget build(BuildContext context) { return GlobalLoaderOverlay( - child: MaterialApp.router( - title: 'MyQuranKhatm', - debugShowCheckedModeBanner: false, - locale: context.watch().state.currentLocale, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - theme: context.watch().state.theme.themeData, - routerConfig: AppRouter.router, + child: AppListener( + navigationKey: rootNavigatorKey, + child: MaterialApp.router( + title: 'MyQuranKhatm', + debugShowCheckedModeBanner: false, + locale: context.watch().state.currentLocale, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + theme: context.watch().state.theme.themeData, + routerConfig: AppRouter.router, + ), ), ); } diff --git a/app/lib/config/router/app_router.dart b/app/lib/config/router/app_router.dart index bbaee742..c14ac9d5 100644 --- a/app/lib/config/router/app_router.dart +++ b/app/lib/config/router/app_router.dart @@ -7,7 +7,7 @@ import 'package:my_quran/app/app.dart'; import 'package:my_quran/config/config.dart'; import 'package:my_quran/modules/modules.dart'; -final _rootNavigatorKey = GlobalKey(); +final rootNavigatorKey = GlobalKey(); final _sectionNavigatorKey1 = GlobalKey(debugLabel: 'home'); final _sectionNavigatorKey2 = GlobalKey(debugLabel: 'quran'); final _sectionNavigatorKey3 = GlobalKey(debugLabel: 'quran-audio'); @@ -38,7 +38,7 @@ final class AppRouter { static final router = GoRouter( initialLocation: '/home', - navigatorKey: _rootNavigatorKey, + navigatorKey: rootNavigatorKey, debugLogDiagnostics: kDebugMode, routes: [ GoRoute( @@ -132,7 +132,7 @@ final class AppRouter { GoRoute( path: '$hatimRead/:isHatim/:pages', name: hatimRead, - parentNavigatorKey: _rootNavigatorKey, + parentNavigatorKey: rootNavigatorKey, builder: (context, state) { final args = ParseParams.parseRead(state.pathParameters); return ReadView(args.$1, isHatim: args.$2); @@ -148,7 +148,7 @@ final class AppRouter { GoRoute( path: '$read/:isHatim/:pages', name: read, - parentNavigatorKey: _rootNavigatorKey, + parentNavigatorKey: rootNavigatorKey, builder: (context, state) { final args = ParseParams.parseRead(state.pathParameters); return ReadView(args.$1, isHatim: args.$2); diff --git a/app/lib/core/core.dart b/app/lib/core/core.dart index e21feb9b..5b98d50d 100644 --- a/app/lib/core/core.dart +++ b/app/lib/core/core.dart @@ -10,3 +10,4 @@ export 'exceptions/convert_exception.dart'; export 'launch/app_launch.dart'; export 'enums/fetch_status.dart'; export 'analytics/mq_analytic.dart'; +export 'remote_config/mq_remote_config.dart'; diff --git a/app/lib/core/remote_config/mq_remote_config.dart b/app/lib/core/remote_config/mq_remote_config.dart new file mode 100644 index 00000000..21377116 --- /dev/null +++ b/app/lib/core/remote_config/mq_remote_config.dart @@ -0,0 +1,64 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/foundation.dart'; + +@immutable +class MqRemoteConfig { + const MqRemoteConfig({ + required this.remoteConfig, + required this.buildNumber, + }); + + final FirebaseRemoteConfig remoteConfig; + final String buildNumber; + + Future initialise() async { + await remoteConfig.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(minutes: 1), + minimumFetchInterval: const Duration(hours: 1), + ), + ); + + await remoteConfig.setDefaults(_defaultParams(currentBuildNumber)); + await remoteConfig.fetchAndActivate(); + } + + int get currentBuildNumber => int.tryParse(buildNumber) ?? 100; + int get requiredBuildNumber => version.$1; + int get recommendedBuildNumber => version.$2; + + (int, int) get version { + final data = jsonDecode(remoteConfig.getString(_appVersion)) as Map; + final platformName = Platform.isIOS ? 'ios' : 'android'; + final versionData = data[platformName] as Map; + return (versionData['requiredBuildNumber'], versionData['recommendedBuildNumber']); + } + + static const _hatimIsEnable = 'hatimIsEnable'; + static const _appVersion = 'appVersion'; + + static Map _defaultAppVersionValue(int currentBuildNumber) { + return { + 'android': { + 'requiredBuildNumber': currentBuildNumber, + 'recommendedBuildNumber': currentBuildNumber, + }, + 'ios': { + 'requiredBuildNumber': currentBuildNumber, + 'recommendedBuildNumber': currentBuildNumber, + }, + }; + } + + static Map _defaultParams(int currentBuildNumber) { + return { + _hatimIsEnable: true, + _appVersion: jsonEncode(_defaultAppVersionValue(currentBuildNumber)), + }; + } + + bool get hatimIsEnable => remoteConfig.getBool(_hatimIsEnable); +} diff --git a/app/lib/l10n/arb/app_ar.arb b/app/lib/l10n/arb/app_ar.arb index 5e975fe2..2ce8ffe0 100644 --- a/app/lib/l10n/arb/app_ar.arb +++ b/app/lib/l10n/arb/app_ar.arb @@ -81,5 +81,11 @@ "signOutContext": "هل أنت متأكد أنك تريد تسجيل الخروج؟", "yes": "نعم", "no": "لا", - "appleSignInNotAvailable": "تسجيل الدخول عبر Apple غير متاح على أجهزة Android." + "appleSignInNotAvailable": "تسجيل الدخول عبر Apple غير متاح على أجهزة Android.", + "appUpdateAvailable": "تحديث التطبيق متاح", + "requiredVersionDescription": "نسخة جديدة من التطبيق متاحة. يجب عليك تحديثه للمتابعة.", + "recommendedVersionDescription": "نسخة جديدة من التطبيق متاحة.", + "remindMeLater": "ذكرني لاحقاً", + "updateNow": "تحديث الآن", + "hatimNotAvailable": "خاتم غير متاح" } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_en.arb b/app/lib/l10n/arb/app_en.arb index d8293876..d81ead11 100644 --- a/app/lib/l10n/arb/app_en.arb +++ b/app/lib/l10n/arb/app_en.arb @@ -81,5 +81,11 @@ "signOutContext": "Are you sure you want to logout?", "yes": "Yes", "no": "No", - "appleSignInNotAvailable": "Apple Sign-In is not available on Android devices." + "appleSignInNotAvailable": "Apple Sign-In is not available on Android devices.", + "appUpdateAvailable":"App update available", + "requiredVersionDescription": "A new version of the app is available. You need to update it to continue.", + "recommendedVersionDescription": "A new version of the app is available.", + "remindMeLater": "Remind me later", + "updateNow": "Update now", + "hatimNotAvailable": "Hatim is not available." } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_id.arb b/app/lib/l10n/arb/app_id.arb index 65b9381c..6871a509 100644 --- a/app/lib/l10n/arb/app_id.arb +++ b/app/lib/l10n/arb/app_id.arb @@ -81,5 +81,11 @@ "signOutContext": "Apakah Anda yakin ingin keluar?", "yes": "Ya", "no": "Tidak", - "appleSignInNotAvailable": "Masuk dengan Apple tidak tersedia di perangkat Android." + "appleSignInNotAvailable": "Masuk dengan Apple tidak tersedia di perangkat Android.", + "appUpdateAvailable": "Pembaruan aplikasi tersedia", + "requiredVersionDescription": "Versi baru dari aplikasi tersedia. Anda perlu memperbarui untuk melanjutkan.", + "recommendedVersionDescription": "Versi baru dari aplikasi tersedia.", + "remindMeLater": "Ingatkan saya nanti", + "updateNow": "Perbarui sekarang", + "hatimNotAvailable": "Hatim tidak tersedia." } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_kk.arb b/app/lib/l10n/arb/app_kk.arb index ad2c7e06..63c30cd4 100644 --- a/app/lib/l10n/arb/app_kk.arb +++ b/app/lib/l10n/arb/app_kk.arb @@ -81,5 +81,11 @@ "signOutContext": "Шығыңыз келетініне сенімдісіз бе?", "yes": "Иә", "no": "Жоқ", - "appleSignInNotAvailable": "Apple жүйесіне кіру Android құрылғыларында қол жетімді емес." + "appleSignInNotAvailable": "Apple жүйесіне кіру Android құрылғыларында қол жетімді емес.", + "appUpdateAvailable": "Қолданба жаңарту қолданысқа қойылатын", + "requiredVersionDescription": "Қолданбаның жаңа нұсқасы қолайлы. Жалғастыру үшін оны жаңартуыңыз керек.", + "recommendedVersionDescription": "Қолданбаның жаңа нұсқасы қолайлы.", + "remindMeLater": "Кейін еске салу", + "updateNow": "Қазір жаңарту", + "hatimNotAvailable": "Хатым жетімді емес." } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_ky.arb b/app/lib/l10n/arb/app_ky.arb index 4bb862a8..47cd19e7 100644 --- a/app/lib/l10n/arb/app_ky.arb +++ b/app/lib/l10n/arb/app_ky.arb @@ -81,5 +81,11 @@ "signOutContext": "Чын эле чыгып кеткиңиз келеби?", "yes": "Ооба", "no": "Жок", - "appleSignInNotAvailable": "Apple аркылуу кирүү Android системаларында жеткиликтүү эмес." -} \ No newline at end of file + "appleSignInNotAvailable": "Apple аркылуу кирүү Android системаларында жеткиликтүү эмес.", + "appUpdateAvailable": "Колдонмону жаңыртуу жеткиликтүү", + "requiredVersionDescription": "Колдонмонун жаңы версиясы жеткиликтүү. Улантуу үчүн аны жаңыртышыңыз керек.", + "recommendedVersionDescription": "Колдонмонун жаңы версиясы жеткиликтүү", + "remindMeLater": "Кийинчерээк эстетип коюу", + "updateNow": "Азыр жаңыртуу", + "hatimNotAvailable": "Хатымга катышуу азырынча жеткиликтүү эмес." + } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_ru.arb b/app/lib/l10n/arb/app_ru.arb index 20a57914..46afede8 100644 --- a/app/lib/l10n/arb/app_ru.arb +++ b/app/lib/l10n/arb/app_ru.arb @@ -81,5 +81,11 @@ "signOutContext": "Вы уверены, что хотите выйти?", "yes": "Да", "no": "Нет", - "appleSignInNotAvailable": "Вход с Apple недоступен на устройствах Android." + "appleSignInNotAvailable": "Вход с Apple недоступен на устройствах Android.", + "appUpdateAvailable": "Доступно обновление приложения", + "requiredVersionDescription": "Доступна новая версия приложения. Вам необходимо обновить его, чтобы продолжить использование.", + "recommendedVersionDescription": "Доступна новая версия приложения.", + "remindMeLater": "Напомнить позже", + "updateNow": "Обновить сейчас", + "hatimNotAvailable": "Хатим недоступен." } \ No newline at end of file diff --git a/app/lib/l10n/arb/app_tr.arb b/app/lib/l10n/arb/app_tr.arb index f6157833..17d2ce90 100644 --- a/app/lib/l10n/arb/app_tr.arb +++ b/app/lib/l10n/arb/app_tr.arb @@ -81,5 +81,11 @@ "signOutContext": "Çıkış yapmak istediğinizden emin misiniz?", "yes": "Evet", "no": "Hayır", - "appleSignInNotAvailable": "Apple ile giriş Android cihazlarda mevcut değil." + "appleSignInNotAvailable": "Apple ile giriş Android cihazlarda mevcut değil.", + "appUpdateAvailable": "Uygulama güncellemesi mevcut", + "requiredVersionDescription": "Uygulamanın yeni bir sürümü mevcut. Devam etmek için güncellemelisiniz.", + "recommendedVersionDescription": "Uygulamanın yeni bir sürümü mevcut.", + "remindMeLater": "Daha sonra hatırlat", + "updateNow": "Şimdi güncelle", + "hatimNotAvailable": "Hatim mevcut değil." } \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index 25f41ac0..405b3419 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -48,8 +49,15 @@ Future main({AppConfig? appConfig}) async { Bloc.observer = const AppBlocObserver(onLog: log); final storage = await PreferencesStorage.getInstance(); + final packageInfo = await PackageInfo.fromPlatform(); + final remoteConfig = MqRemoteConfig( + remoteConfig: FirebaseRemoteConfig.instance, + buildNumber: packageInfo.buildNumber, + ); + + await remoteConfig.initialise(); appConfig ??= AppConfig(storage: storage); appConfig.init(); @@ -63,6 +71,7 @@ Future main({AppConfig? appConfig}) async { RepositoryProvider( create: (context) => NetworkClient(Connectivity()), ), + RepositoryProvider(create: (context) => remoteConfig), RepositoryProvider( create: (context) => RemoteClient(Client(), context.read()), ), diff --git a/app/lib/modules/home/presentation/view/home_view.dart b/app/lib/modules/home/presentation/view/home_view.dart index c461bf69..fb6df8ee 100644 --- a/app/lib/modules/home/presentation/view/home_view.dart +++ b/app/lib/modules/home/presentation/view/home_view.dart @@ -1,12 +1,8 @@ -import 'dart:io' show Platform; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:mq_ci_keys/mq_ci_keys.dart'; import 'package:my_quran/config/config.dart'; -import 'package:upgrader/upgrader.dart'; import 'package:my_quran/core/core.dart'; import 'package:my_quran/app/app.dart'; @@ -29,89 +25,78 @@ class _HomeViewState extends State { final authCubit = context.read(); final user = authCubit.state.user; final validName = user?.username.replaceAll(RegExp(r'\W+'), '_'); - if (homeCubit.state.status != FetchStatus.success && authCubit.state.user != null) { - MqCrashlytics.setUserIdentifier( - validName ?? user!.accessToken, - ); - MqAnalytic.setUserProperty(validName ?? user!.accessToken); - homeCubit.getData(user!.accessToken); + if (homeCubit.state.status != FetchStatus.success && user != null) { + MqCrashlytics.setUserIdentifier(validName ?? user.accessToken); + MqAnalytic.setUserProperty(validName ?? user.accessToken); + homeCubit.getData(user.accessToken); } super.initState(); } - @override - Widget build(BuildContext context) { - return Scaffold( - body: RepositoryProvider.of(context).isIntegrationTest - ? const HomeBody() - : UpgradeAlert( - dialogStyle: kIsWeb || Platform.isAndroid ? UpgradeDialogStyle.material : UpgradeDialogStyle.cupertino, - shouldPopScope: () => true, - barrierDismissible: true, - child: const HomeBody(), - ), - ); - } -} - -class HomeBody extends StatelessWidget { - const HomeBody({super.key}); - @override Widget build(BuildContext context) { final l10n = context.l10n; final homeCubit = context.watch(); final colorScheme = Theme.of(context).colorScheme; - return RefreshIndicator( - onRefresh: () async { - MqAnalytic.track(AnalyticKey.refreshHomePage); - if (context.read().state.user != null) { - await context.read().getData(context.read().state.user!.accessToken); - } - }, - child: SafeArea( - child: ListView( - key: const Key(MqKeys.homeListView), - children: [ - const SizedBox(height: 10), - Assets.icons.alQuran.svg( - key: const Key(MqKeys.alQuran), - height: 100, - colorFilter: ColorFilter.mode(colorScheme.primary, BlendMode.srcIn), - ), - HomeCard( - key: const Key(MqKeys.allHatimCount), - titleText: l10n.homeAllHatim, - descriptioText: l10n.homeAllHatimDesc, - valueText: '${homeCubit.state.homeModel?.allDoneHatims ?? 0}', - ), - HomeCard( - key: const Key(MqKeys.allReadedPageCount), - titleText: l10n.allDonePages, - descriptioText: l10n.allDonePagesDesc, - valueText: '${homeCubit.state.homeModel?.allDonePages ?? 0}', - verticalSpace: 0, - ), - HomeCard( - titleText: l10n.homeUserReadAllPage, - descriptioText: l10n.homeUserReadAllPageDesc, - valueText: '${homeCubit.state.homeModel?.donePages ?? 0}', - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: CustomButton( - key: const Key(MqKeys.participantToHatim), - text: l10n.homeGoHatim, - onPressed: () { - MqAnalytic.track(AnalyticKey.goHatim); - context.goNamed(AppRouter.hatim); - }, + return Scaffold( + body: RefreshIndicator( + onRefresh: () async { + MqAnalytic.track(AnalyticKey.refreshHomePage); + final user = context.read().state.user; + if (user != null) { + await context.read().getData(user.accessToken); + } + }, + child: SafeArea( + child: ListView( + key: const Key(MqKeys.homeListView), + children: [ + const SizedBox(height: 10), + Assets.icons.alQuran.svg( + key: const Key(MqKeys.alQuran), + height: 100, + colorFilter: ColorFilter.mode(colorScheme.primary, BlendMode.srcIn), + ), + HomeCard( + key: const Key(MqKeys.allHatimCount), + titleText: l10n.homeAllHatim, + descriptioText: l10n.homeAllHatimDesc, + valueText: '${homeCubit.state.homeModel?.allDoneHatims ?? 0}', + ), + HomeCard( + key: const Key(MqKeys.allReadedPageCount), + titleText: l10n.allDonePages, + descriptioText: l10n.allDonePagesDesc, + valueText: '${homeCubit.state.homeModel?.allDonePages ?? 0}', + verticalSpace: 0, ), - ), - const SizedBox(height: 20), - ], + HomeCard( + titleText: l10n.homeUserReadAllPage, + descriptioText: l10n.homeUserReadAllPageDesc, + valueText: '${homeCubit.state.homeModel?.donePages ?? 0}', + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: BlocBuilder( + builder: (context, state) { + return CustomButton( + key: const Key(MqKeys.participantToHatim), + text: l10n.homeGoHatim, + onPressed: state.isHatimEnable ? _onJoinToHatim : null, + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ), ), ), ); } + + void _onJoinToHatim() { + MqAnalytic.track(AnalyticKey.goHatim); + context.goNamed(AppRouter.hatim); + } } diff --git a/app/pubspec.lock b/app/pubspec.lock index 0ff1a870..ffe8d9c8 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: a315d1c444402c3fa468de626d33a1c666041c87e9e195e8fb355b7084aefcc1 + sha256: b46f62516902afb04befa4b30eb6a12ac1f58ca8cb25fb9d632407259555dd3d url: "https://pub.dev" source: hosted - version: "1.3.38" + version: "1.3.39" analyzer: dependency: transitive description: @@ -249,14 +249,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - csslib: - dependency: transitive - description: - name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" - url: "https://pub.dev" - source: hosted - version: "1.0.0" cupertino_icons: dependency: "direct main" description: @@ -289,22 +281,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" - device_info_plus: - dependency: transitive - description: - name: device_info_plus - sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 - url: "https://pub.dev" - source: hosted - version: "10.1.0" - device_info_plus_platform_interface: - dependency: transitive - description: - name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 - url: "https://pub.dev" - source: hosted - version: "7.0.0" equatable: dependency: "direct main" description: @@ -341,58 +317,58 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "726596f4ac3352978238274c33234435e61bdb811484ea3d6a2b857bf47a2715" + sha256: "2017da2cb0745fa912e13aadfc94e49691796cf6b7ed37edbca2a2388713da11" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "11.2.0" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: db445c727aa38038f91a3c6f6873d045a6740c79d03c0b6c61959e0c6ecfd771 + sha256: "4ea00fe31ff74c92c613e082193b55bd21f0653b536dd77cc1ba4cf60771d214" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "49a8a5ca0bf7fd7541e4b0915b8eb99816edc7a610d5078b89ae013a813fd567" + sha256: e5ce84f0c4e6fb8f8ca673d009445938a61845d8fa6b42c11913d5bbdbedf300 url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.5.9" firebase_auth: dependency: "direct main" description: name: firebase_auth - sha256: "087fdcb54b0af6f4c5c756e1db4f90e9b65871b9b3a75fabaa0e0ee578301669" + sha256: a41b56878fa6aef3ea52962329b47eee333672d4b0ecc406e071b9fc729f242c url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "5.1.2" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: "8fac689f71ac3489a785579e99a4bad24a93ad3d78c313fb786ee517012d25f1" + sha256: d1c68097588f3b75ef79a22102ff96c311735c254353bccf6824d19f1a7e86b9 url: "https://pub.dev" source: hosted - version: "7.4.1" + version: "7.4.2" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "486b2527fcfcab01278378d8d4791f4f7bee8a9f15bf35e801ba08fbdd84c234" + sha256: e66ec0ae5697ee39ccd4865d6887cb0df220dd4ea0b21404910c68ca4c1a731a url: "https://pub.dev" source: hosted - version: "5.12.3" + version: "5.12.4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "1e06b0538ab3108a61d895ee16951670b491c4a94fce8f2d30e5de7a5eca4b28" + sha256: "5159984ce9b70727473eb388394650677c02c925aaa6c9439905e1f30966a4d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.2.0" firebase_core_platform_interface: dependency: transitive description: @@ -405,26 +381,50 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "6643fe3dbd021e6ccfb751f7882b39df355708afbdeb4130fc50f9305a9d1a3d" + sha256: "23509cb3cddfb3c910c143279ac3f07f06d3120f7d835e4a5d4b42558e978712" url: "https://pub.dev" source: hosted - version: "2.17.2" + version: "2.17.3" firebase_crashlytics: dependency: "direct main" description: name: firebase_crashlytics - sha256: "54c06fa45585ed77e978b049f8e488db7677313d5dc808c54d24384a6e5bf0c8" + sha256: da32da3b441d1bee73ca990085a3ae174b9fb3585229f02a278a2ea42454d784 url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "8ec63ebefe9233d3cdc744f75d5b88cf16b6241d8680e6284c2d272bcb0a10af" + sha256: b7567106ed57bbadaa0610774cc17a10b82ed04a1aba99790f303385ac4ba78f + url: "https://pub.dev" + source: hosted + version: "3.6.39" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: aa150fcbaa1fe5afcb912ccf6a059f1a8ef8566dceccaa45ff72c8498ca2103e + url: "https://pub.dev" + source: hosted + version: "5.0.3" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: "0c4f4b473074ab37b069360629998dbc7175c334afc249051d8ad590521741a8" url: "https://pub.dev" source: hosted - version: "3.6.38" + version: "1.4.39" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "45fcb61f5bd46eada6dc11d7167512e3441db50cfadd05394272fc1fdce4a999" + url: "https://pub.dev" + source: hosted + version: "1.6.11" fixnum: dependency: transitive description: @@ -607,14 +607,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - html: - dependency: transitive - description: - name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" - url: "https://pub.dev" - source: hosted - version: "0.15.4" http: dependency: "direct main" description: @@ -845,14 +837,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - os_detect: - dependency: transitive - description: - name: os_detect - sha256: faf3bcf39515e64da8ff76b2f2805b20a6ff47ae515393e535f8579ff91d6b7f - url: "https://pub.dev" - source: hosted - version: "2.0.1" package_config: dependency: transitive description: @@ -1306,14 +1290,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - upgrader: - dependency: "direct main" - description: - name: upgrader - sha256: d45483694620883107c2f5ca1dff7cdd4237b16810337a9c9c234203eb79eb5f - url: "https://pub.dev" - source: hosted - version: "10.3.0" url_launcher: dependency: "direct main" description: @@ -1418,14 +1394,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - version: - dependency: transitive - description: - name: version - sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" - url: "https://pub.dev" - source: hosted - version: "3.0.2" very_good_analysis: dependency: "direct dev" description: @@ -1490,14 +1458,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.5.1" - win32_registry: - dependency: transitive - description: - name: win32_registry - sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" - url: "https://pub.dev" - source: hosted - version: "1.1.3" xdg_directories: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index a2dfec36..4517a988 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -15,10 +15,11 @@ dependencies: flutter_localizations: sdk: flutter cupertino_icons: ^1.0.8 - firebase_core: ^3.1.1 - firebase_auth: ^5.1.1 - firebase_crashlytics: ^4.0.2 - firebase_analytics: ^11.1.0 + firebase_core: ^3.2.0 + firebase_auth: ^5.1.2 + firebase_crashlytics: ^4.0.3 + firebase_analytics: ^11.2.0 + firebase_remote_config: ^5.0.3 google_sign_in: ^6.2.1 web_socket_channel: ^2.4.5 # 3.0.0 sign_in_with_apple: ^6.1.1 @@ -34,7 +35,6 @@ dependencies: url_launcher: ^6.3.0 json_annotation: ^4.9.0 font_awesome_flutter: ^10.7.0 - upgrader: ^10.3.0 just_audio: ^0.9.38 just_audio_background: ^0.0.1-beta.12 meta: ^1.12.0 # 1.15.0 diff --git a/app/test/helpers/pump_app.dart b/app/test/helpers/pump_app.dart index 1bdda3b0..9f55d757 100644 --- a/app/test/helpers/pump_app.dart +++ b/app/test/helpers/pump_app.dart @@ -3,7 +3,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:my_quran/app/app.dart'; import 'package:my_quran/config/app_config.dart'; +import 'package:my_quran/core/core.dart'; import 'package:my_quran/modules/modules.dart'; +import 'package:package_info_plus/package_info_plus.dart'; extension PumpApp on WidgetTester { Future pumpApp( @@ -19,10 +21,15 @@ extension PumpApp on WidgetTester { PatchGenderUseCase patchGenderUseCase, PatchLocaleCodeUseCase patchLocaleCodeUseCase, LogoutUseCase logoutUseCase, + MqRemoteConfig remoteConfig, + PackageInfo packageInfo, ) { return pumpWidget( - RepositoryProvider( - create: (context) => const AppConfig(isIntegrationTest: true), + MultiRepositoryProvider( + providers: [ + RepositoryProvider(create: (context) => const AppConfig(isIntegrationTest: true)), + RepositoryProvider(create: (context) => remoteConfig), + ], child: MultiBlocProvider( providers: [ BlocProvider( @@ -47,6 +54,12 @@ extension PumpApp on WidgetTester { BlocProvider( create: (context) => HomeCubit(GetHomeDataUseCase(homeRepo)), ), + BlocProvider( + create: (context) => RemoteConfigCubit( + packageInfo: packageInfo, + remoteConfig: remoteConfig, + ), + ), ], child: const QuranApp(), ), diff --git a/app/test/mocks/app_mocks.dart b/app/test/mocks/app_mocks.dart new file mode 100644 index 00000000..449dc93d --- /dev/null +++ b/app/test/mocks/app_mocks.dart @@ -0,0 +1,61 @@ +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +import 'package:mocktail/mocktail.dart'; +import 'package:mq_storage/mq_storage.dart'; +import 'package:my_quran/core/core.dart'; +import 'package:my_quran/modules/modules.dart'; + +final class MockPreferencesStorage extends Mock implements PreferencesStorage {} + +final class MockRemoteClient extends Mock implements RemoteClient {} + +final class MockSccialAuth extends Mock implements SoccialAuth {} + +final class MockPackageInfo extends Mock implements PackageInfo { + @override + String get version => '1.3.0'; + + @override + String get buildNumber => '10'; +} + +final class MockHomeRepositoryImpl implements HomeRepository { + @override + Future getData(String token) async { + return const HomeEntity(allDoneHatims: 8, allDonePages: 5325, donePages: 634); + } +} + +final class MockFirebaseRemoteConfig extends Mock implements FirebaseRemoteConfig { + @override + Stream get onConfigUpdated { + return const Stream.empty(); + } +} + +class MockMqRemoteConfig implements MqRemoteConfig { + @override + String get buildNumber => '1.3.0'; + + @override + int get currentBuildNumber => 10; + + @override + Future initialise() async {} + + @override + bool get hatimIsEnable => false; + + @override + int get recommendedBuildNumber => 10; + + @override + int get requiredBuildNumber => 10; + + @override + (int, int) get version => (10, 10); + + @override + FirebaseRemoteConfig get remoteConfig => MockFirebaseRemoteConfig(); +} diff --git a/app/test/widget_test.dart b/app/test/widget_test.dart index 38ca7330..7b154882 100644 --- a/app/test/widget_test.dart +++ b/app/test/widget_test.dart @@ -1,32 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:mq_storage/mq_storage.dart'; import 'package:my_quran/app/app.dart'; import 'package:my_quran/constants/contants.dart'; -import 'package:my_quran/core/core.dart'; -import 'package:my_quran/modules/modules.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'helpers/helpers.dart'; - -final class MockPreferencesStorage extends Mock implements PreferencesStorage {} - -final class MockRemoteClient extends Mock implements RemoteClient {} - -final class MockSccialAuth extends Mock implements SoccialAuth {} - -final class MockPackageInfo extends Mock implements PackageInfo { - @override - String get version => '1.3.0'; -} - -final class MockHomeRepositoryImpl implements HomeRepository { - @override - Future getData(String token) async { - return const HomeEntity(allDoneHatims: 8, allDonePages: 5325, donePages: 634); - } -} +import 'mocks/app_mocks.dart'; // flutter test @@ -35,6 +14,7 @@ void main() { final storage = MockPreferencesStorage(); final packageInfo = MockPackageInfo(); final remoteClient = MockRemoteClient(); + final remoteConfig = MockMqRemoteConfig(); final homeRepo = MockHomeRepositoryImpl(); final appLocalDataSource = AppLocalDataSource(packageInfo: packageInfo); @@ -82,6 +62,8 @@ void main() { pathGenderUseCase, patchLocaleCodeUseCase, logoutUseCase, + remoteConfig, + packageInfo, ); await tester.pumpAndSettle(); expect(find.byType(MaterialApp), findsOneWidget);