Skip to content

Commit 9cd13dd

Browse files
committed
Add activity logging across app
1 parent a7033e0 commit 9cd13dd

File tree

11 files changed

+132
-13
lines changed

11 files changed

+132
-13
lines changed

lib/core/database/pockaw_database.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class AppDatabase extends _$AppDatabase {
175175
Future<void> _deleteAllUsers() => delete(users).go();
176176
Future<void> _deleteAllWallets() => delete(wallets).go();
177177
Future<void> _deleteAllCategories() => delete(categories).go();
178+
Future<void> _deleteAllUserActivities() => delete(userActivities).go();
178179

179180
/// Clears all data from all tables in the correct order to respect foreign key constraints.
180181
Future<void> clearAllTables() async {
@@ -188,6 +189,7 @@ class AppDatabase extends _$AppDatabase {
188189
await _deleteAllUsers(); // Users table has no incoming FKs from other tables
189190
await _deleteAllWallets();
190191
await _deleteAllCategories();
192+
await _deleteAllUserActivities();
191193
});
192194
Log.i('All database tables cleared.', label: 'database');
193195
}

lib/features/authentication/presentation/riverpod/auth_provider.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,10 @@ class AuthNotifier extends Notifier<UserModel> {
152152
.read(activeWalletProvider.notifier)
153153
.setActiveWalletByID(wallet.id);
154154
}
155+
156+
ref
157+
.read(userActivityServiceProvider)
158+
.logActivity(action: UserActivityAction.signIn);
155159
} else {
156160
await _setSession();
157161
final currencyNotifier = ref.read(currenciesStaticProvider.notifier);
@@ -170,12 +174,11 @@ class AuthNotifier extends Notifier<UserModel> {
170174
int walletID = await db.walletDao.addWallet(wallet);
171175
Log.d(wallet.toJson(), label: 'selected wallet');
172176
ref.read(activeWalletProvider.notifier).setActiveWalletByID(walletID);
173-
}
174177

175-
/// log user journey started
176-
await ref
177-
.read(userActivityServiceProvider)
178-
.logActivity(action: UserActivityAction.journeyStarted);
178+
ref
179+
.read(userActivityServiceProvider)
180+
.logActivity(action: UserActivityAction.journeyStarted);
181+
}
179182

180183
// Navigate to main screen
181184
if (context.mounted) {

lib/features/budget/presentation/screens/budget_form_screen.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import 'package:pockaw/features/budget/presentation/riverpod/budget_providers.da
2424
import 'package:pockaw/features/budget/presentation/riverpod/date_picker_provider.dart'
2525
as budget_date_provider; // Alias to avoid conflict
2626
import 'package:pockaw/features/category/data/model/category_model.dart';
27+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
28+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
2729
import 'package:pockaw/features/wallet/data/model/wallet_model.dart';
2830
import 'package:pockaw/features/wallet/riverpod/wallet_providers.dart';
2931
import 'package:toastification/toastification.dart';
@@ -181,6 +183,16 @@ class BudgetFormScreen extends HookConsumerWidget {
181183
await budgetDao.addBudget(budgetToSave);
182184
Toast.show('Budget created!', type: ToastificationType.success);
183185
}
186+
187+
ref
188+
.read(userActivityServiceProvider)
189+
.logActivity(
190+
action: isEditing
191+
? UserActivityAction.budgetUpdated
192+
: UserActivityAction.budgetCreated,
193+
subjectId: budgetToSave.id,
194+
);
195+
184196
if (context.mounted) context.pop();
185197
} catch (e) {
186198
Log.e('Failed to save budget: $e');
@@ -214,6 +226,14 @@ class BudgetFormScreen extends HookConsumerWidget {
214226
context.pop(); // close detail screen
215227

216228
ref.read(budgetDaoProvider).deleteBudget(budgetId!);
229+
230+
ref
231+
.read(userActivityServiceProvider)
232+
.logActivity(
233+
action: UserActivityAction.budgetDeleted,
234+
subjectId: budgetId,
235+
);
236+
217237
Toast.show('Budget deleted!');
218238
},
219239
),

lib/features/category/presentation/riverpod/category_form_service.dart

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:pockaw/core/utils/logger.dart';
99
import 'package:pockaw/features/category/data/model/category_model.dart';
1010
import 'package:pockaw/features/category/presentation/riverpod/category_actions_provider.dart';
1111
import 'package:pockaw/features/category/presentation/riverpod/category_providers.dart';
12+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
13+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
1214
import 'package:toastification/toastification.dart';
1315

1416
class CategoryFormService {
@@ -49,17 +51,27 @@ class CategoryFormService {
4951
); // Use upsert for create/update
5052

5153
Log.d(row, label: 'row affected');
54+
55+
ref
56+
.read(userActivityServiceProvider)
57+
.logActivity(
58+
action: categoryModel.id == null
59+
? UserActivityAction.categoryCreated
60+
: UserActivityAction.categoryUpdated,
61+
subjectId: categoryModel.id,
62+
);
63+
5264
// Clear the selected parent state after saving
5365
ref.read(selectedParentCategoryProvider.notifier).clear();
5466
if (!context.mounted) return;
5567
context.pop(); // Go back after successful save
5668
} catch (e) {
5769
// Handle database save errors
5870
if (!context.mounted) return;
59-
toastification.show(
60-
context: context, // optional if you use ToastificationWrapper
61-
title: Text('Failed to save category: $e'),
62-
autoCloseDuration: const Duration(seconds: 5),
71+
72+
Toast.show(
73+
'Failed to save category',
74+
type: ToastificationType.error,
6375
);
6476
}
6577
}
@@ -80,5 +92,12 @@ class CategoryFormService {
8092

8193
// Then delete the main category
8294
actions.delete(categoryModel.id ?? 0);
95+
96+
ref
97+
.read(userActivityServiceProvider)
98+
.logActivity(
99+
action: UserActivityAction.categoryDeleted,
100+
subjectId: categoryModel.id,
101+
);
83102
}
84103
}

lib/features/goal/presentation/services/goal_form_service.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import 'package:pockaw/features/goal/data/model/checklist_item_model.dart';
88
import 'package:pockaw/features/goal/data/model/goal_model.dart';
99
import 'package:pockaw/features/goal/presentation/riverpod/checklist_actions_provider.dart';
1010
import 'package:pockaw/features/goal/presentation/riverpod/goals_actions_provider.dart';
11+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
12+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
1113

1214
class GoalFormService {
1315
Future<void> save(
@@ -53,6 +55,15 @@ class GoalFormService {
5355
);
5456
}
5557

58+
ref
59+
.read(userActivityServiceProvider)
60+
.logActivity(
61+
action: isEditing
62+
? UserActivityAction.goalUpdated
63+
: UserActivityAction.goalCreated,
64+
subjectId: goal.id,
65+
);
66+
5667
if (!context.mounted) return;
5768
context.pop();
5869
}

lib/features/settings/presentation/screens/personal_details_screen.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import 'package:pockaw/core/constants/app_colors.dart';
1313
import 'package:pockaw/core/constants/app_spacing.dart';
1414
import 'package:pockaw/core/services/image_service/image_service.dart';
1515
import 'package:pockaw/features/authentication/presentation/riverpod/auth_provider.dart';
16+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
17+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
1618
import 'package:toastification/toastification.dart';
1719

1820
class PersonalDetailsScreen extends HookConsumerWidget {
@@ -142,6 +144,10 @@ class PersonalDetailsScreen extends HookConsumerWidget {
142144
type: ToastificationType.success,
143145
);
144146

147+
ref
148+
.read(userActivityServiceProvider)
149+
.logActivity(action: UserActivityAction.profileUpdated);
150+
145151
if (context.mounted) context.pop();
146152
},
147153
).floatingBottomContained,

lib/features/splash/presentation/screens/splash_screen.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ class SplashScreen extends HookConsumerWidget {
5151
final auth = ref.read(authStateProvider.notifier);
5252
final user = await auth.getSession();
5353
if (context.mounted) {
54-
// Ensure context is still valid
5554
if (user == null) {
5655
GoRouter.of(rootNavKey.currentContext!).go(Routes.onboarding);
5756
} else {
57+
ref
58+
.read(userActivityServiceProvider)
59+
.logActivity(action: UserActivityAction.signInWithSession);
5860
GoRouter.of(rootNavKey.currentContext!).go(Routes.main);
5961
}
6062
}

lib/features/transaction/presentation/riverpod/transaction_form_state.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import 'package:pockaw/core/services/image_service/riverpod/image_notifier.dart'
1616
import 'package:pockaw/core/utils/logger.dart';
1717
import 'package:pockaw/features/category/data/model/category_model.dart';
1818
import 'package:pockaw/features/transaction/data/model/transaction_model.dart';
19+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
20+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
1921
import 'package:pockaw/features/wallet/riverpod/wallet_providers.dart';
2022
import 'package:toastification/toastification.dart';
2123

@@ -135,6 +137,15 @@ class TransactionFormState {
135137
await _adjustWalletBalance(ref, initialTransaction, transactionToSave);
136138
}
137139

140+
ref
141+
.read(userActivityServiceProvider)
142+
.logActivity(
143+
action: isEditing
144+
? UserActivityAction.transactionUpdated
145+
: UserActivityAction.transactionCreated,
146+
subjectId: savedTransactionId,
147+
);
148+
138149
if (context.mounted) {
139150
context.pop();
140151
}
@@ -178,6 +189,13 @@ class TransactionFormState {
178189
initialTransaction!.id!,
179190
);
180191

192+
ref
193+
.read(userActivityServiceProvider)
194+
.logActivity(
195+
action: UserActivityAction.transactionDeleted,
196+
subjectId: id,
197+
);
198+
181199
Log.d(id, label: 'deleted transaction id');
182200
},
183201
),

lib/features/user_activity/data/enum/user_activity_action.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
enum UserActivityAction {
66
// Authentication
77
signIn,
8+
signInWithSession,
89
signOut,
910

1011
// Onboarding / journey
@@ -47,12 +48,14 @@ enum UserActivityAction {
4748
goalUpdated,
4849
goalDeleted,
4950

51+
walletSelected,
5052
walletCreated,
5153
walletUpdated,
5254
walletDeleted,
5355

5456
categoryCreated,
5557
categoryUpdated,
58+
categoryDeleted,
5659

5760
// Media
5861
imageUploaded,

lib/features/wallet/riverpod/wallet_providers.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:flutter_riverpod/flutter_riverpod.dart';
22
import 'package:pockaw/core/database/database_provider.dart';
33
import 'package:pockaw/core/utils/logger.dart';
4+
import 'package:pockaw/features/user_activity/data/enum/user_activity_action.dart';
5+
import 'package:pockaw/features/user_activity/riverpod/user_activity_provider.dart';
46
import 'package:pockaw/features/wallet/data/model/wallet_model.dart';
57
// import 'package:pockaw/features/wallet/data/repositories/wallet_repo.dart'; // No longer needed for hardcoded list
68

@@ -55,32 +57,65 @@ class ActiveWalletNotifier extends AsyncNotifier<WalletModel?> {
5557
state = AsyncValue.data(
5658
wallets.firstWhere((wallet) => wallet.id == walletID),
5759
);
60+
61+
ref
62+
.read(userActivityServiceProvider)
63+
.logActivity(
64+
action: UserActivityAction.walletSelected,
65+
subjectId: walletID,
66+
);
5867
}
5968
}
6069

70+
/// create new wallet and set as active wallet
71+
Future<void> createNewActiveWallet(WalletModel newWallet) async {
72+
final db = ref.read(databaseProvider);
73+
Log.d(newWallet.toJson(), label: 'new wallet');
74+
int id = await db.walletDao.addWallet(newWallet);
75+
Log.d(id, label: 'new wallet');
76+
77+
ref
78+
.read(userActivityServiceProvider)
79+
.logActivity(
80+
action: UserActivityAction.walletCreated,
81+
subjectId: id,
82+
);
83+
}
84+
6185
void updateActiveWallet(WalletModel? newWalletData) {
6286
final currentActiveWallet = state.asData?.value;
6387
final currentActiveWalletId = currentActiveWallet?.id;
88+
UserActivityAction action = UserActivityAction.walletSelected;
6489

6590
if (newWalletData != null && newWalletData.id == currentActiveWalletId) {
6691
Log.d(
6792
'Updating active wallet ID ${newWalletData.id} with new data: ${newWalletData.toJson()}',
6893
label: 'ActiveWalletNotifier',
6994
);
7095
state = AsyncValue.data(newWalletData);
96+
action = UserActivityAction.walletUpdated;
7197
} else if (newWalletData != null && currentActiveWalletId == null) {
7298
Log.d(
7399
'Setting active wallet (was null) to ID ${newWalletData.id} via updateActiveWallet: ${newWalletData.toJson()}',
74100
label: 'ActiveWalletNotifier',
75101
);
76102
state = AsyncValue.data(newWalletData);
103+
action = UserActivityAction.walletSelected;
77104
} else if (newWalletData == null && currentActiveWalletId != null) {
78105
Log.d(
79106
'Clearing active wallet (was ID $currentActiveWalletId) via updateActiveWallet.',
80107
label: 'ActiveWalletNotifier',
81108
);
82109
state = const AsyncValue.data(null);
110+
action = UserActivityAction.walletDeleted;
83111
}
112+
113+
ref
114+
.read(userActivityServiceProvider)
115+
.logActivity(
116+
action: action,
117+
subjectId: newWalletData?.id,
118+
);
84119
}
85120

86121
Future<void> refreshActiveWallet() async {

0 commit comments

Comments
 (0)