Пользовательский интерфейс приложения для iOS, показанный при запуске перед SplashIOS

Программируем под IOS
Ответить
Anonymous
 Пользовательский интерфейс приложения для iOS, показанный при запуске перед Splash

Сообщение Anonymous »

Я строю приложение для iOS/Android, используя Flutter. Когда начинается приложение, поток: < /p>
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'; // ✅ Now this works

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('✅ Package info loaded');
}
} catch (e) {
if (kDebugMode) {
Logger.log('❌ Package info failed: $e');
}
_version = 'v1.0.0';
}

// 🔒 iOS location permission check
try {
if (Platform.isIOS) {
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (kDebugMode) {
Logger.log('✅ [iOS] Location service enabled: $serviceEnabled');
}
if (serviceEnabled) {
var permission = await Geolocator.checkPermission();
if (kDebugMode) {
Logger.log('✅ [iOS] Location permission status: $permission');
}
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (kDebugMode) {
Logger.log('✅ [iOS] Location permission requested: $permission');
}
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('iOS Location permission request failed: $e');
}
}

// ✅ Android 13+ notification permission
try {
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (kDebugMode) {
Logger.log('✅ Android SDK: ${androidInfo.version.sdkInt}');
}
if (androidInfo.version.sdkInt >= 33) {
final status = await Permission.notification.request();
if (kDebugMode) {
Logger.log('✅ Notification permission: $status');
}
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');
}
}

// ✅ Android location permissions: foreground and background
try {
if (Platform.isAndroid) {
// ✅ 1. Check and request notification permission (Android 13+)
final notifStatus = await Permission.notification.status;
if (!notifStatus.isGranted) {
if (kDebugMode) {
Logger.log('🔔 Requesting notification permission...');
}
final notifResult = await Permission.notification.request();
if (kDebugMode) {
Logger.log('🔔 Notification permission result: $notifResult');
}
}

// ✅ 2. Check foreground location permission
if (kDebugMode) {
Logger.log('📍 Checking foreground location permission...');
}
var fgStatus = await Permission.location.status;
if (kDebugMode) {
Logger.log('📍 Foreground location permission status: $fgStatus');
}

if (!fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Requesting foreground location permission...');
}
fgStatus = await Permission.location.request();
if (kDebugMode) {
Logger.log('📍 Foreground location permission result: $fgStatus');
}
}

// ✅ 3. Check background location permission (if foreground granted)
if (fgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Checking background location permission...');
}
var bgStatus = await Permission.locationAlways.status;
if (kDebugMode) {
Logger.log('📍 Background location permission status: $bgStatus');
}

if (!bgStatus.isGranted) {
if (kDebugMode) {
Logger.log('📍 Requesting background location permission...');
}
bgStatus = await Permission.locationAlways.request();
if (kDebugMode) {
Logger.log('📍 Background location permission result: $bgStatus');
}
} else {
if (kDebugMode) {
Logger.log('✅ Background location permission already granted');
}
}
} else {
if (kDebugMode) {
Logger.log('❌ Foreground location denied, skipping background request');
}
}
}
} catch (e) {
if (kDebugMode) {
Logger.log('❗ Permission request failed: $e');
}
}

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 // 👈 Add this import for Maps

@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.")
}

// ✅ Register Google Maps API key
GMSServices.provideAPIKey("key") // 👈 Replace with your actual 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»