From e38c1a08ee2c9e38bb45f520f5d3a3c714fefa91 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:56:40 -0500 Subject: [PATCH] 1110 check app version and prompt user to update (#1215) * initial work for checking app version * fix error in version endpoint url * show dialog on new version available --------- Co-authored-by: wcjord <32568597+wcjord@users.noreply.github.com> --- assets/l10n/intl_en.arb | 8 +- env.ocal_choreo | 16 -- lib/config/app_config.dart | 3 + lib/pages/chat_list/chat_list.dart | 3 + lib/pangea/constants/local.key.dart | 1 + lib/pangea/constants/model_keys.dart | 5 + .../controllers/app_version_controller.dart | 175 ++++++++++++++++++ lib/pangea/network/urls.dart | 2 + 8 files changed, 196 insertions(+), 17 deletions(-) delete mode 100644 env.ocal_choreo create mode 100644 lib/pangea/controllers/app_version_controller.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 61c3a268d..e5f694652 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4589,5 +4589,11 @@ "createChatAndInviteUsers": "Create chat and invite users", "updatedNewSpaceDescription": "Spaces allow you to consolidate your chats and build private or public communities.", "joinWithCode": "Join with code", - "enterCodeToJoin": "Enter code to join" + "enterCodeToJoin": "Enter code to join", + "mandatoryUpdateRequired": "Mandatory Update Required", + "updateAvailable": "Update Available", + "mandatoryUpdateRequiredDesc": "A new version of the app is required to continue. Please update now to proceed.", + "updateAvailableDesc": "A new version of the app is available. Update now for the latest features and improvements!", + "updateNow": "Update Now", + "updateLater": "Later" } diff --git a/env.ocal_choreo b/env.ocal_choreo deleted file mode 100644 index 36c87c253..000000000 --- a/env.ocal_choreo +++ /dev/null @@ -1,16 +0,0 @@ -BASE_API='https://api.staging.pangea.chat/api/v1' -CHOREO_API = "http://localhost:8000/choreo" -FRONTEND_URL='https://app.pangea.chat' - -SYNAPSE_URL = 'matrix.staging.pangea.chat' -CHOREO_API_KEY = 'e6fa9fa97031ba0c852efe78457922f278a2fbc109752fe18e465337699e9873' - -RC_PROJECT = 'a499dc21' -RC_KEY = 'sk_eVGBdPyInaOfJrKlPBgFVnRynqKJB' - -RC_GOOGLE_KEY = 'goog_paQMrzFKGzuWZvcMTPkkvIsifJe' -RC_IOS_KEY = 'appl_DUPqnxuLjkBLzhBPTWeDjqNENuv' -RC_STRIPE_KEY = 'strp_YWZxWUeEfvagiefDNoofinaRCOl' -RC_OFFERING_NAME = 'test' - -STRIPE_MANAGEMENT_LINK = 'https://billing.stripe.com/p/login/test_9AQaI8d3O9lmaXe5kk' \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 82a132aa3..5a0da605e 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -156,6 +156,9 @@ abstract class AppConfig { static String iosPromoCode = "https://apps.apple.com/redeem?ctx=offercodes&id=1445118630&code="; static String trialSubscriptionId = "pangea_new_user_trial"; + static String androidUpdateURL = + "https://play.google.com/store/apps/details?id=com.talktolearn.chat"; + static String iosUpdateURL = "itms-apps://itunes.apple.com/app/id1445118630"; // Pangea# static void loadFromJson(Map json) { diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 854cc6c79..905fba451 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/send_file_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; +import 'package:fluffychat/pangea/controllers/app_version_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; @@ -570,6 +571,8 @@ class ChatListController extends State Matrix.of(context).store.getString(_serverStoreNamespace); Matrix.of(context).backgroundPush?.setupPush(); UpdateNotifier.showUpdateSnackBar(context); + + AppVersionController.showAppVersionDialog(context); } // Workaround for system UI overlay style not applied on app start diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart index cc2834c72..5b45f394a 100644 --- a/lib/pangea/constants/local.key.dart +++ b/lib/pangea/constants/local.key.dart @@ -8,4 +8,5 @@ class PLocalKey { static const String completedActivities = 'completedActivities'; static const String justInputtedCode = 'justInputtedCode'; static const String availableSubscriptionInfo = 'availableSubscriptionInfo'; + static const String showedUpdateDialog = 'showedUpdateDialog'; } diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index b3f89e8c5..e82514bd3 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -132,4 +132,9 @@ class ModelKey { // room code static const String joinRule = "join_rule"; static const String accessCode = "access_code"; + + // app version + static const String latestVersion = "latest_version"; + static const String latestBuildNumber = "latest_build_number"; + static const String mandatoryUpdate = "mandatory_update"; } diff --git a/lib/pangea/controllers/app_version_controller.dart b/lib/pangea/controllers/app_version_controller.dart new file mode 100644 index 000000000..e5eab5543 --- /dev/null +++ b/lib/pangea/controllers/app_version_controller.dart @@ -0,0 +1,175 @@ +import 'dart:convert'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/config/environment.dart'; +import 'package:fluffychat/pangea/constants/local.key.dart'; +import 'package:fluffychat/pangea/constants/model_keys.dart'; +import 'package:fluffychat/pangea/network/requests.dart'; +import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:http/http.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:universal_html/html.dart' as html; +import 'package:url_launcher/url_launcher_string.dart'; + +class AppVersionController { + static Future _getAppVersion( + String accessToken, + ) async { + final packageInfo = await PackageInfo.fromPlatform(); + final currentVersion = packageInfo.version; + final currentBuildNumber = packageInfo.buildNumber; + + final Requests request = Requests( + choreoApiKey: Environment.choreoApiKey, + accessToken: accessToken, + ); + + Map json = {}; + final Response res = await request.post( + url: PApiUrls.appVersion, + body: { + "current_version": currentVersion, + "current_build_number": currentBuildNumber, + }, + ); + + json = jsonDecode(res.body); + return AppVersionResponse.fromJson(json); + } + + static Future showAppVersionDialog(BuildContext context) async { + final packageInfo = await PackageInfo.fromPlatform(); + final currentVersion = packageInfo.version; + final currentBuildNumber = packageInfo.buildNumber; + + final accessToken = MatrixState.pangeaController.userController.accessToken; + final AppVersionResponse resp = await _getAppVersion(accessToken); + + final remoteVersion = resp.latestVersion; + final remoteBuildNumber = resp.latestBuildNumber; + final mandatoryUpdate = resp.mandatoryUpdate; + + if (currentVersion == remoteVersion && + currentBuildNumber == remoteBuildNumber) { + return; + } + + if (!mandatoryUpdate && + showedUpdateDialog != null && + DateTime.now().difference(showedUpdateDialog!) < + const Duration(days: 1)) { + return; + } + + final OkCancelResult dialogResponse = + await _showDialog(context, mandatoryUpdate); + + if (!mandatoryUpdate && dialogResponse != OkCancelResult.ok) { + await MatrixState.pangeaController.pStoreService.save( + PLocalKey.showedUpdateDialog, + DateTime.now().toIso8601String(), + ); + } + + if (dialogResponse == OkCancelResult.ok) { + _launchUpdate(); + } + } + + static Future _showDialog( + BuildContext context, + bool mandatoryUpdate, + ) async { + final title = mandatoryUpdate + ? L10n.of(context).mandatoryUpdateRequired + : L10n.of(context).updateAvailable; + final message = mandatoryUpdate + ? L10n.of(context).mandatoryUpdateRequiredDesc + : L10n.of(context).updateAvailableDesc; + return mandatoryUpdate + ? showOkAlertDialog( + context: context, + title: title, + message: message, + canPop: false, + barrierDismissible: false, + okLabel: L10n.of(context).updateNow, + ) + : showOkCancelAlertDialog( + context: context, + title: title, + message: message, + canPop: false, + barrierDismissible: false, + okLabel: L10n.of(context).updateNow, + cancelLabel: L10n.of(context).updateLater, + ); + } + + static Future _launchUpdate() async { + if (kIsWeb) { + html.window.location.reload(); + return; + } + + final String url = PlatformInfos.isIOS + ? AppConfig.iosUpdateURL + : AppConfig.androidUpdateURL; + await launchUrlString(url); + } + + static DateTime? get showedUpdateDialog { + final entry = MatrixState.pangeaController.pStoreService + .read(PLocalKey.showedUpdateDialog); + if (entry == null) return null; + try { + return DateTime.parse(entry); + } catch (e) { + return null; + } + } +} + +class AppVersionResponse { + final String latestVersion; + final String latestBuildNumber; + final bool mandatoryUpdate; + + AppVersionResponse({ + required this.latestVersion, + required this.latestBuildNumber, + required this.mandatoryUpdate, + }); + + factory AppVersionResponse.fromJson(Map json) { + if (json[ModelKey.mandatoryUpdate] is! bool) { + throw Exception("mandatory_update is not a boolean"); + } + if (json[ModelKey.latestVersion] is! String) { + throw Exception("latest_version is not a string"); + } + if (json[ModelKey.latestBuildNumber] is! String) { + throw Exception("latest_build_number is not a string"); + } + + return AppVersionResponse( + latestVersion: json[ModelKey.latestVersion], + latestBuildNumber: json[ModelKey.latestBuildNumber], + mandatoryUpdate: json[ModelKey.mandatoryUpdate], + ); + } + + Map toJson() { + return { + ModelKey.latestVersion: latestVersion, + ModelKey.latestBuildNumber: latestBuildNumber, + ModelKey.mandatoryUpdate: mandatoryUpdate, + }; + } +} diff --git a/lib/pangea/network/urls.dart b/lib/pangea/network/urls.dart index 328f3e500..35fd2a49a 100644 --- a/lib/pangea/network/urls.dart +++ b/lib/pangea/network/urls.dart @@ -70,4 +70,6 @@ class PApiUrls { "${PApiUrls.subscriptionEndpoint}/all_products"; static String rcSubscription = "$rcApiV1/subscribers"; + + static String appVersion = "${PApiUrls.choreoEndpoint}/version"; }