mobile bluesky app made with flutter
lazurite.stormlightlabs.org/
mobile
bluesky
flutter
1import 'dart:async';
2
3import 'package:flutter/material.dart';
4import 'package:lazurite/src/core/providers/app_lifecycle_provider.dart';
5import 'package:lazurite/src/core/utils/logger_provider.dart';
6import 'package:lazurite/src/features/auth/application/auth_providers.dart';
7import 'package:lazurite/src/features/auth/domain/auth_state.dart';
8import 'package:lazurite/src/features/settings/application/settings_providers.dart';
9import 'package:riverpod_annotation/riverpod_annotation.dart';
10
11part 'preference_sync_controller.g.dart';
12
13/// Controller that manages automatic background synchronization of Bluesky preferences.
14///
15/// Listens to app lifecycle changes and triggers preference sync when the app is resumed
16/// or when the user logs in. Also processes the preference sync queue to retry failed updates.
17@Riverpod(keepAlive: true)
18void preferenceSyncController(Ref ref) {
19 final logger = ref.watch(loggerProvider('PreferenceSync'));
20 var hasInitialized = false;
21
22 Future<void> runSync([String? ownerDid]) async {
23 logger.debug('runSync() called', {'ownerDid': ownerDid});
24
25 final authState = ref.read(authProvider);
26 final effectiveOwnerDid =
27 ownerDid ?? ((authState is AuthStateAuthenticated) ? authState.session.did : null);
28
29 if (effectiveOwnerDid == null) {
30 logger.debug('Skipping sync: no ownerDid available');
31 return;
32 }
33
34 try {
35 final repo = ref.read(blueskyPreferencesRepositoryProvider);
36 logger.debug('Syncing preferences from remote');
37 await repo.syncPreferencesFromRemote(effectiveOwnerDid);
38 logger.info('Preferences synced successfully');
39
40 logger.debug('Processing preference sync queue');
41 await repo.processSyncQueue(effectiveOwnerDid);
42 logger.info('Preference sync queue processed');
43 } catch (e, stack) {
44 logger.error('Failed to sync preferences on resume', e, stack);
45 }
46 }
47
48 ref.listen(appLifecycleProvider, (previous, next) {
49 if (next == AppLifecycleState.resumed) {
50 logger.debug('App resumed, triggering preference sync');
51 unawaited(runSync());
52 }
53 });
54
55 ref.listen(authProvider, (previous, next) {
56 logger.debug('Auth state changed: ${previous.runtimeType} → ${next.runtimeType}');
57 if (hasInitialized) {
58 final wasAuthed = previous is AuthStateAuthenticated;
59 final isAuthed = next is AuthStateAuthenticated;
60 logger.debug('wasAuthed=$wasAuthed, isAuthed=$isAuthed');
61
62 if (wasAuthed != isAuthed) {
63 if (isAuthed) {
64 logger.info('User logged in - triggering preference sync');
65 final newDid = next.session.did;
66 unawaited(runSync(newDid));
67 } else {
68 logger.info('User logged out - clearing cached preferences');
69 final oldDid = (previous as AuthStateAuthenticated).session.did;
70 unawaited(ref.read(blueskyPreferencesRepositoryProvider).clearAll(oldDid));
71 }
72 } else if (isAuthed && wasAuthed) {
73 final prevSession = previous.session;
74 final nextSession = next.session;
75 if (prevSession.accessJwt != nextSession.accessJwt && prevSession.did == nextSession.did) {
76 logger.debug('Session refreshed - triggering sync to fetch preferences');
77 unawaited(runSync(nextSession.did));
78 } else if (prevSession.did != nextSession.did) {
79 logger.info('User switched - clearing old prefs and syncing new');
80 unawaited(ref.read(blueskyPreferencesRepositoryProvider).clearAll(prevSession.did));
81 unawaited(runSync(nextSession.did));
82 }
83 }
84 } else {
85 logger.debug('Not initialized yet, skipping auth change handling');
86 }
87 });
88
89 Future.microtask(() async {
90 logger.debug('Controller initializing...');
91 final authState = ref.read(authProvider);
92 logger.debug('Initial auth state: ${authState.runtimeType}');
93
94 if (authState is AuthStateAuthenticated) {
95 logger.info('User is authenticated, running initial sync');
96 await runSync(authState.session.did);
97 } else {
98 logger.info('User not authenticated, skipping initial sync');
99 }
100
101 hasInitialized = true;
102 logger.info('Controller initialized');
103 });
104}