main.dart
Нативный всплеск показывает логотип Dev (во время которого происходит запуск приложения)
authgate
adp widget (извлеченные это из основного, чтобы уменьшить, сколько выполняет основная работа)
fluther splash, показывающий логотип приложений (во время того, что я называют «Br />» < /> pr /br /> < /br /br /br /br /> < /> < /br /br /br /> < /> < /br /> < /br /br /> < /br /> < /> < /br /br /br /br /br /> < /> < /br /br /br /br /br /br /br /br /br /br /br /br /br /br /br /randing -cersing). Когда начинается приложение iOS, оно кратко мигает последнего просмотренного экрана пользовательского интерфейса перед нативным экраном Splash. например Если я просмотрел экран «Настройки» в приложении, то покинул приложение, в следующий раз, когда я запускаю приложение, первое, что я вижу до того, как нативный экран Splash - это вспышка экрана настройки. Кажется, мне удалось сделать что -то в начале, что создало эту проблему, которую никто другой не получает? Это сводит меня с ума. Учитывая, что это происходит до нативного всплеска, я предполагаю, что он является снимком, особенно для iOS, поскольку он не происходит в приложении Android, и, хотя он не является проблемой, он не выглядит великолепно с точки зрения пользователя. Uiapplication.shared.ignoresnapshotonnextapplicationlaunch () к AppDelegate без изменения поведения
1. Main.dart
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
tz.initializeTimeZones();
await Future.delayed(const Duration(seconds: 2));
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
await Hive.initFlutter();
Hive.registerAdapter(MedicationAdapter()); // TypeID 0
Hive.registerAdapter(DoseEntryAdapter()); // TypeID 1
Hive.registerAdapter(HealthProfessionalAdapter()); // TypeID 2
Hive.registerAdapter(DiscussionItemAdapter()); // TypeID 3
Hive.registerAdapter(AppointmentAdapter()); // TypeID 4
Hive.registerAdapter(UserProfileAdapter()); // TypeID 5
Hive.registerAdapter(TodoItemAdapter()); // TypeID 7
Hive.registerAdapter(DiaryEntryAdapter()); // TypeID 8
Hive.registerAdapter(SelectedTrackersAdapter()); // TypeID 11
Hive.registerAdapter(SelectedGoalsAdapter()); // TypeID 70
Hive.registerAdapter(GpsActivityEntryAdapter()); // TypeID 60
Hive.registerAdapter(WeightEntryAdapter()); // TypeID 50
Hive.registerAdapter(WeightGoalAdapter()); // TypeID 31
Hive.registerAdapter(ActivityGoalAdapter()); // TypeID 28
Hive.registerAdapter(DailyGoalProgressAdapter()); // TypeID 33
Hive.registerAdapter(WeeklyWeightEntryAdapter()); // TypeID 32
Hive.registerAdapter(MenstrualCycleEntryAdapter()); // TypeID 43
Hive.registerAdapter(WorkoutActivityAdapter()); // TypeID 41
Hive.registerAdapter(MeditationGoalAdapter()); // TypeID 98
Hive.registerAdapter(DailyMeditationProgressAdapter()); // TypeID 97
await Hive.openBox('settings');
await Hive.openBox('medicationAlertsSent');
await Hive.openBox('menstrualCycleBox');
await Hive.openBox('gps_activities');
await Hive.openBox('gpsActivity');
await NotificationService.initialize();
await LocalisationSettingsService.init();
// Load saved locale
final savedLocaleCode = LocalisationSettingsService.getSelectedLocale();
final initialLocale = savedLocaleCode != null ? Locale(savedLocaleCode) : null;
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ThemeNotifier()),
ChangeNotifierProvider(create: (_) => LocaleNotifier()..setLocale(initialLocale)),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final themeNotifier = Provider.of(context);
final localeNotifier = Provider.of(context);
return MaterialApp(
title: 'Pebbl',
navigatorKey: navigatorKey,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: themeNotifier.themeMode,
debugShowCheckedModeBanner: false,
home: const NativeSplashWrapper(),
locale: localeNotifier.locale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
localeResolutionCallback: (locale, supportedLocales) {
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
return const Locale('en', 'GB');
}
);
}
}
< /code>
2. Native splash wrapper
import 'package:flutter/material.dart';
import 'package:pebbl/screens/login/auth_gate.dart'; //
class NativeSplashWrapper extends StatefulWidget {
const NativeSplashWrapper({super.key});
@override
State createState() => _NativeSplashWrapperState();
}
class _NativeSplashWrapperState extends State {
bool _ready = false;
@override
void initState() {
super.initState();
_init();
}
Future _init() async {
await Future.delayed(const Duration(milliseconds: 100));
if (mounted) {
setState(() {
_ready = true;
});
}
}
@override
Widget build(BuildContext context) {
if (!_ready) {
return const Scaffold(
backgroundColor: Color(0xFFF5F5F5),
body: Center(
child: Image(
image: AssetImage('assets/images/pebbl_logo.png'),
width: 200,
height: 200,
),
),
);
}
return const AuthGate();
}
}
< /code>
3. Pubspec.yaml (snippet for flutter native splash)
flutter_native_splash:
color: "#F5F5F5"
image: assets/images/dev_logo.png
fullscreen: true
android: true
ios: true
android_12:
image: assets/images/android12_dev_logo.png
< /code>
4. Auth gate
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:pebbl/screens/login/splash_screen.dart';
import 'package:pebbl/screens/login/login_screen.dart';
import 'package:pebbl/widgets/pebbl_app.dart'; // We’ll extract PebblApp too (see below)
class AuthGate extends StatelessWidget {
const AuthGate({super.key});
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, authSnapshot) {
final isLoggedIn = authSnapshot.hasData;
final screen = isLoggedIn ? SplashScreen() : const LoginScreen();
return PebblApp(initialScreen: screen);
},
);
}
}
< /code>
5. Pebbl app widget
import 'package:flutter/material.dart';
import 'package:pebbl/screens/landing/landing_screen.dart';
import 'package:pebbl/services/notification_service.dart';
import 'package:pebbl/utils/logger.dart';
import 'package:pebbl/main.dart'; // for navigatorKey
class PebblApp extends StatefulWidget {
final Widget initialScreen;
const PebblApp({super.key, required this.initialScreen});
@override
State createState() => _PebblAppState();
}
class _PebblAppState extends State with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
final payload = NotificationService.getAndClearPendingPayload();
if (payload != null) {
Logger.log('App resumed. Routing to payload: $payload');
navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => LandingScreen(payload: payload)),
(route) => false,
);
}
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: widget.initialScreen,
);
}
}
< /code>
6. Splash screen
import 'package:flutter/foundation.dart';
import 'package:pebbl/utils/logger.dart';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:pebbl/screens/landing/landing_screen.dart';
import 'package:pebbl/screens/login/login_screen.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pebbl/services/notification_service.dart';
import 'package:geolocator/geolocator.dart';
class SplashScreen extends StatefulWidget {
final String? payloadOverride;
const SplashScreen({super.key, this.payloadOverride});
@override
State createState() => _SplashScreenState();
}
class _SplashScreenState extends State with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation _animation;
String _version = '';
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..forward();
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_init();
}
Future _init() async {
try {
final info = await PackageInfo.fromPlatform();
setState(() {
_version = 'v${info.version}+${info.buildNumber}';
});
if (kDebugMode) {
Logger.log('
}
} catch (e) {
if (kDebugMode) {
Logger.log('
}
_version = 'v1.0.0';
}
//
try {
if (Platform.isIOS) {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (kDebugMode) {
Logger.log('
}
if (serviceEnabled) {
var permission = await Geolocator.checkPermission();
if (kDebugMode) {
Logger.log('
}
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (kDebugMode) {
Logger.log('
}
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('iOS Location permission request failed: $e');
}
}
//
try {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (kDebugMode) {
Logger.log('
}
if (androidInfo.version.sdkInt >= 33) {
final status = await Permission.notification.request();
if (kDebugMode) {
Logger.log('
}
if (!status.isGranted) {
if (kDebugMode) {
Logger.log('Notification permission denied');
}
// Optionally show a dialog here
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('Notification permission request failed: $e');
}
}
//
try {
if (Platform.isAndroid) {
//
final notifStatus = await Permission.notification.status;
if (!notifStatus.isGranted) {
if (kDebugMode) {
Logger.log('
}
final notifResult = await Permission.notification.request();
if (kDebugMode) {
Logger.log('
}
}
//
if (kDebugMode) {
Logger.log('
}
var fgStatus = await Permission.location.status;
if (kDebugMode) {
Logger.log('
}
if (!fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('
}
fgStatus = await Permission.location.request();
if (kDebugMode) {
Logger.log('
}
}
//
if (fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('
}
var bgStatus = await Permission.locationAlways.status;
if (kDebugMode) {
Logger.log('
}
if (!bgStatus.isGranted) {
if (kDebugMode) {
Logger.log('
}
bgStatus = await Permission.locationAlways.request();
if (kDebugMode) {
Logger.log('
}
} else {
if (kDebugMode) {
Logger.log('
}
}
} else {
if (kDebugMode) {
Logger.log('
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('
}
}
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
final user = FirebaseAuth.instance.currentUser;
if (kDebugMode) {
Logger.log('SplashScreen: currentUser = ${user?.uid}');
}
final payload = widget.payloadOverride ??
NotificationService.getInitialPayload() ??
NotificationService.consumePendingPayload();
if (kDebugMode) {
Logger.log('SplashScreen init: Firebase user: ${user?.uid}, payload: $payload');
}
if (kDebugMode) {
Logger.log('SplashScreen: payload resolved = $payload');
}
if (user != null) {
try {
if (kDebugMode) {
Logger.log('Calling rescheduleAllReminders...');
}
await NotificationService.rescheduleAllReminders()
.timeout(const Duration(seconds: 5));
if (kDebugMode) {
Logger.log('rescheduleAllReminders completed');
}
} catch (e) {
if (kDebugMode) {
Logger.log('rescheduleAllReminders failed or timed out: $e');
}
}
}
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => user == null
? const LoginScreen()
: LandingScreen(payload: payload),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
FadeTransition(
opacity: _animation,
child: Center(
child: Image.asset(
'assets/images/pebbl_logo.png',
width: 200,
height: 200,
),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Text(
_version,
style: const TextStyle(color: Colors.black54),
),
),
],
),
);
}
}
< /code>
AppDelegate
import UIKit
import Flutter
import UserNotifications
import AVFAudio
import GoogleMaps //
@main
@objc class AppDelegate: FlutterAppDelegate {
//lazy var flutterEngine = FlutterEngine(name: "my_engine")
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UIApplication.shared.ignoreSnapshotOnNextApplicationLaunch()
// Start engine (plugins auto-register when the engine is attached to the view)
//flutterEngine.run()
// Set up audio playback category
do {
try AVAudioSession.sharedInstance().setCategory(
.playback,
mode: .default,
options: [.mixWithOthers]
)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("Failed to set audio session category.")
}
//
GMSServices.provideAPIKey("key") //
// Ensure notification taps are delivered to Flutter
UNUserNotificationCenter.current().delegate = self
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
func applicationWillResignActive(_ application: UIApplication) {
// Add a white view over the window before backgrounding
let whiteView = UIView(frame: window?.bounds ?? .zero)
whiteView.backgroundColor = UIColor.white
whiteView.tag = 999 // So we can remove it later
window?.addSubview(whiteView)
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Remove the white view when app becomes active
if let whiteView = window?.viewWithTag(999) {
whiteView.removeFromSuperview()
}
}
}
}
< /code>
SceneDelegate
import UIKit
import Flutter
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
//let appDelegate = UIApplication.shared.delegate as! AppDelegate
//let flutterEngine = appDelegate.flutterEngine
let flutterViewController = FlutterViewController()
GeneratedPluginRegistrant.register(with: flutterViewController.engine)
window.rootViewController = flutterViewController
self.window = window
window.makeKeyAndVisible()
}
}
Подробнее здесь: https://stackoverflow.com/questions/797 ... ore-splash
Мобильная версия