diff --git a/android/fastlane/metadata/android/en-GB/changelogs/42.txt b/android/fastlane/metadata/android/en-GB/changelogs/42.txt new file mode 100644 index 0000000..804c8df --- /dev/null +++ b/android/fastlane/metadata/android/en-GB/changelogs/42.txt @@ -0,0 +1 @@ +* Hidden improvements to help support visible new features later in the year \ No newline at end of file diff --git a/lib/credentials/quick_unlocker.dart b/lib/credentials/quick_unlocker.dart index 0f22fee..f447cee 100644 --- a/lib/credentials/quick_unlocker.dart +++ b/lib/credentials/quick_unlocker.dart @@ -60,6 +60,9 @@ class QuickUnlocker { ), ); + // In 2023 we changed the user string to be the user ID rather than emailHashed. Since we didn't change + // any existing user's ID and they used to default to emailHashed anyway, this will keep working. + // Maybe one day we could/should rename the user parameter to complete the tidy-up. Future initialiseForUser(String user, bool force) async { if (!force && _currentCreds != null && _currentUser != null && _currentUser == user) { return QUStatus.credsAvailable; diff --git a/lib/cubit/account_cubit.dart b/lib/cubit/account_cubit.dart index 3c8418a..eda19a9 100644 --- a/lib/cubit/account_cubit.dart +++ b/lib/cubit/account_cubit.dart @@ -34,6 +34,15 @@ class AccountCubit extends Cubit { } } + User? get currentUserIfIdKnown { + final AccountState currentState = state; + if (currentState is AccountChosen && currentState.user.id != null) { + return currentState.user; + } else { + return null; + } + } + Future startup() async { if (state is! AccountInitial) return null; l.d('starting account cubit'); @@ -89,7 +98,7 @@ class AccountCubit extends Cubit { l.d('sign in procedure now awaits a password and resulting SRP parameters'); emit(AccountIdentified(user, false)); } on KeeServiceTransportException catch (e) { - l.i('Unable to identify user due to a transport error. App should continue to work offline if user has previously stored their Vault. Details: $e'); + l.i('Unable to identify user due to a transport error. App should continue to work offline if user has previously stored their Vault unless they have changed their email address previously. Details: $e'); emit(AccountIdentified(user, false)); } } @@ -108,11 +117,17 @@ class AccountCubit extends Cubit { l.i('Unable to authenticate due to a transport error. App should continue to work offline if user has previously stored their Vault. Details: $e'); final prefs = await SharedPreferences.getInstance(); await prefs.setString('user.current.email', user.email!); + if (user.id?.isNotEmpty ?? false) { + await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!); + } emit(AccountAuthenticationBypassed(user)); } on KeeMaybeOfflineException { l.i('Unable to authenticate since initial identification failed, probably due to a transport error. App should continue to work offline if user has previously stored their Vault.'); final prefs = await SharedPreferences.getInstance(); await prefs.setString('user.current.email', user.email!); + if (user.id?.isNotEmpty ?? false) { + await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!); + } emit(AccountAuthenticationBypassed(user)); } return user; @@ -152,6 +167,7 @@ class AccountCubit extends Cubit { user = await _userRepo.finishSignin(key, user); final prefs = await SharedPreferences.getInstance(); await prefs.setString('user.current.email', user.email!); + await prefs.setString('user.authMaterialUserIdMap.${user.emailHashed}', user.id!); await _userRepo.setQuickUnlockUser(user); final subscriptionStatus = user.subscriptionStatus; if (subscriptionStatus == AccountSubscriptionStatus.current) { diff --git a/lib/cubit/vault_cubit.dart b/lib/cubit/vault_cubit.dart index 12f1f72..e7aa8f4 100644 --- a/lib/cubit/vault_cubit.dart +++ b/lib/cubit/vault_cubit.dart @@ -865,7 +865,7 @@ class VaultCubit extends Cubit { final requireFullPasswordPeriod = int.tryParse(Settings.getValue('requireFullPasswordPeriod') ?? '60') ?? 60; l.d('Will require a full password to be entered every $requireFullPasswordPeriod days'); - final quStatus = await _qu.initialiseForUser(user?.emailHashed ?? _qu.localUserMagicString, true); + final quStatus = await _qu.initialiseForUser(user?.id ?? _qu.localUserMagicString, true); if (quStatus != QUStatus.mapAvailable && quStatus != QUStatus.credsAvailable) { l.w("Quick unlock credential provider is unavailable or unknown. Can't proceed to save credentials in this state."); return; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index cb019d9..83ae31e 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -557,6 +557,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Subscription expired"), "subscriptionExpiredDetails": MessageLookupByLibrary.simpleMessage( "Your subscription or trial period has ended. Provide up to date payment details and re-enable your subscription on the Kee Vault Account Management web site."), + "subscriptionExpiredNoAction": MessageLookupByLibrary.simpleMessage( + "Your subscription or trial period has ended. Please contact us to discuss options for renewal."), "subscriptionExpiredTrialAvailable": MessageLookupByLibrary.simpleMessage( "Welcome back to Kee Vault. You can enable a new 30 day free trial to see what has improved since you first created your Kee Vault account."), "tagRename": MessageLookupByLibrary.simpleMessage("Rename"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 1fed3cb..d5c08c6 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -3040,6 +3040,16 @@ class S { ); } + /// `Your subscription or trial period has ended. Please contact us to discuss options for renewal.` + String get subscriptionExpiredNoAction { + return Intl.message( + 'Your subscription or trial period has ended. Please contact us to discuss options for renewal.', + name: 'subscriptionExpiredNoAction', + desc: '', + args: [], + ); + } + /// `Restart subscription` String get restartSubscription { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3ae82f5..2feb451 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -298,6 +298,7 @@ "offerToSave": "Offer to save passwords", "subscriptionExpired": "Subscription expired", "subscriptionExpiredDetails": "Your subscription or trial period has ended. Provide up to date payment details and re-enable your subscription on the Kee Vault Account Management web site.", + "subscriptionExpiredNoAction": "Your subscription or trial period has ended. Please contact us to discuss options for renewal.", "restartSubscription": "Restart subscription", "subscriptionExpiredTrialAvailable": "Welcome back to Kee Vault. You can enable a new 30 day free trial to see what has improved since you first created your Kee Vault account.", "startFreeTrial": "Start free trial", diff --git a/lib/local_vault_repository.dart b/lib/local_vault_repository.dart index 5a2342a..fde9776 100644 --- a/lib/local_vault_repository.dart +++ b/lib/local_vault_repository.dart @@ -106,7 +106,7 @@ class LocalVaultRepository { Future load(User user, Future Function() getCredentials) async { final directory = await getStorageDirectory(); - final file = await _loadLocalFile(getCredentials, '${directory.path}/${user.emailHashedB64url}/current.kdbx'); + final file = await _loadLocalFile(getCredentials, '${directory.path}/${user.idB64url}/current.kdbx'); return file; } @@ -166,7 +166,7 @@ class LocalVaultRepository { Future create(User user, LockedVaultFile lockedKdbx) async { final directory = await getStorageDirectory(); - final file = File('${directory.path}/${user.emailHashedB64url}/current.kdbx'); + final file = File('${directory.path}/${user.idB64url}/current.kdbx'); await file.create(recursive: true); await file.writeAsBytes(lockedKdbx.kdbxBytes, flush: true); } @@ -181,7 +181,7 @@ class LocalVaultRepository { Future merge(User user, LocalVaultFile local, RemoteVaultFile remote) async { final directory = await getStorageDirectory(); - final file = File('${directory.path}/${user.emailHashedB64url}/current.kdbx'); + final file = File('${directory.path}/${user.idB64url}/current.kdbx'); final firstKdbx = await local.files.remoteMergeTarget; if (firstKdbx == null) { throw Exception("Missing remote merge target. Can't proceed with merge."); @@ -194,8 +194,7 @@ class LocalVaultRepository { finalKdbx = firstKdbx; kdbxData = await kdbxFormat().save(firstKdbx); } on KdbxUnsupportedException catch (e) { - final backupFilename = - '${directory.path}/${user.emailHashedB64url}/backup-${DateTime.now().millisecondsSinceEpoch}.kdbx'; + final backupFilename = '${directory.path}/${user.idB64url}/backup-${DateTime.now().millisecondsSinceEpoch}.kdbx'; l.w('Merge from remote failed! Most likely this is due to the user resetting their account on another device and then signing in to this device AND they reset their password to the same as it was before. We will create a backup file at $backupFilename just in case manual recovery becomes critical. Detailed reason: ${e.hint}'); await file.copy(backupFilename); finalKdbx = secondKdbx; @@ -226,7 +225,7 @@ class LocalVaultRepository { } final directory = await getStorageDirectory(); - final file = File('${directory.path}/${user.emailHashedB64url}/staged.kdbx'); + final file = File('${directory.path}/${user.idB64url}/staged.kdbx'); await file.writeAsBytes(bytes, flush: true); l.d('staging complete'); } @@ -236,15 +235,15 @@ class LocalVaultRepository { final directory = await getStorageDirectory(); return await _loadRemoteFile( getCredentials, - '${directory.path}/${user.emailHashedB64url}/staged.kdbx', + '${directory.path}/${user.idB64url}/staged.kdbx', ifNewerThan, ); } remove(User user) async { final directory = await getStorageDirectory(); - final file = File('${directory.path}/${user.emailHashedB64url}/current.kdbx'); - final stagedFile = File('${directory.path}/${user.emailHashedB64url}/staged.kdbx'); + final file = File('${directory.path}/${user.idB64url}/current.kdbx'); + final stagedFile = File('${directory.path}/${user.idB64url}/staged.kdbx'); try { await file.delete(); } on Exception { @@ -279,7 +278,7 @@ class LocalVaultRepository { Future save(User? user, LocalVaultFile vault, Future Function(KdbxFile vaultFile) applyAndConsumePendingAutofillAssociations) async { final directory = await getStorageDirectory(); - final userFolder = user?.emailHashedB64url ?? 'local_user'; + final userFolder = user?.idB64url ?? 'local_user'; final file = File('${directory.path}/$userFolder/current.kdbx'); (await vault.files.pending)?.merge(vault.files.current); final kdbxToSave = await beforeSave( @@ -314,7 +313,7 @@ class LocalVaultRepository { Future tryAutofillMerge(User? user, Credentials creds, LocalVaultFile vault) async { final directory = await getStorageDirectory(); - final userFolder = user?.emailHashedB64url ?? 'local_user'; + final userFolder = user?.idB64url ?? 'local_user'; final fileNameCurrent = File('${directory.path}/$userFolder/current.kdbx'); final fileNameAutofill = '${directory.path}/$userFolder/autofill.kdbx'; final fileAutofill = File(fileNameAutofill); diff --git a/lib/user_repository.dart b/lib/user_repository.dart index 7f4e8ac..c797c3d 100644 --- a/lib/user_repository.dart +++ b/lib/user_repository.dart @@ -10,8 +10,8 @@ class UserRepository { UserRepository(this.userService, this.qu); Future setQuickUnlockUser(User user, {bool force = false}) async { - if (user.emailHashed == null) return QUStatus.unknown; - final quStatus = await qu.initialiseForUser(user.emailHashed!, force); + if (user.id == null) return QUStatus.unknown; + final quStatus = await qu.initialiseForUser(user.id!, force); if (quStatus == QUStatus.mapAvailable || quStatus == QUStatus.credsAvailable) { if (user.passKey?.isNotEmpty ?? false) { await qu.saveQuickUnlockUserPassKey(user.passKey); @@ -42,6 +42,7 @@ class UserRepository { } await userService.loginFinish(user); + if (user.id?.isEmpty ?? true) throw KeeInvalidStateException(); return user; } } diff --git a/lib/vault_backend/claim.dart b/lib/vault_backend/claim.dart index 5d7f7e1..13ed660 100644 --- a/lib/vault_backend/claim.dart +++ b/lib/vault_backend/claim.dart @@ -6,6 +6,7 @@ class Claim { int iat; List features; int featureExpiry; + String? subscriptionId; // a missing ID indicates user has not finished setup Claim.fromJson(Map data) : sub = data['sub'], @@ -14,5 +15,6 @@ class Claim { exp = data['exp'], iat = data['iat'], featureExpiry = data['featureExpiry'], - features = List.from(data['features']); + features = List.from(data['features']), + subscriptionId = data['subscriptionId']; } diff --git a/lib/vault_backend/storage_item.dart b/lib/vault_backend/storage_item.dart index b0bd918..1a60716 100644 --- a/lib/vault_backend/storage_item.dart +++ b/lib/vault_backend/storage_item.dart @@ -14,7 +14,7 @@ class URLlist { } class StorageItem { - String emailHashed; + String userId; int schemaVersion; String? id; String? location; @@ -23,7 +23,7 @@ class StorageItem { String? name; StorageItem( - {required this.emailHashed, + {required this.userId, this.id, this.location, this.name, @@ -31,12 +31,12 @@ class StorageItem { required this.type, this.urls}); - static fromEmailHash(String emailHashed) { - return StorageItem(emailHashed: emailHashed, schemaVersion: 1, type: StorageType.keeS3); + static fromUserId(String userId) { + return StorageItem(userId: userId, schemaVersion: 1, type: StorageType.keeS3); } - static fromEmailHashAndId(String emailHashed, String id) { - return StorageItem(emailHashed: emailHashed, schemaVersion: 1, type: StorageType.keeS3, id: id); + static fromUserIdAndId(String userId, String id) { + return StorageItem(userId: userId, schemaVersion: 1, type: StorageType.keeS3, id: id); } StorageItem.fromJson(Map data) @@ -44,7 +44,7 @@ class StorageItem { name = data['name'], location = data['location'], type = data['type'], - emailHashed = data['emailHashed'], + userId = data['emailHashed'], schemaVersion = data['schemaVersion'], urls = URLlist.fromJson(data['urls']); } diff --git a/lib/vault_backend/user.dart b/lib/vault_backend/user.dart index 99fdb42..a250445 100644 --- a/lib/vault_backend/user.dart +++ b/lib/vault_backend/user.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'login_parameters.dart'; import 'account_verification_status.dart'; import 'features.dart'; @@ -9,7 +11,8 @@ import 'utils.dart'; class User { String? email; String? emailHashed; - String? emailHashedB64url; + String? id; + String? idB64url; String? salt; String? passKey; List? kms; @@ -17,25 +20,57 @@ class User { Tokens? tokens; LoginParameters? loginParameters; AccountVerificationStatus verificationStatus = AccountVerificationStatus.never; + String? subscriptionId; // hashedMasterKey may come from a combination of password and keyfile in // future but for now, we require a text password - static Future fromEmailAndKey(String email, List hashedMasterKey) async { - final user = User(); - user.email = email; - user.passKey = await derivePassKey(email, hashedMasterKey); - user.emailHashed = await hashString(email, EMAIL_ID_SALT); - user.emailHashedB64url = - user.emailHashed!.replaceAll(RegExp(r'\+'), '-').replaceAll(RegExp(r'/'), '_').replaceAll(RegExp(r'='), '.'); - return user; - } + // static Future fromEmailAndKey(String email, List hashedMasterKey) async { + // final user = User(); + // user.email = email; + // user.passKey = await derivePassKey(email, hashedMasterKey); + // user.emailHashed = await hashString(email, EMAIL_ID_SALT); + // user.idB64url = + // user.emailHashed!.replaceAll(RegExp(r'\+'), '-').replaceAll(RegExp(r'/'), '_').replaceAll(RegExp(r'='), '.'); + // return user; + // } static Future fromEmail(String email) async { final user = User(); user.email = email; + final prefsFuture = SharedPreferences.getInstance(); user.emailHashed = await hashString(email, EMAIL_ID_SALT); - user.emailHashedB64url = - user.emailHashed!.replaceAll(RegExp(r'\+'), '-').replaceAll(RegExp(r'/'), '_').replaceAll(RegExp(r'='), '.'); + final prefs = await prefsFuture; + + // We need to persistently store the map of email address hash to user ID so that we can + // allow offline access in the increasingly common case that a user's ID is not that same + // as their hashed email address. When we "forget" a user, this mapping of non-personally + // identifiable information will remain on the device ready for if/when they sign in again + // in future. + + // If a user changes email address and then another person signs up with it later, they + // would be able to use the original user's device to associate their sign-in credentials + // with the user ID of the original user. This can't result in abuse of either the KDBX + // file or Kee Vault authentication service because the remote server can see the real + // up to date relationship and the KDBX file can only be decrypted if the new user has + // selected the same password as the old user (in which case they could access the + // data in any number of alternative ways). + String? userId; + try { + userId = prefs.getString('user.authMaterialUserIdMap.${user.emailHashed}'); + } on Exception { + // no action required + // User ID may not be known (e.g. if user has never signed in on this device before) + } + + // On upgrade from an earlier version, subscribers may only have a locally stored copy of the email + // address, rather than their user ID, however, since they can't have modified their email address + // using the old version, we can safely assume their emailHashed still equals their user.emailHashed + // property. Perhaps someone upgrading a different device after an email address change will + // experience a problem but that'll be rare and signing out and back in again should resolve it + // for them since they'll then load the user id after loginFinish. + user.id = userId ?? user.emailHashed; + + user.idB64url = user.id!.replaceAll(RegExp(r'\+'), '-').replaceAll(RegExp(r'/'), '_').replaceAll(RegExp(r'='), '.'); return user; } @@ -51,7 +86,14 @@ class User { now - (86400 * 548 * 1000), DateTime.utc(2022, 4, 1).millisecondsSinceEpoch); // 18 months or 1st April 2022 if (subValidUntil >= now) { return AccountSubscriptionStatus.current; - } else if (subValidUntil < newestSubExpiryAllowedForNewTrial) { + } else if (subValidUntil < newestSubExpiryAllowedForNewTrial && + subscriptionSource == AccountSubscriptionSource.chargeBee) { + // We don't allow retrials for anything except Chargebee and user must have + // set up a subscription first. Thus users who get given a temporary subscription + // when registering can't later enable a Chargebee trial, even if they never + // completed their subscription setup from a different source. We'll render + // "create new subscription" features in future using knowledge of the current + // device platform rather than the user object. return AccountSubscriptionStatus.freeTrialAvailable; } else { return AccountSubscriptionStatus.expired; @@ -61,9 +103,22 @@ class User { // a user that does not exist in the Kee Vault service return AccountSubscriptionStatus.unknown; } + + AccountSubscriptionSource get subscriptionSource { + if (subscriptionId != null) { + if (subscriptionId!.startsWith('adhoc_')) { + return AccountSubscriptionSource.adHoc; + } else if (subscriptionId!.startsWith('cb_')) { + return AccountSubscriptionSource.chargeBee; + } + } + return AccountSubscriptionSource.unknown; + } } enum AccountSubscriptionStatus { unknown, current, expired, freeTrialAvailable } +enum AccountSubscriptionSource { unknown, adHoc, chargeBee } + // ignore: constant_identifier_names const EMAIL_ID_SALT = 'a7d60f672fc7836e94dabbd7000f7ef4e5e72bfbc66ba4372add41d7d46a1c24'; diff --git a/lib/vault_backend/user_service.dart b/lib/vault_backend/user_service.dart index 97e39dd..293e746 100644 --- a/lib/vault_backend/user_service.dart +++ b/lib/vault_backend/user_service.dart @@ -146,7 +146,11 @@ class UserService { // Don't do anything in the unlikely event that the JWT has already expired if (claim.exp > DateTime.now().millisecondsSinceEpoch) { user.features = Features(enabled: claim.features, source: 'unknown', validUntil: claim.featureExpiry); + user.id = claim.sub; + user.idB64url = + user.id!.replaceAll(RegExp(r'\+'), '-').replaceAll(RegExp(r'/'), '_').replaceAll(RegExp(r'='), '.'); user.tokens!.client = jwt; + user.subscriptionId = claim.subscriptionId; } } break; diff --git a/lib/widgets/account_expired.dart b/lib/widgets/account_expired.dart index 065bff3..a3188e2 100644 --- a/lib/widgets/account_expired.dart +++ b/lib/widgets/account_expired.dart @@ -5,6 +5,7 @@ import 'package:keevault/config/environment_config.dart'; import 'package:keevault/config/routes.dart'; import 'package:keevault/cubit/account_cubit.dart'; import 'package:keevault/cubit/vault_cubit.dart'; +import 'package:keevault/vault_backend/user.dart'; import '../generated/l10n.dart'; import 'dialog_utils.dart'; @@ -37,12 +38,20 @@ class _AccountExpiredWidgetState extends State { style: theme.textTheme.headline6, ), ), - Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: Text( - widget.trialAvailable ? str.subscriptionExpiredTrialAvailable : str.subscriptionExpiredDetails, - ), - ), + BlocBuilder(builder: (context, state) { + if (state is AccountExpired) { + return Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: Text( + widget.trialAvailable + ? str.subscriptionExpiredTrialAvailable + : expiryMessageForSubscriptionSource(state.user.subscriptionSource, str), + ), + ); + } else { + return Text(str.unexpected_error('Account in invalid state for ExpiredWidget')); + } + }), BlocBuilder( builder: (context, state) { if (state is AccountTrialRestartFinished) { @@ -85,40 +94,57 @@ class _AccountExpiredWidgetState extends State { } else if (state is AccountExpired) { final userEmail = state.user.email; final loading = state is AccountTrialRestartStarted; - return widget.trialAvailable - ? ElevatedButton.icon( - onPressed: loading - ? null - : () async { - final accountCubit = BlocProvider.of(context); - await accountCubit.restartTrial(); - }, - label: Text(str.startFreeTrial), - icon: loading - ? Container( - width: 24, - height: 24, - padding: const EdgeInsets.all(2.0), - child: const CircularProgressIndicator( - color: Colors.white, - strokeWidth: 3, - ), - ) - : Icon(Icons.favorite), - ) - : TextButton.icon( - icon: Text(str.restartSubscription), - label: Icon(Icons.open_in_new), - onPressed: () async { - final accountCubit = BlocProvider.of(context); - final vaultCubit = BlocProvider.of(context); - await DialogUtils.openUrl(EnvironmentConfig.webUrl + '/#pfEmail=$userEmail,dest=manageAccount'); - vaultCubit.signout(); - await accountCubit.signout(); - await AppConfig.router - .navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true); - }, - ); + if (state.user.subscriptionSource == AccountSubscriptionSource.chargeBee) { + return widget.trialAvailable + ? ElevatedButton.icon( + onPressed: loading + ? null + : () async { + final accountCubit = BlocProvider.of(context); + await accountCubit.restartTrial(); + }, + label: Text(str.startFreeTrial), + icon: loading + ? Container( + width: 24, + height: 24, + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ) + : Icon(Icons.favorite), + ) + : TextButton.icon( + icon: Text(str.restartSubscription), + label: Icon(Icons.open_in_new), + onPressed: () async { + final accountCubit = BlocProvider.of(context); + final vaultCubit = BlocProvider.of(context); + await DialogUtils.openUrl( + EnvironmentConfig.webUrl + '/#pfEmail=$userEmail,dest=manageAccount'); + vaultCubit.signout(); + await accountCubit.signout(); + await AppConfig.router + .navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true); + }, + ); + } else { + return TextButton.icon( + icon: Text(str.visitTheForum), + label: Icon(Icons.open_in_new), + onPressed: () async { + final accountCubit = BlocProvider.of(context); + final vaultCubit = BlocProvider.of(context); + await DialogUtils.openUrl('https://forum.kee.pm'); + vaultCubit.signout(); + await accountCubit.signout(); + await AppConfig.router + .navigateTo(AppConfig.navigatorKey.currentContext!, Routes.root, clearStack: true); + }, + ); + } } else { return Text(str.unexpected_error('Account in invalid state for ExpiredWidget')); } @@ -127,4 +153,11 @@ class _AccountExpiredWidgetState extends State { ]), ); } + + expiryMessageForSubscriptionSource(AccountSubscriptionSource subscriptionSource, S str) { + if (subscriptionSource == AccountSubscriptionSource.chargeBee) { + return str.subscriptionExpiredDetails; + } + return str.subscriptionExpiredNoAction; + } } diff --git a/lib/widgets/settings.dart b/lib/widgets/settings.dart index c0422ea..5e023ac 100644 --- a/lib/widgets/settings.dart +++ b/lib/widgets/settings.dart @@ -226,7 +226,7 @@ class _BiometricSettingWidgetState extends State { onChange: (_) async { final vaultCubit = BlocProvider.of(context); await vaultCubit.disableQuickUnlock(); - final user = BlocProvider.of(context).currentUserIfKnown; + final user = BlocProvider.of(context).currentUserIfIdKnown; await vaultCubit.enableQuickUnlock( user, vaultCubit.currentVaultFile?.files.current, @@ -251,7 +251,7 @@ class _BiometricSettingWidgetState extends State { onChange: (_) async { final vaultCubit = BlocProvider.of(context); await vaultCubit.disableQuickUnlock(); - final user = BlocProvider.of(context).currentUserIfKnown; + final user = BlocProvider.of(context).currentUserIfIdKnown; await vaultCubit.enableQuickUnlock( user, vaultCubit.currentVaultFile?.files.current, @@ -279,7 +279,7 @@ class _BiometricSettingWidgetState extends State { if (!value) { await vaultCubit.disableQuickUnlock(); } else { - final user = BlocProvider.of(context).currentUserIfKnown; + final user = BlocProvider.of(context).currentUserIfIdKnown; await vaultCubit.enableQuickUnlock( user, vaultCubit.currentVaultFile?.files.current, diff --git a/pubspec.lock b/pubspec.lock index 8126bd3..fff9e4b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -262,10 +262,10 @@ packages: description: path: "." ref: master - resolved-ref: a2e4055d8159e6e0ebd819550ed784e709dc8914 + resolved-ref: "256d53b9965c087e3f0e1d2699fc313b3d537321" url: "https://github.com/kee-org/flutter_autofill_service.git" source: git - version: "0.15.0" + version: "0.16.1" flutter_bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 55f81ee..d755ebe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.17+41 +version: 1.0.18+42 environment: sdk: '>=2.18.0 <3.0.0' @@ -126,8 +126,8 @@ dev_dependencies: flutter_native_splash: ^2.2.14 dependency_overrides: - # barcode_scan2: - # path: ../barcode_scan2/ + # flutter_autofill_service: + # path: ../flutter_autofill_service/ # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec