diff --git a/FloconAndroid/.idea/xcode.xml b/FloconAndroid/.idea/xcode.xml
new file mode 100644
index 000000000..4eb324224
--- /dev/null
+++ b/FloconAndroid/.idea/xcode.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/FloconAndroid/flocon-base/build.gradle.kts b/FloconAndroid/flocon-base/build.gradle.kts
index 32a8e7893..d92d2d3e4 100644
--- a/FloconAndroid/flocon-base/build.gradle.kts
+++ b/FloconAndroid/flocon-base/build.gradle.kts
@@ -15,12 +15,10 @@ kotlin {
jvm()
- /*
iosX64()
iosArm64()
iosSimulatorArm64()
- */
-
+
sourceSets {
val commonMain by getting {
dependencies {
@@ -38,7 +36,6 @@ kotlin {
}
}
- /*
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
@@ -48,7 +45,6 @@ kotlin {
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
- */
}
}
diff --git a/FloconAndroid/flocon-no-op/build.gradle.kts b/FloconAndroid/flocon-no-op/build.gradle.kts
index 7dc7679d5..ee80a3e2d 100644
--- a/FloconAndroid/flocon-no-op/build.gradle.kts
+++ b/FloconAndroid/flocon-no-op/build.gradle.kts
@@ -15,12 +15,10 @@ kotlin {
jvm()
- /*
iosX64()
iosArm64()
iosSimulatorArm64()
- */
-
+
sourceSets {
val commonMain by getting {
dependencies {
@@ -39,7 +37,6 @@ kotlin {
}
}
- /*
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
@@ -49,7 +46,6 @@ kotlin {
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
- */
}
}
diff --git a/FloconAndroid/flocon/build.gradle.kts b/FloconAndroid/flocon/build.gradle.kts
index 25443132e..7ab35c0b7 100644
--- a/FloconAndroid/flocon/build.gradle.kts
+++ b/FloconAndroid/flocon/build.gradle.kts
@@ -16,12 +16,10 @@ kotlin {
jvm()
- /*
iosX64()
iosArm64()
iosSimulatorArm64()
- */
-
+
sourceSets {
val commonMain by getting {
dependencies {
@@ -50,7 +48,6 @@ kotlin {
}
}
- /*
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
@@ -61,9 +58,18 @@ kotlin {
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.darwin)
+
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.client.logging)
+ implementation(libs.ktor.serialization.kotlinx.json)
+
+ implementation(libs.androidx.sqlite.bundled)
+
+ // to store the device id
+ implementation("com.russhwolf:multiplatform-settings:1.3.0")
}
}
- */
}
}
diff --git a/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/deeplinks/FloconDeeplinksPlugin.kt b/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/deeplinks/FloconDeeplinksPlugin.kt
index ded188966..481ef24e0 100644
--- a/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/deeplinks/FloconDeeplinksPlugin.kt
+++ b/FloconAndroid/flocon/src/commonMain/kotlin/io/github/openflocon/flocon/plugins/deeplinks/FloconDeeplinksPlugin.kt
@@ -6,12 +6,14 @@ import io.github.openflocon.flocon.core.FloconMessageSender
import io.github.openflocon.flocon.core.FloconPlugin
import io.github.openflocon.flocon.model.FloconMessageFromServer
import io.github.openflocon.flocon.plugins.deeplinks.model.DeeplinkModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
internal class FloconDeeplinksPluginImpl(
private val sender: FloconMessageSender,
) : FloconPlugin, FloconDeeplinksPlugin {
- private val deeplinks = java.util.concurrent.atomic.AtomicReference?>(null)
+ private val deeplinks = MutableStateFlow?>(null)
override fun onMessageReceived(
messageFromServer: FloconMessageFromServer,
@@ -21,13 +23,15 @@ internal class FloconDeeplinksPluginImpl(
override fun onConnectedToServer() {
// on connected, send known dashboard
- deeplinks.get()?.let {
+ deeplinks.value?.let {
registerDeeplinks(it)
}
}
override fun registerDeeplinks(deeplinks: List) {
- this.deeplinks.set(deeplinks)
+ this.deeplinks.update {
+ deeplinks
+ }
try {
sender.send(
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/Flocon.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/Flocon.ios.kt
index f986f2127..14a0608d9 100644
--- a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/Flocon.ios.kt
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/Flocon.ios.kt
@@ -1,6 +1,8 @@
package io.github.openflocon.flocon
object Flocon : FloconCore() {
-
+ fun initialize() {
+ super.initializeFlocon(context = FloconContext())
+ }
}
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconCore.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconCore.ios.kt
new file mode 100644
index 000000000..1bbf0ee58
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconCore.ios.kt
@@ -0,0 +1,7 @@
+package io.github.openflocon.flocon
+
+actual class FloconContext
+
+internal actual fun displayClearTextError(context: FloconContext) {
+ // no op on ios
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconFile.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconFile.ios.kt
new file mode 100644
index 000000000..3a937a4da
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/FloconFile.ios.kt
@@ -0,0 +1,5 @@
+package io.github.openflocon.flocon
+
+internal actual class FloconFile {
+ // TODO
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/ServerHost.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/ServerHost.ios.kt
new file mode 100644
index 000000000..ae08a2327
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/ServerHost.ios.kt
@@ -0,0 +1,5 @@
+package io.github.openflocon.flocon
+
+internal actual fun getServerHost(floconContext: FloconContext): String {
+ return "localhost"
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/core/AppInfos.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/core/AppInfos.ios.kt
new file mode 100644
index 000000000..79db76d8e
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/core/AppInfos.ios.kt
@@ -0,0 +1,41 @@
+package io.github.openflocon.flocon.core
+
+import com.russhwolf.settings.NSUserDefaultsSettings
+import io.github.openflocon.flocon.FloconContext
+import platform.Foundation.NSBundle
+import platform.Foundation.NSUUID
+import platform.Foundation.NSUserDefaults
+import platform.UIKit.UIDevice
+
+private fun getDeviceId() : String {
+ val settings = NSUserDefaultsSettings(
+ NSUserDefaults.standardUserDefaults()
+ )
+ val id = settings.getStringOrNull("deviceId")
+ return if(id != null) {
+ id
+ } else {
+ val newId = NSUUID.UUID().UUIDString()
+ settings.putString("deviceId", newId)
+ newId
+ }
+}
+
+private fun deviceName(): String {
+ val device = UIDevice.currentDevice
+ return "${device.systemName()} ${device.model}"
+}
+
+internal actual fun getAppInfos(floconContext: FloconContext): AppInfos {
+ val bundle = NSBundle.mainBundle
+ val appName = bundle.objectForInfoDictionaryKey("CFBundleName") as? String ?: "Unknown"
+ val appPackageName = bundle.bundleIdentifier ?: "Unknown"
+
+ return AppInfos(
+ deviceId = getDeviceId(),
+ deviceName = deviceName(),
+ appName = appName,
+ appPackageName = appPackageName,
+ platform = "ios",
+ )
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/database/FloconDatabasePlugin.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/database/FloconDatabasePlugin.ios.kt
new file mode 100644
index 000000000..26eef7645
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/database/FloconDatabasePlugin.ios.kt
@@ -0,0 +1,125 @@
+package io.github.openflocon.flocon.plugins.database
+
+import androidx.sqlite.SQLiteConnection
+import androidx.sqlite.driver.NativeSQLiteDriver
+import io.github.openflocon.flocon.FloconContext
+import io.github.openflocon.flocon.plugins.database.model.FloconDatabaseModel
+import io.github.openflocon.flocon.plugins.database.model.fromdevice.DatabaseExecuteSqlResponse
+import io.github.openflocon.flocon.plugins.database.model.fromdevice.DeviceDataBaseDataModel
+import platform.Foundation.NSFileManager
+import platform.posix.close
+
+internal actual fun buildFloconDatabaseDataSource(context: FloconContext): FloconDatabaseDataSource {
+ return FloconDatabaseDataSourceIos(context)
+}
+
+internal class FloconDatabaseDataSourceIos(
+ private val context: FloconContext
+) : FloconDatabaseDataSource {
+
+ override fun executeSQL(
+ registeredDatabases: List,
+ databaseName: String,
+ query: String
+ ): DatabaseExecuteSqlResponse {
+ val fileManager = NSFileManager.defaultManager
+ if (!fileManager.fileExistsAtPath(databaseName)) {
+ return DatabaseExecuteSqlResponse.Error(
+ message = "Database file not found: $databaseName",
+ originalSql = query
+ )
+ }
+
+ val driver = NativeSQLiteDriver()
+ val connection = driver.open(fileName = databaseName)
+
+ return try {
+ val firstWord = getFirstWord(query).uppercase()
+ when (firstWord) {
+ "SELECT", "PRAGMA", "EXPLAIN" -> executeSelect(connection, query)
+ "INSERT" -> executeInsert(connection, query)
+ "UPDATE", "DELETE" -> executeUpdateDelete(connection, query)
+ else -> executeRawQuery(connection, query)
+ }
+ } catch (t: Throwable) {
+ DatabaseExecuteSqlResponse.Error(
+ message = t.message ?: "Error executing SQL",
+ originalSql = query
+ )
+ } finally {
+ connection.close()
+ }
+ }
+
+ override fun getAllDataBases(
+ registeredDatabases: List
+ ): List {
+ val fileManager = NSFileManager.defaultManager
+ return registeredDatabases.mapNotNull {
+ if (fileManager.fileExistsAtPath(it.absolutePath)) {
+ DeviceDataBaseDataModel(
+ id = it.absolutePath,
+ name = it.displayName
+ )
+ } else null
+ }
+ }
+}
+
+// --- SQL execution helpers ---
+
+private fun executeSelect(connection: SQLiteConnection, query: String): DatabaseExecuteSqlResponse {
+ val cursor = connection.prepare(query).use { statement ->
+ val columnCount = statement.getColumnCount()
+ val columns = (0 until columnCount).map { statement.getColumnName(it) }
+ val rows = mutableListOf>()
+
+ while (statement.step()) {
+ val row = (0 until columnCount).map { idx ->
+ statement.getText(idx)
+ }
+ rows.add(row)
+ }
+
+ statement.close() // maybe remove
+ DatabaseExecuteSqlResponse.Select(columns, rows)
+ }
+ return cursor
+}
+
+private fun executeUpdateDelete(connection: SQLiteConnection, query: String): DatabaseExecuteSqlResponse {
+ connection.prepare(query).use { statement ->
+ statement.close()
+ }
+ // sqlite-kt n'expose pas encore `changes()`, on renvoie 0
+ return DatabaseExecuteSqlResponse.UpdateDelete(affectedCount = 0)
+}
+
+private fun executeInsert(connection: SQLiteConnection, query: String): DatabaseExecuteSqlResponse {
+ connection.prepare(query).use { statement ->
+ statement.close()
+ }
+
+ // Récupération du dernier ID inséré
+ var id = -1L
+ connection.prepare("SELECT last_insert_rowid()").use {
+ id = if (it.step()) it.getLong(0) else -1L
+ it.close() // maybe remove
+ }
+
+ return DatabaseExecuteSqlResponse.Insert(id)
+}
+
+private fun executeRawQuery(connection: SQLiteConnection, query: String): DatabaseExecuteSqlResponse {
+ connection.prepare(query).use { statement ->
+ statement.close() // maybe remove
+ }
+ return DatabaseExecuteSqlResponse.RawSuccess
+}
+
+// --- Utilities ---
+private fun getFirstWord(s: String): String {
+ val trimmed = s.trim()
+ val firstSpace = trimmed.indexOf(' ')
+ return if (firstSpace >= 0) trimmed.substring(0, firstSpace) else trimmed
+}
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/FloconDevicePluginImpl.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/FloconDevicePluginImpl.ios.kt
new file mode 100644
index 000000000..8ee95eefc
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/FloconDevicePluginImpl.ios.kt
@@ -0,0 +1,7 @@
+package io.github.openflocon.flocon.plugins.device
+
+import io.github.openflocon.flocon.FloconContext
+
+internal actual fun restartApp(context: FloconContext) {
+ // TODO
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/GetAppIconUtils.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/GetAppIconUtils.ios.kt
new file mode 100644
index 000000000..af0dafd29
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/device/GetAppIconUtils.ios.kt
@@ -0,0 +1,7 @@
+package io.github.openflocon.flocon.plugins.device
+
+import io.github.openflocon.flocon.FloconContext
+
+actual fun getAppIconBase64(context: FloconContext): String? {
+ return null // TODO
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/files/FloconFilesPlugin.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/files/FloconFilesPlugin.ios.kt
new file mode 100644
index 000000000..a4e79769c
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/files/FloconFilesPlugin.ios.kt
@@ -0,0 +1,35 @@
+package io.github.openflocon.flocon.plugins.files
+
+import io.github.openflocon.flocon.FloconContext
+import io.github.openflocon.flocon.FloconFile
+import io.github.openflocon.flocon.plugins.files.model.fromdevice.FileDataModel
+
+internal actual fun fileDataSource(context: FloconContext): FileDataSource {
+ return FileDataSourceIOs()
+}
+
+// TODO
+internal class FileDataSourceIOs : FileDataSource {
+ override fun getFile(
+ path: String,
+ isConstantPath: Boolean
+ ): FloconFile? {
+ return null
+ }
+
+ override fun getFolderContent(
+ path: String,
+ isConstantPath: Boolean
+ ): List {
+ return emptyList()
+ }
+
+ override fun deleteFile(path: String) {
+ // TODO
+ }
+
+ override fun deleteFolderContent(folder: FloconFile) {
+ // TODO
+ }
+
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/network/FloconNetworkPluginImpl.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/network/FloconNetworkPluginImpl.ios.kt
new file mode 100644
index 000000000..bdccd4100
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/network/FloconNetworkPluginImpl.ios.kt
@@ -0,0 +1,29 @@
+package io.github.openflocon.flocon.plugins.network
+
+import io.github.openflocon.flocon.FloconContext
+import io.github.openflocon.flocon.plugins.network.model.BadQualityConfig
+import io.github.openflocon.flocon.plugins.network.model.MockNetworkResponse
+
+internal actual fun buildFloconNetworkDataSource(context: FloconContext): FloconNetworkDataSource {
+ return FloconNetworkDataSourceIOs()
+}
+
+// TODO
+internal class FloconNetworkDataSourceIOs : FloconNetworkDataSource {
+ override fun saveMocksToFile(mocks: List) {
+ // TODO
+ }
+
+ override fun loadMocksFromFile(): List {
+ return emptyList()
+ }
+
+ override fun saveBadNetworkConfig(config: BadQualityConfig?) {
+ // TODO
+ }
+
+ override fun loadBadNetworkConfig(): BadQualityConfig? {
+ return null
+ }
+
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt
new file mode 100644
index 000000000..2d24594e8
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/plugins/sharedprefs/FloconSharedPrefsPlugin.ios.kt
@@ -0,0 +1,30 @@
+package io.github.openflocon.flocon.plugins.sharedprefs
+
+import io.github.openflocon.flocon.FloconContext
+import io.github.openflocon.flocon.plugins.sharedprefs.model.SharedPreferencesDescriptor
+import io.github.openflocon.flocon.plugins.sharedprefs.model.fromdevice.SharedPreferenceRowDataModel
+import io.github.openflocon.flocon.plugins.sharedprefs.model.todevice.ToDeviceEditSharedPreferenceValueMessage
+
+internal actual fun buildFloconSharedPreferencesDataSource(context: FloconContext): FloconSharedPreferencesDataSource {
+ return FloconSharedPreferencesDataSourceIOs()
+}
+
+// TODO try to bind with ios storage
+internal class FloconSharedPreferencesDataSourceIOs : FloconSharedPreferencesDataSource {
+ override fun getAllSharedPreferences(): List {
+ return emptyList()
+ }
+
+ override fun getSharedPreferenceContent(sharedPreferencesName: String): List {
+ return emptyList()
+ }
+
+ override fun setSharedPreferenceRowValue(
+ sharedPreferencesName: String,
+ preferenceName: String,
+ message: ToDeviceEditSharedPreferenceValueMessage
+ ) {
+ // TODO
+ }
+
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconHttpClient.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconHttpClient.ios.kt
new file mode 100644
index 000000000..96c94ada7
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconHttpClient.ios.kt
@@ -0,0 +1,81 @@
+package io.github.openflocon.flocon.websocket
+
+import io.github.openflocon.flocon.FloconFile
+import io.github.openflocon.flocon.model.FloconFileInfo
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.darwin.Darwin
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.logging.LogLevel
+import io.ktor.client.plugins.logging.Logging
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
+
+internal actual fun buildFloconHttpClient(): FloconHttpClient {
+ return FloconHttpClientIOs()
+}
+
+internal class FloconHttpClientIOs() : FloconHttpClient {
+
+ // client configurable selon la plateforme (Android, iOS, JVM, etc.)
+ private val client = HttpClient(Darwin.create()) {
+ install(ContentNegotiation) {
+ json(Json {
+ ignoreUnknownKeys = true
+ prettyPrint = false
+ })
+ }
+ install(Logging) {
+ level = LogLevel.INFO
+ }
+ }
+
+ override suspend fun send(
+ file: FloconFile,
+ infos: FloconFileInfo,
+ address: String,
+ port: Int,
+ deviceId: String,
+ appPackageName: String,
+ appInstance: Long
+ ): Boolean {
+ /* no op for now on ios
+ val uploadUrl = "http://$address:$port/upload"
+
+ try {
+ val response: HttpResponse = client.submitFormWithBinaryData(
+ url = uploadUrl,
+ formData = formData {
+ append("remotePath", infos.path)
+ append("requestId", infos.requestId)
+ append("deviceId", deviceId)
+ append("appPackageName", appPackageName)
+ append("appInstance", appInstance.toString())
+
+ // Ajout du fichier binaire
+ val fileBytes = file.file.readBytes()
+ append(
+ key = "file",
+ value = fileBytes,
+ headers = Headers.build {
+ append(HttpHeaders.ContentDisposition, "filename=\"${file.file.name}\"")
+ append(HttpHeaders.ContentType, "application/octet-stream")
+ }
+ )
+ }
+ )
+
+ if (response.status.isSuccess()) {
+ println("✅ Upload réussi : ${response.bodyAsText()}")
+ return true
+ } else {
+ println("❌ Erreur serveur (${response.status}): ${response.bodyAsText()}")
+ return false
+ }
+ } catch (e: Exception) {
+ println("❌ Erreur lors de l’envoi : ${e.message}")
+ return false
+ }
+ */
+ return false
+ }
+}
\ No newline at end of file
diff --git a/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconWebSocketClient.ios.kt b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconWebSocketClient.ios.kt
new file mode 100644
index 000000000..25e557a97
--- /dev/null
+++ b/FloconAndroid/flocon/src/iosMain/kotlin/io/github/openflocon/flocon/websocket/FloconWebSocketClient.ios.kt
@@ -0,0 +1,110 @@
+package io.github.openflocon.flocon.websocket
+
+
+import io.github.openflocon.flocon.FloconLogger
+import io.ktor.client.*
+import io.ktor.client.engine.darwin.Darwin
+import io.ktor.client.plugins.websocket.*
+import io.ktor.http.HttpMethod
+import io.ktor.websocket.*
+import kotlinx.coroutines.*
+
+internal actual fun buildFloconWebSocketClient(): FloconWebSocketClient {
+ return FloconWebSocketClientIOs()
+}
+
+internal class FloconWebSocketClientIOs() : FloconWebSocketClient {
+
+ private val client = HttpClient(Darwin.create()) {
+ install(WebSockets)
+ }
+
+ private var session: DefaultClientWebSocketSession? = null
+ private val queue = MessageQueue(capacity = 200)
+
+ override suspend fun connect(
+ address: String,
+ port: Int,
+ onMessageReceived: (String) -> Unit,
+ onClosed: () -> Unit,
+ ) {
+ if (session != null) return
+
+ try {
+ FloconLogger.log("🔌 Trying to connect ws://$address:$port ...")
+
+ val wsSession = client.webSocketSession(
+ method = HttpMethod.Get,
+ host = address,
+ port = port,
+ path = "/"
+ )
+
+ session = wsSession
+ FloconLogger.log("✅ WebSocket connected at ws://$address:$port")
+
+ // Lancer un job pour écouter les messages entrants
+ CoroutineScope(Dispatchers.Default).launch {
+ try {
+ for (frame in wsSession.incoming) {
+ when (frame) {
+ is Frame.Text -> {
+ val text = frame.readText()
+ FloconLogger.log("WEBSOCKET <----- received : $text")
+ onMessageReceived(text)
+ }
+ is Frame.Close -> {
+ FloconLogger.log("🔒 WebSocket closed (${frame.readReason()})")
+ disconnect()
+ onClosed()
+ break
+ }
+ else -> Unit
+ }
+ }
+ FloconLogger.log("❌ WebSocket closed without reason")
+ } catch (e: Exception) {
+ FloconLogger.logError("❌ WebSocket error : ${e.message}", e)
+ } finally {
+ disconnect()
+ onClosed()
+ }
+ }
+
+ } catch (t: Throwable) {
+ FloconLogger.logError("❌ WebSocket connection failed : ${t.message}", t)
+ throw t
+ }
+ }
+
+ override suspend fun sendMessage(message: String): Boolean {
+ val currentSession = session
+ if (currentSession == null) {
+ queue.add(message)
+ FloconLogger.log("🕒 WebSocket not connected, message saved in the queue : $message")
+ return false
+ }
+
+ return try {
+ currentSession.send(Frame.Text(message))
+ FloconLogger.log("WEBSOCKET ----> Sent : $message")
+ true
+ } catch (e: Exception) {
+ FloconLogger.logError("❌ Failed to send : ${e.message}", e)
+ false
+ }
+ }
+
+ override suspend fun sendPendingMessages() {
+ var message = queue.poll()
+ while (message != null) {
+ sendMessage(message)
+ message = queue.poll()
+ }
+ }
+
+ override suspend fun disconnect() {
+ session?.close(CloseReason(CloseReason.Codes.NORMAL, "Client disconnecting"))
+ session = null
+ }
+}
diff --git a/FloconAndroid/gradle/libs.versions.toml b/FloconAndroid/gradle/libs.versions.toml
index 42801d01f..bcd03c606 100644
--- a/FloconAndroid/gradle/libs.versions.toml
+++ b/FloconAndroid/gradle/libs.versions.toml
@@ -18,7 +18,7 @@ composeBom = "2025.06.01"
appcompat = "1.7.1"
material = "1.12.0"
okhttpBom = "4.12.0"
-room = "2.8.1"
+room = "2.7.2"
# for grpc
gson = "2.11.0"
grpc = "1.70.0"
@@ -33,6 +33,7 @@ sqliteJdbc = "3.50.3.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
+androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
apollo-http-okhttprealization = { module = "com.apollographql.apollo:apollo-http-okhttprealization", version.ref = "apollo" }
apollo-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" }
@@ -68,6 +69,7 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesBom" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
@@ -87,6 +89,7 @@ sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sql
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
+androidx-room = { id = "androidx.room", version.ref = "room" }
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "compose" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
diff --git a/FloconAndroid/iosApp/Configuration/Config.xcconfig b/FloconAndroid/iosApp/Configuration/Config.xcconfig
new file mode 100644
index 000000000..174571bab
--- /dev/null
+++ b/FloconAndroid/iosApp/Configuration/Config.xcconfig
@@ -0,0 +1,7 @@
+TEAM_ID=
+
+PRODUCT_NAME=MyApplication
+PRODUCT_BUNDLE_IDENTIFIER=com.florent37.myapplication.MyApplication$(TEAM_ID)
+
+CURRENT_PROJECT_VERSION=1
+MARKETING_VERSION=1.0
\ No newline at end of file
diff --git a/FloconAndroid/iosApp/iosApp.xcodeproj/project.pbxproj b/FloconAndroid/iosApp/iosApp.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..d060c14b0
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -0,0 +1,373 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXFileReference section */
+ 945F32D77964E525ED8EF789 /* MyApplication.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyApplication.app; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 2222611597AAAB7FA468A2D4 /* Exceptions for "iosApp" folder in "iosApp" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Info.plist,
+ );
+ target = F72BA213590F4A64A9920B23 /* iosApp */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+ 9F68AB18AE61F38D531E138A /* iosApp */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 2222611597AAAB7FA468A2D4 /* Exceptions for "iosApp" folder in "iosApp" target */,
+ );
+ path = iosApp;
+ sourceTree = "";
+ };
+ 4CE8BEAB133D682E2FAB0FA0 /* Configuration */ = {
+ isa = PBXFileSystemSynchronizedRootGroup;
+ path = Configuration;
+ sourceTree = "";
+ };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 17300DE33A8E57440B0A39DA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 648A03B5F372A28A6911BD98 = {
+ isa = PBXGroup;
+ children = (
+ 4CE8BEAB133D682E2FAB0FA0 /* Configuration */,
+ 9F68AB18AE61F38D531E138A /* iosApp */,
+ 2D6DEEEAD01D508D294BF2D1 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 2D6DEEEAD01D508D294BF2D1 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 945F32D77964E525ED8EF789 /* MyApplication.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ F72BA213590F4A64A9920B23 /* iosApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = BEF6CD0831484A55FEE5EE5B /* Build configuration list for PBXNativeTarget "iosApp" */;
+ buildPhases = (
+ 641A9F6B03025D7E56985AB4 /* Compile Kotlin Framework */,
+ 5ADB98277D7997E7E3EA4A03 /* Sources */,
+ 17300DE33A8E57440B0A39DA /* Frameworks */,
+ F2CA5FC4BCA7479C5529496F /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ fileSystemSynchronizedGroups = (
+ 9F68AB18AE61F38D531E138A /* iosApp */,
+ );
+ name = iosApp;
+ packageProductDependencies = (
+ );
+ productName = iosApp;
+ productReference = 945F32D77964E525ED8EF789 /* MyApplication.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ F634829F4BFB9DAE2DCEC0CF /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1620;
+ LastUpgradeCheck = 1620;
+ TargetAttributes = {
+ F72BA213590F4A64A9920B23 = {
+ CreatedOnToolsVersion = 16.2;
+ };
+ };
+ };
+ buildConfigurationList = 9F469B551C5AAB0B6BEBF1CC /* Build configuration list for PBXProject "iosApp" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 648A03B5F372A28A6911BD98;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = 2D6DEEEAD01D508D294BF2D1 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ F72BA213590F4A64A9920B23 /* iosApp */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ F2CA5FC4BCA7479C5529496F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 641A9F6B03025D7E56985AB4 /* Compile Kotlin Framework */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Compile Kotlin Framework";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :sample-multiplatform:embedAndSignAppleFrameworkForXcode\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 5ADB98277D7997E7E3EA4A03 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 9A35A7AE2A76F92A3C969D7C /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 4CE8BEAB133D682E2FAB0FA0 /* Configuration */;
+ baseConfigurationReferenceRelativePath = Config.xcconfig;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.2;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ E4F51F41DF48307B7966ED4C /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = 4CE8BEAB133D682E2FAB0FA0 /* Configuration */;
+ baseConfigurationReferenceRelativePath = Config.xcconfig;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.2;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ A628CA29E04D790DEF8559BF /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = arm64;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 89C9FAC4CD5DAEA923D097C1 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ARCHS = arm64;
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
+ DEVELOPMENT_TEAM = "${TEAM_ID}";
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = iosApp/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 9F469B551C5AAB0B6BEBF1CC /* Build configuration list for PBXProject "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 9A35A7AE2A76F92A3C969D7C /* Debug */,
+ E4F51F41DF48307B7966ED4C /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ BEF6CD0831484A55FEE5EE5B /* Build configuration list for PBXNativeTarget "iosApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ A628CA29E04D790DEF8559BF /* Debug */,
+ 89C9FAC4CD5DAEA923D097C1 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = F634829F4BFB9DAE2DCEC0CF /* Project object */;
+}
\ No newline at end of file
diff --git a/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist b/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 000000000..ee3458dd7
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/iosApp.xcscheme b/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/iosApp.xcscheme
new file mode 100644
index 000000000..e18c140af
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/iosApp.xcscheme
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist b/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 000000000..fa59f97d5
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp.xcodeproj/xcuserdata/florentchampigny.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ SchemeUserState
+
+ iosApp.xcscheme
+
+ orderHint
+ 0
+
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..4e8d485bf
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,36 @@
+{
+ "images" : [
+ {
+ "filename" : "app-icon-1024.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "tinted"
+ }
+ ],
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png
new file mode 100644
index 000000000..53fc536fb
Binary files /dev/null and b/FloconAndroid/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png differ
diff --git a/FloconAndroid/iosApp/iosApp/Assets.xcassets/Contents.json b/FloconAndroid/iosApp/iosApp/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FloconAndroid/iosApp/iosApp/ContentView.swift b/FloconAndroid/iosApp/iosApp/ContentView.swift
new file mode 100644
index 000000000..c765ff2a5
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/ContentView.swift
@@ -0,0 +1,21 @@
+import UIKit
+import SwiftUI
+import ComposeApp
+
+struct ComposeView: UIViewControllerRepresentable {
+ func makeUIViewController(context: Context) -> UIViewController {
+ MainViewControllerKt.MainViewController()
+ }
+
+ func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
+}
+
+struct ContentView: View {
+ var body: some View {
+ ComposeView()
+ .ignoresSafeArea()
+ }
+}
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp/Info.plist b/FloconAndroid/iosApp/iosApp/Info.plist
new file mode 100644
index 000000000..11845e1da
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/Info.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+
+
diff --git a/FloconAndroid/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/FloconAndroid/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/FloconAndroid/iosApp/iosApp/iOSApp.swift b/FloconAndroid/iosApp/iosApp/iOSApp.swift
new file mode 100644
index 000000000..d83dca611
--- /dev/null
+++ b/FloconAndroid/iosApp/iosApp/iOSApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct iOSApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
\ No newline at end of file
diff --git a/FloconAndroid/ktor-interceptor/build.gradle.kts b/FloconAndroid/ktor-interceptor/build.gradle.kts
index c8ff913ea..4434e9f19 100644
--- a/FloconAndroid/ktor-interceptor/build.gradle.kts
+++ b/FloconAndroid/ktor-interceptor/build.gradle.kts
@@ -15,12 +15,10 @@ kotlin {
jvm()
- /*
iosX64()
iosArm64()
iosSimulatorArm64()
- */
-
+
sourceSets {
val commonMain by getting {
dependencies {
@@ -40,7 +38,6 @@ kotlin {
}
}
- /*
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
@@ -50,7 +47,6 @@ kotlin {
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
- */
}
}
diff --git a/FloconAndroid/sample-multiplatform/build.gradle.kts b/FloconAndroid/sample-multiplatform/build.gradle.kts
index 3ad2f90cb..56992d2ba 100644
--- a/FloconAndroid/sample-multiplatform/build.gradle.kts
+++ b/FloconAndroid/sample-multiplatform/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
+ alias(libs.plugins.androidx.room)
}
kotlin {
@@ -15,7 +16,7 @@ kotlin {
}
}
}
-
+
jvm("desktop") {
compilations.all {
kotlinOptions {
@@ -24,12 +25,17 @@ kotlin {
}
}
- /*
- iosX64()
- iosArm64()
- iosSimulatorArm64()
- */
-
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64()
+ ).forEach { iosTarget ->
+ iosTarget.binaries.framework {
+ baseName = "ComposeApp"
+ isStatic = true
+ }
+ }
+
sourceSets {
val commonMain by getting {
dependencies {
@@ -39,12 +45,13 @@ kotlin {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.json)
-
+
// Ktor client core
implementation(libs.ktor.client.core)
// room
implementation(libs.androidx.room.runtime)
+ implementation(libs.androidx.sqlite.bundled)
// Compose Multiplatform
implementation(compose.runtime)
@@ -56,7 +63,7 @@ kotlin {
implementation(libs.coil.network.ktor)
}
}
-
+
val androidMain by getting {
dependencies {
implementation(libs.kotlinx.coroutines.android)
@@ -68,7 +75,7 @@ kotlin {
implementation(libs.ktor.client.okhttp)
}
}
-
+
val desktopMain by getting {
dependencies {
// Ktor client for desktop/JVM
@@ -84,7 +91,6 @@ kotlin {
}
}
- /*
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
@@ -98,7 +104,6 @@ kotlin {
implementation(libs.ktor.client.cio)
}
}
- */
}
}
@@ -117,14 +122,14 @@ android {
}
signingConfigs {
- named("debug") {
+ named("debug") {
// just a dummy keystore to be able to test the release build
keyAlias = "release"
keyPassword = "release"
storeFile = file("../app/release.jks")
storePassword = "release"
}
- register("release") {
+ register("release") {
keyAlias = "release"
keyPassword = "release"
storeFile = file("../app/release.jks")
@@ -146,23 +151,36 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
-
+
buildFeatures {
compose = true
}
-
+
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
}
dependencies {
- ksp(libs.androidx.room.compiler)
+ listOf(
+ "kspAndroid",
+ "kspDesktop",
+ "kspIosSimulatorArm64",
+ "kspIosX64",
+ "kspIosArm64"
+ ).forEach {
+ add(it, libs.androidx.room.compiler)
+ }
+ //ksp(libs.androidx.room.compiler)
+}
+
+room {
+ schemaDirectory("$projectDir/schemas")
}
compose.desktop {
application {
mainClass = "io.github.openflocon.flocon.myapplication.multi.MainKt"
-
+
nativeDistributions {
targetFormats(
org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg,
diff --git a/FloconAndroid/sample-multiplatform/src/androidMain/AndroidManifest.xml b/FloconAndroid/sample-multiplatform/src/androidMain/AndroidManifest.xml
index 8fc30e8b4..7943fc036 100644
--- a/FloconAndroid/sample-multiplatform/src/androidMain/AndroidManifest.xml
+++ b/FloconAndroid/sample-multiplatform/src/androidMain/AndroidManifest.xml
@@ -5,6 +5,7 @@
{
+ override fun initialize(): DogDatabase
+}
\ No newline at end of file
diff --git a/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/database/FoodDatabase.kt b/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/database/FoodDatabase.kt
index 5704b6d7d..a3dca7eca 100644
--- a/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/database/FoodDatabase.kt
+++ b/FloconAndroid/sample-multiplatform/src/commonMain/kotlin/io/github/openflocon/flocon/myapplication/multi/database/FoodDatabase.kt
@@ -1,7 +1,9 @@
package io.github.openflocon.flocon.myapplication.multi.database
+import androidx.room.ConstructedBy
import androidx.room.Database
import androidx.room.RoomDatabase
+import androidx.room.RoomDatabaseConstructor
import io.github.openflocon.flocon.myapplication.multi.database.dao.FoodDao
import io.github.openflocon.flocon.myapplication.multi.database.model.FoodEntity
@@ -10,7 +12,13 @@ import io.github.openflocon.flocon.myapplication.multi.database.model.FoodEntity
version = 1,
exportSchema = false,
)
+@ConstructedBy(FoodDatabaseConstructor::class)
abstract class FoodDatabase : RoomDatabase() {
abstract fun foodDao(): FoodDao
}
+// room will generate the constructor
+@Suppress("KotlinNoActualForExpect")
+expect object FoodDatabaseConstructor : RoomDatabaseConstructor {
+ override fun initialize(): FoodDatabase
+}
\ No newline at end of file
diff --git a/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/Databases.kt b/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/Databases.kt
new file mode 100644
index 000000000..883a94170
--- /dev/null
+++ b/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/Databases.kt
@@ -0,0 +1,55 @@
+package com.florent37.myapplication
+
+import androidx.room.Room
+import androidx.sqlite.driver.NativeSQLiteDriver
+import io.github.openflocon.flocon.myapplication.multi.database.DogDatabase
+import io.github.openflocon.flocon.myapplication.multi.database.FoodDatabase
+import io.github.openflocon.flocon.plugins.database.floconRegisterDatabase
+import kotlinx.cinterop.ExperimentalForeignApi
+import platform.Foundation.NSDocumentDirectory
+import platform.Foundation.NSFileManager
+import platform.Foundation.NSUserDomainMask
+
+object Databases {
+ private var dogDatabase: DogDatabase? = null
+
+ fun getDogDatabase(): DogDatabase {
+ val dbFile = "${documentDirectory()}/dog_database.db"
+ floconRegisterDatabase(absolutePath = dbFile, displayName = "Dog Database")
+ return dogDatabase ?: run {
+ val instance = Room.databaseBuilder(
+ name = dbFile,
+ ).fallbackToDestructiveMigration(
+ dropAllTables = true
+ ).setDriver(NativeSQLiteDriver()).build()
+ dogDatabase = instance
+ instance
+ }
+ }
+
+ private var foodDatabase: FoodDatabase? = null
+
+ fun getFoodDatabase(): FoodDatabase {
+ val dbFile = "${documentDirectory()}/food_database.db"
+ floconRegisterDatabase(absolutePath = dbFile, displayName = "Food Database")
+ return foodDatabase ?: run {
+ val instance = Room.databaseBuilder(
+ name = dbFile,
+ ).setDriver(NativeSQLiteDriver()).build()
+ foodDatabase = instance
+ instance
+ }
+ }
+}
+
+@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/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/MainViewController.kt b/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/MainViewController.kt
new file mode 100644
index 000000000..a29b13ec8
--- /dev/null
+++ b/FloconAndroid/sample-multiplatform/src/iosMain/kotlin/com/florent37/myapplication/MainViewController.kt
@@ -0,0 +1,32 @@
+package com.florent37.myapplication
+
+import androidx.compose.ui.window.ComposeUIViewController
+import io.github.openflocon.flocon.Flocon
+import io.github.openflocon.flocon.ktor.FloconKtorPlugin
+import io.github.openflocon.flocon.myapplication.multi.DummyHttpKtorCaller
+import io.github.openflocon.flocon.myapplication.multi.database.initializeDatabases
+import io.github.openflocon.flocon.myapplication.multi.ui.App
+import io.ktor.client.HttpClient
+import io.ktor.client.engine.darwin.Darwin
+
+fun MainViewController() = ComposeUIViewController(
+ configure = {
+ Flocon.initialize()
+
+ initializeDatabases(
+ dogDatabase = Databases.getDogDatabase(),
+ foodDatabase = Databases.getFoodDatabase(),
+ )
+
+ val ktorClient = HttpClient(Darwin) {
+ install(FloconKtorPlugin) {
+ isImage = {
+ it.request.url.toString().contains("picsum.photos")
+ }
+ }
+ }
+ DummyHttpKtorCaller.initialize(ktorClient)
+ }
+) {
+ App()
+}
\ No newline at end of file