mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 619 lines 28 kB view raw
1import 'dart:async'; 2 3import 'package:bluesky/bluesky.dart'; 4import 'package:bluesky/bluesky_chat.dart'; 5import 'package:firebase_messaging/firebase_messaging.dart'; 6import 'package:flutter/material.dart'; 7import 'package:flutter_bloc/flutter_bloc.dart'; 8import 'package:go_router/go_router.dart'; 9import 'package:lazurite/core/bootstrap/auth_bootstrap.dart'; 10import 'package:lazurite/core/cache/offline_cache_policy.dart'; 11import 'package:lazurite/core/database/app_database.dart'; 12import 'package:lazurite/core/embedding/embedding_service.dart'; 13import 'package:lazurite/core/logging/app_logger.dart'; 14import 'package:lazurite/core/logging/logging_bloc_observer.dart'; 15import 'package:lazurite/core/logging/logging_navigator_observer.dart'; 16import 'package:lazurite/core/network/app_view_fallback_service.dart'; 17import 'package:lazurite/core/network/app_view_provider.dart'; 18import 'package:lazurite/core/network/app_view_router.dart'; 19import 'package:lazurite/core/network/xrpc_client_factory.dart'; 20import 'package:lazurite/core/objectbox/objectbox_store.dart'; 21import 'package:lazurite/core/router/app_router.dart'; 22import 'package:lazurite/core/scheduler/post_scheduler.dart'; 23import 'package:lazurite/core/theme/app_theme.dart'; 24import 'package:lazurite/features/account/cubit/account_switcher_cubit.dart'; 25import 'package:lazurite/features/auth/bloc/auth_bloc.dart'; 26import 'package:lazurite/features/auth/data/auth_repository.dart'; 27import 'package:lazurite/features/auth/data/models/auth_models.dart'; 28import 'package:lazurite/features/connectivity/cubit/connectivity_cubit.dart'; 29import 'package:lazurite/features/connectivity/presentation/connectivity_banner_host.dart'; 30import 'package:lazurite/features/devtools/cubit/dev_tools_cubit.dart'; 31import 'package:lazurite/features/feed/bloc/feed_bloc.dart'; 32import 'package:lazurite/features/feed/cubit/feed_preferences_cubit.dart'; 33import 'package:lazurite/features/feed/cubit/liked_posts_sync_cubit.dart'; 34import 'package:lazurite/features/feed/cubit/post_action_cache.dart'; 35import 'package:lazurite/features/feed/cubit/saved_posts_cubit.dart'; 36import 'package:lazurite/features/feed/data/feed_repository.dart'; 37import 'package:lazurite/features/feed/data/liked_posts_repository.dart'; 38import 'package:lazurite/features/feed/data/post_action_repository.dart'; 39import 'package:lazurite/features/feed/data/post_thread_repository.dart'; 40import 'package:lazurite/features/lists/data/list_repository.dart'; 41import 'package:lazurite/features/messages/bloc/convo_list_bloc.dart'; 42import 'package:lazurite/features/messages/data/convo_repository.dart'; 43import 'package:lazurite/features/moderation/data/moderation_service.dart'; 44import 'package:lazurite/features/notifications/data/notification_repository.dart'; 45import 'package:lazurite/features/notifications/data/flutter_local_notification_adapter.dart'; 46import 'package:lazurite/features/notifications/data/firebase_push_token_provider.dart'; 47import 'package:lazurite/features/notifications/domain/local_notification_adapter.dart'; 48import 'package:lazurite/features/notifications/domain/notification_deep_link_navigator.dart'; 49import 'package:lazurite/features/notifications/domain/notification_domain_service.dart'; 50import 'package:lazurite/features/notifications/domain/notification_local_models.dart'; 51import 'package:lazurite/features/notifications/domain/push_registration_service.dart'; 52import 'package:lazurite/features/notifications/background/notification_background_worker.dart'; 53import 'package:lazurite/features/profile/bloc/profile_bloc.dart'; 54import 'package:lazurite/features/profile/data/profile_action_repository.dart'; 55import 'package:lazurite/features/profile/data/profile_repository.dart'; 56import 'package:lazurite/features/search/bloc/search_bloc.dart'; 57import 'package:lazurite/features/search/cubit/semantic_index_cubit.dart'; 58import 'package:lazurite/features/search/cubit/semantic_search_cubit.dart'; 59import 'package:lazurite/features/search/data/embedding_repository.dart'; 60import 'package:lazurite/features/search/data/search_repository.dart'; 61import 'package:lazurite/features/search/data/semantic_indexer.dart'; 62import 'package:lazurite/features/search/data/semantic_search_repository.dart'; 63import 'package:lazurite/features/settings/bloc/settings_cubit.dart'; 64import 'package:lazurite/features/settings/bloc/settings_state.dart'; 65import 'package:lazurite/features/settings/data/video_repository.dart'; 66import 'package:lazurite/features/starter_packs/data/starter_pack_repository.dart'; 67import 'package:lazurite/features/typeahead/data/typeahead_repository.dart'; 68import 'package:lazurite/shared/presentation/widgets/global_tap_outside_unfocus.dart'; 69 70Future<void> main() async { 71 WidgetsFlutterBinding.ensureInitialized(); 72 imageCache.maximumSize = OfflineCachePolicy.imageMemoryEntryLimit; 73 imageCache.maximumSizeBytes = OfflineCachePolicy.imageMemoryByteLimit; 74 75 await log.initialize(); 76 await PostScheduler.initialize(); 77 FirebaseMessaging.onBackgroundMessage(notificationFirebaseMessagingBackgroundHandler); 78 await NotificationBackgroundScheduler.ensureScheduled(); 79 Bloc.observer = LoggingBlocObserver(); 80 81 final database = AppDatabase(); 82 final appViewFallbackService = AppViewFallbackService(); 83 final objectBoxStore = await ObjectBoxStore.open(); 84 final embeddingService = EmbeddingService(); 85 unawaited(embeddingService.initialize()); 86 final settingsCubit = SettingsCubit(database: database); 87 final authBootstrap = await bootstrapAuthDependencies( 88 loadSettings: settingsCubit.loadSettings, 89 createAuthRepository: () => AuthRepository( 90 database: database, 91 oauthServiceResolver: () { 92 final provider = AppViewProviders.descriptorForSetting(settingsCubit.state.appViewProvider); 93 final router = AppViewRouter(provider: provider); 94 return router.entrywayForAuth().host; 95 }, 96 slingshotIdentityFallbackEnabledResolver: () => settingsCubit.state.slingshotIdentityFallbackEnabled, 97 ), 98 restoreSession: (authRepository) => authRepository.restoreSession(), 99 ); 100 final authRepository = authBootstrap.authRepository; 101 final restoredSession = authBootstrap.restoredSession; 102 final authBloc = AuthBloc( 103 authRepository: authRepository, 104 initialState: restoredSession != null 105 ? AuthState.authenticated(restoredSession) 106 : const AuthState.unauthenticated(), 107 ); 108 final connectivityCubit = ConnectivityCubit(simulateOffline: settingsCubit.state.simulateOffline); 109 110 final accountSwitcherCubit = AccountSwitcherCubit(database: database, authRepository: authRepository); 111 await accountSwitcherCubit.loadAccounts(); 112 final localNotificationAdapter = FlutterLocalNotificationAdapter(); 113 final pushTokenProvider = FirebasePushTokenProvider(); 114 final pushRegistrationService = PushRegistrationService( 115 tokenProvider: pushTokenProvider, 116 notificationRepositoryFactory: (tokens) { 117 final bluesky = createBlueskyClient(tokens); 118 if (bluesky == null) { 119 throw StateError('Unable to create Bluesky client for push registration'); 120 } 121 return NotificationRepository( 122 bluesky: bluesky, 123 appViewProviderResolver: () => settingsCubit.state.appViewProvider, 124 ); 125 }, 126 ); 127 128 log.i('AppLogger: App started'); 129 130 runApp( 131 LazuriteApp.from( 132 authBloc, 133 database, 134 appViewFallbackService, 135 objectBoxStore, 136 embeddingService, 137 settingsCubit, 138 connectivityCubit, 139 accountSwitcherCubit, 140 localNotificationAdapter, 141 pushRegistrationService, 142 ), 143 ); 144} 145 146class LazuriteApp extends StatefulWidget { 147 const LazuriteApp({ 148 super.key, 149 required this.authBloc, 150 required this.database, 151 required this.appViewFallbackService, 152 required this.objectBoxStore, 153 required this.embeddingService, 154 required this.settingsCubit, 155 required this.connectivityCubit, 156 required this.accountSwitcherCubit, 157 required this.localNotificationAdapter, 158 required this.pushRegistrationService, 159 }); 160 161 final AuthBloc authBloc; 162 final AppDatabase database; 163 final AppViewFallbackService appViewFallbackService; 164 final ObjectBoxStore objectBoxStore; 165 final EmbeddingService embeddingService; 166 final SettingsCubit settingsCubit; 167 final ConnectivityCubit connectivityCubit; 168 final AccountSwitcherCubit accountSwitcherCubit; 169 final LocalNotificationAdapter localNotificationAdapter; 170 final PushRegistrationService pushRegistrationService; 171 172 /// factory constructor with positional params 173 static LazuriteApp from( 174 AuthBloc authBloc, 175 AppDatabase database, 176 AppViewFallbackService appViewFallbackService, 177 ObjectBoxStore objectBoxStore, 178 EmbeddingService embeddingService, 179 SettingsCubit settingsCubit, 180 ConnectivityCubit connectivityCubit, 181 AccountSwitcherCubit accountSwitcherCubit, 182 LocalNotificationAdapter localNotificationAdapter, 183 PushRegistrationService pushRegistrationService, 184 ) => LazuriteApp( 185 authBloc: authBloc, 186 database: database, 187 appViewFallbackService: appViewFallbackService, 188 objectBoxStore: objectBoxStore, 189 embeddingService: embeddingService, 190 settingsCubit: settingsCubit, 191 connectivityCubit: connectivityCubit, 192 accountSwitcherCubit: accountSwitcherCubit, 193 localNotificationAdapter: localNotificationAdapter, 194 pushRegistrationService: pushRegistrationService, 195 ); 196 197 @override 198 State<LazuriteApp> createState() => _LazuriteAppState(); 199} 200 201class _LazuriteAppState extends State<LazuriteApp> { 202 static final _navigatorObserver = LoggingNavigatorObserver(); 203 late GoRouter _router; 204 late String _routerSessionKey; 205 late final StreamSubscription<String> _authSubscription; 206 late final StreamSubscription<AuthTokens?> _pushRegistrationSubscription; 207 StreamSubscription<RemoteMessage>? _pushForegroundMessageSubscription; 208 late final StreamSubscription<bool> _simulateOfflineSubscription; 209 late final StreamSubscription<String> _appViewProviderSubscription; 210 late final StreamSubscription<AppViewRoutingEvent> _appViewEventSubscription; 211 late String _observedAppViewProvider; 212 var _routerGeneration = 0; 213 var _isSoftRestarting = false; 214 215 @override 216 void initState() { 217 super.initState(); 218 _routerSessionKey = _sessionKeyFor(widget.authBloc.state); 219 _observedAppViewProvider = widget.settingsCubit.state.appViewProvider; 220 _router = _createRouter(); 221 unawaited( 222 widget.localNotificationAdapter.initialize(onTap: _handleNotificationDeepLink).then((_) { 223 return widget.localNotificationAdapter.requestPermissions(); 224 }), 225 ); 226 unawaited(widget.pushRegistrationService.start(initialTokens: widget.authBloc.state.tokens)); 227 _pushRegistrationSubscription = widget.authBloc.stream.map((state) => state.tokens).listen((tokens) { 228 unawaited(widget.pushRegistrationService.updateSession(tokens)); 229 }); 230 _pushForegroundMessageSubscription = FirebaseMessaging.onMessage.listen((message) { 231 unawaited(notificationPushPayloadEntrypoint(message.data)); 232 }); 233 _authSubscription = widget.authBloc.stream.map(_sessionKeyFor).distinct().listen(_handleSessionKeyChanged); 234 _simulateOfflineSubscription = widget.settingsCubit.stream 235 .map((state) => state.simulateOffline) 236 .distinct() 237 .listen(widget.connectivityCubit.setSimulatedOffline); 238 _appViewProviderSubscription = widget.settingsCubit.stream.map((state) => state.appViewProvider).listen((provider) { 239 if (provider == _observedAppViewProvider) { 240 return; 241 } 242 243 _observedAppViewProvider = provider; 244 245 if (!widget.authBloc.state.isAuthenticated) { 246 return; 247 } 248 249 unawaited(_softRestartForProviderChange()); 250 }); 251 _appViewEventSubscription = widget.appViewFallbackService.events.listen( 252 widget.settingsCubit.recordAppViewRoutingEvent, 253 ); 254 255 unawaited(widget.settingsCubit.refreshAppViewHealth()); 256 } 257 258 @override 259 void dispose() { 260 _authSubscription.cancel(); 261 _pushRegistrationSubscription.cancel(); 262 _pushForegroundMessageSubscription?.cancel(); 263 _simulateOfflineSubscription.cancel(); 264 _appViewProviderSubscription.cancel(); 265 _appViewEventSubscription.cancel(); 266 unawaited(widget.pushRegistrationService.dispose()); 267 268 widget.connectivityCubit.close(); 269 widget.appViewFallbackService.dispose(); 270 271 _router.dispose(); 272 273 super.dispose(); 274 } 275 276 GoRouter _createRouter() { 277 return AppRouter(authBloc: widget.authBloc, navigatorObserver: _navigatorObserver).router; 278 } 279 280 String _sessionKeyFor(AuthState state) => state.tokens?.did ?? 'guest'; 281 282 void _handleSessionKeyChanged(String sessionKey) { 283 if (!mounted || sessionKey == _routerSessionKey) { 284 return; 285 } 286 287 final previousRouter = _router; 288 setState(() { 289 _routerSessionKey = sessionKey; 290 _router = _createRouter(); 291 }); 292 previousRouter.dispose(); 293 } 294 295 Future<void> _softRestartForProviderChange() async { 296 if (!mounted || _isSoftRestarting) { 297 return; 298 } 299 300 setState(() { 301 _isSoftRestarting = true; 302 }); 303 304 final previousRouter = _router; 305 306 widget.settingsCubit.bumpRoutingEpoch(); 307 308 setState(() { 309 _routerGeneration += 1; 310 _router = _createRouter(); 311 }); 312 313 previousRouter.dispose(); 314 await Future<void>.delayed(const Duration(milliseconds: 200)); 315 316 if (mounted) { 317 setState(() { 318 _isSoftRestarting = false; 319 }); 320 unawaited(widget.settingsCubit.refreshAppViewHealth()); 321 } 322 } 323 324 void _handleNotificationDeepLink(NotificationDeepLink deepLink) { 325 if (!mounted) { 326 return; 327 } 328 NotificationDeepLinkNavigator.navigate(_router, deepLink); 329 } 330 331 bool _isAlertsRouteActive() { 332 final path = _router.routerDelegate.currentConfiguration.uri.path; 333 return path.startsWith('/alerts'); 334 } 335 336 Bluesky? _createBluesky(AuthState state) => state.isAuthenticated ? createBlueskyClient(state.tokens) : null; 337 338 BlueskyChat? _createBlueskyChat(AuthState state) => 339 state.isAuthenticated ? createBlueSkyChatClient(state.tokens) : null; 340 341 @override 342 Widget build(BuildContext context) { 343 return MultiBlocProvider( 344 providers: [ 345 BlocProvider.value(value: widget.authBloc), 346 BlocProvider.value(value: widget.settingsCubit), 347 BlocProvider.value(value: widget.connectivityCubit), 348 BlocProvider.value(value: widget.accountSwitcherCubit), 349 ], 350 child: BlocBuilder<AuthBloc, AuthState>( 351 builder: (context, authState) { 352 final bluesky = _createBluesky(authState); 353 final blueskyChat = _createBlueskyChat(authState); 354 final appShell = BlocBuilder<SettingsCubit, SettingsState>( 355 builder: (context, settingsState) { 356 final themeMode = settingsState.useSystemTheme 357 ? ThemeMode.system 358 : (settingsState.themeVariant == AppThemeVariant.light ? ThemeMode.light : ThemeMode.dark); 359 360 final lightTheme = AppTheme.getTheme(settingsState.themePalette, AppThemeVariant.light); 361 final darkTheme = AppTheme.getTheme(settingsState.themePalette, AppThemeVariant.dark); 362 363 return MaterialApp.router( 364 key: ValueKey('router-$_routerSessionKey-$_routerGeneration'), 365 title: 'Lazurite', 366 debugShowCheckedModeBanner: false, 367 theme: lightTheme, 368 darkTheme: darkTheme, 369 themeMode: themeMode, 370 routerConfig: _router, 371 builder: (context, child) => GlobalTapOutsideUnfocus( 372 child: Stack( 373 children: [ 374 ConnectivityBannerHost(child: child ?? const SizedBox.shrink()), 375 if (_isSoftRestarting) 376 const ColoredBox( 377 color: Color(0xC0000000), 378 child: Center( 379 child: Card( 380 child: Padding( 381 padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16), 382 child: Row( 383 mainAxisSize: MainAxisSize.min, 384 children: [ 385 SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2.5)), 386 SizedBox(width: 12), 387 Text('Applying provider change...'), 388 ], 389 ), 390 ), 391 ), 392 ), 393 ), 394 ], 395 ), 396 ), 397 ); 398 }, 399 ); 400 401 if (bluesky == null || blueskyChat == null) { 402 return appShell; 403 } 404 405 final accountDid = authState.tokens?.did ?? ''; 406 407 return KeyedSubtree( 408 key: ValueKey('account-$accountDid-routing-${context.read<SettingsCubit>().state.routingEpoch}'), 409 child: MultiRepositoryProvider( 410 providers: [ 411 RepositoryProvider( 412 create: (_) { 413 final settingsCubit = context.read<SettingsCubit>(); 414 final moderationService = ModerationService( 415 bluesky: bluesky, 416 database: widget.database, 417 accountDid: accountDid, 418 userDid: accountDid, 419 appViewProviderResolver: () => settingsCubit.state.appViewProvider, 420 ); 421 unawaited(moderationService.ensureInitialized()); 422 return moderationService; 423 }, 424 dispose: (moderationService) => moderationService.dispose(), 425 ), 426 RepositoryProvider( 427 create: (context) => FeedRepository( 428 bluesky: bluesky, 429 database: widget.database, 430 accountDid: accountDid, 431 moderationService: context.read<ModerationService>(), 432 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 433 crossProviderFallbackEnabledResolver: () => 434 context.read<SettingsCubit>().state.crossProviderFallbackEnabled, 435 appViewFallbackService: widget.appViewFallbackService, 436 routingEpoch: context.read<SettingsCubit>().state.routingEpoch, 437 routingEpochResolver: () => context.read<SettingsCubit>().state.routingEpoch, 438 ), 439 ), 440 RepositoryProvider( 441 create: (context) { 442 final settingsCubit = context.read<SettingsCubit>(); 443 return SearchRepository( 444 bluesky: bluesky, 445 moderationService: context.read<ModerationService>(), 446 appViewProviderResolver: () => settingsCubit.state.appViewProvider, 447 crossProviderFallbackEnabledResolver: () => settingsCubit.state.crossProviderFallbackEnabled, 448 appViewFallbackService: widget.appViewFallbackService, 449 routingEpoch: settingsCubit.state.routingEpoch, 450 routingEpochResolver: () => settingsCubit.state.routingEpoch, 451 ); 452 }, 453 ), 454 RepositoryProvider( 455 create: (context) { 456 final settingsCubit = context.read<SettingsCubit>(); 457 return TypeaheadRepository( 458 bluesky: bluesky, 459 providerResolver: () => settingsCubit.state.typeaheadProvider, 460 appViewProviderResolver: () => settingsCubit.state.appViewProvider, 461 moderationService: context.read<ModerationService>(), 462 ); 463 }, 464 ), 465 RepositoryProvider( 466 create: (context) => ListRepository( 467 bluesky: bluesky, 468 moderationService: context.read<ModerationService>(), 469 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 470 ), 471 ), 472 RepositoryProvider( 473 create: (context) { 474 final service = context.read<ModerationService>(); 475 return ProfileRepository( 476 database: widget.database, 477 bluesky: bluesky, 478 moderationService: service, 479 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 480 ); 481 }, 482 ), 483 RepositoryProvider( 484 create: (context) => NotificationRepository( 485 bluesky: bluesky, 486 moderationService: context.read<ModerationService>(), 487 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 488 ), 489 ), 490 RepositoryProvider( 491 create: (context) => NotificationDomainService( 492 notificationRepository: context.read<NotificationRepository>(), 493 database: widget.database, 494 accountDid: accountDid, 495 localNotificationAdapter: widget.localNotificationAdapter, 496 shouldSuppressLocalNotifications: _isAlertsRouteActive, 497 ), 498 ), 499 RepositoryProvider( 500 create: (context) => PostThreadRepository( 501 bluesky: bluesky, 502 database: widget.database, 503 accountDid: accountDid, 504 moderationService: context.read<ModerationService>(), 505 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 506 ), 507 ), 508 RepositoryProvider( 509 create: (context) => StarterPackRepository( 510 bluesky: bluesky, 511 moderationService: context.read<ModerationService>(), 512 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 513 ), 514 ), 515 RepositoryProvider( 516 create: (context) => PostActionRepository( 517 bluesky: bluesky, 518 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 519 ), 520 ), 521 RepositoryProvider( 522 create: (context) => ProfileActionRepository( 523 bluesky: bluesky, 524 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 525 ), 526 ), 527 RepositoryProvider(create: (_) => ConvoRepository(chat: blueskyChat)), 528 RepositoryProvider(create: (_) => PostActionCache()), 529 RepositoryProvider(create: (_) => VideoRepository(bluesky: bluesky)), 530 RepositoryProvider.value(value: bluesky), 531 RepositoryProvider.value(value: widget.database), 532 RepositoryProvider.value(value: widget.objectBoxStore), 533 RepositoryProvider.value(value: widget.embeddingService), 534 RepositoryProvider(create: (context) => EmbeddingRepository(context.read<ObjectBoxStore>())), 535 RepositoryProvider( 536 create: (context) => SemanticIndexer( 537 embeddingService: context.read<EmbeddingService>(), 538 embeddingRepository: context.read<EmbeddingRepository>(), 539 database: widget.database, 540 ), 541 ), 542 RepositoryProvider( 543 create: (context) => LikedPostsRepository( 544 bluesky: bluesky, 545 database: widget.database, 546 semanticIndexer: context.read<SemanticIndexer>(), 547 appViewProviderResolver: () => context.read<SettingsCubit>().state.appViewProvider, 548 ), 549 ), 550 RepositoryProvider( 551 create: (context) => SemanticSearchRepository( 552 embeddingService: context.read<EmbeddingService>(), 553 embeddingRepository: context.read<EmbeddingRepository>(), 554 database: widget.database, 555 ), 556 ), 557 RepositoryProvider.value(value: accountDid), 558 ], 559 child: MultiBlocProvider( 560 providers: [ 561 BlocProvider(create: (context) => ProfileBloc(profileRepository: context.read<ProfileRepository>())), 562 BlocProvider(create: (context) => FeedBloc(feedRepository: context.read<FeedRepository>())), 563 BlocProvider( 564 create: (context) => FeedPreferencesCubit( 565 feedRepository: context.read<FeedRepository>(), 566 database: widget.database, 567 accountDid: accountDid, 568 )..loadPreferences(), 569 ), 570 BlocProvider(create: (_) => DevToolsCubit(atproto: bluesky.atproto)), 571 BlocProvider( 572 create: (context) => SearchBloc( 573 searchRepository: context.read<SearchRepository>(), 574 typeaheadRepository: context.read<TypeaheadRepository>(), 575 database: widget.database, 576 accountDid: accountDid, 577 ), 578 ), 579 BlocProvider( 580 create: (context) => 581 ConvoListBloc(convoRepository: context.read<ConvoRepository>()) 582 ..add(const ConvosRequested(limit: 100)), 583 ), 584 BlocProvider( 585 create: (context) => SavedPostsCubit( 586 database: widget.database, 587 accountDid: accountDid, 588 postActionRepository: context.read<PostActionRepository>(), 589 semanticIndexer: context.read<SemanticIndexer>(), 590 ), 591 ), 592 BlocProvider( 593 create: (context) => SemanticSearchCubit( 594 repository: context.read<SemanticSearchRepository>(), 595 embeddingService: context.read<EmbeddingService>(), 596 accountDid: accountDid, 597 ), 598 ), 599 BlocProvider( 600 create: (context) => SemanticIndexCubit( 601 indexer: context.read<SemanticIndexer>(), 602 embeddingRepository: context.read<EmbeddingRepository>(), 603 accountDid: accountDid, 604 ), 605 ), 606 BlocProvider( 607 create: (context) => 608 LikedPostsSyncCubit(repository: context.read<LikedPostsRepository>(), accountDid: accountDid), 609 ), 610 ], 611 child: appShell, 612 ), 613 ), 614 ); 615 }, 616 ), 617 ); 618 } 619}