Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate remote config for updates #224

Merged
merged 13 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ PODS:
- Firebase/Crashlytics (10.27.0):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 10.27.0)
- Firebase/RemoteConfig (10.27.0):
- Firebase/CoreOnly
- FirebaseRemoteConfig (~> 10.27.0)
- firebase_analytics (11.1.0):
- Firebase/Analytics (= 10.27.0)
- firebase_core
Expand All @@ -42,6 +45,12 @@ PODS:
- Firebase/Crashlytics (= 10.27.0)
- firebase_core
- Flutter
- firebase_remote_config (5.0.2):
- Firebase/RemoteConfig (= 10.27.0)
- firebase_core
- Flutter
- FirebaseABTesting (10.28.0):
- FirebaseCore (~> 10.0)
- FirebaseAnalytics (10.27.0):
- FirebaseAnalytics/AdIdSupport (= 10.27.0)
- FirebaseCore (~> 10.0)
Expand Down Expand Up @@ -90,6 +99,14 @@ PODS:
- GoogleUtilities/Environment (~> 7.8)
- GoogleUtilities/UserDefaults (~> 7.8)
- PromisesObjC (~> 2.1)
- FirebaseRemoteConfig (10.27.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)
Expand All @@ -100,6 +117,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)
Expand Down Expand Up @@ -166,10 +184,10 @@ PODS:
- GTMAppAuth (4.1.1):
- AppAuth/Core (~> 1.7)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher (3.4.1):
- GTMSessionFetcher/Full (= 3.4.1)
- GTMSessionFetcher/Core (3.4.1)
- GTMSessionFetcher/Full (3.4.1):
- GTMSessionFetcher (3.5.0):
- GTMSessionFetcher/Full (= 3.5.0)
- GTMSessionFetcher/Core (3.5.0)
- GTMSessionFetcher/Full (3.5.0):
- GTMSessionFetcher/Core
- just_audio (0.0.1):
- Flutter
Expand Down Expand Up @@ -207,6 +225,7 @@ DEPENDENCIES:
- 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`)
Expand All @@ -221,6 +240,7 @@ SPEC REPOS:
trunk:
- AppAuth
- Firebase
- FirebaseABTesting
- FirebaseAnalytics
- FirebaseAppCheckInterop
- FirebaseAuth
Expand All @@ -229,8 +249,10 @@ SPEC REPOS:
- FirebaseCoreInternal
- FirebaseCrashlytics
- FirebaseInstallations
- FirebaseRemoteConfig
- FirebaseRemoteConfigInterop
- FirebaseSessions
- FirebaseSharedSwift
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleSignIn
Expand Down Expand Up @@ -259,6 +281,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:
Expand Down Expand Up @@ -289,6 +313,8 @@ SPEC CHECKSUMS:
firebase_auth: cc4ea3f47dda7afc4c64f59540aa570aca60814d
firebase_core: f8d0424c45e0f1e596811085fc12c638d628457c
firebase_crashlytics: 39ca2155bac4fa2eec0aec9f0eb5e938a08bca23
firebase_remote_config: 962876d64b52d7710d756ea85e27c768002dc628
FirebaseABTesting: 589bc28c0ab3e5554336895a34aa262e24276665
FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1
FirebaseAppCheckInterop: 5315f40293191bfec04b2cfab0215760e441540a
FirebaseAuth: 77a012b7e08042bf44d0db835ca2e86e6ca7bbd3
Expand All @@ -297,16 +323,18 @@ SPEC CHECKSUMS:
FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698
FirebaseCrashlytics: 81ea6ec96519388687f6061beb838a8eec482293
FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e
FirebaseRemoteConfig: 37a2ba3c8c454be8553a41ba1a2f4a4f0b845670
FirebaseRemoteConfigInterop: 70d200c6956ef3b5c3592a95e824c1210682d785
FirebaseSessions: 20da8500ad66bb12622743e170459bf62a0768e8
FirebaseSharedSwift: 48de4aec81a6b79bb30404e5e6db43ea74848fed
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
google_sign_in_ios: 07375bfbf2620bc93a602c0e27160d6afc6ead38
GoogleAppMeasurement: f65fc137531af9ad647f1c0a42f3b6a4d3a98049
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 8000756fc1c19d2e5697b90311f7832d2e33f6cd
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
nanopb: 438bc412db1928dac798aa6fd75726007be04262
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
Expand Down
5 changes: 1 addition & 4 deletions app/lib/app/presentation/cubit/app_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ class AppCubit extends Cubit<AppState> {
required this.setColorUseCase,
required this.getAppVersionUseCase,
}) : super(
AppState(
getInitialThemeUseCase.call,
getAppVersionUseCase.call,
),
AppState(getInitialThemeUseCase.call, getAppVersionUseCase.call),
);

final GetAppInitialThemeUseCase getInitialThemeUseCase;
Expand Down
23 changes: 23 additions & 0 deletions app/lib/app/presentation/cubit/remote_config_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

part 'remote_config_state.dart';

class RemoteConfigCubit extends Cubit<RemoteConfigState> {
RemoteConfigCubit() : super(const RemoteConfigState(appVersionStatus: NoNewVersion()));

Future<void> setAppVersionStatus({
required int requiredBuildNumber,
required int recommendedBuildNumber,
required int currentBuildNumber,
}) async {
if (currentBuildNumber < requiredBuildNumber) {
emit(state.copyWith(appVersionStatus: YesRequiredVersion(requiredBuildNumber)));
} else if (currentBuildNumber < recommendedBuildNumber) {
emit(state.copyWith(appVersionStatus: YesRecommendedVersion(recommendedBuildNumber)));
} else {
emit(state.copyWith(appVersionStatus: const NoNewVersion()));
}
}
}
49 changes: 49 additions & 0 deletions app/lib/app/presentation/cubit/remote_config_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
part of 'remote_config_cubit.dart';

class RemoteConfigState extends Equatable {
const RemoteConfigState({
required this.appVersionStatus,
aidaiym marked this conversation as resolved.
Show resolved Hide resolved
this.deviceId,
});

final AppVersionStatus appVersionStatus;
final String? deviceId;

@override
List<Object?> get props => [appVersionStatus, deviceId];

RemoteConfigState copyWith({
AppVersionStatus? appVersionStatus,
String? deviceId,
bool? disableHatim,
}) {
return RemoteConfigState(
appVersionStatus: appVersionStatus ?? this.appVersionStatus,
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;
}
2 changes: 2 additions & 0 deletions app/lib/app/presentation/presenation.dart
Original file line number Diff line number Diff line change
@@ -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';
80 changes: 80 additions & 0 deletions app/lib/app/presentation/view/app_listener.dart
Original file line number Diff line number Diff line change
@@ -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<NavigatorState> navigationKey;

@override
Widget build(BuildContext context) {
return BlocListener<RemoteConfigCubit, RemoteConfigState>(
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<void>(
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';
}
}
}
53 changes: 44 additions & 9 deletions app/lib/app/presentation/view/app_view.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -78,27 +79,61 @@ class MyApp extends StatelessWidget {
context.read<NetworkClient>(),
)..init(),
),
BlocProvider(
create: (context) => RemoteConfigCubit(),
),
],
child: const QuranApp(),
);
}
}

class QuranApp extends StatelessWidget {
class QuranApp extends StatefulWidget {
const QuranApp({super.key});

@override
State<QuranApp> createState() => _QuranAppState();
}

class _QuranAppState extends State<QuranApp> {
@override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
_listenRemoteConfig();
});
}

@override
Widget build(BuildContext context) {
return GlobalLoaderOverlay(
child: MaterialApp.router(
title: 'MyQuranKhatm',
debugShowCheckedModeBanner: false,
locale: context.watch<AuthCubit>().state.currentLocale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: context.watch<AppCubit>().state.theme.themeData,
routerConfig: AppRouter.router,
child: AppListener(
navigationKey: rootNavigatorKey,
child: MaterialApp.router(
title: 'MyQuranKhatm',
debugShowCheckedModeBanner: false,
locale: context.watch<AuthCubit>().state.currentLocale,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: context.watch<AppCubit>().state.theme.themeData,
routerConfig: AppRouter.router,
),
),
);
}

void _listenRemoteConfig() {
final remoteConfig = context.read<MqRemoteConfig>();

context.read<RemoteConfigCubit>().setAppVersionStatus(
requiredBuildNumber: remoteConfig.requiredBuildNumber,
recommendedBuildNumber: remoteConfig.recommendedBuildNumber,
currentBuildNumber: remoteConfig.currentBuildNumber,
);

remoteConfig.remoteConfig.onConfigUpdated.listen((event) async {
await remoteConfig.remoteConfig.activate();
setState(() {});
});
}
}
Loading
Loading