171 lines
5.5 KiB
Dart
171 lines
5.5 KiB
Dart
import 'package:facebook_app_events/facebook_app_events.dart';
|
||
import 'package:flutter/material.dart';
|
||
|
||
import 'core/auth/auth_service.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_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);
|
||
// 冷启动时进程已在 resumed,didChangeAppLifecycleState 往往收不到「变为 resumed」,需首帧后再手动打一次
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_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,
|
||
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 Positioned.fill(
|
||
child: AbsorbPointer(
|
||
child: Container(
|
||
color: Colors.black.withValues(alpha: 0.2),
|
||
child: const Center(
|
||
child: CircularProgressIndicator(
|
||
color: AppColors.primary,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
);
|
||
},
|
||
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);
|
||
},
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _MainScaffold extends StatelessWidget {
|
||
const _MainScaffold({
|
||
required this.currentTab,
|
||
required this.onTabSelected,
|
||
});
|
||
|
||
final NavTab currentTab;
|
||
final ValueChanged<NavTab> onTabSelected;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
body: IndexedStack(
|
||
index: currentTab.index,
|
||
children: [
|
||
HomeScreen(isActive: currentTab == NavTab.home),
|
||
GalleryScreen(isActive: currentTab == NavTab.gallery),
|
||
ProfileScreen(isActive: currentTab == NavTab.profile),
|
||
],
|
||
),
|
||
bottomNavigationBar: BottomNavBar(
|
||
currentTab: currentTab,
|
||
onTabSelected: onTabSelected,
|
||
),
|
||
);
|
||
}
|
||
}
|