diff --git a/lib/main.dart b/lib/main.dart index 11fd40b..9561d5a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,7 @@ import 'package:t3_vault/src/common/localization/timezone/timezone_helper.dart'; import 'package:t3_vault/src/common/notifications/domain/notifications_service.dart'; import 'package:t3_vault/src/common/notifications/state/notifications_state.dart'; import 'package:t3_vault/src/features/greatwall/states/derivation_state.dart'; -import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart'; import 'src/app.dart'; import 'src/common/settings/domain/entities/settings_service.dart'; @@ -37,9 +37,9 @@ void main() async { // it is stored within the application documents directory. final filePath = '${directory.path}/t3-profiles.json'; - // Initialize the MemoCardRepository with the specified file path, allowing - // the application to read and write memo card data to the JSON file. - final memoCardRepository = MemoCardRepository( + // Initialize the ProfileRepository with the specified file path, allowing + // the application to read and write data to the JSON file. + final profileRepository = ProfileRepository( filePath: filePath, notificationService: notificationService); // Load the user's preferred theme while the splash screen is displayed. @@ -57,7 +57,7 @@ void main() async { ], child: T3Vault( settingsController: settingsController, - memoCardRepository: memoCardRepository, + profileRepository: profileRepository, )), ); } diff --git a/lib/src/app.dart b/lib/src/app.dart index 3b95659..cb20412 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:go_router/go_router.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_memassist/memory_assistant.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/pages/eka_memo_card_practice_page.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/pages/memo_card_decks_page.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/pages/sa0_memo_card_practice_page.dart'; @@ -23,7 +25,6 @@ import 'features/landing/presentation/pages/agreement_page.dart'; import 'features/landing/presentation/pages/home_page.dart'; import 'features/landing/presentation/pages/policy_page.dart'; import 'features/landing/presentation/pages/splash_page.dart'; -import 'features/memorization_assistant/domain/repositories/memo_card_json_repository.dart'; import 'features/memorization_assistant/presentation/blocs/blocs.dart'; import 'features/memorization_assistant/presentation/pages/memo_card_details_page.dart'; import 'features/memorization_assistant/presentation/pages/memo_cards_page.dart'; @@ -31,12 +32,12 @@ import 'features/memorization_assistant/presentation/pages/memo_cards_page.dart' /// The Widget that configures your application. class T3Vault extends StatelessWidget { final SettingsController settingsController; - final MemoCardRepository memoCardRepository; + final ProfileRepository profileRepository; const T3Vault({ super.key, required this.settingsController, - required this.memoCardRepository, + required this.profileRepository, }); @override @@ -56,15 +57,15 @@ class T3Vault extends StatelessWidget { create: (BuildContext context) => FormosaBloc(), ), BlocProvider( - create: (BuildContext context) => GreatWallBloc(), + create: (BuildContext context) => GreatWallBloc(profileRepository), ), BlocProvider( create: (BuildContext context) => - MemoCardSetBloc(memoCardRepository: memoCardRepository), + MemoCardSetBloc(profileRepository: profileRepository), ), BlocProvider( create: (BuildContext context) => - MemoCardRatingBloc(memoCardRepository: memoCardRepository), + MemoCardRatingBloc(profileRepository: profileRepository), ), ], child: Builder( @@ -301,10 +302,15 @@ class T3Vault extends StatelessWidget { path: ConfirmationPage.routeName, pageBuilder: (BuildContext context, GoRouterState state) { - return const MaterialPage( + final args = + state.extra as Map; + final eka = args['eka'] as Eka; + return MaterialPage( restorationId: 'router.root.knowledge.' 'formosa_inputs.confirmation', - child: ConfirmationPage(), + child: ConfirmationPage( + eka: eka, + ), ); }, ), @@ -325,10 +331,15 @@ class T3Vault extends StatelessWidget { path: ConfirmationPage.routeName, pageBuilder: (BuildContext context, GoRouterState state) { - return const MaterialPage( + final args = + state.extra as Map; + final eka = args['eka'] as Eka; + return MaterialPage( restorationId: 'router.root.knowledge.' 'hashviz_inputs.confirmation', - child: ConfirmationPage(), + child: ConfirmationPage( + eka: eka, + ), ); }, ), @@ -340,9 +351,14 @@ class T3Vault extends StatelessWidget { path: ConfirmationPage.routeName, pageBuilder: (BuildContext context, GoRouterState state) { - return const MaterialPage( + final args = + state.extra as Map; + final eka = args['eka'] as Eka; + return MaterialPage( restorationId: 'router.root.confirmation', - child: ConfirmationPage(), + child: ConfirmationPage( + eka: eka, + ), ); }, ), diff --git a/lib/src/common/cryptography/presentation/widgets/password_promt_widget.dart b/lib/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart similarity index 94% rename from lib/src/common/cryptography/presentation/widgets/password_promt_widget.dart rename to lib/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart index c47708a..d2d1972 100644 --- a/lib/src/common/cryptography/presentation/widgets/password_promt_widget.dart +++ b/lib/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -class PasswordPrompt extends StatelessWidget { - const PasswordPrompt({super.key}); +class EkaInputPromtWidget extends StatelessWidget { + const EkaInputPromtWidget({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/src/common/cryptography/presentation/widgets/eka_new_deriation_question_promt_widget.dart b/lib/src/common/cryptography/presentation/widgets/eka_new_deriation_question_promt_widget.dart new file mode 100644 index 0000000..d699e68 --- /dev/null +++ b/lib/src/common/cryptography/presentation/widgets/eka_new_deriation_question_promt_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class EkaNewDeriationQuestionPromtWidget extends StatelessWidget { + const EkaNewDeriationQuestionPromtWidget({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Is this a new derivation?"), + content: const Text(""), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: const Text("No. I already have an EKA."), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: const Text("Yes. First time for these parameters."), + ), + ], + ); + } +} diff --git a/lib/src/common/cryptography/usecases/eka_flow_handler.dart b/lib/src/common/cryptography/usecases/eka_flow_handler.dart new file mode 100644 index 0000000..533f8c9 --- /dev/null +++ b/lib/src/common/cryptography/usecases/eka_flow_handler.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_new_deriation_question_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/input_key_error_promt_widget.dart'; + +class EkaFlowHandler { + static Future showEkaFlow(BuildContext context) async { + String? optionalEka; + // Ask if it's a new derivation + final bool? isANewDerivation = await showDialog( + context: context, + builder: (context) => const EkaNewDeriationQuestionPromtWidget(), + ); + + if (isANewDerivation == null || !isANewDerivation) { // Not a new derivation, prompt for existing EKA + if (!context.mounted) return null; + optionalEka = await showDialog( + context: context, + builder: (context) => const EkaInputPromtWidget(), + ); + } else { // It's a new derivation, generate and show EKA + Eka eka = Eka(); + + // Prompt for optional EKA + if (!context.mounted) return null; + optionalEka = await showDialog( + context: context, + builder: (context) => EKAPromptWidget(eka: eka.key), + ); + + // Ask user to input EKA + if (!context.mounted) return null; + final String? enteredEka = await showDialog( + context: context, + builder: (context) => const EkaInputPromtWidget(), + ); + + // Validate entered EKA + if (enteredEka != eka.key) { + optionalEka = null; + if (!context.mounted) return null; + await showDialog( + context: context, + builder: (context) => const InputKeyErrorPromtWidget(), + ); + } + } + + return optionalEka != null ? Eka.fromKey(optionalEka) : null; + } +} diff --git a/lib/src/common/localization/app_en.arb b/lib/src/common/localization/app_en.arb index c35b4c8..be2100c 100644 --- a/lib/src/common/localization/app_en.arb +++ b/lib/src/common/localization/app_en.arb @@ -76,9 +76,9 @@ "@inputKeyErrorWidgetTitle": { "description": "Decryption Error Widget: Title Dialog" }, - "secretWidgetTitle": "Enter secret:", + "secretWidgetTitle": "Enter EKA:", "@secretWidgetTitle": { - "description": "Secret Widget: Title Dialog" + "description": "EKA Widget: Title Dialog" }, "theme": "Select Theme", @@ -299,7 +299,7 @@ "@inputKeyErrorDescription": { "description": "Decryption Error Widget: Text description" }, - "secretHint": "Enter ephemeral key to decrypt memo data.", + "secretHint": "Enter ephemeral key (EKA).", "@secretHint": { "description": "Secret Widget: Hint secret" }, diff --git a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_bloc.dart b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_bloc.dart index 1049d6d..48ee7b6 100644 --- a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_bloc.dart +++ b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_bloc.dart @@ -1,15 +1,19 @@ +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:great_wall/great_wall.dart'; import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_vault/src/features/greatwall/domain/usecases/tree_input_validator.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart'; import 'bloc.dart'; class GreatWallBloc extends Bloc { GreatWall? _greatWall; int _currentLevel = 1; + final ProfileRepository profileRepository; - GreatWallBloc() : super(GreatWallInitial()) { + GreatWallBloc(this.profileRepository) : super(GreatWallInitial()) { on(_onGreatWallSymmetricToggled); on(_onPasswordVisibilityToggled); on(_onGreatWallArityChanged); @@ -23,6 +27,8 @@ class GreatWallBloc extends Bloc { on(_onKAVisibilityToggled); on(_onGreatWallPracticeLevel); on(_onGreatWallPracticeStepMade); + on(_onGreatWallOngoingDerivationLoadRequested); + on(_onGreatWallOngoingDerivationAdded); } void _onDerivationStepMade( @@ -74,12 +80,20 @@ class GreatWallBloc extends Bloc { GreatWallDerivationStarted event, Emitter emit) async { emit(GreatWallDeriveInProgress()); - await Future.delayed( - const Duration(seconds: 1), - () { - _greatWall!.startDerivation(); - }, - ); + _greatWall!.intermediateStatesStream.listen((List sa1iList) async { + debugPrint("Intermediate states length: ${sa1iList.length}"); + var intermediateStateEntities = sa1iList + .map((sa1i) => IntermediateDerivationStateEntity( + encryptedValue: + sa1i.value.toString(), // TODO: pending to encrypt + currentIteration: sa1i.currentIteration, + totalIterations: sa1i.totalIterations, + )) + .toList(); + await profileRepository.addIntermediateStates(intermediateStateEntities); + }); + + await _greatWall!.startDerivation(); emit( GreatWallDeriveStepSuccess( @@ -167,7 +181,9 @@ class GreatWallBloc extends Bloc { timeLockPuzzleParam: event.timeLockPuzzleParam, tacitKnowledge: event.tacitKnowledge, ); - _greatWall!.sa0 = Sa0(Formosa.fromMnemonic(event.sa0Mnemonic, formosaTheme: FormosaTheme.global)); + _greatWall!.initializeDerivation( + Sa0(Formosa.fromMnemonic(event.sa0Mnemonic, formosaTheme: FormosaTheme.global)), + []); emit( GreatWallInitialSuccess( @@ -195,7 +211,7 @@ class GreatWallBloc extends Bloc { } void _onGreatWallReset(GreatWallReset event, Emitter emit) { - _greatWall!.initialDerivation(); + _greatWall!.resetDerivation(); emit(GreatWallInitial()); } @@ -220,4 +236,20 @@ class GreatWallBloc extends Bloc { ), ); } + + Future _onGreatWallOngoingDerivationLoadRequested( + GreatWallOngoingDerivationLoadRequested event, + Emitter emit, + ) async { + await profileRepository.readProfile(); + return emit(GreatWallOngoingDerivationLoaded(ongoingDerivationEntity: profileRepository.ongoingDerivation)); + } + + Future _onGreatWallOngoingDerivationAdded( + GreatWallOngoingDerivationAdded event, + Emitter emit, + ) async { + await profileRepository.setOngoingDerivation(event.ongoingDerivationEntity); + return emit(GreatWallOngoingDerivationAddSuccess(ongoingDerivationEntity: profileRepository.ongoingDerivation!)); + } } diff --git a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_event.dart b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_event.dart index 5c4abce..5106a11 100644 --- a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_event.dart +++ b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_event.dart @@ -2,6 +2,9 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; import 'package:great_wall/great_wall.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; sealed class GreatWallEvent extends Equatable { @override @@ -41,6 +44,7 @@ final class GreatWallInitialized extends GreatWallEvent { final int timeLockPuzzleParam; final TacitKnowledge tacitKnowledge; final String sa0Mnemonic; + final List intermediateDerivationStates; GreatWallInitialized({ required this.treeArity, @@ -48,11 +52,12 @@ final class GreatWallInitialized extends GreatWallEvent { required this.timeLockPuzzleParam, required this.tacitKnowledge, required this.sa0Mnemonic, + required this.intermediateDerivationStates, }); @override List get props => - [treeDepth, timeLockPuzzleParam, tacitKnowledge, sa0Mnemonic]; + [treeDepth, timeLockPuzzleParam, tacitKnowledge, sa0Mnemonic, intermediateDerivationStates]; } final class GreatWallReset extends GreatWallEvent {} @@ -106,3 +111,23 @@ final class GreatWallPracticeStepMade extends GreatWallEvent { @override List get props => [currentHash, choiceNumber]; } + +final class GreatWallOngoingDerivationLoadRequested extends GreatWallEvent {} + +final class GreatWallOngoingDerivationAdded extends GreatWallEvent { + final OngoingDerivationEntity ongoingDerivationEntity; + + GreatWallOngoingDerivationAdded(this.ongoingDerivationEntity); + + @override + List get props => [ongoingDerivationEntity]; +} + +final class GreatWallOngoingDerivationRemoved extends GreatWallEvent { + final OngoingDerivationEntity ongoingDerivationEntity; + + GreatWallOngoingDerivationRemoved(this.ongoingDerivationEntity); + + @override + List get props => [ongoingDerivationEntity]; +} diff --git a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_state.dart b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_state.dart index 9dc35f6..a07ef4c 100644 --- a/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_state.dart +++ b/lib/src/features/greatwall/presentation/blocs/greatwall/greatwall_state.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; import 'package:great_wall/great_wall.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; sealed class GreatWallState extends Equatable { @override @@ -122,3 +123,27 @@ final class GreatWallPracticeLevelFinish extends GreatWallState { @override List get props => [selectedNode]; } + +final class GreatWallOngoingDerivationLoaded extends GreatWallState { + final OngoingDerivationEntity? ongoingDerivationEntity; + GreatWallOngoingDerivationLoaded({required this.ongoingDerivationEntity}); + + // @override + // List get props => [ongoingDerivationEntity]; +} + +final class GreatWallOngoingDerivationAddSuccess extends GreatWallState { + final OngoingDerivationEntity ongoingDerivationEntity; + GreatWallOngoingDerivationAddSuccess({required this.ongoingDerivationEntity}); + + @override + List get props => [ongoingDerivationEntity]; +} + +final class GreatWallOngoingDerivationRemoveSuccess extends GreatWallState { + final OngoingDerivationEntity ongoingDerivationEntity; + GreatWallOngoingDerivationRemoveSuccess({required this.ongoingDerivationEntity}); + + @override + List get props => [ongoingDerivationEntity]; +} diff --git a/lib/src/features/greatwall/presentation/pages/confirmation_page.dart b/lib/src/features/greatwall/presentation/pages/confirmation_page.dart index eddef40..52ddc94 100644 --- a/lib/src/features/greatwall/presentation/pages/confirmation_page.dart +++ b/lib/src/features/greatwall/presentation/pages/confirmation_page.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_vault/src/features/greatwall/states/derivation_state.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; import '../../../../common/settings/presentation/pages/settings_page.dart'; import '../blocs/blocs.dart'; @@ -11,7 +13,12 @@ import 'derivation_level_page.dart'; class ConfirmationPage extends StatelessWidget { static const routeName = 'confirmation'; - const ConfirmationPage({super.key}); + final Eka eka; + + const ConfirmationPage({ + super.key, + required this.eka, + }); @override Widget build(BuildContext context) { @@ -82,8 +89,26 @@ class ConfirmationPage extends StatelessWidget { const Duration(seconds: 1), () { if (!context.mounted) return; - context.read().updateSa0Mnemonic(state.sa0Mnemonic); - context.read().updateTacitKnowledge(state.tacitKnowledge); + context + .read() + .updateSa0Mnemonic(state.sa0Mnemonic); + context + .read() + .updateTacitKnowledge(state.tacitKnowledge); + context.read().updateEka(eka); + context + .read() + .add(GreatWallOngoingDerivationLoadRequested()); + context.read().add( + GreatWallOngoingDerivationAdded( + OngoingDerivationEntity( + treeArity: state.treeArity, + treeDepth: state.treeDepth, + timeLockPuzzleParam: + state.timeLockPuzzleParam, + tacitKnowledge: state.tacitKnowledge, + encryptedSa0: state.sa0Mnemonic, // TODO: encrypt sa0 + intermediateDerivationStates: []))); context .read() .add(GreatWallDerivationStarted()); @@ -98,7 +123,8 @@ class ConfirmationPage extends StatelessWidget { ), ); } else { - return Center(child: Text(AppLocalizations.of(context)!.noParameters)); + return Center( + child: Text(AppLocalizations.of(context)!.noParameters)); } }, ), diff --git a/lib/src/features/greatwall/presentation/pages/derivation_result_page.dart b/lib/src/features/greatwall/presentation/pages/derivation_result_page.dart index 8ea1d64..0c2b3f1 100644 --- a/lib/src/features/greatwall/presentation/pages/derivation_result_page.dart +++ b/lib/src/features/greatwall/presentation/pages/derivation_result_page.dart @@ -9,7 +9,7 @@ import 'package:provider/provider.dart'; import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_memassist/memory_assistant.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/input_key_error_promt_widget.dart'; -import 'package:t3_vault/src/common/cryptography/presentation/widgets/password_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart'; import 'package:t3_vault/src/features/greatwall/presentation/widgets/deckname_promt_widget.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_promt_widget.dart'; @@ -128,7 +128,7 @@ class DerivationResultPage extends StatelessWidget { if (deckName != null) { String? enteredEKA = await showDialog( context: context, - builder: (context) => const PasswordPrompt(), + builder: (context) => const EkaInputPromtWidget(), ); if (!context.mounted) return; if (enteredEKA != eka.key){ diff --git a/lib/src/features/greatwall/presentation/pages/formosa_tree_inputs_page.dart b/lib/src/features/greatwall/presentation/pages/formosa_tree_inputs_page.dart index 2a48324..3508072 100644 --- a/lib/src/features/greatwall/presentation/pages/formosa_tree_inputs_page.dart +++ b/lib/src/features/greatwall/presentation/pages/formosa_tree_inputs_page.dart @@ -6,6 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:great_wall/great_wall.dart'; import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/sa0_mnemonic_input_widget.dart'; +import 'package:t3_vault/src/common/cryptography/usecases/eka_flow_handler.dart'; import '../../../../common/settings/presentation/pages/settings_page.dart'; import '../../../memorization_assistant/presentation/blocs/blocs.dart'; @@ -61,7 +62,6 @@ class FormosaTreeInputsPage extends StatelessWidget { children: [ BlocBuilder( builder: (context, state) { - if (state is FormosaThemeSelectSuccess) { selectedTheme = state.theme; } @@ -135,30 +135,42 @@ class FormosaTreeInputsPage extends StatelessWidget { return ElevatedButton( onPressed: isButtonEnabled ? () async { - final arity = int.parse(_arityController.text); - final depth = int.parse(_depthController.text); - final timeLock = int.parse(_timeLockController.text); - Future.delayed( - const Duration(seconds: 1), - () { - if (!context.mounted) return; - context.read().add( - GreatWallInitialized( - treeArity: arity, - treeDepth: depth, - timeLockPuzzleParam: timeLock, - tacitKnowledge: FormosaTacitKnowledge( - configs: {'formosaTheme': selectedTheme}, + final Eka? eka = + await EkaFlowHandler.showEkaFlow(context); + if (eka != null) { + final arity = int.parse(_arityController.text); + final depth = int.parse(_depthController.text); + final timeLock = + int.parse(_timeLockController.text); + + Future.delayed( + const Duration(seconds: 1), + () { + if (!context.mounted) return; + context.read().add( + GreatWallInitialized( + treeArity: arity, + treeDepth: depth, + timeLockPuzzleParam: timeLock, + tacitKnowledge: FormosaTacitKnowledge( + configs: { + 'formosaTheme': selectedTheme + }, + ), + sa0Mnemonic: _passwordController.text, + intermediateDerivationStates: const [], ), - sa0Mnemonic: _passwordController.text, - ), - ); - context.go( - '/${KnowledgeTypesPage.routeName}/$routeName/' - '${ConfirmationPage.routeName}', - ); - }, - ); + ); + context.go( + '/${KnowledgeTypesPage.routeName}/$routeName/' + '${ConfirmationPage.routeName}', + extra: { + 'eka': eka, + }, + ); + }, + ); + } } : null, child: Text(AppLocalizations.of(context)!.startDerivation), diff --git a/lib/src/features/greatwall/presentation/pages/hashviz_tree_inputs_page.dart b/lib/src/features/greatwall/presentation/pages/hashviz_tree_inputs_page.dart index ff54c0f..5722fea 100644 --- a/lib/src/features/greatwall/presentation/pages/hashviz_tree_inputs_page.dart +++ b/lib/src/features/greatwall/presentation/pages/hashviz_tree_inputs_page.dart @@ -4,8 +4,9 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:great_wall/great_wall.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/sa0_mnemonic_input_widget.dart'; - +import 'package:t3_vault/src/common/cryptography/usecases/eka_flow_handler.dart'; import '../../../../common/settings/presentation/pages/settings_page.dart'; import '../../../memorization_assistant/presentation/blocs/blocs.dart'; @@ -168,53 +169,65 @@ class HashvizTreeInputsPage extends StatelessWidget { return ElevatedButton( onPressed: isMnemonicValid - ? () { - final arity = int.parse(_arityController.text); - final depth = int.parse(_depthController.text); - final timeLock = - int.parse(_timeLockController.text); - final hashvizSize = - int.parse(_sizeController.text); - final numColors = - int.parse(_colorsNumberController.text); - final saturation = - double.parse(_saturationController.text); - final brightness = - double.parse(_brightnessController.text); - final minHue = int.parse(_minHueController.text); - final maxHue = int.parse(_maxHueController.text); + ? () async { + final Eka? eka = + await EkaFlowHandler.showEkaFlow(context); + if (eka != null) { + final arity = int.parse(_arityController.text); + final depth = int.parse(_depthController.text); + final timeLock = + int.parse(_timeLockController.text); + final hashvizSize = + int.parse(_sizeController.text); + final numColors = + int.parse(_colorsNumberController.text); + final saturation = + double.parse(_saturationController.text); + final brightness = + double.parse(_brightnessController.text); + final minHue = + int.parse(_minHueController.text); + final maxHue = + int.parse(_maxHueController.text); - final isSymmetric = _isSymmetricNotifier.value; + final isSymmetric = _isSymmetricNotifier.value; - Future.delayed( - const Duration(seconds: 1), - () { - if (!context.mounted) return; - context.read().add( - GreatWallInitialized( - treeArity: arity, - treeDepth: depth, - timeLockPuzzleParam: timeLock, - tacitKnowledge: HashVizTacitKnowledge( - configs: { - 'hashvizSize': hashvizSize, - 'isSymmetric': isSymmetric, - 'numColors': numColors, - 'saturation': saturation, - 'brightness': brightness, - 'minHue': minHue, - 'maxHue': maxHue, - }, + Future.delayed( + const Duration(seconds: 1), + () { + if (!context.mounted) return; + context.read().add( + GreatWallInitialized( + treeArity: arity, + treeDepth: depth, + timeLockPuzzleParam: timeLock, + tacitKnowledge: + HashVizTacitKnowledge( + configs: { + 'hashvizSize': hashvizSize, + 'isSymmetric': isSymmetric, + 'numColors': numColors, + 'saturation': saturation, + 'brightness': brightness, + 'minHue': minHue, + 'maxHue': maxHue, + }, + ), + sa0Mnemonic: + _passwordController.text, + intermediateDerivationStates: const [], ), - sa0Mnemonic: _passwordController.text, - ), - ); - context.go( - '/${KnowledgeTypesPage.routeName}/$routeName/' - '${ConfirmationPage.routeName}', - ); - }, - ); + ); + context.go( + '/${KnowledgeTypesPage.routeName}/$routeName/' + '${ConfirmationPage.routeName}', + extra: { + 'eka': eka, + }, + ); + }, + ); + } } : null, child: diff --git a/lib/src/features/greatwall/presentation/widgets/resume_derivation_widget.dart b/lib/src/features/greatwall/presentation/widgets/resume_derivation_widget.dart new file mode 100644 index 0000000..5d500b8 --- /dev/null +++ b/lib/src/features/greatwall/presentation/widgets/resume_derivation_widget.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/input_key_error_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart'; +import 'package:t3_vault/src/features/greatwall/presentation/blocs/blocs.dart'; +import 'package:t3_vault/src/features/greatwall/presentation/pages/confirmation_page.dart'; +import 'package:t3_vault/src/features/greatwall/presentation/pages/knowledge_types_page.dart'; +import 'package:t3_vault/src/features/landing/presentation/pages/home_page.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; + +class ResumeDerivationWidget extends StatelessWidget { + final OngoingDerivationEntity ongoingDerivationEntity; + + const ResumeDerivationWidget( + {super.key, required this.ongoingDerivationEntity}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text("Resume ongoing derivation"), + content: const Text( + "There is an ongoing derivation. Would you like to resume it?"), + actions: [ + TextButton( + onPressed: () { + // TODO: remove ongoing derivation + Navigator.of(context).pop(); + context.go('/${KnowledgeTypesPage.routeName}'); + }, + child: const Text("No"), + ), + TextButton( + onPressed: () async { + String? key = await showDialog( + context: context, + builder: (context) => const EkaInputPromtWidget(), + ); + if (key != null && + key.isNotEmpty && + await isValid(key, ongoingDerivationEntity)) { + List intermediateDerivationStates = []; + for (IntermediateDerivationStateEntity intermediateDerivationStateEntity + in ongoingDerivationEntity.intermediateDerivationStates) { + intermediateDerivationStates.add(Sa1i( + await getIntermediateSateValue( + intermediateDerivationStateEntity.encryptedValue, key), + intermediateDerivationStateEntity.currentIteration, + intermediateDerivationStateEntity.totalIterations)); + } + if (!context.mounted) return; + context.read().add( + GreatWallInitialized( + treeArity: ongoingDerivationEntity.treeArity, + treeDepth: ongoingDerivationEntity.treeDepth, + timeLockPuzzleParam: + ongoingDerivationEntity.timeLockPuzzleParam, + tacitKnowledge: ongoingDerivationEntity.tacitKnowledge, + sa0Mnemonic: ongoingDerivationEntity.encryptedSa0, + intermediateDerivationStates: + intermediateDerivationStates, + ), + ); + + Navigator.of(context).pop(); + context.go( + '/${KnowledgeTypesPage.routeName}/${HomePage.routeName}/' + '${ConfirmationPage.routeName}', + ); + } else { + if (!context.mounted) return; + await showDialog( + context: context, + builder: (context) => const InputKeyErrorPromtWidget(), + ); + } + }, + child: const Text("Yes"), + ), + ], + ); + } + + Future isValid( + String key, OngoingDerivationEntity ongoingDerivationEntity) async { + Eka eka = Eka.fromKey(key); + String ciphertext = + ongoingDerivationEntity.intermediateDerivationStates[0].encryptedValue; + try { + await eka.decrypt(base64Decode(ciphertext)); + return true; + } catch (e) { + debugPrint('Error decryting intermediate state value: $e'); + return false; + } + } + + Future getIntermediateSateValue( + String cipherText, String key) async { + final ephemeralKA = Eka.fromKey(key); + + var critical = await ephemeralKA.decrypt(base64Decode(cipherText)); + return critical.value; + } +} diff --git a/lib/src/features/greatwall/states/derivation_state.dart b/lib/src/features/greatwall/states/derivation_state.dart index 10ea942..d7e3112 100644 --- a/lib/src/features/greatwall/states/derivation_state.dart +++ b/lib/src/features/greatwall/states/derivation_state.dart @@ -1,21 +1,30 @@ import 'package:flutter/material.dart'; import 'package:great_wall/great_wall.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; class DerivationState with ChangeNotifier { String _sa0Mnemonic = ""; TacitKnowledge? _tacitKnowledge; + Eka? _eka; String get sa0Mnemonic => _sa0Mnemonic; TacitKnowledge? get tacitKnowledge => _tacitKnowledge; + Eka? get eka => _eka; + void updateSa0Mnemonic(String sa0Mnemonic) { _sa0Mnemonic = sa0Mnemonic; notifyListeners(); } - void updateTacitKnowledge(TacitKnowledge newValue) { + void updateTacitKnowledge(TacitKnowledge newValue) { _tacitKnowledge = newValue; notifyListeners(); } + + void updateEka(Eka newValue) { + _eka = newValue; + notifyListeners(); + } } \ No newline at end of file diff --git a/lib/src/features/landing/presentation/pages/home_page.dart b/lib/src/features/landing/presentation/pages/home_page.dart index 8ffc6a8..3b42a26 100644 --- a/lib/src/features/landing/presentation/pages/home_page.dart +++ b/lib/src/features/landing/presentation/pages/home_page.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:provider/provider.dart'; import 'package:t3_vault/src/common/notifications/state/notifications_state.dart'; +import 'package:t3_vault/src/features/greatwall/presentation/blocs/greatwall/bloc.dart'; +import 'package:t3_vault/src/features/greatwall/presentation/widgets/resume_derivation_widget.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/pages/memo_card_decks_page.dart'; import '../../../../common/settings/presentation/pages/settings_page.dart'; @@ -16,7 +18,14 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - final notificationHistory = context.watch().notificationHistory; + final notificationHistory = + context.watch().notificationHistory; + + WidgetsBinding.instance.addPostFrameCallback((_) { + context + .read() + .add(GreatWallOngoingDerivationLoadRequested()); + }); return Scaffold( appBar: AppBar( @@ -31,50 +40,64 @@ class HomePage extends StatelessWidget { ], ), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - "assets/images/flutter_logo.png", - width: 100, - height: 100, - ), - const SizedBox(height: 30), - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedButton( - onPressed: () { - context.go('/${KnowledgeTypesPage.routeName}'); - }, - child: Text(AppLocalizations.of(context)!.deriveKeys), - ), - const SizedBox(height: 10), - ElevatedButton( - onPressed: () { - context.go(MemoCardDecksPage.routeName); - }, - child: Stack( - alignment: Alignment.center, - clipBehavior: Clip.none, - children: [ - Text(AppLocalizations.of(context)!.memorizeKeys), - if (notificationHistory.isNotEmpty) - const Positioned( - right: -20, - top: -10, - child: CircleAvatar( - radius: 5, - backgroundColor: Colors.red, - ), - ), - ], - ), + Image.asset( + "assets/images/flutter_logo.png", + width: 100, + height: 100, ), const SizedBox(height: 30), + Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + if (state is GreatWallOngoingDerivationLoaded && + state.ongoingDerivationEntity != null) { + showDialog( + context: context, + builder: (context) => ResumeDerivationWidget( + ongoingDerivationEntity: + state.ongoingDerivationEntity!), + ); + } else { + context.go('/${KnowledgeTypesPage.routeName}'); + } + }, + child: Text(AppLocalizations.of(context)!.deriveKeys), + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () { + context.go(MemoCardDecksPage.routeName); + }, + child: Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + Text(AppLocalizations.of(context)!.memorizeKeys), + if (notificationHistory.isNotEmpty) + const Positioned( + right: -20, + top: -10, + child: CircleAvatar( + radius: 5, + backgroundColor: Colors.red, + ), + ), + ], + ), + ), + const SizedBox(height: 30), + ], + ), ], - ), - ], + ); + }, ), ), ); diff --git a/lib/src/features/memorization_assistant/domain/converters/ongoing_derivation_converter.dart b/lib/src/features/memorization_assistant/domain/converters/ongoing_derivation_converter.dart new file mode 100644 index 0000000..5ac0035 --- /dev/null +++ b/lib/src/features/memorization_assistant/domain/converters/ongoing_derivation_converter.dart @@ -0,0 +1,113 @@ +import 'package:great_wall/great_wall.dart'; +import 'package:t3_crypto_objects/crypto_objects.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; + +class OngoingDerivationConverter { + /// Converts a JSON object into an `OngoingDerivationEntity` instance. + static OngoingDerivationEntity fromJson(Map json) { + return OngoingDerivationEntity( + treeArity: json['treeArity'] as int, + treeDepth: json['treeDepth'] as int, + timeLockPuzzleParam: json['timeLockPuzzleParam'] as int, + tacitKnowledge: getTacitKnowledge(json)!, + encryptedSa0: json['encryptedSa0'] as String, + intermediateDerivationStates: (json['intermediateDerivationStates'] as List) + .map((state) => IntermediateDerivationStateEntity( + encryptedValue: state['value'] as String, + currentIteration: state['currentIteration'] as int, + totalIterations: state['totalIterations'] as int, + )) + .toList(), + ); + } + + /// Converts an `OngoingDerivationEntity` instance into a JSON object. + static Map toJson(OngoingDerivationEntity entity) { + final tacitKnowledge = entity.tacitKnowledge; + final formosaThemeName = tacitKnowledge.configs['formosaTheme']?.name; + final tacitKnowledgeType = _getTacitKnowledgeType(tacitKnowledge); + final tacitKnowledgeConfigs = + _getConfigsWithFormosaThemeName(formosaThemeName, tacitKnowledge); + return { + 'treeArity': entity.treeArity, + 'treeDepth': entity.treeDepth, + 'timeLockPuzzleParam': entity.timeLockPuzzleParam, + 'tacitKnowledgeConfigs': tacitKnowledgeConfigs, + 'tacitKnowledgeType': tacitKnowledgeType, + 'encryptedSa0': entity.encryptedSa0, + 'intermediateDerivationStates': entity.intermediateDerivationStates.map((state) { + return { + 'value': state.encryptedValue, + 'currentIteration': state.currentIteration, + 'totalIterations': state.totalIterations, + }; + }).toList(), + }; + } + +// TODO: move next duplicated methods to an utils file. + + /// Creates a configuration map including the Formosa theme name. + /// + /// Combines existing configurations from [tacitKnowledge] and adds + /// [formosaThemeName], if provided, returning a map containing the + /// configurations. + static Map _getConfigsWithFormosaThemeName( + formosaThemeName, tacitKnowledge) { + final configs = {}; + if (tacitKnowledge.configs != null) { + configs.addAll(tacitKnowledge.configs); + } + if (formosaThemeName != null) { + configs['formosaTheme'] = formosaThemeName; + } + return configs; + } + + /// Identifies the type of tacit knowledge. + /// + /// Checks if [tacitKnowledge] is of type [FormosaTacitKnowledge] or + /// [HashVizTacitKnowledge] and returns a corresponding string or an + /// empty string if unknown. + static String _getTacitKnowledgeType(tacitKnowledge) { + String tacitKnowledgeType; + if (tacitKnowledge is FormosaTacitKnowledge) { + tacitKnowledgeType = 'FormosaTacitKnowledge'; + } else if (tacitKnowledge is HashVizTacitKnowledge) { + tacitKnowledgeType = 'HashVizTacitKnowledge'; + } else { + tacitKnowledgeType = ""; + } + return tacitKnowledgeType; + } + + static TacitKnowledge? getTacitKnowledge(knowledge) { + TacitKnowledge? tacitKnowledge; + + if (knowledge.containsKey('tacitKnowledgeConfigs')) { + final tacitKnowledgeConfigs = knowledge['tacitKnowledgeConfigs']; + final tacitKnowledgeType = knowledge['tacitKnowledgeType']; + + // Create TacitKnowledge instance based on its type + if (tacitKnowledgeType == 'FormosaTacitKnowledge') { + final formosaThemeName = tacitKnowledgeConfigs['formosaTheme']; + FormosaTheme? formosaTheme; + + if (formosaThemeName != null) { + formosaTheme = FormosaTheme.values + .firstWhere((theme) => theme.name == formosaThemeName); + } + tacitKnowledgeConfigs['formosaTheme'] = formosaTheme; + tacitKnowledge = FormosaTacitKnowledge( + configs: tacitKnowledgeConfigs, + ); + } else if (tacitKnowledgeType == 'HashVizTacitKnowledge') { + tacitKnowledge = HashVizTacitKnowledge( + configs: tacitKnowledgeConfigs, + ); + } + } + return tacitKnowledge; + } +} \ No newline at end of file diff --git a/lib/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart b/lib/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart new file mode 100644 index 0000000..7464f97 --- /dev/null +++ b/lib/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart @@ -0,0 +1,12 @@ +/// Represents an intermediate state with additional fields. +class IntermediateDerivationStateEntity { + final String encryptedValue; + final int currentIteration; + final int totalIterations; + + IntermediateDerivationStateEntity({ + required this.encryptedValue, + required this.currentIteration, + required this.totalIterations, + }); +} diff --git a/lib/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart b/lib/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart new file mode 100644 index 0000000..1cde780 --- /dev/null +++ b/lib/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart @@ -0,0 +1,21 @@ +import 'package:great_wall/great_wall.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; + +/// Repository entity that represents an ongoing derivation +class OngoingDerivationEntity { + final int treeArity; + final int treeDepth; + final int timeLockPuzzleParam; + final TacitKnowledge tacitKnowledge; + final String encryptedSa0; + final List intermediateDerivationStates; + + OngoingDerivationEntity({ + required this.treeArity, + required this.treeDepth, + required this.timeLockPuzzleParam, + required this.tacitKnowledge, + required this.encryptedSa0, + required this.intermediateDerivationStates, + }); +} diff --git a/lib/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart b/lib/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart deleted file mode 100644 index 8782b37..0000000 --- a/lib/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart'; -import 'package:t3_memassist/memory_assistant.dart'; -import 'package:t3_vault/src/common/notifications/domain/notifications_service.dart'; -import '../converters/memo_card_json_converter.dart'; - -class MemoCardRepository { - final String filePath; - final Set _memoCards = {}; - final NotificationService notificationService; - - MemoCardRepository({required this.filePath, required this.notificationService}); - - /// Reads memo cards from a JSON file and returns a list of memo cards. - Future readMemoCards() async { - try { - final file = File(filePath); - if (!await file.exists()) { - return []; - } - final contents = await file.readAsString(); - final jsonData = jsonDecode(contents) as List; - - _memoCards.clear(); - for (var json in jsonData) { - final memoCard = MemoCardConverter.fromJson(json); - _memoCards.add(memoCard); - } - return List.unmodifiable(_memoCards); - } catch (e) { - debugPrint('Error reading from repository: $e'); - return []; - } - } - - /// Adds a new memo card to the repository. - Future addMemoCard(List memoCards) async { - _memoCards.addAll(memoCards); - await _writeMemoCards(); - } - - /// Removes a memo card from the repository by ID. - Future removeMemoCard(String id) async { - _memoCards.removeWhere((card) => card.id == id); - await _writeMemoCards(); - } - - /// Updates an existing memo card in the repository. - Future updateMemoCard(MemoCard memoCard) async { - _memoCards.removeWhere((card) => card.id == memoCard.id); - - _memoCards.add(memoCard); - - await _writeMemoCards(); - } - - /// Writes the current list of memo cards to the JSON file. - Future _writeMemoCards() async { - final file = File(filePath); - - final List> jsonData = _memoCards.map((card) { - _scheduleMemoCardReviewNotification(card); - return MemoCardConverter.toJson(card); - }).toList(); - - await file.writeAsString(jsonEncode(jsonData)); - } - - Set get memoCards => _memoCards; - - void _scheduleMemoCardReviewNotification(MemoCard card) { - notificationService.scheduleNotification( - id: card.id.hashCode, - title: 'Time to review ${card.title}', - body: 'You have a card to review!', - scheduledDate: card.due.toLocal(), - payload: jsonEncode(MemoCardConverter.toJson(card)), - ); - } -} diff --git a/lib/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart b/lib/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart new file mode 100644 index 0000000..76e610c --- /dev/null +++ b/lib/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:t3_memassist/memory_assistant.dart'; +import 'package:t3_vault/src/common/notifications/domain/notifications_service.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/converters/ongoing_derivation_converter.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/converters/memo_card_json_converter.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/intermediate_derivation_state_entity.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/entities/ongoing_derivation_entity.dart'; + +class ProfileRepository { + final String filePath; + final Set _memoCards = {}; + OngoingDerivationEntity? _ongoingDerivation; + final NotificationService notificationService; + + ProfileRepository( + {required this.filePath, required this.notificationService}); + + /// Reads memo cards and intermediate states from a JSON file and update [_memoCards] and [_intermediateStates]. + Future readProfile() async { + try { + final file = File(filePath); + if (!await file.exists()) { + return; + } + final contents = await file.readAsString(); + final jsonData = jsonDecode(contents) as Map; + + // Processing memoCards + final memoCardsJson = jsonData['memoCards'] as List; + _memoCards.clear(); + for (var json in memoCardsJson) { + final memoCard = MemoCardConverter.fromJson(json); + _memoCards.add(memoCard); + } + + // Processing ongoingDerivation + if (jsonData['ongoingDerivation'] != null) { + _ongoingDerivation = OngoingDerivationConverter.fromJson(jsonData['ongoingDerivation']); + } + } catch (e) { + debugPrint('Error reading profile: $e'); + } + } + + /// Writes [_memoCards] and [_ongoingDerivation] to the JSON file. + Future writeProfile() async { + final file = File(filePath); + + final jsonData = { + 'memoCards': _memoCards.map((card) { + _scheduleMemoCardReviewNotification(card); + return MemoCardConverter.toJson(card); + }).toList(), + 'ongoingDerivation': _ongoingDerivation != null + ? OngoingDerivationConverter.toJson(_ongoingDerivation!) + : null, + }; + + await file.writeAsString(jsonEncode(jsonData)); + } + + /// Adds a new memo card to the repository. + Future addMemoCards(List memoCards) async { + _memoCards.addAll(memoCards); + await writeProfile(); + } + + /// Removes a memo card from the repository by ID. + Future removeMemoCard(String id) async { + _memoCards.removeWhere((card) => card.id == id); + await writeProfile(); + } + + /// Updates an existing memo card in the repository. + Future updateMemoCard(MemoCard memoCard) async { + _memoCards.removeWhere((card) => card.id == memoCard.id); + _memoCards.add(memoCard); + await writeProfile(); + } + + /// Sets the ongoing derivation in the repository. + Future setOngoingDerivation(OngoingDerivationEntity derivation) async { + _ongoingDerivation = derivation; + await writeProfile(); + } + + /// Adds an intermediate [state] to the [_ongoingDerivation] and save it. + Future addIntermediateStates(List states) async { + if (_ongoingDerivation != null) { + _ongoingDerivation!.intermediateDerivationStates.addAll(states); + } + await writeProfile(); + } + + /// Clears the ongoing derivation. + Future clearOngoingDerivation() async { + _ongoingDerivation = null; + await writeProfile(); + } + + Set get memoCards => Set.unmodifiable(_memoCards); + + OngoingDerivationEntity? get ongoingDerivation => _ongoingDerivation; + + void _scheduleMemoCardReviewNotification(MemoCard card) { + notificationService.scheduleNotification( + id: card.id.hashCode, + title: 'Time to review ${card.title}', + body: 'You have a card to review!', + scheduledDate: card.due.toLocal(), + payload: jsonEncode(MemoCardConverter.toJson(card)), + ); + } +} diff --git a/lib/src/features/memorization_assistant/domain/strategies/sa0_memo_card_strategy.dart b/lib/src/features/memorization_assistant/domain/strategies/sa0_memo_card_strategy.dart index 0749297..55ae176 100644 --- a/lib/src/features/memorization_assistant/domain/strategies/sa0_memo_card_strategy.dart +++ b/lib/src/features/memorization_assistant/domain/strategies/sa0_memo_card_strategy.dart @@ -8,7 +8,7 @@ import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_memassist/memory_assistant.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/input_key_error_promt_widget.dart'; -import 'package:t3_vault/src/common/cryptography/presentation/widgets/password_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/sa0_mnemonic_promt_widget.dart'; import 'package:t3_vault/src/features/memorization_assistant/domain/strategies/memo_card_strategy.dart'; @@ -22,7 +22,7 @@ class Sa0MemoCardStrategy extends MemoCardStrategy { onPressed: () async { String? key = await showDialog( context: context, - builder: (context) => const PasswordPrompt(), + builder: (context) => const EkaInputPromtWidget(), ); if (key != null && key.isNotEmpty) { @@ -53,7 +53,7 @@ class Sa0MemoCardStrategy extends MemoCardStrategy { onPressed: () async { String? key = await showDialog( context: context, - builder: (context) => const PasswordPrompt(), + builder: (context) => const EkaInputPromtWidget(), ); if (key == null || key.isEmpty) { diff --git a/lib/src/features/memorization_assistant/domain/strategies/tacit_knowledge_strategy.dart b/lib/src/features/memorization_assistant/domain/strategies/tacit_knowledge_strategy.dart index 7231820..8895bcf 100644 --- a/lib/src/features/memorization_assistant/domain/strategies/tacit_knowledge_strategy.dart +++ b/lib/src/features/memorization_assistant/domain/strategies/tacit_knowledge_strategy.dart @@ -8,7 +8,7 @@ import 'package:t3_crypto_objects/crypto_objects.dart'; import 'package:t3_memassist/memory_assistant.dart'; import 'package:t3_vault/src/common/cryptography/presentation/widgets/input_key_error_promt_widget.dart'; -import 'package:t3_vault/src/common/cryptography/presentation/widgets/password_promt_widget.dart'; +import 'package:t3_vault/src/common/cryptography/presentation/widgets/eka_input_promt_widget.dart'; import 'package:t3_vault/src/features/greatwall/presentation/blocs/blocs.dart'; import 'package:t3_vault/src/features/memorization_assistant/domain/strategies/memo_card_strategy.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/pages/memo_card_decks_page.dart'; @@ -28,7 +28,7 @@ class TacitKnowledgeStrategy extends MemoCardStrategy { onPressed: () async { String? key = await showDialog( context: context, - builder: (context) => const PasswordPrompt(), + builder: (context) => const EkaInputPromtWidget(), ); if (key != null && key.isNotEmpty && await isValid(key, memoCard)) { @@ -44,6 +44,7 @@ class TacitKnowledgeStrategy extends MemoCardStrategy { timeLockPuzzleParam: 1, tacitKnowledge: tacitKnowledge, sa0Mnemonic: _mockSa0Mnemonic, // not needed sa0 for practice level + intermediateDerivationStates: const [], // not needed for practice level ), ); context.go( diff --git a/lib/src/features/memorization_assistant/presentation/blocs/memo_card_rating/memo_card_rating_bloc.dart b/lib/src/features/memorization_assistant/presentation/blocs/memo_card_rating/memo_card_rating_bloc.dart index 3637c4e..0581cf2 100644 --- a/lib/src/features/memorization_assistant/presentation/blocs/memo_card_rating/memo_card_rating_bloc.dart +++ b/lib/src/features/memorization_assistant/presentation/blocs/memo_card_rating/memo_card_rating_bloc.dart @@ -1,13 +1,13 @@ import 'package:bloc/bloc.dart'; -import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart'; +import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart'; import 'package:t3_vault/src/features/memorization_assistant/presentation/blocs/blocs.dart'; class MemoCardRatingBloc extends Bloc { - final MemoCardRepository memoCardRepository; + final ProfileRepository profileRepository; - MemoCardRatingBloc({required this.memoCardRepository}) + MemoCardRatingBloc({required this.profileRepository}) : super(MemoCardRatedAgain()) { on(_onMemoCardRatingEvent); } @@ -33,6 +33,6 @@ class MemoCardRatingBloc emit(MemoCardRatedEasy()); } - memoCardRepository.updateMemoCard(event.memoCard); + profileRepository.updateMemoCard(event.memoCard); } } diff --git a/lib/src/features/memorization_assistant/presentation/blocs/memo_card_set/memo_card_set_bloc.dart b/lib/src/features/memorization_assistant/presentation/blocs/memo_card_set/memo_card_set_bloc.dart index c942ae3..dbd9985 100644 --- a/lib/src/features/memorization_assistant/presentation/blocs/memo_card_set/memo_card_set_bloc.dart +++ b/lib/src/features/memorization_assistant/presentation/blocs/memo_card_set/memo_card_set_bloc.dart @@ -1,14 +1,15 @@ +import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/profile_json_repository.dart'; + import 'bloc.dart'; import 'package:bloc/bloc.dart'; -import 'package:t3_vault/src/features/memorization_assistant/domain/repositories/memo_card_json_repository.dart'; class MemoCardSetBloc extends Bloc { - final MemoCardRepository memoCardRepository; + final ProfileRepository profileRepository; - MemoCardSetBloc({required this.memoCardRepository}) : super(MemoCardSetEmpty()) { + MemoCardSetBloc({required this.profileRepository}) : super(MemoCardSetEmpty()) { on(_onMemoCardSetEvent); on(_onMemoCardSetEvent); on(_onMemoCardSetEvent); @@ -22,34 +23,32 @@ class MemoCardSetBloc ) async { if (event is MemoCardSetLoadRequested) { - await _loadMemoCardsFromRepository(); - return emit(MemoCardSetChangeNothing(memoCards: memoCardRepository.memoCards)); + await () async { + await profileRepository.readProfile(); + }(); + return emit(MemoCardSetChangeNothing(memoCards: profileRepository.memoCards)); } if (event is MemoCardSetUnchanged) { - return emit(MemoCardSetChangeNothing(memoCards: memoCardRepository.memoCards)); + return emit(MemoCardSetChangeNothing(memoCards: profileRepository.memoCards)); } if (event is MemoCardSetCardsAdding) { - return emit(MemoCardSetAdding(memoCards: memoCardRepository.memoCards)); + return emit(MemoCardSetAdding(memoCards: profileRepository.memoCards)); } if (event is MemoCardSetCardsAdded) { - await memoCardRepository.addMemoCard(event.memoCards); - return emit(MemoCardSetAddSuccess(memoCards: memoCardRepository.memoCards)); + await profileRepository.addMemoCards(event.memoCards); + return emit(MemoCardSetAddSuccess(memoCards: profileRepository.memoCards)); } if (event is MemoCardSetCardRemoved) { - await memoCardRepository.removeMemoCard(event.memoCard.id); - if (memoCardRepository.memoCards.isEmpty) { + await profileRepository.removeMemoCard(event.memoCard.id); + if (profileRepository.memoCards.isEmpty) { return emit(MemoCardSetEmpty()); } else { - return emit(MemoCardSetRemoveSuccess(memoCards: memoCardRepository.memoCards)); + return emit(MemoCardSetRemoveSuccess(memoCards: profileRepository.memoCards)); } } } - - Future _loadMemoCardsFromRepository() async { - await memoCardRepository.readMemoCards(); - } } \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4569fdc..7334164 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ dependencies: great_wall: git: url: git@github.com:T3-InfoSec/great-wall-dart.git - ref: development + ref: issue76_integrate_sa1i t3_memassist: git: url: git@github.com:T3-InfoSec/t3-memassist.git @@ -34,11 +34,12 @@ dependencies: t3_crypto_objects: git: url: git@github.com:T3-InfoSec/t3-crypto-objects-dart.git - ref: development + ref: issue4_create_sa1i mooncake: - git: - url: git@github.com:T3-InfoSec/mooncake.git - ref: development + # git: + # url: git@github.com:T3-InfoSec/mooncake.git + # ref: development + path: ../mooncake # TODO: Delete dev_dependencies: flutter_test: sdk: flutter