refactor(apps): 重构数据层目录结构并新增启动预热编排器
This commit is contained in:
+6
-65
@@ -1,21 +1,15 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'di/injection.dart';
|
||||
import '../data/models/reminder_payload.dart';
|
||||
import '../data/services/calendar_service.dart';
|
||||
import '../data/services/local_notification_service.dart';
|
||||
import '../data/services/reminder_notification_callbacks.dart';
|
||||
import '../core/l10n/l10n.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../features/auth/presentation/bloc/auth_bloc.dart';
|
||||
import '../features/auth/presentation/bloc/auth_event.dart';
|
||||
import '../features/auth/presentation/bloc/auth_state.dart';
|
||||
import '../features/notification/domain/models/reminder_action.dart';
|
||||
import '../features/notification/domain/services/reminder_action_executor.dart';
|
||||
import 'services/app_prewarm_orchestrator.dart';
|
||||
import 'router/app_router.dart';
|
||||
import '../core/theme/app_theme.dart';
|
||||
|
||||
@@ -29,7 +23,6 @@ class LinksyApp extends StatefulWidget {
|
||||
class _LinksyAppState extends State<LinksyApp> {
|
||||
late final AuthBloc _authBloc;
|
||||
late final GoRouter _router;
|
||||
String? _reminderBootstrapUserId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -37,7 +30,6 @@ class _LinksyAppState extends State<LinksyApp> {
|
||||
_authBloc = sl<AuthBloc>();
|
||||
_authBloc.add(AuthStarted());
|
||||
_router = createAppRouter(_authBloc);
|
||||
unawaited(_bindNotificationResponseHandler());
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -52,13 +44,13 @@ class _LinksyAppState extends State<LinksyApp> {
|
||||
value: _authBloc,
|
||||
child: BlocListener<AuthBloc, AuthState>(
|
||||
listener: (context, state) {
|
||||
if (state is AuthAuthenticated &&
|
||||
state.user.id != _reminderBootstrapUserId) {
|
||||
_reminderBootstrapUserId = state.user.id;
|
||||
unawaited(_rebuildUpcomingReminders());
|
||||
if (state is AuthAuthenticated) {
|
||||
unawaited(
|
||||
sl<AppPrewarmOrchestrator>().ensureStartedFor(state.user.id),
|
||||
);
|
||||
}
|
||||
if (state is AuthUnauthenticated) {
|
||||
_reminderBootstrapUserId = null;
|
||||
sl<AppPrewarmOrchestrator>().reset();
|
||||
}
|
||||
},
|
||||
child: MaterialApp.router(
|
||||
@@ -79,55 +71,4 @@ class _LinksyAppState extends State<LinksyApp> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _rebuildUpcomingReminders() async {
|
||||
final now = DateTime.now();
|
||||
final start = now.subtract(const Duration(days: 90));
|
||||
final end = now.add(const Duration(days: 90));
|
||||
try {
|
||||
final events = await sl<CalendarService>().getEventsForRange(start, end);
|
||||
await sl<LocalNotificationService>().rebuildUpcomingReminders(events);
|
||||
} catch (error) {
|
||||
debugPrint('reminder bootstrap skipped: $error');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _bindNotificationResponseHandler() async {
|
||||
await ReminderNotificationCallbacks.bindResponseHandler((response) async {
|
||||
final payloadRaw = response.payload;
|
||||
if (payloadRaw == null || payloadRaw.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReminderPayload payload;
|
||||
try {
|
||||
payload = ReminderPayload.fromJson(
|
||||
Map<String, dynamic>.from(jsonDecode(payloadRaw) as Map),
|
||||
);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
|
||||
final actionId = response.actionId;
|
||||
ReminderAction? action;
|
||||
if (actionId != null) {
|
||||
try {
|
||||
action = ReminderAction.fromValue(actionId);
|
||||
} catch (_) {
|
||||
action = null;
|
||||
}
|
||||
}
|
||||
if (action == null) {
|
||||
ReminderNotificationCallbacks.onNotificationPayloadReceived?.call(
|
||||
payload,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await sl<ReminderActionExecutor>().handleAction(
|
||||
action: action,
|
||||
payload: payload,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,42 +2,37 @@ import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../data/cache/cache_invalidator.dart';
|
||||
import '../../data/cache/hybrid_cache_store.dart';
|
||||
import '../../data/cache/memory_cache_store.dart';
|
||||
import '../../data/cache/persistent_cache_store.dart';
|
||||
import '../../data/repositories/calendar_event_repository.dart';
|
||||
import '../../data/repositories/calendar_repository.dart';
|
||||
import '../../data/repositories/friend_repository.dart';
|
||||
import '../../data/repositories/inbox_repository.dart';
|
||||
import '../../data/repositories/user_repository.dart';
|
||||
import '../../data/cache/cache_store.dart';
|
||||
import '../../features/calendar/data/repositories/calendar_repository.dart';
|
||||
import '../../features/contacts/data/repositories/friend_repository.dart';
|
||||
import '../../features/messages/data/repositories/inbox_repository.dart';
|
||||
import '../../features/contacts/data/repositories/user_repository.dart';
|
||||
import '../../core/auth/session_controller.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../../core/network/i_api_client.dart';
|
||||
import '../../core/storage/app_preferences.dart';
|
||||
import '../../core/storage/token_storage.dart';
|
||||
import '../../data/network/api_client.dart';
|
||||
import '../../data/network/i_api_client.dart';
|
||||
import '../../data/storage/token_storage.dart';
|
||||
import '../../core/config/env.dart';
|
||||
import '../../data/services/local_notification_service.dart';
|
||||
import '../../features/auth/data/auth_api.dart';
|
||||
import '../../features/auth/data/auth_repository.dart';
|
||||
import '../../features/auth/data/auth_repository_impl.dart';
|
||||
import '../../features/auth/data/apis/auth_api.dart';
|
||||
import '../../features/auth/data/repositories/auth_repository.dart';
|
||||
import '../../features/auth/data/repositories/auth_repository_impl.dart';
|
||||
import '../../features/auth/presentation/bloc/auth_bloc.dart';
|
||||
import '../../features/auth/presentation/bloc/auth_event.dart';
|
||||
import '../../features/chat/presentation/bloc/chat_bloc.dart';
|
||||
import '../../features/calendar/data/calendar_api.dart';
|
||||
import '../../data/services/calendar_service.dart';
|
||||
import '../../features/notification/domain/services/reminder_action_executor.dart';
|
||||
import '../../features/chat/data/repositories/chat_history_repository.dart';
|
||||
import '../../features/calendar/data/apis/calendar_api.dart';
|
||||
import '../../features/calendar/data/services/calendar_service.dart';
|
||||
import '../../shared/state/calendar_state_manager.dart';
|
||||
import '../../features/contacts/data/friends_api.dart';
|
||||
import '../../features/messages/data/inbox_api.dart';
|
||||
import '../../features/settings/data/settings_api.dart';
|
||||
import '../../features/settings/data/services/automation_jobs_api.dart';
|
||||
import '../../features/settings/data/services/user_profile_cache_repository.dart';
|
||||
import '../../features/contacts/data/apis/friends_api.dart';
|
||||
import '../../features/messages/data/apis/inbox_api.dart';
|
||||
import '../../features/settings/data/apis/settings_api.dart';
|
||||
import '../../features/settings/data/apis/automation_jobs_api.dart';
|
||||
import '../../features/settings/data/repositories/user_profile_cache_repository.dart';
|
||||
import '../../features/settings/data/services/user_profile_service.dart';
|
||||
import '../../features/settings/data/services/memory_service.dart';
|
||||
import '../../features/contacts/data/users/users_api.dart';
|
||||
import '../../features/todo/data/todo_api.dart';
|
||||
import '../../features/todo/data/todo_repository.dart';
|
||||
import '../../features/contacts/data/apis/users_api.dart';
|
||||
import '../../features/todo/data/apis/todo_api.dart';
|
||||
import '../../features/todo/data/repositories/todo_repository.dart';
|
||||
import '../services/app_prewarm_orchestrator.dart';
|
||||
import '../services/auth_session_controller.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
@@ -71,10 +66,9 @@ Future<void> configureDependencies() async {
|
||||
|
||||
final sharedPreferences = await SharedPreferences.getInstance();
|
||||
sl.registerSingleton<SharedPreferences>(sharedPreferences);
|
||||
sl.registerSingleton<AppPreferences>(AppPreferences(sharedPreferences));
|
||||
|
||||
final memoryCacheStore = MemoryCacheStore();
|
||||
final persistentCacheStore = PersistentCacheStore();
|
||||
final persistentCacheStore = PersistentCacheStore(prefs: sharedPreferences);
|
||||
final hybridCacheStore = HybridCacheStore(
|
||||
memory: memoryCacheStore,
|
||||
persistent: persistentCacheStore,
|
||||
@@ -100,10 +94,6 @@ Future<void> configureDependencies() async {
|
||||
|
||||
final calendarApi = CalendarApi(apiClient);
|
||||
sl.registerSingleton<CalendarApi>(calendarApi);
|
||||
sl.registerSingleton<CalendarEventRepository>(
|
||||
CalendarEventRepositoryImpl(apiClient),
|
||||
);
|
||||
|
||||
final calendarService = CalendarService(
|
||||
apiClient: apiClient,
|
||||
invalidator: sl<CacheInvalidator>(),
|
||||
@@ -116,17 +106,11 @@ Future<void> configureDependencies() async {
|
||||
);
|
||||
sl.registerSingleton<CalendarRepository>(calendarRepository);
|
||||
|
||||
sl.registerSingleton<LocalNotificationService>(LocalNotificationService());
|
||||
|
||||
final reminderActionExecutor = ReminderActionExecutor(
|
||||
calendarService: calendarService,
|
||||
notificationService: sl<LocalNotificationService>(),
|
||||
);
|
||||
sl.registerSingleton<ReminderActionExecutor>(reminderActionExecutor);
|
||||
|
||||
final friendsApi = FriendsApi(apiClient);
|
||||
sl.registerSingleton<FriendsApi>(friendsApi);
|
||||
sl.registerSingleton<FriendRepository>(FriendRepositoryImpl(apiClient));
|
||||
sl.registerSingleton<FriendRepository>(
|
||||
FriendRepositoryImpl(apiClient: apiClient, store: hybridCacheStore),
|
||||
);
|
||||
|
||||
final settingsApi = SettingsApi(apiClient);
|
||||
sl.registerSingleton<SettingsApi>(settingsApi);
|
||||
@@ -139,7 +123,15 @@ Future<void> configureDependencies() async {
|
||||
|
||||
final inboxApi = InboxApi(apiClient);
|
||||
sl.registerSingleton<InboxApi>(inboxApi);
|
||||
sl.registerSingleton<InboxRepository>(InboxRepositoryImpl(apiClient));
|
||||
sl.registerSingleton<InboxRepository>(
|
||||
InboxRepositoryImpl(apiClient: apiClient, store: hybridCacheStore),
|
||||
);
|
||||
|
||||
final chatHistoryRepository = ChatHistoryRepository(
|
||||
apiClient: apiClient,
|
||||
store: hybridCacheStore,
|
||||
);
|
||||
sl.registerSingleton<ChatHistoryRepository>(chatHistoryRepository);
|
||||
|
||||
final todoApi = TodoApi(apiClient);
|
||||
sl.registerSingleton<TodoApi>(todoApi);
|
||||
@@ -163,10 +155,20 @@ Future<void> configureDependencies() async {
|
||||
);
|
||||
sl.registerSingleton<AuthRepository>(authRepository);
|
||||
|
||||
sl.registerSingleton<AppPrewarmOrchestrator>(
|
||||
AppPrewarmOrchestrator(
|
||||
calendarRepository: calendarRepository,
|
||||
inboxRepository: sl<InboxRepository>(),
|
||||
chatHistoryRepository: chatHistoryRepository,
|
||||
),
|
||||
);
|
||||
|
||||
final authBloc = AuthBloc(authRepository);
|
||||
sl.registerSingleton<AuthBloc>(authBloc);
|
||||
sl.registerSingleton<SessionController>(AuthSessionController(authBloc));
|
||||
sl.registerSingleton<ChatBloc>(ChatBloc(apiClient: apiClient));
|
||||
sl.registerSingleton<ChatBloc>(
|
||||
ChatBloc(apiClient: apiClient, historyRepository: chatHistoryRepository),
|
||||
);
|
||||
|
||||
apiClient.setRefreshCallback((token) async {
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,7 @@ import '../../../features/settings/presentation/screens/work_memory_view_screen.
|
||||
import '../../../features/settings/presentation/screens/user_memory_detail_screen.dart';
|
||||
import '../../../features/settings/presentation/screens/work_memory_detail_screen.dart';
|
||||
import '../../../features/settings/presentation/screens/edit_profile_screen.dart';
|
||||
import '../services/app_prewarm_orchestrator.dart';
|
||||
|
||||
final _homeSecondLevelRoutes = [
|
||||
AppRoutes.shellCalendarBranch,
|
||||
@@ -60,6 +61,7 @@ final _protectedRoutes = [
|
||||
String? resolveAuthRedirect({
|
||||
required AuthState authState,
|
||||
required String matchedLocation,
|
||||
AppPrewarmOrchestrator? prewarm,
|
||||
}) {
|
||||
final isAuthenticated = authState is AuthAuthenticated;
|
||||
final isAuthChecking = authState is AuthInitial || authState is AuthLoading;
|
||||
@@ -71,11 +73,21 @@ String? resolveAuthRedirect({
|
||||
final isProtected =
|
||||
isHomeRoute ||
|
||||
_protectedRoutes.any((route) => matchedLocation.startsWith(route));
|
||||
final prewarmStatus = prewarm?.status ?? AppPrewarmStatus.completed;
|
||||
final shouldBlockForPrewarm =
|
||||
isAuthenticated && prewarmStatus == AppPrewarmStatus.running;
|
||||
|
||||
if (shouldBlockForPrewarm && !isBootRoute) {
|
||||
return AppRoutes.authBoot;
|
||||
}
|
||||
|
||||
if (isAuthChecking && !isBootRoute) {
|
||||
return AppRoutes.authBoot;
|
||||
}
|
||||
if (!isAuthChecking && isBootRoute) {
|
||||
if (shouldBlockForPrewarm) {
|
||||
return null;
|
||||
}
|
||||
return isAuthenticated ? AppRoutes.homeMain : AppRoutes.authLogin;
|
||||
}
|
||||
if (!isAuthenticated && isProtected) {
|
||||
@@ -95,14 +107,17 @@ Widget buildHomeRouteScreen() {
|
||||
}
|
||||
|
||||
GoRouter createAppRouter(AuthBloc authBloc) {
|
||||
final authRefresh = GoRouterRefreshStream(authBloc.stream);
|
||||
final prewarm = sl<AppPrewarmOrchestrator>();
|
||||
return GoRouter(
|
||||
initialLocation: AppRoutes.authBoot,
|
||||
observers: [appRouteObserver],
|
||||
refreshListenable: GoRouterRefreshStream(authBloc.stream),
|
||||
refreshListenable: Listenable.merge([authRefresh, prewarm]),
|
||||
redirect: (context, state) {
|
||||
return resolveAuthRedirect(
|
||||
authState: authBloc.state,
|
||||
matchedLocation: state.matchedLocation,
|
||||
prewarm: prewarm,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../features/calendar/data/repositories/calendar_repository.dart';
|
||||
import '../../features/messages/data/repositories/inbox_repository.dart';
|
||||
import '../../features/chat/data/repositories/chat_history_repository.dart';
|
||||
|
||||
enum AppPrewarmStatus { idle, running, completed, timedOut, failed }
|
||||
|
||||
class AppPrewarmOrchestrator extends ChangeNotifier {
|
||||
AppPrewarmOrchestrator({
|
||||
required CalendarRepository calendarRepository,
|
||||
required InboxRepository inboxRepository,
|
||||
required ChatHistoryRepository chatHistoryRepository,
|
||||
this.bootBudget = const Duration(milliseconds: 1200),
|
||||
Future<void> Function()? prewarmChatHistory,
|
||||
Future<void> Function()? prewarmCalendarToday,
|
||||
Future<void> Function()? prewarmUnreadInbox,
|
||||
}) : _calendarRepository = calendarRepository,
|
||||
_inboxRepository = inboxRepository,
|
||||
_chatHistoryRepository = chatHistoryRepository,
|
||||
_prewarmChatHistory = prewarmChatHistory,
|
||||
_prewarmCalendarToday = prewarmCalendarToday,
|
||||
_prewarmUnreadInbox = prewarmUnreadInbox;
|
||||
|
||||
final CalendarRepository _calendarRepository;
|
||||
final InboxRepository _inboxRepository;
|
||||
final ChatHistoryRepository _chatHistoryRepository;
|
||||
final Duration bootBudget;
|
||||
final Future<void> Function()? _prewarmChatHistory;
|
||||
final Future<void> Function()? _prewarmCalendarToday;
|
||||
final Future<void> Function()? _prewarmUnreadInbox;
|
||||
|
||||
AppPrewarmStatus _status = AppPrewarmStatus.idle;
|
||||
AppPrewarmStatus get status => _status;
|
||||
|
||||
String? _userId;
|
||||
Future<void>? _running;
|
||||
|
||||
bool get isBootBlocking => _status == AppPrewarmStatus.running;
|
||||
|
||||
Future<void> ensureStartedFor(String userId) {
|
||||
if (_userId == userId &&
|
||||
(_status == AppPrewarmStatus.completed ||
|
||||
_status == AppPrewarmStatus.timedOut)) {
|
||||
return Future<void>.value();
|
||||
}
|
||||
if (_userId == userId && _running != null) {
|
||||
return _running!;
|
||||
}
|
||||
|
||||
_userId = userId;
|
||||
_status = AppPrewarmStatus.running;
|
||||
notifyListeners();
|
||||
|
||||
final tasks = Future.wait<void>([
|
||||
_runPrewarmChatHistory(),
|
||||
_runPrewarmCalendarToday(),
|
||||
_runPrewarmUnreadInbox(),
|
||||
]);
|
||||
|
||||
final running = _runWithBudget(tasks);
|
||||
_running = running;
|
||||
return running.whenComplete(() {
|
||||
if (identical(_running, running)) {
|
||||
_running = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _runPrewarmChatHistory() {
|
||||
final override = _prewarmChatHistory;
|
||||
if (override != null) {
|
||||
return override();
|
||||
}
|
||||
return _chatHistoryRepository.loadHistory();
|
||||
}
|
||||
|
||||
Future<void> _runPrewarmCalendarToday() {
|
||||
final override = _prewarmCalendarToday;
|
||||
if (override != null) {
|
||||
return override();
|
||||
}
|
||||
return _calendarRepository.getDayEvents(DateTime.now());
|
||||
}
|
||||
|
||||
Future<void> _runPrewarmUnreadInbox() {
|
||||
final override = _prewarmUnreadInbox;
|
||||
if (override != null) {
|
||||
return override();
|
||||
}
|
||||
return _inboxRepository.getMessages(isRead: false);
|
||||
}
|
||||
|
||||
Future<void> _runWithBudget(Future<void> tasks) async {
|
||||
try {
|
||||
await tasks.timeout(bootBudget);
|
||||
_status = AppPrewarmStatus.completed;
|
||||
notifyListeners();
|
||||
} on TimeoutException {
|
||||
_status = AppPrewarmStatus.timedOut;
|
||||
notifyListeners();
|
||||
} catch (_) {
|
||||
_status = AppPrewarmStatus.failed;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_userId = null;
|
||||
_running = null;
|
||||
if (_status != AppPrewarmStatus.idle) {
|
||||
_status = AppPrewarmStatus.idle;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user