diff --git a/lib/config.dart b/lib/config.dart index 8f559d0a..43a2f29c 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -30,6 +30,7 @@ abstract class Config { // Wallet-app specific endpoints static const dapps = "/wallet-app/dapps/dapps.json"; static dappLocale(String locale) => "/wallet-app/dapps/locales/$locale.json"; + static const appVersionCheck = "/wallet-app/version-check.json"; // General/ecosystem endpoints (shared across Qubic ecosystem) static const smartContracts = "/general/data/smart_contracts.json"; diff --git a/lib/di.dart b/lib/di.dart index d87647e9..57c17f05 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -12,6 +12,7 @@ import 'package:qubic_wallet/resources/qubic_cmd.dart'; import 'package:qubic_wallet/resources/secure_storage.dart'; import 'package:qubic_wallet/services/biometric_service.dart'; import 'package:qubic_wallet/services/wallet_connect_service.dart'; +import 'package:qubic_wallet/stores/app_update_store.dart'; import 'package:qubic_wallet/stores/application_store.dart'; import 'package:qubic_wallet/stores/network_store.dart'; import 'package:qubic_wallet/stores/root_jailbreak_flag_store.dart'; @@ -43,6 +44,7 @@ Future setupDI() async { await getIt().initialize(); getIt.registerSingleton(HiveStorage()); await getIt().initialize(); + getIt.registerSingleton(AppUpdateStore()); //Providers getIt.registerSingleton(GlobalSnackBar()); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 51da1dae..286dd154 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1047,5 +1047,15 @@ "androidDeprecationDialogMessageBefore": "Future updates will require Android 7.0 or higher. To continue receiving updates and ensure long-term wallet access, please ", "androidDeprecationDialogMessageHighlight": "back up your private seeds or export your wallet vault", "androidDeprecationDialogMessageAfter": " from Settings.", - "androidDeprecationDialogButton": "Got it" + "androidDeprecationDialogButton": "Got it", + "updateRequiredTitle": "Update Required", + "updateRequiredMessage": "A new version of Qubic Wallet is required to continue. Please update to the latest version.", + "updateAvailableTitle": "Update Available", + "updateAvailableMessage": "A new version of Qubic Wallet is available with improvements and bug fixes.", + "updateButton": "Update Now", + "laterButton": "Remind Me Later", + "ignoreVersionButton": "Skip This Version", + "updateScreenCurrentVersion": "Current Version", + "updateScreenNewVersion": "New Version", + "updateScreenWhatsNew": "What's New" } diff --git a/lib/main.dart b/lib/main.dart index 6fd9c4e8..9187a662 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:qubic_wallet/stores/application_store.dart'; import 'package:qubic_wallet/stores/wallet_content_store.dart'; import 'package:qubic_wallet/stores/root_jailbreak_flag_store.dart'; import 'package:qubic_wallet/stores/settings_store.dart'; +import 'package:qubic_wallet/stores/app_update_store.dart'; import 'package:qubic_wallet/styles/button_styles.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -44,6 +45,7 @@ Future main() async { getIt.get().setBuildNumber(packageInfo.buildNumber); getIt.get().checkWalletIsInitialized(); + await getIt.get().checkForUpdate(); } catch (e) { appLogger.e(e.toString()); } diff --git a/lib/models/app_version_check_model.dart b/lib/models/app_version_check_model.dart new file mode 100644 index 00000000..4f53cb32 --- /dev/null +++ b/lib/models/app_version_check_model.dart @@ -0,0 +1,80 @@ +import 'package:universal_platform/universal_platform.dart'; + +enum UpdateType { + force, + flexible, + none, +} + +class AppVersionCheckModel { + final String version; + final String? releaseNotes; + final bool showLaterButton; + final bool showIgnoreButton; + final Map updateUrls; + final List platforms; + + static const List _defaultPlatforms = [ + 'android', + 'ios', + ]; + + static const Map _defaultUpdateUrls = { + 'ios': 'https://apps.apple.com/app/qubic-wallet/id6502265811', + 'android': 'https://play.google.com/store/apps/details?id=org.qubic.wallet', + }; + + AppVersionCheckModel({ + required this.version, + this.releaseNotes, + this.showLaterButton = false, + this.showIgnoreButton = false, + this.updateUrls = _defaultUpdateUrls, + this.platforms = _defaultPlatforms, + }); + + /// Derive update type from button visibility: + /// - If both buttons are hidden -> force update + /// - If any button is visible -> flexible update + UpdateType get updateType { + if (!showLaterButton && !showIgnoreButton) { + return UpdateType.force; + } + return UpdateType.flexible; + } + + static AppVersionCheckModel? fromJson(Map? json) { + if (json == null || json.isEmpty) { + return null; + } + + final updateUrls = json['update_urls'] != null + ? Map.from(json['update_urls']) + : _defaultUpdateUrls; + + final platforms = json['platforms'] != null + ? List.from(json['platforms']) + : _defaultPlatforms; + + return AppVersionCheckModel( + version: json['version'], + releaseNotes: json['release_notes'], + showLaterButton: json['show_later_button'] ?? false, + showIgnoreButton: json['show_ignore_button'] ?? false, + updateUrls: updateUrls, + platforms: platforms, + ); + } + + bool isApplicableForCurrentPlatform() { + if (UniversalPlatform.isIOS) return platforms.contains('ios'); + if (UniversalPlatform.isAndroid) return platforms.contains('android'); + return false; + } + + String? getUpdateUrlForPlatform() { + if (UniversalPlatform.isIOS) return updateUrls['ios']; + if (UniversalPlatform.isAndroid) return updateUrls['android']; + return null; + } +} diff --git a/lib/pages/update/app_update_screen.dart b/lib/pages/update/app_update_screen.dart new file mode 100644 index 00000000..81155e5c --- /dev/null +++ b/lib/pages/update/app_update_screen.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:go_router/go_router.dart'; +import 'package:qubic_wallet/di.dart'; +import 'package:qubic_wallet/flutter_flow/theme_paddings.dart'; +import 'package:qubic_wallet/helpers/app_logger.dart'; +import 'package:qubic_wallet/l10n/l10n.dart'; +import 'package:qubic_wallet/models/app_version_check_model.dart'; +import 'package:qubic_wallet/stores/app_update_store.dart'; +import 'package:qubic_wallet/stores/settings_store.dart'; +import 'package:qubic_wallet/styles/text_styles.dart'; +import 'package:qubic_wallet/styles/themed_controls.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +part 'components/app_update_logo.dart'; +part 'components/app_update_header.dart'; +part 'components/app_update_info_card.dart'; +part 'components/app_update_buttons.dart'; + +class AppUpdateScreen extends StatelessWidget { + const AppUpdateScreen({super.key}); + + @override + Widget build(BuildContext context) { + final appUpdateStore = getIt(); + final settingsStore = getIt(); + + return Observer( + builder: (context) { + final versionInfo = appUpdateStore.currentVersionInfo; + + if (versionInfo == null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + context.go('/'); + } + }); + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } + + final isForceUpdate = versionInfo.updateType == UpdateType.force; + + return Scaffold( + backgroundColor: LightThemeColors.background, + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(ThemePaddings.hugePadding), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + const _AppUpdateLogo(), + _AppUpdateHeader(isForceUpdate: isForceUpdate), + ThemedControls.spacerVerticalBig(), + _AppUpdateInfoCard( + versionInfo: versionInfo, + currentVersion: settingsStore.versionInfo, + onUpdatePressed: () => _launchUpdateUrl(versionInfo), + ), + const Spacer(), + _AppUpdateButtons( + versionInfo: versionInfo, + onUpdatePressed: () => _launchUpdateUrl(versionInfo), + onLaterPressed: () { + appUpdateStore.handleLaterAction(); + context.go('/'); + }, + onIgnorePressed: () { + appUpdateStore.handleIgnoreAction(versionInfo.version); + context.go('/'); + }, + ), + ], + ), + ), + ), + ); + }, + ); + } + + Future _launchUpdateUrl(AppVersionCheckModel versionInfo) async { + final url = versionInfo.getUpdateUrlForPlatform(); + if (url == null) { + appLogger.e('[AppUpdateScreen] No update URL available for platform'); + return; + } + + try { + await launchUrlString(url, mode: LaunchMode.externalApplication); + } catch (e) { + appLogger.e('[AppUpdateScreen] Failed to launch update URL: $e'); + return; + } + } +} diff --git a/lib/pages/update/components/app_update_buttons.dart b/lib/pages/update/components/app_update_buttons.dart new file mode 100644 index 00000000..4620d5b5 --- /dev/null +++ b/lib/pages/update/components/app_update_buttons.dart @@ -0,0 +1,82 @@ +part of '../app_update_screen.dart'; + +class _AppUpdateButtons extends StatelessWidget { + final AppVersionCheckModel versionInfo; + final VoidCallback onUpdatePressed; + final VoidCallback onLaterPressed; + final VoidCallback onIgnorePressed; + + const _AppUpdateButtons({ + required this.versionInfo, + required this.onUpdatePressed, + required this.onLaterPressed, + required this.onIgnorePressed, + }); + + bool get _hasAllOptions => + versionInfo.showLaterButton && versionInfo.showIgnoreButton; + + @override + Widget build(BuildContext context) { + final l10n = l10nOf(context); + + // Scenario 1: Force update - only Update button at bottom + if (versionInfo.updateType == UpdateType.force) { + return ThemedControls.primaryButtonBig( + onPressed: onUpdatePressed, + text: l10n.updateButton, + ); + } + + final showLater = versionInfo.showLaterButton; + final showIgnore = versionInfo.showIgnoreButton; + + // Scenario 3: All three options - Update in card, Later & Skip in row + if (_hasAllOptions) { + return Row( + children: [ + Expanded( + child: ThemedControls.transparentButtonNormal( + onPressed: onLaterPressed, + text: l10n.laterButton, + ), + ), + ThemedControls.spacerHorizontalNormal(), + Expanded( + child: ThemedControls.dangerButtonBigWithClild( + onPressed: onIgnorePressed, + child: Text( + l10n.ignoreVersionButton, + style: TextStyles.destructiveButtonText, + ), + ), + ), + ], + ); + } + + // Scenario 2: Two options - Update first, then the other option + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ThemedControls.primaryButtonBig( + onPressed: onUpdatePressed, + text: l10n.updateButton, + ), + if (showLater) + ThemedControls.transparentButtonNormal( + onPressed: onLaterPressed, + text: l10n.laterButton, + ), + if (showIgnore) + ThemedControls.dangerButtonBigWithClild( + onPressed: onIgnorePressed, + child: Text( + l10n.ignoreVersionButton, + style: TextStyles.destructiveButtonText, + ), + ), + ], + ); + } +} diff --git a/lib/pages/update/components/app_update_header.dart b/lib/pages/update/components/app_update_header.dart new file mode 100644 index 00000000..9cc7ed5e --- /dev/null +++ b/lib/pages/update/components/app_update_header.dart @@ -0,0 +1,35 @@ +part of '../app_update_screen.dart'; + +class _AppUpdateHeader extends StatelessWidget { + final bool isForceUpdate; + + const _AppUpdateHeader({required this.isForceUpdate}); + + @override + Widget build(BuildContext context) { + final l10n = l10nOf(context); + + return Column( + children: [ + Text( + isForceUpdate ? l10n.updateRequiredTitle : l10n.updateAvailableTitle, + style: TextStyles.textEnormous.copyWith( + fontWeight: FontWeight.bold, + color: LightThemeColors.primary, + ), + textAlign: TextAlign.center, + ), + ThemedControls.spacerVerticalBig(), + Text( + isForceUpdate + ? l10n.updateRequiredMessage + : l10n.updateAvailableMessage, + style: TextStyles.textNormal.copyWith( + color: LightThemeColors.textColorSecondary, + ), + textAlign: TextAlign.center, + ), + ], + ); + } +} diff --git a/lib/pages/update/components/app_update_info_card.dart b/lib/pages/update/components/app_update_info_card.dart new file mode 100644 index 00000000..ca068fb0 --- /dev/null +++ b/lib/pages/update/components/app_update_info_card.dart @@ -0,0 +1,92 @@ +part of '../app_update_screen.dart'; + +class _AppUpdateInfoCard extends StatelessWidget { + final AppVersionCheckModel versionInfo; + final String? currentVersion; + final VoidCallback onUpdatePressed; + + const _AppUpdateInfoCard({ + required this.versionInfo, + required this.currentVersion, + required this.onUpdatePressed, + }); + + bool get _hasAllOptions => + versionInfo.showLaterButton && versionInfo.showIgnoreButton; + + @override + Widget build(BuildContext context) { + final l10n = l10nOf(context); + + return ThemedControls.card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _AppUpdateInfoRow( + label: l10n.updateScreenCurrentVersion, + value: currentVersion ?? 'Unknown', + ), + ThemedControls.spacerVerticalSmall(), + _AppUpdateInfoRow( + label: l10n.updateScreenNewVersion, + value: versionInfo.version, + ), + if (versionInfo.releaseNotes != null) ...[ + ThemedControls.spacerVerticalSmall(), + Text( + l10n.updateScreenWhatsNew, + style: TextStyles.secondaryText, + ), + ThemedControls.spacerVerticalSmall(), + Text( + versionInfo.releaseNotes!, + style: TextStyles.textNormal.copyWith( + color: LightThemeColors.primary, + ), + ), + ], + if (_hasAllOptions) ...[ + ThemedControls.spacerVerticalBig(), + SizedBox( + width: double.infinity, + child: ThemedControls.primaryButtonBig( + onPressed: onUpdatePressed, + text: l10n.updateButton, + ), + ), + ], + ], + ), + ); + } +} + +class _AppUpdateInfoRow extends StatelessWidget { + final String label; + final String value; + + const _AppUpdateInfoRow({ + required this.label, + required this.value, + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyles.secondaryText, + ), + Text( + value, + style: TextStyles.textNormal.copyWith( + fontWeight: FontWeight.w600, + color: LightThemeColors.primary, + ), + ), + ], + ); + } +} diff --git a/lib/pages/update/components/app_update_logo.dart b/lib/pages/update/components/app_update_logo.dart new file mode 100644 index 00000000..58baa3bc --- /dev/null +++ b/lib/pages/update/components/app_update_logo.dart @@ -0,0 +1,15 @@ +part of '../app_update_screen.dart'; + +class _AppUpdateLogo extends StatelessWidget { + const _AppUpdateLogo(); + + @override + Widget build(BuildContext context) { + return Center( + child: Image.asset( + 'assets/images/blue-logo.png', + height: 100, + ), + ); + } +} diff --git a/lib/resources/apis/static/qubic_static_api.dart b/lib/resources/apis/static/qubic_static_api.dart index 40e7403e..5f57b509 100644 --- a/lib/resources/apis/static/qubic_static_api.dart +++ b/lib/resources/apis/static/qubic_static_api.dart @@ -1,6 +1,9 @@ import 'package:dio/dio.dart'; import 'package:qubic_wallet/config.dart'; import 'package:qubic_wallet/dtos/dapp_dto.dart'; +import 'package:qubic_wallet/helpers/app_logger.dart'; +import 'package:qubic_wallet/models/app_version_check_model.dart' + show AppVersionCheckModel; import 'package:qubic_wallet/models/exchange_model.dart'; import 'package:qubic_wallet/models/labeled_address_model.dart'; import 'package:qubic_wallet/models/protocol_model.dart'; @@ -73,4 +76,26 @@ class QubicStaticApi { throw Exception('Failed to fetch protocol data'); } } + + Future getAppVersionCheck() async { + try { + // TODO: Replace with real API call before production + if (Config.useDevEnvironment) { + return AppVersionCheckModel.fromJson({ + 'version': '2.5.0', + 'release_notes': + '- Critical security fixes\n- Improved transaction reliability\n- Enhanced wallet performance', + 'show_later_button': false, + 'show_ignore_button': false, + 'platforms': ['android', 'ios'], + }); + } + + final response = await _dio.get(Config.appVersionCheck); + return AppVersionCheckModel.fromJson(response.data); + } catch (e) { + appLogger.e('[QubicStaticApi] Failed to fetch app version check: $e'); + throw Exception('Failed to fetch app version check'); + } + } } diff --git a/lib/routes.dart b/lib/routes.dart index da39c3c9..f2bb9255 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,9 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; import 'package:qubic_wallet/di.dart'; +import 'package:qubic_wallet/helpers/app_logger.dart'; import 'package:qubic_wallet/pages/auth/sign_in.dart'; import 'package:qubic_wallet/pages/main/main_screen.dart'; +import 'package:qubic_wallet/pages/update/app_update_screen.dart'; import 'package:qubic_wallet/stores/application_store.dart'; +import 'package:qubic_wallet/stores/app_update_store.dart'; CustomTransitionPage buildPageWithDefaultTransition({ required BuildContext context, @@ -34,15 +37,33 @@ bool isSignedIn() { return getIt().isSignedIn; } +bool needsUpdate() { + final result = getIt().shouldShowUpdateScreen; + appLogger.i('[Routes] needsUpdate() called, result: $result'); + return result; +} + // GoRouter configuration final appRouter = GoRouter( navigatorKey: rootNavigatorKey, routes: [ + GoRoute( + path: '/update', + name: 'update', + pageBuilder: defaultPageBuilder(const AppUpdateScreen()), + ), GoRoute( path: '/signIn', name: 'signIn', builder: (context, state) => const SignIn(), pageBuilder: defaultPageBuilder(const SignIn()), + redirect: (BuildContext context, GoRouterState state) { + // Check for update BEFORE auth + if (needsUpdate()) { + return '/update'; + } + return null; + }, ), GoRoute( path: '/signInNoAuth', @@ -50,6 +71,13 @@ final appRouter = GoRouter( builder: (context, state) => SignIn( disableLocalAuth: state.pathParameters['disableLocalAuth'] == 'true'), pageBuilder: defaultPageBuilder(const SignIn(disableLocalAuth: true)), + redirect: (BuildContext context, GoRouterState state) { + // Check for update BEFORE auth + if (needsUpdate()) { + return '/update'; + } + return null; + }, ), GoRoute( path: '/', @@ -57,6 +85,11 @@ final appRouter = GoRouter( builder: (context, state) => const MainScreen(), pageBuilder: defaultPageBuilder(const MainScreen()), redirect: (BuildContext context, GoRouterState state) { + // Check for update FIRST + if (needsUpdate()) { + return '/update'; + } + // Then check auth if (!isSignedIn()) { return '/signin'; } diff --git a/lib/stores/app_update_store.dart b/lib/stores/app_update_store.dart new file mode 100644 index 00000000..3523caa7 --- /dev/null +++ b/lib/stores/app_update_store.dart @@ -0,0 +1,76 @@ +// ignore_for_file: library_private_types_in_public_api + +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:version/version.dart'; +import 'package:qubic_wallet/di.dart'; +import 'package:qubic_wallet/models/app_version_check_model.dart' + show UpdateType, AppVersionCheckModel; +import 'package:qubic_wallet/resources/apis/static/qubic_static_api.dart'; +import 'package:qubic_wallet/stores/settings_store.dart'; + +part 'app_update_store.g.dart'; + +class AppUpdateStore = _AppUpdateStore with _$AppUpdateStore; + +abstract class _AppUpdateStore with Store { + static const String _ignoredVersionKey = 'ignored_update_version'; + + final QubicStaticApi _staticApi = getIt(); + final SettingsStore _settingsStore = getIt(); + + /// Latest version info fetched from the API + @observable + AppVersionCheckModel? _versionInfo; + + bool _dismissedForSession = false; + String? _ignoredVersion; + + bool get shouldShowUpdateScreen { + if (_versionInfo == null || _dismissedForSession) return false; + return _shouldShowForVersion(_versionInfo!); + } + + @computed + AppVersionCheckModel? get currentVersionInfo => + shouldShowUpdateScreen ? _versionInfo : null; + + bool _shouldShowForVersion(AppVersionCheckModel info) { + if (!info.isApplicableForCurrentPlatform()) return false; + + final currentVersion = _parseVersion(_settingsStore.versionInfo); + final requiredVersion = _parseVersion(info.version); + final isOutdated = currentVersion < requiredVersion; + final isIgnored = + info.updateType == UpdateType.flexible && _ignoredVersion == info.version; + + return isOutdated && !isIgnored; + } + + Version _parseVersion(String? versionString) { + try { + return Version.parse(versionString ?? '0.0.0'); + } catch (_) { + return Version(0, 0, 0); + } + } + + @action + Future checkForUpdate() async { + final prefs = await SharedPreferences.getInstance(); + _ignoredVersion = prefs.getString(_ignoredVersionKey); + _versionInfo = await _staticApi.getAppVersionCheck(); + } + + @action + void handleLaterAction() { + _dismissedForSession = true; + } + + @action + Future handleIgnoreAction(String version) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_ignoredVersionKey, version); + _ignoredVersion = version; + } +} diff --git a/lib/stores/app_update_store.g.dart b/lib/stores/app_update_store.g.dart new file mode 100644 index 00000000..8bd47274 --- /dev/null +++ b/lib/stores/app_update_store.g.dart @@ -0,0 +1,74 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_update_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 _$AppUpdateStore on _AppUpdateStore, Store { + Computed? _$currentVersionInfoComputed; + + @override + AppVersionCheckModel? get currentVersionInfo => + (_$currentVersionInfoComputed ??= Computed( + () => super.currentVersionInfo, + name: '_AppUpdateStore.currentVersionInfo')) + .value; + + late final _$_versionInfoAtom = + Atom(name: '_AppUpdateStore._versionInfo', context: context); + + @override + AppVersionCheckModel? get _versionInfo { + _$_versionInfoAtom.reportRead(); + return super._versionInfo; + } + + @override + set _versionInfo(AppVersionCheckModel? value) { + _$_versionInfoAtom.reportWrite(value, super._versionInfo, () { + super._versionInfo = value; + }); + } + + late final _$checkForUpdateAsyncAction = + AsyncAction('_AppUpdateStore.checkForUpdate', context: context); + + @override + Future checkForUpdate() { + return _$checkForUpdateAsyncAction.run(() => super.checkForUpdate()); + } + + late final _$handleIgnoreActionAsyncAction = + AsyncAction('_AppUpdateStore.handleIgnoreAction', context: context); + + @override + Future handleIgnoreAction(String version) { + return _$handleIgnoreActionAsyncAction + .run(() => super.handleIgnoreAction(version)); + } + + late final _$_AppUpdateStoreActionController = + ActionController(name: '_AppUpdateStore', context: context); + + @override + void handleLaterAction() { + final _$actionInfo = _$_AppUpdateStoreActionController.startAction( + name: '_AppUpdateStore.handleLaterAction'); + try { + return super.handleLaterAction(); + } finally { + _$_AppUpdateStoreActionController.endAction(_$actionInfo); + } + } + + @override + String toString() { + return ''' +currentVersionInfo: ${currentVersionInfo} + '''; + } +} diff --git a/pubspec.lock b/pubspec.lock index 5e58f6af..68cfbd9f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -253,10 +253,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: @@ -1081,18 +1081,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" maybe_just_nothing: dependency: transitive description: @@ -1710,10 +1710,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.10" timeago: dependency: "direct main" description: @@ -1858,6 +1858,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + version: + dependency: "direct main" + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" vm_service: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5ce56dca..21187bcb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ dependencies: flutter_is_ios_app_on_mac: ^1.0.1 cryptography: ^2.7.0 no_screenshot: ^0.3.6 + version: ^3.0.0 implicitly_animated_list: ^2.3.0 cached_network_image: ^3.4.1 pub_semver: ^2.1.5