diff --git a/CHANGELOG.md b/CHANGELOG.md index e159604..ce2ecbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ verzování dle [ajtak standardu](#verzování). ## [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 - **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í diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj index 465bcf0..1e793cb 100644 --- a/app/ios/Runner.xcodeproj/project.pbxproj +++ b/app/ios/Runner.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ AA00000000000000000000A2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = AA00000000000000000000A1 /* PrivacyInfo.xcprivacy */; }; AB10000000000000000000B1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB10000000000000000000C0 /* InfoPlist.strings */; }; 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 */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + AA00000000000000000000B1 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -126,6 +128,7 @@ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + AA00000000000000000000B1 /* SceneDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; @@ -285,6 +288,7 @@ buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + AA00000000000000000000B2 /* SceneDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift index 241178a..f9669a5 100644 --- a/app/ios/Runner/AppDelegate.swift +++ b/app/ios/Runner/AppDelegate.swift @@ -7,6 +7,12 @@ import flutter_callkit_incoming @main @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í. private var voipRegistry: PKPushRegistry? private var apnsChannel: FlutterMethodChannel? @@ -17,31 +23,24 @@ import flutter_callkit_incoming _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - // Bez force-castu: kdyby root nebyl FlutterViewController (jiný storyboard, - // budoucí scény), appka by spadla hned při startu. Registrar dá messenger - // 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 - } + flutterEngine.run() + GeneratedPluginRegistrant.register(with: flutterEngine) - 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. let registry = PKPushRegistry(queue: DispatchQueue.main) diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist index f69cdad..78f5aea 100644 --- a/app/ios/Runner/Info.plist +++ b/app/ios/Runner/Info.plist @@ -48,8 +48,25 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/app/ios/Runner/SceneDelegate.swift b/app/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..7c7a180 --- /dev/null +++ b/app/ios/Runner/SceneDelegate.swift @@ -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 + } +} diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 01e0752..d4d441e 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -1,7 +1,7 @@ name: sepot description: "Šepot — Soukromý messenger s E2E šifrováním" publish_to: 'none' -version: 1.2.3+26 +version: 1.2.3+27 environment: sdk: ^3.10.8