diff --git a/lib/activity_log/activity_log.dart b/lib/activity_log/activity_log.dart new file mode 100644 index 000000000..4e830bc56 --- /dev/null +++ b/lib/activity_log/activity_log.dart @@ -0,0 +1,3 @@ +export 'activity_log_manager.dart'; +export 'log_class.dart'; +export 'log_enum.dart'; diff --git a/lib/activity_log/activity_log_manager.dart b/lib/activity_log/activity_log_manager.dart new file mode 100644 index 000000000..2b54f64f3 --- /dev/null +++ b/lib/activity_log/activity_log_manager.dart @@ -0,0 +1,119 @@ +import 'dart:convert'; + +import 'package:altme/activity_log/activity_log.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class ActivityLogManager { + ActivityLogManager(SecureStorageProvider secureStorageProvider) + : _secureStorageProvider = secureStorageProvider; + + final SecureStorageProvider _secureStorageProvider; + + static const int maxLogsPerBatch = 100; + + /// Get logs from single batch + Future> _getLogsForCurrentBatch(int index) async { + final logDataJson = await _secureStorageProvider.get('log_batch_$index'); + + if (logDataJson != null) { + final logStrings = logDataJson.split('\n'); + return logStrings + .map( + (logString) => LogData.fromJson( + jsonDecode(logString) as Map, + ), + ) + .toList(); + } + return []; + } + + /// Set log entry + Future saveLog(LogData log) async { + final currentBatchIndex = await _getCurrentBatchIndex(); + + List logs = await _getLogsForCurrentBatch(currentBatchIndex); + + // If the batch is full, increment the batch index and start a new batch + if (logs.length >= maxLogsPerBatch) { + await _incrementBatchIndex(); + logs = []; + } + + logs.add(log); + + final logJsonList = logs.map((log) => jsonEncode(log.toJson())).toList(); + + await _secureStorageProvider.set( + 'log_batch_$currentBatchIndex', + logJsonList.join('\n'), + ); + } + + /// Get logs from all batches + Future> readAllLogs() async { + final currentBatchIndex = await _getCurrentBatchIndex(); + final allLogs = []; + + for (int i = currentBatchIndex; i == 0; i--) { + final logData = await _getLogsForCurrentBatch(i); + allLogs.addAll(logData.reversed.toList()); + } + return allLogs; + } + + // /// Get paginated logs + // Future> readLogs(int limit, int offset) async { + // final allLogs = []; + + // final currentBatchIndex = await _getCurrentBatchIndex(); + // // Retrieve logs from all batches + // for (int i = 0; i <= currentBatchIndex; i++) { + // final logData = await _secureStorageProvider.get('log_batch_$i'); + // if (logData != null) { + // final logStrings = logData.split('\n'); + // final logs = logStrings + // .map( + // (logString) => LogData.fromJson( + // jsonDecode(logString) as Map, + // ), + // ) + // .toList(); + // allLogs.addAll(logs); + // } + // } + + // // Apply pagination logic (limit and offset) + // // but this logic needs to be improved.. it is paginating later.. + // // we need to consider while fetching + // return allLogs.skip(offset).take(limit).toList(); + // } + + /// Clear all logs + Future clearLogs() async { + final currentBatchIndex = await _getCurrentBatchIndex(); + for (int i = 0; i <= currentBatchIndex; i++) { + await _secureStorageProvider.delete('log_batch_$i'); + } + await _secureStorageProvider.delete('currentBatchIndex'); + } + + /// get Current Batch + Future _getCurrentBatchIndex() async { + final index = await _secureStorageProvider.get('currentBatchIndex'); + if (index != null) { + return int.parse(index); + } + + return 0; + } + + Future _incrementBatchIndex() async { + final currentBatchIndex = await _getCurrentBatchIndex(); + final newBatchIndex = currentBatchIndex + 1; + await _secureStorageProvider.set( + 'currentBatchIndex', + newBatchIndex.toString(), + ); + } +} diff --git a/lib/activity_log/log_class.dart b/lib/activity_log/log_class.dart new file mode 100644 index 000000000..30e9ee41e --- /dev/null +++ b/lib/activity_log/log_class.dart @@ -0,0 +1,55 @@ +import 'package:altme/activity_log/activity_log.dart'; +import 'package:altme/app/app.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'log_class.g.dart'; + +@JsonSerializable() +class LogData extends Equatable { + LogData({ + required this.type, + DateTime? timestamp, + this.vcInfo, + }) : timestamp = timestamp ?? DateTime.now(); + + factory LogData.fromJson(Map json) => + _$LogDataFromJson(json); + + final LogType type; + final DateTime timestamp; + final VCInfo? vcInfo; + + Map toJson() => _$LogDataToJson(this); + + @override + List get props => [ + type, + timestamp, + vcInfo, + ]; +} + +@JsonSerializable() +class VCInfo extends Equatable { + const VCInfo({ + required this.id, + required this.name, + this.domain, + }); + + factory VCInfo.fromJson(Map json) => _$VCInfoFromJson(json); + + final String id; + final String name; + final String? domain; + + Map toJson() => _$VCInfoToJson(this); + + @override + List get props => [ + id, + name, + domain, + ]; +} diff --git a/lib/activity_log/log_enum.dart b/lib/activity_log/log_enum.dart new file mode 100644 index 000000000..1e34b71b8 --- /dev/null +++ b/lib/activity_log/log_enum.dart @@ -0,0 +1,9 @@ +enum LogType { + walletInit, + backupData, + restoreWallet, + addVC, + deleteVC, + presentVC, + importKey, +} diff --git a/lib/app/shared/constants/constants_json.dart b/lib/app/shared/constants/constants_json.dart index b35291a54..aa4f01f25 100644 --- a/lib/app/shared/constants/constants_json.dart +++ b/lib/app/shared/constants/constants_json.dart @@ -305,26 +305,26 @@ abstract class ConstantsJson { static const walletMetadataForIssuers = { 'vp_formats_supported': { 'jwt_vp': { - 'alg_values_supported': ['ES256', 'ES256K', 'EdDSA'], + 'alg': ['ES256', 'ES256K', 'EdDSA'], }, 'jwt_vc': { - 'alg_values_supported': ['ES256', 'ES256K', 'EdDSA'], + 'alg': ['ES256', 'ES256K', 'EdDSA'], }, 'jwt_vp_json': { - 'alg_values_supported': ['ES256', 'ES256K', 'EdDSA'], + 'alg': ['ES256', 'ES256K', 'EdDSA'], }, 'jwt_vc_json': { - 'alg_values_supported': ['ES256', 'ES256K', 'EdDSA'], + 'alg': ['ES256', 'ES256K', 'EdDSA'], }, 'vc+sd-jwt': { - 'alg_values_supported': ['ES256', 'ES256K', 'EdDSA'], + 'alg': ['ES256', 'ES256K', 'EdDSA'], }, 'ldp_vp': { 'proof_type': [ 'JsonWebSignature2020', 'Ed25519Signature2018', 'EcdsaSecp256k1Signature2019', - 'RsaSignature2018', + 'RsaSignature2018' ], }, 'ldp_vc': { @@ -337,13 +337,12 @@ abstract class ConstantsJson { }, }, 'grant_types': ['authorization code', 'pre-authorized_code'], - 'redirect_uris': [Parameters.redirectUri], + 'redirect_uris': [Parameters.authorizationEndPoint], 'subject_syntax_types_supported': ['did:key', 'did:jwk'], 'subject_syntax_types_discriminations': [ 'did:key:jwk_jcs-pub', 'did:ebsi:v1', ], - 'response_types_supported': ['vp_token', 'id_token'], 'token_endpoint_auth_method_supported': [ 'none', 'client_id', @@ -351,10 +350,8 @@ abstract class ConstantsJson { 'client_secret_basic', 'client_secret_jwt', ], - 'credential_offer_endpoint_supported': [ - 'openid-credential-offer://', - 'haip://', - ], + 'credential_offer_endpoint': ['openid-credential-offer://', 'haip://'], + 'client_name': '${Parameters.appName} wallet', 'contacts': ['contact@talao.io'], }; diff --git a/lib/app/shared/date/date.dart b/lib/app/shared/date/date.dart index ec4b6ebf4..ea787ddea 100644 --- a/lib/app/shared/date/date.dart +++ b/lib/app/shared/date/date.dart @@ -42,6 +42,13 @@ class UiDate { return outputFormat.format(dateTime); } + static String formatDatetime(DateTime dateTime) { + final date = '${dateTime.year}.${dateTime.month}.${dateTime.day}'; + final time = '${dateTime.hour}:${dateTime.minute}:${dateTime.second}'; + + return '$date $time'; + } + static String? formatTime(String formattedString) { try { final DateTime dt = diff --git a/lib/app/shared/helper_functions/helper_functions.dart b/lib/app/shared/helper_functions/helper_functions.dart index c1241a1e5..1232a445c 100644 --- a/lib/app/shared/helper_functions/helper_functions.dart +++ b/lib/app/shared/helper_functions/helper_functions.dart @@ -650,7 +650,8 @@ bool isSIOPV2OROIDC4VPUrl(Uri uri) { final isOpenIdUrl = uri.toString().startsWith('openid://?') || uri.toString().startsWith('openid-vc://?') || uri.toString().startsWith('openid-hedera://?') || - uri.toString().startsWith('haip://?'); + uri.toString().startsWith('haip://?') || + uri.toString().startsWith('haip://authorize?'); final isSiopv2Url = uri.toString().startsWith('siopv2://?'); final isAuthorizeEndPoint = diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index e4afc035a..a953346f4 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -5,6 +5,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/connection_bridge/connection_bridge.dart'; @@ -149,6 +150,7 @@ class App extends StatelessWidget { jwtDecode: JWTDecode(), profileCubit: context.read(), walletCubit: context.read(), + activityLogManager: ActivityLogManager(secureStorageProvider), ), ), BlocProvider( @@ -184,6 +186,7 @@ class App extends StatelessWidget { walletCubit: context.read(), oidc4vc: OIDC4VC(), jwtDecode: JWTDecode(), + activityLogManager: ActivityLogManager(secureStorageProvider), ), ), BlocProvider( diff --git a/lib/credentials/cubit/credentials_cubit.dart b/lib/credentials/cubit/credentials_cubit.dart index 2a9aa690e..99491fce4 100644 --- a/lib/credentials/cubit/credentials_cubit.dart +++ b/lib/credentials/cubit/credentials_cubit.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/dashboard/home/tab_bar/credentials/models/activity/activity.dart'; @@ -36,6 +37,7 @@ class CredentialsCubit extends Cubit { required this.profileCubit, required this.oidc4vc, required this.walletCubit, + required this.activityLogManager, }) : super(const CredentialsState()); final CredentialsRepository credentialsRepository; @@ -48,6 +50,7 @@ class CredentialsCubit extends Cubit { final ProfileCubit profileCubit; final OIDC4VC oidc4vc; final WalletCubit walletCubit; + final ActivityLogManager activityLogManager; final log = getLogger('CredentialsCubit'); @@ -196,6 +199,10 @@ class CredentialsCubit extends Cubit { bool showMessage = true, }) async { emit(state.loading()); + + final credential = + state.credentials.where((element) => element.id == id).first; + await credentialsRepository.deleteById(id); final credentials = List.of(state.credentials) ..removeWhere((element) => element.id == id); @@ -203,6 +210,13 @@ class CredentialsCubit extends Cubit { credentials: credentials, blockchainType: blockchainType, ); + + await activityLogManager.saveLog( + LogData( + type: LogType.deleteVC, + vcInfo: VCInfo(id: credential.id, name: credential.getName), + ), + ); emit( state.copyWith( status: CredentialsStatus.delete, @@ -325,6 +339,13 @@ class CredentialsCubit extends Cubit { blockchainType: blockchainType, ); + await activityLogManager.saveLog( + LogData( + type: LogType.addVC, + vcInfo: VCInfo(id: credential.id, name: credential.getName), + ), + ); + emit( state.copyWith( status: showStatus ? CredentialsStatus.insert : CredentialsStatus.idle, diff --git a/lib/dashboard/discover/view/discover_page.dart b/lib/dashboard/discover/view/discover_page.dart index dd916ad15..0d66af193 100644 --- a/lib/dashboard/discover/view/discover_page.dart +++ b/lib/dashboard/discover/view/discover_page.dart @@ -29,14 +29,16 @@ class _DiscoverPageState extends State { Widget build(BuildContext context) { return BasePage( scrollView: false, - padding: Parameters.walletHandlesCrypto - ? EdgeInsets.zero - : const EdgeInsets.fromLTRB( - Sizes.spaceSmall, - Sizes.spaceSmall, - Sizes.spaceSmall, - 0, - ), + padding: + // Parameters.walletHandlesCrypto + // ? EdgeInsets.zero + // : + const EdgeInsets.fromLTRB( + Sizes.spaceSmall, + Sizes.spaceSmall, + Sizes.spaceSmall, + 0, + ), backgroundColor: Colors.transparent, body: BlocListener( listenWhen: (previous, current) { diff --git a/lib/dashboard/discover/view/discover_tab_page.dart b/lib/dashboard/discover/view/discover_tab_page.dart index 824989b11..6e869b3b9 100644 --- a/lib/dashboard/discover/view/discover_tab_page.dart +++ b/lib/dashboard/discover/view/discover_tab_page.dart @@ -3,7 +3,6 @@ import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:webview_flutter/webview_flutter.dart'; class DiscoverTabPage extends StatelessWidget { const DiscoverTabPage({super.key}); @@ -121,36 +120,10 @@ class _DiscoverTabPageViewState extends State child: TabBarView( controller: _tabController, physics: const NeverScrollableScrollPhysics(), - children: [ - const DiscoverPage(), - MWebViewPage( - url: Urls.discoverNftsWebView, - onNavigationRequest: (request) async { - if (!request.url.startsWith( - Urls.discoverCoinsWebView, - )) { - await LaunchUrl.launch(request.url); - return NavigationDecision.prevent; - } else { - return NavigationDecision.navigate; - } - }, - ), - MWebViewPage( - url: Urls.discoverCoinsWebView, - onNavigationRequest: (request) async { - if (!request.url - .startsWith(Urls.discoverCoinsWebView)) { - /// if a link has a different base URL than the - /// current webpage, it should be opened in an - /// external browser because of dynamic links - await LaunchUrl.launch(request.url); - return NavigationDecision.prevent; - } else { - return NavigationDecision.navigate; - } - }, - ), + children: const [ + DiscoverPage(), + NftPage(), + TokensPage(), ], ), ), diff --git a/lib/dashboard/drawer/activity_log/activity_log.dart b/lib/dashboard/drawer/activity_log/activity_log.dart new file mode 100644 index 000000000..016ae5009 --- /dev/null +++ b/lib/dashboard/drawer/activity_log/activity_log.dart @@ -0,0 +1,2 @@ +export 'cubit/activity_log_cubit.dart'; +export 'view/activity_log_page.dart'; diff --git a/lib/dashboard/drawer/activity_log/cubit/activity_log_cubit.dart b/lib/dashboard/drawer/activity_log/cubit/activity_log_cubit.dart new file mode 100644 index 000000000..7e1e4112d --- /dev/null +++ b/lib/dashboard/drawer/activity_log/cubit/activity_log_cubit.dart @@ -0,0 +1,28 @@ +import 'package:altme/activity_log/activity_log.dart'; +import 'package:altme/app/app.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'activity_log_cubit.g.dart'; +part 'activity_log_state.dart'; + +class ActivityLogCubit extends Cubit { + ActivityLogCubit({ + required this.activityLogManager, + }) : super(ActivityLogState()); + + final ActivityLogManager activityLogManager; + + final log = getLogger('ActivityLogCubit'); + + Future getAllLogs() async { + final logs = await activityLogManager.readAllLogs(); + emit( + state.copyWith( + logDatas: logs, + status: AppStatus.populate, + ), + ); + } +} diff --git a/lib/dashboard/drawer/activity_log/cubit/activity_log_state.dart b/lib/dashboard/drawer/activity_log/cubit/activity_log_state.dart new file mode 100644 index 000000000..77c21dab7 --- /dev/null +++ b/lib/dashboard/drawer/activity_log/cubit/activity_log_state.dart @@ -0,0 +1,34 @@ +part of 'activity_log_cubit.dart'; + +@JsonSerializable() +class ActivityLogState extends Equatable { + ActivityLogState({ + this.status = AppStatus.init, + List? logDatas, + }) : logDatas = logDatas ?? []; + + factory ActivityLogState.fromJson(Map json) => + _$ActivityLogStateFromJson(json); + + final AppStatus status; + final List logDatas; + + ActivityLogState loading() { + return copyWith(status: AppStatus.loading); + } + + ActivityLogState copyWith({ + AppStatus? status, + List? logDatas, + }) { + return ActivityLogState( + status: status ?? this.status, + logDatas: logDatas ?? this.logDatas, + ); + } + + Map toJson() => _$ActivityLogStateToJson(this); + + @override + List get props => [status, logDatas]; +} diff --git a/lib/dashboard/drawer/activity_log/view/activity_log_page.dart b/lib/dashboard/drawer/activity_log/view/activity_log_page.dart new file mode 100644 index 000000000..d9010ec34 --- /dev/null +++ b/lib/dashboard/drawer/activity_log/view/activity_log_page.dart @@ -0,0 +1,148 @@ +import 'package:altme/activity_log/activity_log.dart'; +import 'package:altme/app/app.dart'; +import 'package:altme/dashboard/dashboard.dart'; +import 'package:altme/l10n/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:secure_storage/secure_storage.dart'; + +class ActivityLogPage extends StatelessWidget { + const ActivityLogPage({super.key}); + + static Route route() { + return MaterialPageRoute( + builder: (_) => const ActivityLogPage(), + settings: const RouteSettings(name: '/ActivityLogPage'), + ); + } + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ActivityLogCubit( + activityLogManager: ActivityLogManager(getSecureStorage), + ), + child: const ActivityLogView(), + ); + } +} + +class ActivityLogView extends StatefulWidget { + const ActivityLogView({super.key}); + + @override + State createState() => _ActivityLogViewState(); +} + +class _ActivityLogViewState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + (_) async { + await context.read().getAllLogs(); + }, + ); + } + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + final colorScheme = Theme.of(context).colorScheme; + return BlocConsumer( + listener: (context, state) { + if (state.status == AppStatus.loading) { + LoadingView().show(context: context); + } else { + LoadingView().hide(); + } + }, + builder: (context, state) { + return BasePage( + scrollView: false, + title: l10n.activityLog, + titleAlignment: Alignment.topCenter, + titleLeading: const BackLeadingButton(), + body: ListView.builder( + itemCount: state.logDatas.length, + padding: EdgeInsets.zero, + shrinkWrap: true, + physics: const ScrollPhysics(), + itemBuilder: (context, index) { + final LogData logData = state.logDatas[index]; + + var message = ''; + + var credentialName = ''; + var domainName = ''; + + if (logData.vcInfo != null) { + credentialName = logData.vcInfo!.name; + domainName = logData.vcInfo!.domain ?? ''; + } + + switch (logData.type) { + case LogType.walletInit: + message = l10n.walletInitialized; + case LogType.backupData: + message = l10n.backupCredentials; + case LogType.restoreWallet: + message = l10n.restoredCredentials; + case LogType.addVC: + message = l10n.addedCredential(credentialName); + case LogType.deleteVC: + message = l10n.deletedCredential(credentialName); + case LogType.presentVC: + message = + l10n.presentedCredential(credentialName, domainName); + case LogType.importKey: + message = l10n.keysImported; + } + + return Column( + children: [ + BackgroundCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + UiDate.formatDatetime(logData.timestamp), + style: TextStyle( + color: colorScheme.onSurface, + fontSize: 12, + fontWeight: FontWeight.w500, + height: 1.333, + ), + ), + ], + ), + const SizedBox(height: 4), + Container( + alignment: Alignment.centerLeft, + child: Text( + message, + style: TextStyle( + color: colorScheme.onSurface, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.5, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + ], + ); + }, + ), + ); + }, + ); + } +} diff --git a/lib/dashboard/drawer/drawer.dart b/lib/dashboard/drawer/drawer.dart index 13d3ae83b..d89374131 100644 --- a/lib/dashboard/drawer/drawer.dart +++ b/lib/dashboard/drawer/drawer.dart @@ -1,4 +1,5 @@ export 'about_altme/about_altme.dart'; +export 'activity_log/activity_log.dart'; export 'advance_settings/advance_settings.dart'; export 'blockchain_settings/blockchain_settings.dart'; export 'help_center/help_center.dart'; diff --git a/lib/dashboard/drawer/src/view/drawer_page.dart b/lib/dashboard/drawer/src/view/drawer_page.dart index f5477ea3e..cfb0dc05e 100644 --- a/lib/dashboard/drawer/src/view/drawer_page.dart +++ b/lib/dashboard/drawer/src/view/drawer_page.dart @@ -170,6 +170,15 @@ class DrawerView extends StatelessWidget { }, ), const SizedBox(height: Sizes.spaceSmall), + DrawerCategoryItem( + title: l10n.activityLog, + subTitle: l10n.activityLogDescription, + onClick: () { + Navigator.of(context) + .push(ActivityLogPage.route()); + }, + ), + const SizedBox(height: Sizes.spaceSmall), DrawerCategoryItem( title: l10n.resetWallet, subTitle: l10n.resetWalletDescription, diff --git a/lib/dashboard/drawer/wallet_security/backup/backup_credential/cubit/backup_credential_cubit.dart b/lib/dashboard/drawer/wallet_security/backup/backup_credential/cubit/backup_credential_cubit.dart index 1834bc03b..e5256d06c 100644 --- a/lib/dashboard/drawer/wallet_security/backup/backup_credential/cubit/backup_credential_cubit.dart +++ b/lib/dashboard/drawer/wallet_security/backup/backup_credential/cubit/backup_credential_cubit.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/polygon_id/polygon_id.dart'; @@ -22,6 +23,7 @@ class BackupCredentialCubit extends Cubit { required this.credentialsCubit, required this.fileSaver, required this.polygonIdCubit, + required this.activityLogManager, }) : super(const BackupCredentialState()); final SecureStorageProvider secureStorageProvider; @@ -29,6 +31,7 @@ class BackupCredentialCubit extends Cubit { final CredentialsCubit credentialsCubit; final FileSaver fileSaver; final PolygonIdCubit polygonIdCubit; + final ActivityLogManager activityLogManager; Future encryptAndDownloadFile() async { emit(state.loading()); @@ -71,6 +74,7 @@ class BackupCredentialCubit extends Cubit { if (filePath != null && filePath.isEmpty) { emit(state.copyWith(status: AppStatus.idle)); } else { + await activityLogManager.saveLog(LogData(type: LogType.backupData)); emit( state.copyWith( status: AppStatus.success, diff --git a/lib/dashboard/drawer/wallet_security/backup/backup_credential/view/backup_credential_page.dart b/lib/dashboard/drawer/wallet_security/backup/backup_credential/view/backup_credential_page.dart index 762e49d88..97f0272d5 100644 --- a/lib/dashboard/drawer/wallet_security/backup/backup_credential/view/backup_credential_page.dart +++ b/lib/dashboard/drawer/wallet_security/backup/backup_credential/view/backup_credential_page.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/cubit/credentials_cubit.dart'; import 'package:altme/dashboard/drawer/drawer.dart'; @@ -32,6 +33,7 @@ class BackupCredentialPage extends StatelessWidget { credentialsCubit: context.read(), fileSaver: FileSaver.instance, polygonIdCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: const BackupCredentialView(), ); diff --git a/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/cubit/backup_polygon_identity_cubit.dart b/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/cubit/backup_polygon_identity_cubit.dart index ed74498ab..15d4f256a 100644 --- a/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/cubit/backup_polygon_identity_cubit.dart +++ b/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/cubit/backup_polygon_identity_cubit.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/polygon_id/polygon_id.dart'; import 'package:altme/wallet/cubit/wallet_cubit.dart'; @@ -22,6 +23,7 @@ class BackupPolygonIdIdentityCubit extends Cubit { required this.walletCubit, required this.fileSaver, required this.polygonIdCubit, + required this.activityLogManager, }) : super(const BackupPolygonIdIdentityState()); final SecureStorageProvider secureStorageProvider; @@ -29,6 +31,7 @@ class BackupPolygonIdIdentityCubit extends Cubit { final WalletCubit walletCubit; final FileSaver fileSaver; final PolygonIdCubit polygonIdCubit; + final ActivityLogManager activityLogManager; Future saveEncryptedFile() async { emit(state.loading()); @@ -84,6 +87,7 @@ class BackupPolygonIdIdentityCubit extends Cubit { if (filePath != null && filePath.isEmpty) { emit(state.copyWith(status: AppStatus.idle)); } else { + await activityLogManager.saveLog(LogData(type: LogType.backupData)); emit( state.copyWith( status: AppStatus.success, diff --git a/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/view/backup_polygon_identity_page.dart b/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/view/backup_polygon_identity_page.dart index 92a9d2217..4cfbf11bb 100644 --- a/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/view/backup_polygon_identity_page.dart +++ b/lib/dashboard/drawer/wallet_security/backup/backup_polygon_identity/view/backup_polygon_identity_page.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/drawer/drawer.dart'; import 'package:altme/l10n/l10n.dart'; @@ -33,6 +34,7 @@ class BackupPolygonIdIdentityPage extends StatelessWidget { walletCubit: context.read(), fileSaver: FileSaver.instance, polygonIdCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: const BackupPolygonIdIdentityView(), ); diff --git a/lib/dashboard/drawer/wallet_security/restore/restore_credential/cubit/restore_credential_cubit.dart b/lib/dashboard/drawer/wallet_security/restore/restore_credential/cubit/restore_credential_cubit.dart index cf0091b77..76c8c4c30 100644 --- a/lib/dashboard/drawer/wallet_security/restore/restore_credential/cubit/restore_credential_cubit.dart +++ b/lib/dashboard/drawer/wallet_security/restore/restore_credential/cubit/restore_credential_cubit.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:io'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -23,6 +24,7 @@ class RestoreCredentialCubit extends Cubit { required this.cryptoKeys, required this.secureStorageProvider, required this.polygonId, + required this.activityLogManager, }) : super(const RestoreCredentialState()); final WalletCubit walletCubit; @@ -30,6 +32,7 @@ class RestoreCredentialCubit extends Cubit { final CryptocurrencyKeys cryptoKeys; final SecureStorageProvider secureStorageProvider; final PolygonId polygonId; + final ActivityLogManager activityLogManager; void setFilePath({String? filePath}) { emit(state.copyWith(backupFilePath: filePath)); @@ -142,6 +145,8 @@ class RestoreCredentialCubit extends Cubit { await credentialsCubit.loadAllCredentials( blockchainType: walletCubit.state.currentAccount!.blockchainType, ); + + await activityLogManager.saveLog(LogData(type: LogType.restoreWallet)); emit(state.success(recoveredCredentialLength: credentialList.length)); } catch (e) { if (e is MessageHandler) { diff --git a/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_credential_page.dart b/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_credential_page.dart index e70098548..149e4c3db 100644 --- a/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_credential_page.dart +++ b/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_credential_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -34,6 +35,7 @@ class RestoreCredentialPage extends StatelessWidget { walletCubit: context.read(), credentialsCubit: context.read(), polygonId: PolygonId(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: const RestoreCredentialView(), ); diff --git a/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_polygonid_credential_page.dart b/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_polygonid_credential_page.dart index e3d687bb7..356ddc318 100644 --- a/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_polygonid_credential_page.dart +++ b/lib/dashboard/drawer/wallet_security/restore/restore_credential/view/restore_polygonid_credential_page.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -33,6 +34,7 @@ class RestorePolygonIdCredentialPage extends StatelessWidget { walletCubit: context.read(), credentialsCubit: context.read(), polygonId: PolygonId(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: const RestorePolygonIdCredentialView(), ); diff --git a/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart b/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart index 0011a8119..5348f2be2 100644 --- a/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart +++ b/lib/dashboard/home/tab_bar/credentials/models/credential_model/credential_model.dart @@ -242,6 +242,27 @@ class CredentialModel extends Equatable { String get getFormat => format != null ? format! : 'ldp_vc'; + String get getName { + try { + var name = + credentialPreview.credentialSubjectModel.credentialSubjectType.name; + + if (name.isNotEmpty) return name; + + name = display?.name ?? ''; + + if (name.isNotEmpty) return name; + + name = credentialPreview.type.last; + + if (name.isNotEmpty) return name; + + return ''; + } catch (e) { + return ''; + } + } + @override List get props => [ id, diff --git a/lib/dashboard/src/view/dashboard_page.dart b/lib/dashboard/src/view/dashboard_page.dart index 52c8943b7..453bf50fe 100644 --- a/lib/dashboard/src/view/dashboard_page.dart +++ b/lib/dashboard/src/view/dashboard_page.dart @@ -256,10 +256,10 @@ class _DashboardViewState extends State { physics: const NeverScrollableScrollPhysics(), children: [ const HomePage(), - if (Parameters.walletHandlesCrypto) - const DiscoverTabPage() - else - const DiscoverPage(), + // if (Parameters.walletHandlesCrypto) + // const DiscoverTabPage() + // else + const DiscoverPage(), if (Parameters.walletHandlesCrypto) const WertPage() else diff --git a/lib/import_wallet/cubit/import_wallet_cubit.dart b/lib/import_wallet/cubit/import_wallet_cubit.dart index 215603bb6..91cbec46e 100644 --- a/lib/import_wallet/cubit/import_wallet_cubit.dart +++ b/lib/import_wallet/cubit/import_wallet_cubit.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/splash/splash.dart'; @@ -22,6 +23,7 @@ class ImportWalletCubit extends Cubit { required this.homeCubit, required this.walletCubit, required this.splashCubit, + required this.activityLogManager, }) : super(const ImportWalletState()); final DIDKitProvider didKitProvider; @@ -31,6 +33,7 @@ class ImportWalletCubit extends Cubit { final WalletCubit walletCubit; final SplashCubit splashCubit; + final ActivityLogManager activityLogManager; void isMnemonicsOrKeyValid(String value) { //different type of tezos private keys start with 'edsk' , @@ -87,6 +90,7 @@ class ImportWalletCubit extends Cubit { accountType: AccountType.ssi, ); await secureStorageProvider.set(SecureStorageKeys.ssiKey, ssiKey); + await activityLogManager.saveLog(LogData(type: LogType.walletInit)); } /// crypto wallet with unknown blockchain type @@ -115,6 +119,8 @@ class ImportWalletCubit extends Cubit { ); } + await activityLogManager.saveLog(LogData(type: LogType.importKey)); + await homeCubit.emitHasWallet(); emit(state.success()); } catch (e, s) { diff --git a/lib/import_wallet/view/import_from_wallet_page.dart b/lib/import_wallet/view/import_from_wallet_page.dart index 9a6784e99..94ef331a9 100644 --- a/lib/import_wallet/view/import_from_wallet_page.dart +++ b/lib/import_wallet/view/import_from_wallet_page.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log_manager.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/import_wallet/import_wallet.dart'; @@ -48,6 +49,7 @@ class ImportFromWalletPage extends StatelessWidget { homeCubit: context.read(), walletCubit: context.read(), splashCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: ImportFromOtherWalletView( walletTypeModel: walletTypeModel, diff --git a/lib/import_wallet/view/import_wallet_page.dart b/lib/import_wallet/view/import_wallet_page.dart index e3b2da00d..a8d5e5343 100644 --- a/lib/import_wallet/view/import_wallet_page.dart +++ b/lib/import_wallet/view/import_wallet_page.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/import_wallet/import_wallet.dart'; @@ -45,6 +46,7 @@ class ImportWalletPage extends StatelessWidget { homeCubit: context.read(), walletCubit: context.read(), splashCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: ImportWalletView( accountName: accountName, diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 1f91b06a5..f1c8aeb9a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1058,10 +1058,10 @@ "toStopDisplayingThisPopupDeactivateTheDeveloperModeInTheSettings": "To stop displaying this popup, deactivate the 'developer mode' in the settings.", "warningDialogSubtitle": "The recovery page contains sensitive information. Please, make sure to keep it private.", "accountPrivateKeyAlert": "The recovery page contains sensitive information. Please, make sure to keep it private.", - "etherlinkNetwork": "Ethereum Network", - "etherlinkAccount": "Ethereum account", - "etherlinkAccountDescription": "Create a new Ethereum blockchain address", - "etherlinkAccountCreationCongratulations": "Your new Ethereum account has been successfully created.", + "etherlinkNetwork": "Etherlink Network", + "etherlinkAccount": "Etherlink account", + "etherlinkAccountDescription": "Create a new Etherlink blockchain address", + "etherlinkAccountCreationCongratulations": "Your new Etherlink account has been successfully created.", "etherlinkProofMessage": "", "notification": "Notification", "notifications": "Notifications", @@ -1076,14 +1076,50 @@ "keyBindingPayload": "Key Binding Payload", "ebsiV4DecentralizedId": "did:key EBSI V4 P-256", "noNotificationsYet": "No notifications yet", - "approveProfileTitle": "Install configuration", - "approveProfileDescription": "Do you consent to install the configuration of {company}?", + "approveProfileTitle": "Install configuration", + "approveProfileDescription": "Do you consent to install the configuration of {company}?", "@approveProfileDescription": { "description": "name of the company owning the configuration", "type": "text", "placeholders": { "company": {} } - } - + }, + "activityLog": "Activity Log", + "activityLogDescription": "See your activities", + "walletInitialized": "Wallet Initialized", + "backupCredentials": "Backup Credentials", + "restoredCredentials": "Restored Credentials", + "addedCredential": "Added credential {credential}", + "@addedCredentialDescription": { + "description": "name of the credential", + "type": "text", + "placeholders": { + "credential": {} + } + }, + "deletedCredential": "Deleted credential {credential}", + "@deletedCredentialDescription": { + "description": "name of the credential", + "type": "text", + "placeholders": { + "credential": {} + } + }, + "presentedCredential": "Presented credential {credential} to {domain}", + "@presentedCredentialDescription": { + "description": "name of the credential", + "type": "text", + "placeholders": { + "credential": {} + } + }, + "@presentedCredentialDomain": { + "description": "name of the credential", + "type": "text", + "placeholders": { + "domain": {} + } + }, + "keysImported": "Keys imported" } diff --git a/lib/l10n/untranslated.json b/lib/l10n/untranslated.json index f7eda209f..6fc0fbf6e 100644 --- a/lib/l10n/untranslated.json +++ b/lib/l10n/untranslated.json @@ -47,7 +47,16 @@ "ebsiV4DecentralizedId", "noNotificationsYet", "approveProfileTitle", - "approveProfileDescription" + "approveProfileDescription", + "activityLog", + "activityLogDescription", + "walletInitialized", + "backupCredentials", + "restoredCredentials", + "addedCredential", + "deletedCredential", + "presentedCredential", + "keysImported" ], "es": [ @@ -98,7 +107,16 @@ "ebsiV4DecentralizedId", "noNotificationsYet", "approveProfileTitle", - "approveProfileDescription" + "approveProfileDescription", + "activityLog", + "activityLogDescription", + "walletInitialized", + "backupCredentials", + "restoredCredentials", + "addedCredential", + "deletedCredential", + "presentedCredential", + "keysImported" ], "fr": [ @@ -154,6 +172,15 @@ "ebsiV4DecentralizedId", "noNotificationsYet", "approveProfileTitle", - "approveProfileDescription" + "approveProfileDescription", + "activityLog", + "activityLogDescription", + "walletInitialized", + "backupCredentials", + "restoredCredentials", + "addedCredential", + "deletedCredential", + "presentedCredential", + "keysImported" ] } diff --git a/lib/matrix_notification/view/notification_details_page.dart b/lib/matrix_notification/view/notification_details_page.dart index bad8111cd..c213e61dd 100644 --- a/lib/matrix_notification/view/notification_details_page.dart +++ b/lib/matrix_notification/view/notification_details_page.dart @@ -1,6 +1,7 @@ import 'package:altme/app/app.dart'; import 'package:altme/chat_room/widget/mxc_image.dart'; import 'package:altme/l10n/l10n.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; @@ -26,29 +27,99 @@ class NotificationDetailsPage extends StatelessWidget { final l10n = context.l10n; final colorScheme = Theme.of(context).colorScheme; - return BasePage( - title: l10n.notification, - scrollView: false, - titleLeading: const BackLeadingButton(), - titleAlignment: Alignment.topCenter, - padding: const EdgeInsets.all(Sizes.spaceSmall), - body: message is TextMessage - ? Text( - (message as TextMessage).text, - style: TextStyle( - color: colorScheme.onSurface, - fontSize: 16, - fontWeight: FontWeight.w500, - height: 1.5, + if (message is ImageMessage) { + return BasePage( + title: l10n.notification, + scrollView: false, + titleLeading: const BackLeadingButton(), + titleAlignment: Alignment.topCenter, + padding: const EdgeInsets.all(Sizes.spaceSmall), + body: MxcImage( + url: (message as ImageMessage).uri, + event: message.metadata!['event'] as Event, + fit: BoxFit.contain, + ), + ); + } else if (message is TextMessage) { + final sentence = (message as TextMessage).text; + + final emailRegExp = + RegExp(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'); + final urlRegExp = RegExp(r'(https?:\/\/[^\s]+)'); + + final style = TextStyle( + color: colorScheme.onSurface, + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.5, + ); + + final textSpans = []; + int lastMatchEnd = 0; + + final matches = [ + ...emailRegExp.allMatches(sentence), + ...urlRegExp.allMatches(sentence), + ]..sort( + (a, b) => a.start.compareTo(b.start), + ); + + // Add non-matching parts of the sentence + for (final match in matches) { + if (match.start > lastMatchEnd) { + textSpans.add( + TextSpan( + text: sentence.substring(lastMatchEnd, match.start), + style: style, + ), + ); + } + + final matchedText = match.group(0); + if (matchedText != null) { + if (emailRegExp.hasMatch(matchedText)) { + textSpans.add( + TextSpan( + text: matchedText, + style: style.copyWith(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () => LaunchUrl.launch('mailto:$matchedText'), ), - ) - : message is ImageMessage - ? MxcImage( - url: (message as ImageMessage).uri, - event: message.metadata!['event'] as Event, - fit: BoxFit.contain, - ) - : Container(), - ); + ); + } else if (urlRegExp.hasMatch(matchedText)) { + textSpans.add( + TextSpan( + text: matchedText, + style: style.copyWith(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () => LaunchUrl.launch(matchedText), + ), + ); + } + } + lastMatchEnd = match.end; + } + + // Add remaining text after the last match + if (lastMatchEnd < sentence.length) { + textSpans.add( + TextSpan( + text: sentence.substring(lastMatchEnd), + style: style, + ), + ); + } + + return BasePage( + title: l10n.notification, + scrollView: false, + titleLeading: const BackLeadingButton(), + titleAlignment: Alignment.topCenter, + padding: const EdgeInsets.all(Sizes.spaceSmall), + body: RichText(text: TextSpan(children: textSpans)), + ); + } else { + return Container(); + } } } diff --git a/lib/matrix_notification/view/notification_page.dart b/lib/matrix_notification/view/notification_page.dart index 798312979..b3ca18956 100644 --- a/lib/matrix_notification/view/notification_page.dart +++ b/lib/matrix_notification/view/notification_page.dart @@ -130,7 +130,6 @@ class _NotificationViewState physics: const ScrollPhysics(), itemBuilder: (context, index) { final Message message = state.messages[index]; - return Column( children: [ TransparentInkWell( @@ -189,15 +188,13 @@ class _NotificationViewState ), ), if (message is ImageMessage) - Container( - alignment: Alignment.centerLeft, - height: 40, - width: 40, + SizedBox( + width: double.infinity, child: MxcImage( url: message.uri, event: message.metadata!['event'] as Event, - fit: BoxFit.contain, + fit: BoxFit.fitWidth, ), ), ], diff --git a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart index 256ee6d42..7cd75cf25 100644 --- a/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart +++ b/lib/onboarding/gen_phrase/cubit/onboarding_gen_phrase_cubit.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/matrix_notification/matrix_notification.dart'; @@ -24,6 +25,7 @@ class OnBoardingGenPhraseCubit extends Cubit { required this.altmeChatSupportCubit, required this.matrixNotificationCubit, required this.profileCubit, + required this.activityLogManager, }) : super(const OnBoardingGenPhraseState()); final KeyGenerator keyGenerator; @@ -35,6 +37,7 @@ class OnBoardingGenPhraseCubit extends Cubit { final AltmeChatSupportCubit altmeChatSupportCubit; final MatrixNotificationCubit matrixNotificationCubit; final ProfileCubit profileCubit; + final ActivityLogManager activityLogManager; final log = getLogger('OnBoardingGenPhraseCubit'); @@ -51,6 +54,7 @@ class OnBoardingGenPhraseCubit extends Cubit { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await profileCubit.secureStorageProvider.set( diff --git a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart index 8e4c268ed..98174fd7d 100644 --- a/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart +++ b/lib/onboarding/gen_phrase/view/onboarding_gen_phrase.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log_manager.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/l10n/l10n.dart'; @@ -12,6 +13,7 @@ import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; class OnBoardingGenPhrasePage extends StatelessWidget { const OnBoardingGenPhrasePage({super.key}); @@ -33,6 +35,7 @@ class OnBoardingGenPhrasePage extends StatelessWidget { altmeChatSupportCubit: context.read(), matrixNotificationCubit: context.read(), profileCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: const OnBoardingGenPhraseView(), ); diff --git a/lib/onboarding/helper_function/helper_function.dart b/lib/onboarding/helper_function/helper_function.dart index 96806f951..d5568bb49 100644 --- a/lib/onboarding/helper_function/helper_function.dart +++ b/lib/onboarding/helper_function/helper_function.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/matrix_notification/matrix_notification.dart'; @@ -16,6 +17,7 @@ Future generateAccount({ required AltmeChatSupportCubit altmeChatSupportCubit, required MatrixNotificationCubit matrixNotificationCubit, required ProfileCubit profileCubit, + required ActivityLogManager activityLogManager, }) async { final mnemonicFormatted = mnemonic.join(' '); @@ -34,6 +36,8 @@ Future generateAccount({ await profileCubit.secureStorageProvider .set(SecureStorageKeys.ssiKey, ssiKey); + await activityLogManager.saveLog(LogData(type: LogType.walletInit)); + /// create profile await profileCubit.load(); diff --git a/lib/onboarding/protect_wallet/view/protect_wallet_page.dart b/lib/onboarding/protect_wallet/view/protect_wallet_page.dart index 273dc627d..9291d2f2c 100644 --- a/lib/onboarding/protect_wallet/view/protect_wallet_page.dart +++ b/lib/onboarding/protect_wallet/view/protect_wallet_page.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log_manager.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/import_wallet/import_wallet.dart'; @@ -14,6 +15,7 @@ import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; class ProtectWalletPage extends StatelessWidget { const ProtectWalletPage({ @@ -46,6 +48,7 @@ class ProtectWalletPage extends StatelessWidget { altmeChatSupportCubit: context.read(), matrixNotificationCubit: context.read(), profileCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: Builder( builder: (context) { diff --git a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart index 10dc19db4..46e154dad 100644 --- a/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart +++ b/lib/onboarding/verify_phrase/cubit/onboarding_verify_phrase_cubit.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/flavor/flavor.dart'; @@ -26,6 +27,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { required this.altmeChatSupportCubit, required this.matrixNotificationCubit, required this.profileCubit, + required this.activityLogManager, }) : super(OnBoardingVerifyPhraseState()); final KeyGenerator keyGenerator; @@ -38,6 +40,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { final AltmeChatSupportCubit altmeChatSupportCubit; final MatrixNotificationCubit matrixNotificationCubit; final ProfileCubit profileCubit; + final ActivityLogManager activityLogManager; final log = getLogger('OnBoardingVerifyPhraseCubit'); @@ -123,6 +126,7 @@ class OnBoardingVerifyPhraseCubit extends Cubit { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); } await profileCubit.secureStorageProvider.set( diff --git a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart index f17bbd366..c35da1fee 100644 --- a/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart +++ b/lib/onboarding/verify_phrase/view/onboarding_verify_phrase.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/dashboard/dashboard.dart'; import 'package:altme/flavor/flavor.dart'; @@ -12,6 +13,7 @@ import 'package:did_kit/did_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:key_generator/key_generator.dart'; +import 'package:secure_storage/secure_storage.dart'; class OnBoardingVerifyPhrasePage extends StatelessWidget { const OnBoardingVerifyPhrasePage({ @@ -48,6 +50,7 @@ class OnBoardingVerifyPhrasePage extends StatelessWidget { altmeChatSupportCubit: context.read(), matrixNotificationCubit: context.read(), profileCubit: context.read(), + activityLogManager: ActivityLogManager(getSecureStorage), ), child: Builder( builder: (context) { diff --git a/lib/scan/cubit/scan_cubit.dart b/lib/scan/cubit/scan_cubit.dart index 9a2ef63a2..ec1c6e509 100644 --- a/lib/scan/cubit/scan_cubit.dart +++ b/lib/scan/cubit/scan_cubit.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/credentials/cubit/credentials_cubit.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -40,6 +41,7 @@ class ScanCubit extends Cubit { required this.walletCubit, required this.oidc4vc, required this.jwtDecode, + required this.activityLogManager, }) : super(const ScanState()); final DioClient client; @@ -50,6 +52,7 @@ class ScanCubit extends Cubit { final WalletCubit walletCubit; final OIDC4VC oidc4vc; final JWTDecode jwtDecode; + final ActivityLogManager activityLogManager; Future credentialOfferOrPresent({ required Uri uri, @@ -287,6 +290,7 @@ class ScanCubit extends Cubit { await presentationActivity( credentialModels: credentialsToBePresented, issuer: issuer, + uri: uri, ); } emit(state.copyWith(status: ScanStatus.success)); @@ -373,6 +377,7 @@ class ScanCubit extends Cubit { await presentationActivity( credentialModels: credentialsToBePresented, issuer: issuer, + uri: Uri.parse(url), ); String? responseMessage; @@ -627,6 +632,7 @@ class ScanCubit extends Cubit { await presentationActivity( credentialModels: credentialsToBePresented, issuer: issuer, + uri: uri, ); emit( state.copyWith( @@ -659,6 +665,7 @@ class ScanCubit extends Cubit { await presentationActivity( credentialModels: credentialsToBePresented, issuer: issuer, + uri: uri, ); String? url; @@ -981,18 +988,34 @@ class ScanCubit extends Cubit { Future presentationActivity({ required List credentialModels, required Issuer issuer, + required Uri uri, }) async { final log = getLogger('ScanCubit'); log.i('adding presentation Activity'); for (final credentialModel in credentialModels) { + final now = DateTime.now(); + final Activity activity = Activity( - presentation: Presentation( - issuer: issuer, - presentedAt: DateTime.now(), - ), + presentation: Presentation(issuer: issuer, presentedAt: now), ); credentialModel.activities.add(activity); + final String responseOrRedirectUri = + uri.queryParameters['redirect_uri'] ?? + uri.queryParameters['response_uri']!; + + await activityLogManager.saveLog( + LogData( + type: LogType.presentVC, + timestamp: now, + vcInfo: VCInfo( + id: credentialModel.id, + name: credentialModel.getName, + domain: Uri.parse(responseOrRedirectUri).origin, + ), + ), + ); + log.i('presentation activity added to the credential'); await credentialsCubit.updateCredential( credential: credentialModel, diff --git a/packages/oidc4vc/lib/src/oidc4vc.dart b/packages/oidc4vc/lib/src/oidc4vc.dart index 1f2770897..327e0f59a 100644 --- a/packages/oidc4vc/lib/src/oidc4vc.dart +++ b/packages/oidc4vc/lib/src/oidc4vc.dart @@ -1299,6 +1299,8 @@ class OIDC4VC { publicKeyJwk['crv'] = 'P-256K'; } + publicKeyJwk.remove('kid'); + late final bool isVerified; if (kty == 'OKP') { isVerified = verifyTokenEdDSA( @@ -1931,7 +1933,13 @@ class OIDC4VC { // final value = {'expiry': expiry, 'data': response.data}; // await secureStorageProvider.set(uri, jsonEncode(value)); - response = await dio.get(uri); + response = await dio.get( + uri, + options: Options().copyWith( + sendTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 10), + ), + ); return response.data; } on FormatException { diff --git a/pubspec.lock b/pubspec.lock index 9955fff6f..981c9029e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -532,10 +532,10 @@ packages: dependency: "direct main" description: name: dart_jsonwebtoken - sha256: "346e9a21e4bf6e6a431e19ece00ebb2e3668e1e339cabdf6f46d18d88692a848" + sha256: adf073720e491d64fa599942615b919915710af2d809b2798146f9b7c4330f3f url: "https://pub.dev" source: hosted - version: "2.14.0" + version: "2.14.1" dart_style: dependency: transitive description: @@ -827,18 +827,18 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4+1" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -851,10 +851,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" fixnum: dependency: transitive description: @@ -1343,10 +1343,10 @@ packages: dependency: transitive description: name: injectable - sha256: "69874ba3ec10e3a0de3f519a184442878291d928f3299d718813f24642585198" + sha256: "5e1556ea1d374fe44cbe846414d9bab346285d3d8a1da5877c01ad0774006068" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.5.0" intl: dependency: "direct overridden" description: @@ -1493,10 +1493,10 @@ packages: dependency: transitive description: name: local_auth_darwin - sha256: "7ba5738c874ca2b910d72385d00d2bebad9d4e807612936cf5e32bc01a048c71" + sha256: "6d2950da311d26d492a89aeb247c72b4653ddc93601ea36a84924a396806d49c" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" local_auth_platform_interface: dependency: transitive description: @@ -2163,10 +2163,10 @@ packages: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_linux: dependency: transitive description: @@ -2641,10 +2641,10 @@ packages: dependency: "direct main" description: name: webview_flutter_android - sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8" + sha256: ed021f27ae621bc97a6019fb601ab16331a3db4bf8afa305e9f6689bdb3edced url: "https://pub.dev" source: hosted - version: "3.16.7" + version: "3.16.8" webview_flutter_platform_interface: dependency: transitive description: @@ -2705,10 +2705,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e47219e78..54fba7978 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: altme description: AltMe Flutter App -version: 2.14.0+519 +version: 2.14.4+523 publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: diff --git a/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart b/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart index f88021152..8478a4327 100644 --- a/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart +++ b/test/onboarding/gen_phrase/view/onboarding_gen_phrase_test.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -68,6 +69,8 @@ class MockProfileCubit extends MockCubit implements ProfileCubit { final state = ProfileState(model: ProfileModel.empty()); } +class MockActivityLogManager extends Mock implements ActivityLogManager {} + void main() { late DIDKitProvider didKitProvider; late KeyGenerator keyGenerator; @@ -78,6 +81,7 @@ void main() { late MatrixNotificationCubit matrixNotificationCubit; late ProfileCubit profileCubit; late OnboardingCubit onboardingCubit; + late MockActivityLogManager activityLogManager; setUpAll(() { WidgetsFlutterBinding.ensureInitialized(); @@ -90,6 +94,7 @@ void main() { matrixNotificationCubit = MockMatrixNotificationCubit(); profileCubit = MockProfileCubit(); onboardingCubit = OnboardingCubit(); + activityLogManager = MockActivityLogManager(); }); group('OnBoarding GenPhrase Page', () { @@ -107,6 +112,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); navigator = MockNavigator(); when(navigator.canPop).thenReturn(true); diff --git a/test/onboarding/helper_function/helper_function_test.dart b/test/onboarding/helper_function/helper_function_test.dart index ded3ffbd1..fb3eaf97c 100644 --- a/test/onboarding/helper_function/helper_function_test.dart +++ b/test/onboarding/helper_function/helper_function_test.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -65,6 +66,8 @@ class MockLangCubit extends MockCubit implements LangCubit {} class MockOIDC4VC extends Mock implements OIDC4VC {} +class MockActivityLogManager extends Mock implements ActivityLogManager {} + void main() { group('generateAccount', () { late KeyGenerator keyGenerator; @@ -76,6 +79,7 @@ void main() { late MatrixNotificationCubit matrixNotificationCubit; late ProfileCubit profileCubit; late MockSecureStorageProvider secureStorageProvider; + late MockActivityLogManager activityLogManager; setUp(() { keyGenerator = KeyGenerator(); @@ -86,6 +90,7 @@ void main() { altmeChatSupportCubit = MockAltmeChatSupportCubit(); matrixNotificationCubit = MockMatrixNotificationCubit(); secureStorageProvider = MockSecureStorageProvider(); + activityLogManager = MockActivityLogManager(); when(() => secureStorageProvider.get(any())).thenAnswer((_) async => ''); @@ -116,6 +121,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); verify( @@ -153,6 +159,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); expect(profileCubit.state.model.walletType, WalletType.enterprise); diff --git a/test/onboarding/protect_wallet/view/protect_wallet_page_test.dart b/test/onboarding/protect_wallet/view/protect_wallet_page_test.dart index 75d95f247..facd03233 100644 --- a/test/onboarding/protect_wallet/view/protect_wallet_page_test.dart +++ b/test/onboarding/protect_wallet/view/protect_wallet_page_test.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:altme/activity_log/activity_log_manager.dart'; import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -74,6 +75,8 @@ class MockLangCubit extends MockCubit implements LangCubit {} class MockOIDC4VC extends Mock implements OIDC4VC {} +class MockActivityLogManager extends Mock implements ActivityLogManager {} + void main() { late DIDKitProvider didKitProvider; late KeyGenerator keyGenerator; @@ -85,6 +88,7 @@ void main() { late OnboardingCubit onboardingCubit; late MockSecureStorageProvider secureStorageProvider; late MockOIDC4VC oidc4vc; + late MockActivityLogManager activityLogManager; const mnemonicString = 'notice photo opera keen climb agent soft parrot best joke field devote'; @@ -109,6 +113,7 @@ void main() { onboardingCubit = OnboardingCubit(); secureStorageProvider = MockSecureStorageProvider(); oidc4vc = MockOIDC4VC(); + activityLogManager = MockActivityLogManager(); when(() => secureStorageProvider.get(any())).thenAnswer((_) async => ''); @@ -206,6 +211,7 @@ void main() { secureStorageProvider: secureStorageProvider, langCubit: MockLangCubit(), ), + activityLogManager: activityLogManager, ), ), BlocProvider.value(value: homeCubit), @@ -250,6 +256,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -294,6 +301,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -338,6 +346,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -394,6 +403,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -450,6 +460,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -506,6 +517,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -563,6 +575,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -629,6 +642,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -684,6 +698,7 @@ void main() { altmeChatSupportCubit: altmeChatSupportCubit, matrixNotificationCubit: matrixNotificationCubit, profileCubit: profileCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( diff --git a/test/onboarding/verify_phrase/view/onboarding_verify_phrase_test.dart b/test/onboarding/verify_phrase/view/onboarding_verify_phrase_test.dart index c435bff6a..4f111406e 100644 --- a/test/onboarding/verify_phrase/view/onboarding_verify_phrase_test.dart +++ b/test/onboarding/verify_phrase/view/onboarding_verify_phrase_test.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/app.dart'; import 'package:altme/chat_room/chat_room.dart'; import 'package:altme/dashboard/dashboard.dart'; @@ -78,6 +79,8 @@ class MockLangCubit extends MockCubit implements LangCubit {} class MockOIDC4VC extends Mock implements OIDC4VC {} +class MockActivityLogManager extends Mock implements ActivityLogManager {} + void main() { late MockDIDKitProvider didKitProvider; late KeyGenerator keyGenerator; @@ -91,6 +94,7 @@ void main() { late MockNavigator navigator; late MockSecureStorageProvider secureStorageProvider; late MockOIDC4VC oidc4vc; + late MockActivityLogManager activityLogManager; const mnemonicString = 'notice photo opera keen climb agent soft parrot best joke field devote'; @@ -109,6 +113,7 @@ void main() { navigator = MockNavigator(); secureStorageProvider = MockSecureStorageProvider(); oidc4vc = MockOIDC4VC(); + activityLogManager = MockActivityLogManager(); }); group('Onboarding Verify Phrase Test', () { @@ -183,6 +188,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ), ), BlocProvider.value(value: homeCubit), @@ -230,6 +236,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -277,6 +284,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -356,6 +364,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -429,6 +438,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( @@ -483,6 +493,7 @@ void main() { langCubit: MockLangCubit(), ), flavorCubit: flavorCubit, + activityLogManager: activityLogManager, ); await tester.pumpApp( diff --git a/test/scan/cubit/scan_cubit.dart b/test/scan/cubit/scan_cubit.dart index 29daade2e..ddbc6614d 100644 --- a/test/scan/cubit/scan_cubit.dart +++ b/test/scan/cubit/scan_cubit.dart @@ -1,3 +1,4 @@ +import 'package:altme/activity_log/activity_log.dart'; import 'package:altme/app/shared/shared.dart'; import 'package:altme/credentials/credentials.dart'; import 'package:altme/dashboard/profile/profile.dart'; @@ -26,6 +27,8 @@ class MockOIDC4VC extends Mock implements OIDC4VC {} class MockJWTDecode extends Mock implements JWTDecode {} +class MockActivityLogManager extends Mock implements ActivityLogManager {} + void main() { group('ScanCubit', () { late MockDioClient mockDioClient; @@ -37,6 +40,7 @@ void main() { late MockOIDC4VC mockOIDC4VC; late MockJWTDecode mockJWTDecode; late ScanCubit scanCubit; + late ActivityLogManager activityLogManager; setUp(() { mockDioClient = MockDioClient(); @@ -47,6 +51,7 @@ void main() { mockWalletCubit = MockWalletCubit(); mockOIDC4VC = MockOIDC4VC(); mockJWTDecode = MockJWTDecode(); + activityLogManager = MockActivityLogManager(); scanCubit = ScanCubit( client: mockDioClient, @@ -57,6 +62,7 @@ void main() { walletCubit: mockWalletCubit, oidc4vc: mockOIDC4VC, jwtDecode: mockJWTDecode, + activityLogManager: activityLogManager, ); });