Files
firka/firka/lib/app/initialization_screen.dart
2026-03-05 08:40:50 +01:00

263 lines
9.4 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:firka/app/app_state.dart';
import 'package:firka/app/initialization.dart';
import 'package:firka/core/bloc/home_refresh_cubit.dart';
import 'package:firka/core/settings.dart';
import 'package:firka/core/bloc/profile_picture_cubit.dart';
import 'package:firka/core/bloc/reauth_cubit.dart';
import 'package:firka/core/bloc/settings_cubit.dart';
import 'package:firka/core/bloc/theme_cubit.dart';
import 'package:firka/core/firka_bundle.dart';
import 'package:firka/routing/app_router.dart';
import 'package:firka/services/watch_sync_helper.dart';
import 'package:firka/ui/theme/style.dart';
import 'package:firka/ui/phone/pages/extras/main_wear_pair.dart';
import 'package:firka/l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:go_router/go_router.dart';
class InitializationScreen extends StatefulWidget {
const InitializationScreen({super.key});
@override
State<InitializationScreen> createState() => _InitializationScreenState();
}
class _InitializationScreenState extends State<InitializationScreen> {
GoRouter? _router;
final Future<AppInitialization> _init = initializeApp().timeout(
const Duration(seconds: 20),
);
ThemeData _buildTheme(FirkaStyle style) {
return ThemeData(
scaffoldBackgroundColor: style.colors.background,
canvasColor: style.colors.background,
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: Colors.transparent,
),
colorScheme: ColorScheme.fromSeed(
seedColor: style.colors.accent,
brightness: style.isLight ? Brightness.light : Brightness.dark,
background: style.colors.background,
surface: style.colors.card,
),
useMaterial3: false,
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder<AppInitialization>(
future: _init,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
logger.shout(
"Error in InitializationScreen",
snapshot.error.toString(),
snapshot.stackTrace,
);
FlutterNativeSplash.remove();
return MaterialApp(
key: const ValueKey('errorPage'),
home: DefaultAssetBundle(
bundle: FirkaBundle(),
child: Scaffold(
body: Center(
child: Text(
'Error initializing app: ${snapshot.error}',
style: const TextStyle(color: Colors.red),
),
),
),
),
);
}
assert(snapshot.data != null);
initData = snapshot.data!;
initDone = true;
FlutterNativeSplash.remove();
WatchSyncHelper.initialize();
if (Platform.isAndroid) {
WatchSyncHelper.setWearSyncMethodCallHandler();
}
if (Platform.isIOS) {
unawaited(() async {
try {
await WatchSyncHelper.sendLanguageToWatch();
} catch (e) {
logger.warning(
'[Init] Failed to publish language to Watch after sync init: $e',
);
}
}());
}
if (!initData.hasWatchListener) {
initData.hasWatchListener = true;
WatchSyncHelper.onWatchMessage = (msg) {
logger.finest("WatchOS IPC [Watch -> Phone]: ${msg["id"]}");
switch (msg["id"]) {
case "ping":
if (initData.tokens.isNotEmpty) {
logger.finest("WatchOS IPC [Phone -> Watch]: pong");
unawaited(
WatchSyncHelper.sendMessageToWatch({'id': 'pong'}),
);
_router?.go('/home');
WidgetsBinding.instance.addPostFrameCallback((_) {
final ctx = navigatorKey.currentContext;
if (ctx != null && ctx.mounted) {
logger.info('Watch init_data: ${jsonEncode(msg)}');
showWearBottomSheet(
ctx,
initData,
Platform.isAndroid ? msg['model'] : 'Apple Watch',
);
}
});
}
break;
case "init_done":
case "sync_done":
final ctx = navigatorKey.currentContext;
if (ctx != null && ctx.mounted) {
ScaffoldMessenger.of(ctx).hideCurrentSnackBar();
}
initData.dismissWearPairingSheet?.call();
initData.dismissWearPairingSheet = null;
break;
}
};
if (Platform.isAndroid) {
WatchSyncHelper.watchMessageStream.listen((msg) async {
WatchSyncHelper.onWatchMessage?.call(msg);
if (msg['id'] == 'request_sync' &&
initDone &&
isWearOsSupportEnabled()) {
final ctx = navigatorKey.currentContext;
if (ctx != null && ctx.mounted) {
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(content: Text(initData.l10n.wear_syncing)),
);
}
await WatchSyncHelper.runWearSyncInForeground(
initData.client,
);
}
});
if (isWearOsSupportEnabled()) {
unawaited(() async {
try {
await WatchSyncHelper.startWearSyncServiceWithFreshCache(
initData.client,
initData.appDir.path,
);
} catch (e) {
logger.warning(
'[Init] Failed to start Wear sync service on launch: $e',
);
}
}());
}
}
}
if (_router == null) {
_router = createAppRouter();
appRouter = _router;
}
final themeCubit = initData.themeCubit!;
final settingsCubit = initData.settingsCubit!;
final profilePictureCubit = initData.profilePictureCubit!;
final reauthCubit = initData.reauthCubit!;
final homeRefreshCubit = initData.homeRefreshCubit!;
return MultiBlocProvider(
providers: [
BlocProvider<ThemeCubit>.value(value: themeCubit),
BlocProvider<SettingsCubit>.value(value: settingsCubit),
BlocProvider<ProfilePictureCubit>.value(
value: profilePictureCubit,
),
BlocProvider<ReauthCubit>.value(value: reauthCubit),
BlocProvider<HomeRefreshCubit>.value(value: homeRefreshCubit),
],
child: BlocBuilder<ThemeCubit, ThemeState>(
builder: (context, themeState) {
final isLight = themeState.isLightMode;
final overlay = SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness:
isLight ? Brightness.dark : Brightness.light,
statusBarBrightness:
isLight ? Brightness.light : Brightness.dark,
systemStatusBarContrastEnforced: false,
);
final themeMode =
isLight ? ThemeMode.light : ThemeMode.dark;
final fallbackBg = isLight
? lightStyle.colors.background
: darkStyle.colors.background;
SystemChrome.setSystemUIOverlayStyle(overlay);
return MaterialApp.router(
title: 'Firka',
key: const ValueKey('firkaApp'),
routerConfig: _router!,
theme: _buildTheme(lightStyle),
darkTheme: _buildTheme(darkStyle),
themeMode: themeMode,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
builder: (context, child) {
return AnnotatedRegion<SystemUiOverlayStyle>(
value: overlay,
child: ColoredBox(
color: fallbackBg,
child: child ?? const SizedBox.shrink(),
),
);
},
);
},
),
);
}
return MaterialApp(
home: DefaultAssetBundle(
bundle: FirkaBundle(),
child: Scaffold(
backgroundColor: const Color(0xFF7CA120),
body: Container(),
),
),
);
},
);
}
}