petsHero-AI/lib/app.dart
2026-03-30 14:29:55 +08:00

275 lines
8.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:facebook_app_events/facebook_app_events.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'core/auth/auth_service.dart';
import 'core/nav/app_route_observer.dart';
import 'core/config/facebook_config.dart';
import 'core/log/app_logger.dart';
import 'core/theme/app_colors.dart';
import 'core/theme/app_theme.dart';
import 'core/user/user_state.dart';
import 'features/gallery/gallery_screen.dart';
import 'features/generate_video/generate_progress_screen.dart';
import 'features/generate_video/generate_video_screen.dart';
import 'features/gallery/models/gallery_task_item.dart';
import 'features/generate_video/generation_result_screen.dart';
import 'features/home/home_playback_resume.dart';
import 'features/home/home_screen.dart';
import 'features/home/models/task_item.dart';
import 'features/profile/profile_screen.dart';
import 'features/recharge/recharge_screen.dart';
import 'shared/widgets/bottom_nav_bar.dart';
import 'shared/tab_selector_scope.dart';
/// Root app widget with navigation
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> with WidgetsBindingObserver {
NavTab _currentTab = NavTab.home;
static final _fbLog = AppLogger('FB');
final FacebookAppEvents _fbAppEvents = FacebookAppEvents();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// 冷启动时进程已在 resumeddidChangeAppLifecycleState 往往收不到「变为 resumed」需首帧后再手动打一次
WidgetsBinding.instance.addPostFrameCallback((_) {
// 去掉原生启动屏(黑底+Logo进入 Flutter之后由登录遮罩承接等待提示
FlutterNativeSplash.remove();
_reportFacebookActivateApp('first_frame');
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
/// `AutoLogAppEventsEnabled=false` 时手动上报 Facebook **安装 + 应用激活**activateApp
void _reportFacebookActivateApp(String reason) {
_fbAppEvents.activateApp().then((_) {
if (FacebookConfig.debugLogs) {
_fbLog.d('activateApp手动: $reason');
}
});
}
/// 从后台回到前台时触发;冷启动依赖 initState 里的 addPostFrameCallback
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_reportFacebookActivateApp('lifecycle_resumed');
}
}
@override
Widget build(BuildContext context) {
return UserCreditsScope(
child: TabSelectorScope(
selectTab: (tab) => setState(() => _currentTab = tab),
child: MaterialApp(
title: 'AI Video App',
theme: AppTheme.light,
debugShowCheckedModeBanner: false,
navigatorObservers: [appRouteObserver],
initialRoute: '/',
builder: (context, child) {
return Stack(
fit: StackFit.expand,
children: [
SafeArea(
top: true,
left: false,
right: false,
bottom: false,
child: child ?? const SizedBox.shrink(),
),
FutureBuilder<void>(
future: AuthService.loginComplete,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return const SizedBox.shrink();
}
return const _StartupLoginOverlay();
},
),
],
);
},
routes: {
'/': (_) => _MainScaffold(
currentTab: _currentTab,
onTabSelected: (tab) => setState(() => _currentTab = tab),
),
'/recharge': (_) => const RechargeScreen(),
'/generate': (ctx) {
final task = ModalRoute.of(ctx)?.settings.arguments as TaskItem?;
return GenerateVideoScreen(task: task);
},
'/progress': (ctx) {
final args = ModalRoute.of(ctx)?.settings.arguments;
final taskId = args is Map ? args['taskId'] : args;
final imagePath = args is Map ? args['imagePath'] as String? : null;
return GenerateProgressScreen(
taskId: taskId,
imagePath: imagePath,
);
},
'/result': (ctx) {
final mediaItem =
ModalRoute.of(ctx)?.settings.arguments as GalleryMediaItem?;
return GenerationResultScreen(mediaItem: mediaItem);
},
},
),
),
);
}
}
/// 启动阶段等待 [AuthService.loginComplete]:久等时提示网络/服务端问题,避免用户误以为「死机」。
class _StartupLoginOverlay extends StatefulWidget {
const _StartupLoginOverlay();
@override
State<_StartupLoginOverlay> createState() => _StartupLoginOverlayState();
}
class _StartupLoginOverlayState extends State<_StartupLoginOverlay> {
static const _longWaitAfter = Duration(seconds: 22);
Timer? _longWaitTimer;
bool _showNetworkHint = false;
@override
void initState() {
super.initState();
_longWaitTimer = Timer(_longWaitAfter, () {
if (mounted) setState(() => _showNetworkHint = true);
});
}
@override
void dispose() {
_longWaitTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Positioned.fill(
child: AbsorbPointer(
child: Container(
color: Colors.black.withValues(alpha: 0.22),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(color: AppColors.primary),
if (_showNetworkHint) ...[
const SizedBox(height: 22),
Text(
'网络较慢或暂时无法连接',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.surface.withValues(alpha: 0.96),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 10),
Text(
'请检查 WiFi 或移动数据,稍候片刻。若网络正常,可能是服务器繁忙,请过一会再试。',
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.surface.withValues(alpha: 0.78),
fontSize: 14,
height: 1.4,
),
),
],
],
),
),
),
),
),
);
}
}
class _MainScaffold extends StatefulWidget {
const _MainScaffold({
required this.currentTab,
required this.onTabSelected,
});
final NavTab currentTab;
final ValueChanged<NavTab> onTabSelected;
@override
State<_MainScaffold> createState() => _MainScaffoldState();
}
class _MainScaffoldState extends State<_MainScaffold> with RouteAware {
bool _routeObserverSubscribed = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_routeObserverSubscribed) {
final route = ModalRoute.of(context);
if (route is PageRoute<dynamic>) {
appRouteObserver.subscribe(this, route);
_routeObserverSubscribed = true;
}
}
}
@override
void dispose() {
if (_routeObserverSubscribed) {
appRouteObserver.unsubscribe(this);
}
super.dispose();
}
/// 挂在 `/` 的 [PageRoute] 上:任意子页面 pop 后底层重新露出时触发(比 [HomeScreen] 内订阅更可靠)
@override
void didPopNext() {
if (widget.currentTab == NavTab.home) {
homePlaybackResumeNonce.value++;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: widget.currentTab.index,
children: [
HomeScreen(isActive: widget.currentTab == NavTab.home),
GalleryScreen(isActive: widget.currentTab == NavTab.gallery),
ProfileScreen(isActive: widget.currentTab == NavTab.profile),
],
),
bottomNavigationBar: BottomNavBar(
currentTab: widget.currentTab,
onTabSelected: widget.onTabSelected,
),
);
}
}