From 32dd051483dc5bca5e631f3803d9bbbcb26840ad Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 12 Feb 2025 19:59:17 +0300 Subject: [PATCH] Intergrate shared KMP module into the iOS app --- android/shared/build.gradle.kts | 6 +++ .../cobble/shared/database/AppDatabase.ios.kt | 22 ++++++++++- .../io/rebble/cobble/shared/main.ios.kt | 10 +++++ ios/Runner.xcodeproj/project.pbxproj | 23 ++++++++++++ ios/Runner/AppDelegate.swift | 6 +++ ios/Runner/bridges/FlutterBridgeSetup.swift | 1 + ios/Runner/bridges/common/KMPApiBridge.swift | 37 +++++++++++++++++++ 7 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/main.ios.kt create mode 100644 ios/Runner/bridges/common/KMPApiBridge.swift diff --git a/android/shared/build.gradle.kts b/android/shared/build.gradle.kts index 9bd868e8..a19a12de 100644 --- a/android/shared/build.gradle.kts +++ b/android/shared/build.gradle.kts @@ -83,6 +83,9 @@ kotlin { implementation(project(":pebblekit_android")) implementation(project(":speex_codec")) } + iosMain { + kotlin.srcDir("build/generated/ksp/metadata") + } commonTest.dependencies { implementation(kotlin("test")) implementation(libs.ktor.client.mock) @@ -110,4 +113,7 @@ dependencies { implementation(libs.androidx.security.crypto.ktx) add("kspCommonMainMetadata", libs.androidx.room.compiler) add("kspAndroid", libs.androidx.room.compiler) + add("kspIosSimulatorArm64", libs.androidx.room.compiler) + add("kspIosX64", libs.androidx.room.compiler) + add("kspIosArm64", libs.androidx.room.compiler) } diff --git a/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/database/AppDatabase.ios.kt b/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/database/AppDatabase.ios.kt index bacc16e0..c2bc4947 100644 --- a/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/database/AppDatabase.ios.kt +++ b/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/database/AppDatabase.ios.kt @@ -1,8 +1,28 @@ package io.rebble.cobble.shared.database +import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.RoomDatabaseConstructor +import kotlinx.cinterop.ExperimentalForeignApi +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSUserDomainMask actual fun getDatabaseBuilder(): RoomDatabase.Builder { - TODO("Not yet implemented") + val dbFilePath = documentDirectory() + "/cobble-room.db" + return Room.databaseBuilder( + name = dbFilePath + ) +} + +@OptIn(ExperimentalForeignApi::class) +private fun documentDirectory(): String { + val documentDirectory = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null, + ) + return requireNotNull(documentDirectory?.path) } \ No newline at end of file diff --git a/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/main.ios.kt b/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/main.ios.kt new file mode 100644 index 00000000..302ca777 --- /dev/null +++ b/android/shared/src/iosMain/kotlin/io/rebble/cobble/shared/main.ios.kt @@ -0,0 +1,10 @@ +package io.rebble.cobble.shared + +import androidx.compose.ui.window.ComposeUIViewController +import androidx.navigation.compose.rememberNavController +import io.rebble.cobble.shared.ui.view.MainView + +fun mainViewController() = ComposeUIViewController { + val navHost = rememberNavController() + MainView(navHost) +} \ No newline at end of file diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ff44c0a2..cbc72655 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5D12BEE72D57FC5500243610 /* KMPApiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D12BEE62D57FC4F00243610 /* KMPApiBridge.swift */; }; 6701505427D1AC4200797B97 /* IntentControlFlutterBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6701505327D1AC4200797B97 /* IntentControlFlutterBridge.swift */; }; 6701505627D1B48800797B97 /* OpenWith.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6701505527D1B48800797B97 /* OpenWith.swift */; }; 6701505827D1B96400797B97 /* AppInstallControlFlutterBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6701505727D1B96400797B97 /* AppInstallControlFlutterBridge.swift */; }; @@ -80,6 +81,7 @@ /* Begin PBXFileReference section */ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 46435D4C3A881E1DFE6583FC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5D12BEE62D57FC4F00243610 /* KMPApiBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KMPApiBridge.swift; sourceTree = ""; }; 620666896E8FA2E5FECAB423 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6701505327D1AC4200797B97 /* IntentControlFlutterBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentControlFlutterBridge.swift; sourceTree = ""; }; 6701505527D1B48800797B97 /* OpenWith.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenWith.swift; sourceTree = ""; }; @@ -261,6 +263,7 @@ 67B33CD427C464B0007FBA39 /* common */ = { isa = PBXGroup; children = ( + 5D12BEE62D57FC4F00243610 /* KMPApiBridge.swift */, 67B33CD527C464B0007FBA39 /* PermissionCheckFlutterBridge.swift */, 67B33CD627C464B0007FBA39 /* ScanFlutterBridge.swift */, 6701505727D1B96400797B97 /* AppInstallControlFlutterBridge.swift */, @@ -370,6 +373,7 @@ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( DB7F622F28ABFEFF1BB24999 /* [CP] Check Pods Manifest.lock */, + 5D12BEE52D57ED8100243610 /* Run Script (KMP) */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -480,6 +484,24 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; + 5D12BEE52D57ED8100243610 /* Run Script (KMP) */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Run Script (KMP)"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/../android\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -562,6 +584,7 @@ 67945BB227C81A4D00DE13E8 /* Pigeons.m in Sources */, 67B33CEA27C464B1007FBA39 /* ConnectionFlutterBridge.swift in Sources */, 67442F7127C9B0D900ACED5B /* AppInstallHandler.swift in Sources */, + 5D12BEE72D57FC5500243610 /* KMPApiBridge.swift in Sources */, 67B33CF927C464B1007FBA39 /* PebbleDevice.swift in Sources */, 67B33CF427C464B1007FBA39 /* LECentral.swift in Sources */, 67B33CE527C464B1007FBA39 /* PersistentStorage.swift in Sources */, diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 47a0d5f6..61c1aaec 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -3,6 +3,7 @@ import Flutter import CocoaLumberjackSwift import CocoaLumberjackSwiftLogBackend import Logging +import shared @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -18,6 +19,7 @@ import Logging LECentral.shared = LECentral() setupFlutter() + setupKmp() return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -30,6 +32,10 @@ import Logging FlutterBridgeSetup.createCommonBridges(binaryMessenger: binaryMessenger) } + private func setupKmp() { + Koin_iosKt.doInitKoin() + } + private func setupLogging() { DDLog.add(DDOSLogger.sharedInstance) } diff --git a/ios/Runner/bridges/FlutterBridgeSetup.swift b/ios/Runner/bridges/FlutterBridgeSetup.swift index 323e1347..ef219c31 100644 --- a/ios/Runner/bridges/FlutterBridgeSetup.swift +++ b/ios/Runner/bridges/FlutterBridgeSetup.swift @@ -20,6 +20,7 @@ class FlutterBridgeSetup { ConnectionControlSetup(binaryMessenger, ConnectionFlutterBridge(callbackMessenger: binaryMessenger)) PigeonLoggerSetup(binaryMessenger, LoggingFlutterBridge()) AppLifecycleControlSetup(binaryMessenger, AppLifecycleFlutterBridge()) + KMPApiSetup(binaryMessenger, KMPApiBridge()) } static func createUIBridges(binaryMessenger: FlutterBinaryMessenger) { diff --git a/ios/Runner/bridges/common/KMPApiBridge.swift b/ios/Runner/bridges/common/KMPApiBridge.swift new file mode 100644 index 00000000..6f015a50 --- /dev/null +++ b/ios/Runner/bridges/common/KMPApiBridge.swift @@ -0,0 +1,37 @@ +// +// KMPApiBridge.swift +// Runner +// +// Created by Timofei Plotnikov on 08.02.2025. +// + +import Foundation +import Flutter +import shared + +class KMPApiBridge: NSObject, KMPApi { + func updateTokenToken( + _ token: StringWrapper, + error: AutoreleasingUnsafeMutablePointer + ) { + //TODO: Implement + } + + func openLockerViewWithError(_ error: AutoreleasingUnsafeMutablePointer) { + guard + let window = UIApplication.shared.windows.first, + let root = window.rootViewController + else { + print("Failed to get rootViewController") + return + } + + // Notes: + // 1. This is not the best places to open up mainViewController. Its here just for + // demonstration purposes + // 2. The screen is presented as a modal, use swipe from top to bottom to return back to + // the flutter app + let mainViewController = Main_iosKt.mainViewController() + root.present(mainViewController, animated: false, completion: nil) + } +}