fix(ios): adopt the UIScene life cycle — 1.2.3 (27)
Build 26 was the first one made with the iOS 27 SDK, and UIKit stops apps without the scene life cycle at launch on iOS 27 (TN3187). The scene delegate only shows the FlutterEngine that AppDelegate starts with the process, so a VoIP push in the background still finds the CallKit plugin and a running Dart isolate when no scene is connected.
This commit is contained in:
@@ -8,6 +8,12 @@ verzování dle [ajtak standardu](#verzování).
|
|||||||
|
|
||||||
## [1.2.3] – 2026-10-23
|
## [1.2.3] – 2026-10-23
|
||||||
|
|
||||||
|
### Opraveno (iOS, build 27)
|
||||||
|
- **Na iOS 27 appka hned po spuštění spadla.** Build 26 vznikl novým
|
||||||
|
Xcode (SDK iOS 27) a ten u appek bez scénového životního cyklu (UIScene)
|
||||||
|
start zastaví. Appka teď scény používá; Flutter běží od startu procesu,
|
||||||
|
takže příchozí hovor na pozadí dál zazvoní i bez otevřeného okna.
|
||||||
|
|
||||||
### Bezpečnost
|
### Bezpečnost
|
||||||
- **24 slov jen po čerstvém ověření.** Zálohovací fráze se z odemčené appky
|
- **24 slov jen po čerstvém ověření.** Zálohovací fráze se z odemčené appky
|
||||||
ukáže až po zadání PINu (nebo otisku/obličeje, je-li zapnutý). Totéž platí
|
ukáže až po zadání PINu (nebo otisku/obličeje, je-li zapnutý). Totéž platí
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
AA00000000000000000000A2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AA00000000000000000000A1 /* PrivacyInfo.xcprivacy */; };
|
AA00000000000000000000A2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AA00000000000000000000A1 /* PrivacyInfo.xcprivacy */; };
|
||||||
AB10000000000000000000B1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB10000000000000000000C0 /* InfoPlist.strings */; };
|
AB10000000000000000000B1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB10000000000000000000C0 /* InfoPlist.strings */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
AA00000000000000000000B2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00000000000000000000B1 /* SceneDelegate.swift */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
AA00000000000000000000B1 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
@@ -126,6 +128,7 @@
|
|||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
AA00000000000000000000B1 /* SceneDelegate.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
@@ -285,6 +288,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
AA00000000000000000000B2 /* SceneDelegate.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|||||||
@@ -7,6 +7,12 @@ import flutter_callkit_incoming
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
|
||||||
|
// Engine startuje s procesem, ne se scénou. VoIP push umí appku spustit na
|
||||||
|
// pozadí, aniž by iOS připojil scénu (a tím vytvořil FlutterViewController).
|
||||||
|
// CallKit plugin musí být zaregistrovaný dřív, než push dorazí, a Dart musí
|
||||||
|
// běžet, aby vyzvedl nabídku hovoru z relaye. SceneDelegate tenhle engine
|
||||||
|
// jen zobrazí.
|
||||||
|
let flutterEngine = FlutterEngine(name: "sepot")
|
||||||
// PushKit registry musí žít po celou dobu běhu appky, jinak VoIP pushe nechodí.
|
// PushKit registry musí žít po celou dobu běhu appky, jinak VoIP pushe nechodí.
|
||||||
private var voipRegistry: PKPushRegistry?
|
private var voipRegistry: PKPushRegistry?
|
||||||
private var apnsChannel: FlutterMethodChannel?
|
private var apnsChannel: FlutterMethodChannel?
|
||||||
@@ -17,31 +23,24 @@ import flutter_callkit_incoming
|
|||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
// Bez force-castu: kdyby root nebyl FlutterViewController (jiný storyboard,
|
flutterEngine.run()
|
||||||
// budoucí scény), appka by spadla hned při startu. Registrar dá messenger
|
GeneratedPluginRegistrant.register(with: flutterEngine)
|
||||||
// enginu i tak.
|
|
||||||
let messenger =
|
|
||||||
(window?.rootViewController as? FlutterViewController)?.binaryMessenger
|
|
||||||
?? registrar(forPlugin: "SepotApns")?.messenger()
|
|
||||||
if let messenger {
|
|
||||||
let channel = FlutterMethodChannel(
|
|
||||||
name: "cz.sepot.sepot/apns", binaryMessenger: messenger)
|
|
||||||
channel.setMethodCallHandler { [weak self] call, result in
|
|
||||||
switch call.method {
|
|
||||||
case "register":
|
|
||||||
// Nativní APNs registrace (bez Firebase) — token přijde v callbacku
|
|
||||||
application.registerForRemoteNotifications()
|
|
||||||
result(nil)
|
|
||||||
case "getToken":
|
|
||||||
result(self?.pendingToken)
|
|
||||||
default:
|
|
||||||
result(FlutterMethodNotImplemented)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apnsChannel = channel
|
|
||||||
}
|
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
let channel = FlutterMethodChannel(
|
||||||
|
name: "cz.sepot.sepot/apns", binaryMessenger: flutterEngine.binaryMessenger)
|
||||||
|
channel.setMethodCallHandler { [weak self] call, result in
|
||||||
|
switch call.method {
|
||||||
|
case "register":
|
||||||
|
// Nativní APNs registrace (bez Firebase) — token přijde v callbacku
|
||||||
|
UIApplication.shared.registerForRemoteNotifications()
|
||||||
|
result(nil)
|
||||||
|
case "getToken":
|
||||||
|
result(self?.pendingToken)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apnsChannel = channel
|
||||||
|
|
||||||
// Hovory: VoIP push z relaye → CallKit.
|
// Hovory: VoIP push z relaye → CallKit.
|
||||||
let registry = PKPushRegistry(queue: DispatchQueue.main)
|
let registry = PKPushRegistry(queue: DispatchQueue.main)
|
||||||
|
|||||||
@@ -48,8 +48,25 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIApplicationSceneManifest</key>
|
||||||
<string>Main</string>
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
<key>UISceneConfigurations</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UISceneClassName</key>
|
||||||
|
<string>UIWindowScene</string>
|
||||||
|
<key>UISceneConfigurationName</key>
|
||||||
|
<string>flutter</string>
|
||||||
|
<key>UISceneDelegateClassName</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
// SDK iOS 27 bez scénového životního cyklu appku nespustí (TN3187). Scéna jen
|
||||||
|
// ukáže engine, který AppDelegate spustil při startu procesu — viz komentář
|
||||||
|
// u `AppDelegate.flutterEngine`.
|
||||||
|
class SceneDelegate: FlutterSceneDelegate {
|
||||||
|
override func scene(
|
||||||
|
_ scene: UIScene,
|
||||||
|
willConnectTo session: UISceneSession,
|
||||||
|
options connectionOptions: UIScene.ConnectionOptions
|
||||||
|
) {
|
||||||
|
guard let windowScene = scene as? UIWindowScene,
|
||||||
|
let appDelegate = UIApplication.shared.delegate as? AppDelegate
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let window = UIWindow(windowScene: windowScene)
|
||||||
|
window.rootViewController = FlutterViewController(
|
||||||
|
engine: appDelegate.flutterEngine, nibName: nil, bundle: nil)
|
||||||
|
self.window = window
|
||||||
|
window.makeKeyAndVisible()
|
||||||
|
|
||||||
|
// Super podle okna scény přihlásí engine k událostem scény pro pluginy.
|
||||||
|
// Musí běžet dřív, než okno dostane i delegát aplikace: našel-li by ho
|
||||||
|
// tam, Flutter by ho bral jako starý způsob startu a přesunul do nového okna.
|
||||||
|
super.scene(scene, willConnectTo: session, options: connectionOptions)
|
||||||
|
|
||||||
|
// Pluginy, které okno hledají přes delegáta aplikace (local_auth_darwin
|
||||||
|
// u dialogu „biometrie není nastavená“), by ho jinak nenašly.
|
||||||
|
appDelegate.window = window
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: sepot
|
name: sepot
|
||||||
description: "Šepot — Soukromý messenger s E2E šifrováním"
|
description: "Šepot — Soukromý messenger s E2E šifrováním"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.2.3+26
|
version: 1.2.3+27
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.8
|
sdk: ^3.10.8
|
||||||
|
|||||||
Reference in New Issue
Block a user