8000 Added Remind me to save the backup key in onboarding while registration and in activity page. by anisha-e10 · Pull Request #3015 · acterglobal/a3 · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Added Remind me to save the backup key in onboarding while registration and in activity page. #3015

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

Merged
merged 18 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .changes/3015-remindme-encryption-backupkey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- [Implemented] : Updated encryption key onboarding with new confirmation flow and UI actions for secure storage.
- [Implemented] : Added visual status indicators and key management options in Security & Privacy settings.
76 changes: 38 additions & 38 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -272,54 +272,54 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"

SPEC CHECKSUMS:
acter_flutter_sdk: e60481171e46975418babb7d0ec8809eaacaaa03
app_badge_plus: 3227d759d2a54e4fc28ec806596ec3b740c3c17a
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
app_settings: 58017cd26b604ae98c3e65acbdd8ba173703cc82
appcheck: e1ab9d4e03736f03e0401554a134d1ed502d7629
camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
acter_flutter_sdk: b60dabd77979100055ae2e3a1f8eda19ec1dffd7
app_badge_plus: 132ae8b39b3d2ca8819feadd647dca9501f310a9
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
appcheck: f3c430f928f9c69ccd8b5d4df04766ac0e57c9aa
camera_avfoundation: 04b44aeb14070126c6529e5ab82cc7c9fca107cf
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_calendar: b55b2c5406cfba45c95a59f9059156daee1f74ed
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
emoji_picker_flutter: 8e50ec5caac456a23a78637e02c6293ea0ac8771
fc_native_video_thumbnail: ded291371a8a82c449f28bf1ff84f576de8b65c5
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
fc_native_video_thumbnail: ea75aa9d0f7e7b58215d2ad99ac9dddafc038bc9
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
firebase_core: ac395f994af4e28f6a38b59e05a88ca57abeb874
firebase_core: 8d552814f6c01ccde5d88939fced4ec26f2f5510
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
pointer_interceptor_ios: 508241697ff0947f853c061945a8b822463947c1
push_ios: 2bd1b4d3f782209da1f571db1250af236957e807
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed
push_ios: d8edf21b48ecb1ee9e384dd0cd9c1b049fd1ca11
screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
sensors_plus: 7229095999f30740798f0eeef5cd120357a8f4f2
sensors_plus: 6a11ed0c2e1d0bd0b20b4029d3bad27d96e0c65b
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
volume_controller: ca1cde542ee70fad77d388f82e9616488110942b
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49

PODFILE CHECKSUM: fcb1d6a8b69ef1614eadff3ce844375007098b37

Expand Down
30 changes: 30 additions & 0 deletions app/lib/features/activities/actions/destroy_enc_key_action.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'package:acter/features/backups/providers/backup_manager_provider.dart';
import 'package:acter/l10n/generated/l10n.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void destroyEncKey(BuildContext context, WidgetRef ref, [VoidCallback? callNextPage]) async {
final lang = L10n.of(context);
try {
final manager = await ref.read(backupManagerProvider.future);
final destroyed = await manager.destroyStoredEncKey();
if (destroyed) {
if (!context.mounted) return;
Navigator.pop(context);
callNextPage != null ? callNextPage() : null;
} else {
if (!context.mounted) return;
EasyLoading.showError(
lang.keyDestroyedFailed,
duration: const Duration(seconds: 3),
);
}
} catch (e) {
if (!context.mounted) return;
EasyLoading.showError(
lang.keyDestroyedFailed,

Check warning on line 26 in app/lib/features/activities/actions/destroy_enc_key_action.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/activities/actions/destroy_enc_key_action.dart#L24-L26

Added lines #L24 - L26 were not covered by tests
duration: const Duration(seconds: 3),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:acter/common/themes/colors/color_scheme.dart';

enum KeyStorageUrgency {
normal, // 0-3 days
warning, // 3-7 days
critical, // 7+ days
}

Color getUrgencyColor(BuildContext context, KeyStorageUrgency urgency) {
final colorScheme = Theme.of(context).colorScheme;
return switch (urgency) {
KeyStorageUrgency.normal => colorScheme.primary,
KeyStorageUrgency.warning => warningColor,
KeyStorageUrgency.critical => colorScheme.error,
};
}
7 changes: 5 additions & 2 deletions app/lib/features/activities/pages/activities_page.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:acter/common/widgets/empty_state_widget.dart';
import 'package:acter/features/activities/widgets/invitation_section/invitation_section_widget.dart';
import 'package:acter/features/activities/widgets/security_and_privacy_section/security_and_privacy_section_widget.dart';
import 'package:acter/features/activities/widgets/security_and_privacy_section/store_the_key_securely_widget.dart';
import 'package:acter/features/activities/widgets/space_activities_section/space_activities_section_widget.dart';
import 'package:acter/features/activities/widgets/sync_section/sync_state_section_widget.dart';
import 'package:acter/l10n/generated/l10n.dart';
Expand All @@ -24,8 +25,10 @@

// Security and Privacy Section
final securityWidget = buildSecurityAndPrivacySectionWidget(context, ref);
if (securityWidget != null) sectionWidgetList.add(securityWidget);

if (securityWidget != null) {
sectionWidgetList.add(securityWidget);
sectionWidgetList.add(const StoreTheKeySecurelyWidget());

Check warning on line 30 in app/lib/features/activities/pages/activities_page.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/activities/pages/activities_page.dart#L29-L30

Added lines #L29 - L30 were not covered by tests
}
// Space Activities Section
final spaceActivitiesWidget = buildSpaceActivitiesSectionWidget(
context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import 'package:acter/features/activities/actions/key_storage_urgency_action.dart';
import 'package:acter/features/datetime/providers/utc_now_provider.dart';
import 'package:riverpod/riverpod.dart';

final keyStorageUrgencyProvider = Provider.family<KeyStorageUrgency, int>((ref, timestamp) {
if (timestamp == 0) return KeyStorageUrgency.normal;

final now = ref.watch(utcNowProvider).millisecondsSinceEpoch ~/ 1000;
final daysSinceStored = (now - timestamp) ~/ (24 * 60 * 60);

if (daysSinceStored <= 3) return KeyStorageUrgency.normal;
if (daysSinceStored <= 7) return KeyStorageUrgency.warning;
return KeyStorageUrgency.critical;
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
class ActivitySectionItemWidget extends ConsumerWidget {
final IconData icon;
final Color? iconColor;
final Color? borderColor;
final String title;
final String? subtitle;
final List<Widget>? actions;
Expand All @@ -12,6 +13,7 @@ class ActivitySectionItemWidget extends ConsumerWidget {
super.key,
required this.icon,
this.iconColor,
this.borderColor,
required this.title,
this.subtitle,
this.actions,
Expand All @@ -20,7 +22,10 @@ class ActivitySectionItemWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(color: borderColor ?? Colors.transparent, width: 0.5),
),
elevation: 3,
child: Padding(
padding: const EdgeInsets.all(16.0),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:acter/common/toolkit/buttons/primary_action_button.dart';
import 'package:acter/features/activities/actions/destroy_enc_key_action.dart';
import 'package:flutter/material.dart';
import 'package:acter/l10n/generated/l10n.dart';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class ShowRecoveryKeyWidget extends ConsumerStatefulWidget {
final String recoveryKey;
final VoidCallback? onKeyDestroyed;

const ShowRecoveryKeyWidget({
super.key,
required this.recoveryKey,
this.onKeyDestroyed,
});

@override
ConsumerState<ShowRecoveryKeyWidget> createState() => ShowRecoveryKeyWidgetState();
}

class ShowRecoveryKeyWidgetState extends ConsumerState<ShowRecoveryKeyWidget> {
@override
Widget build(BuildContext context) {
final lang = L10n.of(context);
return AlertDialog(
title: Text(lang.encryptionBackupRecovery, textAlign: TextAlign.center,),
content: Container(
constraints: const BoxConstraints(maxWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Text(lang.encryptionBackupRecoveryExplainer,textAlign: TextAlign.center,),
const SizedBox(height: 10),
TextFormField(
initialValue: widget.recoveryKey,
readOnly: true,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: const Icon(Icons.copy_rounded),
onPressed: () async {
await Clipboard.setData(ClipboardData(text: widget.recoveryKey));
EasyLoading.showToast(
lang.encryptionBackupRecoveryCopiedToClipboard,
toastPosition: EasyLoadingToastPosition.bottom,
);
},
),
),
),
],
),
),
actionsAlignment: MainAxisAlignment.center,
actions: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
ActerPrimaryActionButton(
onPressed: () {
destroyEncKey(context, ref);
widget.onKeyDestroyed?.call();

Check warning on line 66 in app/lib/features/activities/widgets/security_and_privacy_section/show_recovery_key_widget.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/activities/widgets/security_and_privacy_section/show_recovery_key_widget.dart#L64-L66

Added lines #L64 - L66 were not covered by tests
},
child: Text(lang.dontRemindMe),
),
const SizedBox(height: 10),
OutlinedButton(
onPressed: () => Navigator.pop(context),
child: Text(lang.okay),
),
],
),
],
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:acter/features/activities/providers/key_storage_urgency_provider.dart';
import 'package:acter/features/activities/widgets/activity_section_item_widget.dart';
import 'package:acter/features/activities/widgets/security_and_privacy_section/show_recovery_key_widget.dart';
import 'package:acter/features/backups/providers/backup_manager_provider.dart';
import 'package:acter/features/activities/actions/key_storage_urgency_action.dart';
import 'package:flutter/material.dart';
import 'package:acter/l10n/generated/l10n.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart';

class StoreTheKeySecurelyWidget extends ConsumerWidget {
const StoreTheKeySecurelyWidget({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final lang = L10n.of(context);
final encKey = ref.watch(storedEncKeyProvider).valueOrNull;
final timestamp = ref.watch(storedEncKeyTimestampProvider).valueOrNull;

if (timestamp == null || encKey == null) {
return SizedBox.shrink();
}

final keyText = encKey.text();
if (keyText == null || keyText.isEmpty) {
return SizedBox.shrink();
}

final urgency = ref.watch(keyStorageUrgencyProvider(timestamp));
final urgencyColor = getUrgencyColor(context, urgency);

return ActivitySectionItemWidget(
icon: PhosphorIcons.lock(),
iconColor: urgencyColor,
borderColor: urgencyColor,
title: lang.dontForgetToStoreTheKeySecurely,
subtitle: lang.storeTheKeySecurelyDescription,
actions: [
OutlinedButton(
style: OutlinedButton.styleFrom(
foregroundColor: urgencyColor,
side: BorderSide(color: urgencyColor),
),
onPressed: () {
showDialog(
context: context,
builder: (context) => ShowRecoveryKeyWidget(
recoveryKey: keyText,
onKeyDestroyed: () {
ref.invalidate(storedEncKeyProvider);
ref.invalidate(storedEncKeyTimestampProvider);

Check warning on line 51 in app/lib/features/activities/widgets/security_and_privacy_section/store_the_key_securely_widget.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/activities/widgets/security_and_privacy_section/store_the_key_securely_widget.dart#L49-L51

Added lines #L49 - L51 were not covered by tests
},
),
);
},
child: Text(lang.showKey),
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
Navigator.pop(context);
showRecoveryKeyDialog(context, ref, newKey);
}
ref.invalidate(storedEncKeyProvider);

Check warning on line 61 in app/lib/features/backups/dialogs/show_confirm_disabling.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/backups/dialogs/show_confirm_disabling.dart#L61

Added line #L61 was not covered by tests
} catch (error) {
if (!context.mounted) {
EasyLoading.dismiss();
Expand Down
10 changes: 10 additions & 0 deletions app/lib/features/backups/providers/backup_manager_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@
final manager = await ref.watch(backupManagerProvider.future);
return manager.enable();
});

final storedEncKeyProvider = FutureProvider((ref) async {
final manager = await ref.watch(backupManagerProvider.future);
return manager.storedEncKey();

Check warning on line 17 in app/lib/features/backups/providers/backup_manager_provider.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/backups/providers/backup_manager_provider.dart#L16-L17

Added lines #L16 - L17 were not covered by tests
});

final storedEncKeyTimestampProvider = FutureProvider((ref) async {
final manager = await ref.watch(backupManagerProvider.future);
return manager.storedEncKeyWhen();

Check warning on line 22 in app/lib/features/backups/providers/backup_manager_provider.dart

View check run for this annotation

Codecov / codecov/patch

app/lib/features/backups/providers/backup_manager_provider.dart#L21-L22

Added lines #L21 - L22 were not covered by tests
});
Loading
Loading
0