158 lines
5.8 KiB
Dart
158 lines
5.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:app_links/app_links.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
|
|
import '../common/router/app_router.dart';
|
|
import '../common/lifecycle/refresh_coordinator.dart';
|
|
import '../common/push/device_push_registrar.dart';
|
|
import '../common/push/umeng_push_service.dart';
|
|
import '../common/theme/jinzhi_theme.dart';
|
|
import '../common/theme/spacing.dart';
|
|
import '../common/theme/theme_controller.dart';
|
|
import '../features/ads/application/ad_config_foreground_refresher.dart';
|
|
import '../features/settings/application/update_foreground_service.dart';
|
|
import '../features/web/application/mall_web_host_controller.dart';
|
|
import '../features/auth/application/auth_controller.dart';
|
|
import '../features/auth/application/auth_session_coordinator.dart';
|
|
import '../features/auth/data/auth_models.dart';
|
|
import '../features/assets/application/asset_portfolio_controller.dart';
|
|
import '../common/router/deep_link_service.dart';
|
|
import '../common/services/device_header_service.dart';
|
|
|
|
class JinzhiApp extends ConsumerStatefulWidget {
|
|
const JinzhiApp({super.key});
|
|
|
|
@override
|
|
ConsumerState<JinzhiApp> createState() => _JinzhiAppState();
|
|
}
|
|
|
|
class _JinzhiAppState extends ConsumerState<JinzhiApp>
|
|
with WidgetsBindingObserver {
|
|
late final DevicePushRegistrar _pushRegistrar;
|
|
ProviderSubscription<AsyncValue<AuthState>>? _authSubscription;
|
|
StreamSubscription<Uri>? _appLinkSubscription;
|
|
late final SessionEndListener _sessionEndListener;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
_pushRegistrar = DevicePushRegistrar(ref.read)..start();
|
|
_sessionEndListener = (reason) {
|
|
if (reason == SessionEndReason.accountDeleted) {
|
|
ref.read(assetPortfolioControllerProvider.notifier).clearHoldings();
|
|
}
|
|
};
|
|
ref.read(authSessionCoordinatorProvider).addListener(_sessionEndListener);
|
|
_authSubscription = ref.listenManual<AsyncValue<AuthState>>(authProvider, (
|
|
previous,
|
|
next,
|
|
) {
|
|
final oldUser = previous?.valueOrNull is LoggedInAuthState
|
|
? (previous!.valueOrNull! as LoggedInAuthState).user.id
|
|
: null;
|
|
final newUser = next.valueOrNull is LoggedInAuthState
|
|
? (next.valueOrNull! as LoggedInAuthState).user.id
|
|
: null;
|
|
if (oldUser != newUser) _pushRegistrar.forceRegisterCurrentToken();
|
|
DeepLinkService.notifyAuthChanged();
|
|
});
|
|
UmengPushService.configureNotificationOpen(DeepLinkService.handle);
|
|
unawaited(_startAppLinks());
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
DeepLinkService.attachRouter((location) {
|
|
final context = rootNavigatorKey.currentContext;
|
|
if (context == null) return false;
|
|
context.go(location);
|
|
return true;
|
|
}, authGuard: (_) => !ref.read(authProvider).isLoading);
|
|
// 首次 iOS 启动在欢迎页点击进入后才注册 APNs,确保系统弹窗不被启动页
|
|
// 或其他 Overlay 吞掉;后续启动和 Android 保持原有自动初始化。
|
|
if (!DeviceHeaderService.to.iosStartupRequired) {
|
|
unawaited(UmengPushService.startIfAllowed());
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _startAppLinks() async {
|
|
try {
|
|
final appLinks = AppLinks();
|
|
final initial = await appLinks.getInitialLink();
|
|
if (initial != null) DeepLinkService.handleUri(initial);
|
|
_appLinkSubscription = appLinks.uriLinkStream.listen(
|
|
DeepLinkService.handleUri,
|
|
onError: (_) {},
|
|
);
|
|
} catch (_) {
|
|
// Desktop tests and unsupported platforms may not register the plugin.
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
unawaited(_pushRegistrar.stop());
|
|
_authSubscription?.close();
|
|
ref
|
|
.read(authSessionCoordinatorProvider)
|
|
.removeListener(_sessionEndListener);
|
|
unawaited(_appLinkSubscription?.cancel());
|
|
DeepLinkService.detachRouter();
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
if (state == AppLifecycleState.resumed) {
|
|
unawaited(_pushRegistrar.refresh());
|
|
unawaited(
|
|
ref
|
|
.read(adConfigForegroundRefresherProvider)
|
|
.refresh(
|
|
refreshMallTabConfig:
|
|
!MallWebHostController.instance.state.value.visible,
|
|
),
|
|
);
|
|
final context = rootNavigatorKey.currentContext;
|
|
if (context != null) {
|
|
unawaited(UpdateForegroundService.check(context, ref));
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Keep the app-wide foreground refresh coordinator alive. Features opt in
|
|
// by registering only while their screen is visible.
|
|
ref.watch(refreshCoordinatorProvider);
|
|
final router = ref.watch(routerProvider);
|
|
final mode = ref.watch(themeModeControllerProvider);
|
|
// DM Sans 作主字体(拉丁/数字),中文走系统级联(iOS 苹方 / Android 厂商
|
|
// 中文字体)——主流做法,不显式点名、不内置字体。
|
|
final dmSansFamily = GoogleFonts.dmSans().fontFamily;
|
|
final textTheme = ShadTextTheme(family: dmSansFamily);
|
|
return ShadApp.router(
|
|
title: '金值',
|
|
themeMode: mode,
|
|
theme: ShadThemeData(
|
|
brightness: Brightness.light,
|
|
colorScheme: jinzhiLightScheme,
|
|
textTheme: textTheme,
|
|
radius: BorderRadius.circular(Spacing.radiusBase),
|
|
),
|
|
darkTheme: ShadThemeData(
|
|
brightness: Brightness.dark,
|
|
colorScheme: jinzhiDarkScheme,
|
|
textTheme: textTheme,
|
|
radius: BorderRadius.circular(Spacing.radiusBase),
|
|
),
|
|
routerConfig: router,
|
|
);
|
|
}
|
|
}
|