mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
at main 95 lines 3.4 kB view raw
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/feeds/application/feed_providers.dart'; 9import 'package:riverpod_annotation/riverpod_annotation.dart'; 10 11part 'feed_sync_controller.g.dart'; 12 13/// Controller that manages automatic background synchronization of feeds. 14/// 15/// Listens to app lifecycle changes and triggers [FeedRepository.syncOnResume] when the 16/// app is resumed. 17@Riverpod(keepAlive: true) 18void feedSyncController(Ref ref) { 19 final logger = ref.watch(loggerProvider('FeedSync')); 20 var hasInitialized = false; 21 22 Future<void> seedDefaults() async { 23 try { 24 final authState = ref.read(authProvider); 25 final ownerDid = (authState is AuthStateAuthenticated) ? authState.session.did : 'anonymous'; 26 27 await ref.read(feedRepositoryProvider).seedDefaultFeeds(ownerDid); 28 } catch (e, stack) { 29 logger.warning('Failed to seed default feeds', e, stack); 30 } 31 } 32 33 Future<void> runSync() async { 34 logger.debug('runSync() called'); 35 final authState = ref.read(authProvider); 36 final ownerDid = (authState is AuthStateAuthenticated) ? authState.session.did : 'anonymous'; 37 38 await seedDefaults(); 39 try { 40 logger.debug('Calling repository.syncOnResume()'); 41 await ref.read(feedRepositoryProvider).syncOnResume(ownerDid); 42 logger.info('syncOnResume() completed successfully'); 43 } catch (e, stack) { 44 logger.error('Failed to sync feeds on resume', e, stack); 45 } 46 } 47 48 void setActiveFeed(AuthState state) { 49 final notifier = ref.read(activeFeedProvider.notifier); 50 notifier.resetToDefault(isAuthenticated: state is AuthStateAuthenticated); 51 } 52 53 ref.listen(appLifecycleProvider, (previous, next) { 54 if (next == AppLifecycleState.resumed) { 55 unawaited(runSync()); 56 } 57 }); 58 59 ref.listen(authProvider, (previous, next) { 60 logger.debug('Auth state changed: ${previous.runtimeType}${next.runtimeType}'); 61 if (hasInitialized) { 62 final wasAuthed = previous is AuthStateAuthenticated; 63 final isAuthed = next is AuthStateAuthenticated; 64 logger.debug('wasAuthed=$wasAuthed, isAuthed=$isAuthed'); 65 66 if (wasAuthed != isAuthed) { 67 logger.info('Auth status changed, switching active feed'); 68 setActiveFeed(next); 69 if (isAuthed) { 70 logger.info('User logged in - triggering full sync'); 71 unawaited(runSync()); 72 } 73 } else if (isAuthed && wasAuthed) { 74 final prevSession = previous.session; 75 final nextSession = next.session; 76 if (prevSession.accessJwt != nextSession.accessJwt) { 77 logger.debug('Session refreshed - triggering sync to fetch feeds'); 78 unawaited(runSync()); 79 } 80 } 81 } else { 82 logger.debug('Not initialized yet, skipping auth change handling'); 83 } 84 }); 85 86 Future.microtask(() async { 87 logger.debug('Controller initializing...'); 88 await runSync(); 89 final authState = ref.read(authProvider); 90 logger.debug('Initial auth state: ${authState.runtimeType}'); 91 setActiveFeed(authState); 92 hasInitialized = true; 93 logger.info('Controller initialized'); 94 }); 95}