From 101b64f5cbc7745e448acfd2247eb6b869b7a0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 17 Jul 2024 18:01:44 +0100 Subject: [PATCH 1/6] Clean up repo for a future KMM POC for using the engine through artifacts --- composeApp/build.gradle.kts | 23 +--- .../androidMain/kotlin/di/AndroidModules.kt | 20 ---- .../src/androidMain/kotlin/di/modules.kt | 9 -- .../kotlin/org/ooni/probe/Application.kt | 14 --- .../kotlin/org/ooni/probe/MainActivity.kt | 21 ++-- .../platform/GoOONIProbeClientBridge.kt | 21 ---- .../kotlin/platform/MultiplatformSettings.kt | 13 -- composeApp/src/commonMain/kotlin/App.kt | 50 ++------ .../kotlin/core/probe/OONIProbeClient.kt | 49 -------- .../kotlin/core/settings/SettingsManager.kt | 66 ----------- .../kotlin/core/settings/SettingsStore.kt | 9 -- .../kotlin/core/settings/SettingsStoreImpl.kt | 17 --- .../src/commonMain/kotlin/di/Dependencies.kt | 12 ++ .../src/commonMain/kotlin/di/KoinInit.kt | 19 --- .../src/commonMain/kotlin/di/commonModules.kt | 48 -------- .../src/commonMain/kotlin/main/MainVIew.kt | 69 ----------- .../commonMain/kotlin/main/MainViewModel.kt | 32 ----- .../platform/GoOONIProbeClientBridge.kt | 4 +- .../kotlin/platform/MultiplatformSettings.kt | 9 -- .../commonMain/kotlin/ui/components/AppTab.kt | 36 ------ .../commonMain/kotlin/ui/main/MainScreen.kt | 14 +++ .../kotlin/ui/main/MainViewModel.kt | 22 ++++ .../kotlin/ui/screens/home/HomeScreen.kt | 112 ------------------ .../kotlin/ui/screens/home/HomeScreenModel.kt | 49 -------- .../ui/screens/onboarding/OnboardingView.kt | 67 ----------- .../screens/onboarding/OnboardingViewModel.kt | 14 --- .../src/desktopMain/kotlin/Platform.jvm.kt | 5 - composeApp/src/desktopMain/kotlin/main.kt | 4 +- .../platform/DesktopOONIProbeClientBridge.kt | 6 + .../platform/MultiplatformSettings.jvm.kt | 12 -- .../src/iosMain/kotlin/MainViewController.kt | 4 +- .../platform/IosOONIProbeClientBridge.kt | 6 + .../platform/MultiplatformSettings.ios.kt | 12 -- 33 files changed, 99 insertions(+), 769 deletions(-) delete mode 100644 composeApp/src/androidMain/kotlin/di/AndroidModules.kt delete mode 100644 composeApp/src/androidMain/kotlin/di/modules.kt delete mode 100644 composeApp/src/androidMain/kotlin/platform/GoOONIProbeClientBridge.kt delete mode 100644 composeApp/src/androidMain/kotlin/platform/MultiplatformSettings.kt delete mode 100644 composeApp/src/commonMain/kotlin/core/probe/OONIProbeClient.kt delete mode 100644 composeApp/src/commonMain/kotlin/core/settings/SettingsManager.kt delete mode 100644 composeApp/src/commonMain/kotlin/core/settings/SettingsStore.kt delete mode 100644 composeApp/src/commonMain/kotlin/core/settings/SettingsStoreImpl.kt create mode 100644 composeApp/src/commonMain/kotlin/di/Dependencies.kt delete mode 100644 composeApp/src/commonMain/kotlin/di/KoinInit.kt delete mode 100644 composeApp/src/commonMain/kotlin/di/commonModules.kt delete mode 100644 composeApp/src/commonMain/kotlin/main/MainVIew.kt delete mode 100644 composeApp/src/commonMain/kotlin/main/MainViewModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/platform/MultiplatformSettings.kt delete mode 100644 composeApp/src/commonMain/kotlin/ui/components/AppTab.kt create mode 100644 composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreenModel.kt delete mode 100644 composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingView.kt delete mode 100644 composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingViewModel.kt delete mode 100644 composeApp/src/desktopMain/kotlin/Platform.jvm.kt create mode 100644 composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt delete mode 100644 composeApp/src/desktopMain/kotlin/platform/MultiplatformSettings.jvm.kt create mode 100644 composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt delete mode 100644 composeApp/src/iosMain/kotlin/platform/MultiplatformSettings.ios.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 50bb0b9d..ed183dcd 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -35,8 +35,6 @@ kotlin { androidMain.dependencies { implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") - implementation("io.insert-koin:koin-android:3.5.6") } commonMain.dependencies { implementation(compose.runtime) @@ -49,27 +47,16 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") implementation("io.github.aakira:napier:2.7.1") - implementation("io.insert-koin:koin-core:3.5.6") - // TODO(r2): When koin 3.6.x comes out we can drop this. - // https://github.com/InsertKoinIO/koin/issues/1803 - implementation("io.insert-koin:koin-compose:1.1.5") - - val voyagerVersion = "1.0.0" - implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion") - implementation("cafe.adriel.voyager:voyager-screenmodel:$voyagerVersion") - implementation("cafe.adriel.voyager:voyager-bottom-sheet-navigator:$voyagerVersion") - implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion") - implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion") - implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion") - - implementation("com.russhwolf:multiplatform-settings-no-arg:1.1.1") - implementation("com.russhwolf:multiplatform-settings-coroutines:1.1.1") - } desktopMain.dependencies { implementation(compose.desktop.currentOs) } } + + compilerOptions { + // Common compiler options applied to all Kotlin source sets + freeCompilerArgs.add("-Xexpect-actual-classes") + } } android { diff --git a/composeApp/src/androidMain/kotlin/di/AndroidModules.kt b/composeApp/src/androidMain/kotlin/di/AndroidModules.kt deleted file mode 100644 index 2051dc5d..00000000 --- a/composeApp/src/androidMain/kotlin/di/AndroidModules.kt +++ /dev/null @@ -1,20 +0,0 @@ -package di - -import core.probe.OONIProbeClient -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module -import platform.GoOONIProbeClientBridge - -val androidModule = module { - single { - GoOONIProbeClientBridge( - context = androidContext() - ) - } - - factory { - OONIProbeClient(GoOONIProbeClientBridge( - context = androidContext() - )) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/di/modules.kt b/composeApp/src/androidMain/kotlin/di/modules.kt deleted file mode 100644 index 62746ccc..00000000 --- a/composeApp/src/androidMain/kotlin/di/modules.kt +++ /dev/null @@ -1,9 +0,0 @@ -package di - -import org.koin.core.module.Module -import org.koin.dsl.module - -import platform.MultiplatformSettings -actual fun platformModule(): Module = module { - single { MultiplatformSettings(context = get()).createSettings() } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt index aa74cc33..9483a5f1 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt @@ -1,10 +1,5 @@ package org.ooni.probe -import di.KoinInit -import di.androidModule -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger -import org.koin.core.logger.Level import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier @@ -12,14 +7,5 @@ class Application : android.app.Application() { override fun onCreate() { super.onCreate() Napier.base(DebugAntilog()) - KoinInit().init { - androidLogger(level = Level.DEBUG) - androidContext(androidContext = this@Application) - modules( - listOf( - androidModule, - ) - ) - } } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt index 184c3bbb..6547b885 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt @@ -6,19 +6,26 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview +import di.Dependencies +import platform.GoOONIProbeClientBridge class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val client = GoOONIProbeClient(this) + val dependencies = Dependencies(object : GoOONIProbeClientBridge { + override fun apiCall(funcName: String): String { + return client.call(funcName) + } + + override fun apiCallWithArgs(funcName: String, args: String): String { + return client.callWithArgs(funcName, args) + } + }) + setContent { - App() + App(dependencies) } } } - -@Preview -@Composable -fun AppAndroidPreview() { - App() -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/platform/GoOONIProbeClientBridge.kt b/composeApp/src/androidMain/kotlin/platform/GoOONIProbeClientBridge.kt deleted file mode 100644 index 44e0b1e8..00000000 --- a/composeApp/src/androidMain/kotlin/platform/GoOONIProbeClientBridge.kt +++ /dev/null @@ -1,21 +0,0 @@ -package platform - -import android.content.Context -import io.github.aakira.napier.Napier -import org.ooni.probe.GoOONIProbeClient - -actual class GoOONIProbeClientBridge(context: Context) { - private val client = GoOONIProbeClient(context) - init { - Napier.d("running laoder") - } - - actual fun apiCall(funcName: String): String { - Napier.d("running API call ${funcName}") - return client.call(funcName) - } - actual fun apiCallWithArgs(funcName: String, args : String): String { - Napier.d("running API call with args ${funcName} ${args}") - return client.callWithArgs(funcName, args) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/platform/MultiplatformSettings.kt b/composeApp/src/androidMain/kotlin/platform/MultiplatformSettings.kt deleted file mode 100644 index 6ac31dea..00000000 --- a/composeApp/src/androidMain/kotlin/platform/MultiplatformSettings.kt +++ /dev/null @@ -1,13 +0,0 @@ -package platform - - -import android.content.Context -import com.russhwolf.settings.Settings -import com.russhwolf.settings.SharedPreferencesSettings - -actual class MultiplatformSettings(private val context: Context) { - actual fun createSettings() : Settings { - val delegate = context.getSharedPreferences("ooniprobe_settings", Context.MODE_PRIVATE) - return SharedPreferencesSettings(delegate) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index 4d66e796..dc1f4996 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -1,50 +1,26 @@ import androidx.compose.foundation.layout.fillMaxSize - +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import androidx.compose.material3.Surface -import androidx.compose.material3.MaterialTheme -import cafe.adriel.voyager.navigator.Navigator -import io.github.aakira.napier.Napier - +import di.Dependencies import org.jetbrains.compose.ui.tooling.preview.Preview - - -import org.koin.compose.koinInject -import org.koin.compose.KoinContext - -import main.MainView -import main.MainViewModel -import main.OnboardingState import ui.Theme -import ui.screens.onboarding.OnboardingView +import ui.main.MainScreen @Composable @Preview fun App( - mainViewModel: MainViewModel = koinInject(), + dependencies: Dependencies ) { - KoinContext { - val onboardingState = mainViewModel.onboardingState.collectAsState().value - Theme() { - Surface( - modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background, - ) { - when (onboardingState) { - is OnboardingState.Complete -> { - Navigator( - screen = MainView() - ) - } - is OnboardingState.Incomplete -> { - Navigator( - screen = OnboardingView() - ) - } - else -> {} - } - } + Theme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + MainScreen( + dependencies.mainViewModel + ) } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/probe/OONIProbeClient.kt b/composeApp/src/commonMain/kotlin/core/probe/OONIProbeClient.kt deleted file mode 100644 index 5e7d01c0..00000000 --- a/composeApp/src/commonMain/kotlin/core/probe/OONIProbeClient.kt +++ /dev/null @@ -1,49 +0,0 @@ -package core.probe - -import io.github.aakira.napier.Napier -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonArray -import platform.GoOONIProbeClientBridge - -val laxJson = Json {ignoreUnknownKeys = true} -@Serializable -data class ApiCallValue( - val return_value: String, - val error: String?, -) -@Serializable -data class HTTPResponse( - val body: String -) - -class OONIProbeClient(ooniprobeClientBridge: GoOONIProbeClientBridge) { - private val ooniprobeClientBridge = ooniprobeClientBridge - - fun doHTTPRequest(url : String, retryCount : Int) : HTTPResponse { - val args = buildJsonArray { - add(JsonPrimitive(url)) - add(JsonPrimitive(retryCount)) - } - - val apiCallValue = laxJson.decodeFromString( - ooniprobeClientBridge.apiCallWithArgs("DoHTTPRequest", args.toString()) - ) - if (apiCallValue.error != null) { - throw Error(apiCallValue.error) - } - return laxJson.decodeFromString(apiCallValue.return_value) - } - - fun getPublicIP() : String { - val apiCallValue = laxJson.decodeFromString( - ooniprobeClientBridge.apiCall("GetPublicIP") - ) - Napier.i("getPublicIP: return_value=${apiCallValue.return_value} error=${apiCallValue.error}") - if (apiCallValue.error != null) { - throw Error(apiCallValue.error) - } - return apiCallValue.return_value - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/settings/SettingsManager.kt b/composeApp/src/commonMain/kotlin/core/settings/SettingsManager.kt deleted file mode 100644 index 349357f7..00000000 --- a/composeApp/src/commonMain/kotlin/core/settings/SettingsManager.kt +++ /dev/null @@ -1,66 +0,0 @@ -package core.settings - -import com.russhwolf.settings.ExperimentalSettingsApi -import com.russhwolf.settings.ObservableSettings -import com.russhwolf.settings.Settings -import com.russhwolf.settings.set -import com.russhwolf.settings.coroutines.getBooleanFlow -import com.russhwolf.settings.coroutines.getIntFlow -import com.russhwolf.settings.coroutines.getIntOrNullFlow -import com.russhwolf.settings.coroutines.getLongFlow -import com.russhwolf.settings.coroutines.getStringOrNullFlow -import kotlinx.coroutines.flow.Flow - -class SettingsManager constructor(private val settings: Settings) { - private val observableSettings: ObservableSettings by lazy { settings as ObservableSettings } - - fun setString(key: String, value: String) { - observableSettings.set(key = key, value = value) - } - - fun getNonFlowString(key: String) = observableSettings.getString( - key = key, - defaultValue = "", - ) - - fun getString(key: String) = observableSettings.getStringOrNullFlow(key = key) - - fun setInt(key: String, value: Int) { - observableSettings.set(key = key, value = value) - } - fun getInt(key: String) = observableSettings.getIntOrNullFlow(key = key) - - fun getIntFlow(key: String) = observableSettings.getIntFlow(key = key, defaultValue = 0) - - companion object { - const val PROBE_CREDENTIALS = "probe_credentials_key" - } - - fun clearAllSettings() { - observableSettings.clear() - } - - @OptIn(ExperimentalSettingsApi::class) - fun getBoolean(key: String): Flow { - return observableSettings.getBooleanFlow( - key = key, - defaultValue = false, - ) - } - - fun setBoolean(key: String, value: Boolean) { - observableSettings.set(key = key, value = value) - } - - @OptIn(ExperimentalSettingsApi::class) - fun getLong(key: Any): Flow { - return observableSettings.getLongFlow( - key = key.toString(), - defaultValue = 0, - ) - } - - fun setLong(key: String, value: Long) { - observableSettings.set(key = key, value = value) - } -} diff --git a/composeApp/src/commonMain/kotlin/core/settings/SettingsStore.kt b/composeApp/src/commonMain/kotlin/core/settings/SettingsStore.kt deleted file mode 100644 index 107d7ca5..00000000 --- a/composeApp/src/commonMain/kotlin/core/settings/SettingsStore.kt +++ /dev/null @@ -1,9 +0,0 @@ -package core.settings - -import kotlinx.coroutines.flow.Flow - -interface SettingsStore { - fun clearAll() - fun saveProbeCredentials(value: String) - fun getProbeCredentials(): Flow -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/settings/SettingsStoreImpl.kt b/composeApp/src/commonMain/kotlin/core/settings/SettingsStoreImpl.kt deleted file mode 100644 index 54ee20cf..00000000 --- a/composeApp/src/commonMain/kotlin/core/settings/SettingsStoreImpl.kt +++ /dev/null @@ -1,17 +0,0 @@ -package core.settings - -import kotlinx.coroutines.flow.Flow - -class SettingsStoreImpl( - private val settingsManager: SettingsManager, -) : SettingsStore { - override fun clearAll() { - return settingsManager.clearAllSettings() - } - override fun saveProbeCredentials(value: String) { - return settingsManager.setString(key = SettingsManager.PROBE_CREDENTIALS, value = value) - } - override fun getProbeCredentials(): Flow { - return settingsManager.getString(key = SettingsManager.PROBE_CREDENTIALS) - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/Dependencies.kt b/composeApp/src/commonMain/kotlin/di/Dependencies.kt new file mode 100644 index 00000000..2e509140 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/Dependencies.kt @@ -0,0 +1,12 @@ +package di + +import platform.GoOONIProbeClientBridge +import ui.main.MainViewModel + +class Dependencies( + private val goOONIProbeClientBridge: GoOONIProbeClientBridge +) { + + val mainViewModel by lazy { MainViewModel(goOONIProbeClientBridge) } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/KoinInit.kt b/composeApp/src/commonMain/kotlin/di/KoinInit.kt deleted file mode 100644 index 04af7de2..00000000 --- a/composeApp/src/commonMain/kotlin/di/KoinInit.kt +++ /dev/null @@ -1,19 +0,0 @@ -package di - -import org.koin.core.Koin -import org.koin.core.context.startKoin -import org.koin.dsl.KoinAppDeclaration - -class KoinInit { - fun init(appDeclaration: KoinAppDeclaration = {}): Koin { - return startKoin { - modules( - listOf( - platformModule(), - commonModule(), - ), - ) - appDeclaration() - }.koin - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/di/commonModules.kt b/composeApp/src/commonMain/kotlin/di/commonModules.kt deleted file mode 100644 index 20216c8e..00000000 --- a/composeApp/src/commonMain/kotlin/di/commonModules.kt +++ /dev/null @@ -1,48 +0,0 @@ -package di - -import core.settings.SettingsManager -import core.settings.SettingsStore -import core.settings.SettingsStoreImpl -import org.koin.core.module.Module -import org.koin.dsl.module -import ui.screens.home.HomeScreenModel -import main.MainViewModel -import ui.screens.onboarding.OnboardingViewModel - -fun commonModule() = module { - single { - SettingsManager(settings = get()) - } - - /** - * Stores or repositories in Android speak - */ - single { - SettingsStoreImpl( - settingsManager = get(), - ) - } - - /** - * ScreenModels - */ - single { - HomeScreenModel( - settingsStore = get(), - ooniProbeClient = get() - ) - } - single { - OnboardingViewModel( - settingsStore = get() - ) - } - - single { - MainViewModel( - settingsStore = get() - ) - } - -} -expect fun platformModule(): Module \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/main/MainVIew.kt b/composeApp/src/commonMain/kotlin/main/MainVIew.kt deleted file mode 100644 index 626eb60b..00000000 --- a/composeApp/src/commonMain/kotlin/main/MainVIew.kt +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.RowScope -import androidx.compose.material.BottomNavigation -import androidx.compose.material.BottomNavigationItem -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable - -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.navigator.CurrentScreen -import cafe.adriel.voyager.navigator.tab.LocalTabNavigator -import cafe.adriel.voyager.navigator.tab.Tab -import cafe.adriel.voyager.navigator.tab.TabNavigator -import io.github.aakira.napier.Napier - -import ui.components.AppTab - -class MainView: Screen { - @Composable - override fun Content() { - TabNavigator( - AppTab.HomeTab, - ) { - Scaffold( - content = { CurrentScreen() }, - bottomBar = { - BottomNavigation( - backgroundColor = MaterialTheme.colorScheme.background, - ) { - TabNavigationItem(AppTab.HomeTab) - } - }, - ) - } - } -} - -@Composable -private fun RowScope.TabNavigationItem(tab: Tab) { - val tabNavigator = LocalTabNavigator.current - - // TODO(art): There doesn't seem to be a nice way to pick selected and - // unselected icons in Voyager, so we don't implement it for the moment. - // See: - // https://github.com/adrielcafe/voyager/issues/141 - // https://github.com/adrielcafe/voyager/issues/313 - val isSelected = tabNavigator.current == tab - - BottomNavigationItem( - selected = tabNavigator.current == tab, - onClick = { tabNavigator.current = tab }, - icon = { tab.options.icon?.let { - Icon( - painter = it, - contentDescription = tab.options.title, - tint = if (isSelected) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onBackground - } - ) - } - } - ) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/main/MainViewModel.kt b/composeApp/src/commonMain/kotlin/main/MainViewModel.kt deleted file mode 100644 index 78bc3262..00000000 --- a/composeApp/src/commonMain/kotlin/main/MainViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import cafe.adriel.voyager.core.model.ScreenModel -import core.settings.SettingsStore -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.SharingStarted -import cafe.adriel.voyager.core.model.screenModelScope -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn - -class MainViewModel( - settingsStore: SettingsStore, -) : ScreenModel { - val onboardingState: StateFlow = - settingsStore.getProbeCredentials().map { - if (it.isNullOrEmpty().not()) { - return@map OnboardingState.Complete - } - OnboardingState.Incomplete - }.stateIn( - scope = screenModelScope, - started=SharingStarted.WhileSubscribed(), - initialValue = OnboardingState.Loading, - ) -} - -sealed class OnboardingState { - data object Loading: OnboardingState() - data object Complete : OnboardingState() - data object Incomplete: OnboardingState() -} - diff --git a/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt b/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt index 44aa8290..7b0e9a67 100644 --- a/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt +++ b/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt @@ -1,8 +1,6 @@ package platform -expect class GoOONIProbeClientBridge { - +interface GoOONIProbeClientBridge { fun apiCall(funcName: String): String fun apiCallWithArgs(funcName: String, args : String): String - } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/platform/MultiplatformSettings.kt b/composeApp/src/commonMain/kotlin/platform/MultiplatformSettings.kt deleted file mode 100644 index 6b242261..00000000 --- a/composeApp/src/commonMain/kotlin/platform/MultiplatformSettings.kt +++ /dev/null @@ -1,9 +0,0 @@ -package platform - -import com.russhwolf.settings.Settings - -// This pattern is and related cross platform code is taken from: -// https://github.com/joelkanyi/FocusBloom/blob/develop/shared/src/commonMain/kotlin/com/joelkanyi/focusbloom/platform/MultiplatformSettingsWrapper.kt -expect class MultiplatformSettings { - fun createSettings(): Settings -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/components/AppTab.kt b/composeApp/src/commonMain/kotlin/ui/components/AppTab.kt deleted file mode 100644 index 58198114..00000000 --- a/composeApp/src/commonMain/kotlin/ui/components/AppTab.kt +++ /dev/null @@ -1,36 +0,0 @@ -package ui.components - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Home -import androidx.compose.ui.graphics.vector.rememberVectorPainter - -import cafe.adriel.voyager.navigator.tab.Tab -import cafe.adriel.voyager.navigator.tab.TabOptions - -import ui.screens.home.HomeScreen - -internal sealed class AppTab { - internal object HomeTab : Tab { - override val options: TabOptions - @Composable - get() { - val title = "Home" - val icon = rememberVectorPainter(image = Icons.Outlined.Home) - return remember { - TabOptions( - index = 0u, - title = title, - icon = icon, - ) - } - } - - @Composable - override fun Content() { - HomeScreen() - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt b/composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt new file mode 100644 index 00000000..96e2e46d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt @@ -0,0 +1,14 @@ +package ui.main + +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue + +@Composable +fun MainScreen( + viewModel: MainViewModel +) { + val result by viewModel.result.collectAsState() + Text(result ?: "Waiting result") +} diff --git a/composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt b/composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt new file mode 100644 index 00000000..5c87be7b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt @@ -0,0 +1,22 @@ +package ui.main + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import platform.GoOONIProbeClientBridge + +class MainViewModel( + private val goOONIProbeClientBridge: GoOONIProbeClientBridge +) { + private val _result = MutableStateFlow(null) + val result = _result.asStateFlow() + + init { + CoroutineScope(Dispatchers.IO).launch { + _result.value = goOONIProbeClientBridge.apiCall("GetPublicIP") + } + } +} diff --git a/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreen.kt deleted file mode 100644 index 9871019e..00000000 --- a/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreen.kt +++ /dev/null @@ -1,112 +0,0 @@ -package ui.screens.home - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import org.koin.compose.koinInject - -@Composable -fun HomeScreen( - screenModel: HomeScreenModel = koinInject(), -) { - val publicIP = screenModel.publicIP.collectAsState().value - val httpResponse = screenModel.httpResponse.collectAsState().value - HomeScreenContent( - onClickReset={ - screenModel.clearSettings() - }, - onClickDoRequest = { - screenModel.doHTTPRequest() - }, - onClickDoIPLookup = { - screenModel.lookupIP() - }, - publicIP=publicIP, - httpResponse=httpResponse - ) -} - -@Composable -private fun HomeScreenContent( - publicIP: String, - httpResponse: String, - onClickReset: () -> Unit, - onClickDoIPLookup: () -> Unit, - onClickDoRequest: () -> Unit -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxHeight() - ) { - Row( - modifier = Modifier - .padding(20.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - Column ( - horizontalAlignment = Alignment.CenterHorizontally, - ){ - Text("This is home.", - style = MaterialTheme.typography.headlineLarge, - textAlign = TextAlign.Center - ) - - Spacer(modifier = Modifier.height(24.dp)) - - TextField( - value = publicIP, - onValueChange = {}, - label = {Text("public ip")}, - readOnly = true - ) - Button( - onClick = onClickDoIPLookup, - shape = MaterialTheme.shapes.medium - ) { - Text("DoIPLookup") - } - - TextField( - value = httpResponse, - onValueChange = {}, - label = {Text("http response")}, - readOnly = true - ) - Button( - onClick = onClickDoRequest, - shape = MaterialTheme.shapes.medium - ) { - Text("DoRequest") - } - - Spacer(modifier = Modifier.height(78.dp)) - - Button( - onClick = onClickReset, - shape = MaterialTheme.shapes.medium - ) { - Text("Reset app") - } - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreenModel.kt b/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreenModel.kt deleted file mode 100644 index b5a46dad..00000000 --- a/composeApp/src/commonMain/kotlin/ui/screens/home/HomeScreenModel.kt +++ /dev/null @@ -1,49 +0,0 @@ -package ui.screens.home - -import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.screenModelScope -import core.probe.OONIProbeClient -import core.settings.SettingsStore -import io.github.aakira.napier.Napier -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch - -class HomeScreenModel( - private val settingsStore: SettingsStore, - private val ooniProbeClient: OONIProbeClient -) : ScreenModel { - - private val _httpResponse = MutableStateFlow("") - private val _publicIP = MutableStateFlow("") - - val publicIP = _publicIP.asStateFlow() - val httpResponse = _httpResponse.asStateFlow() - - fun clearSettings() { - settingsStore.clearAll() - } - fun doHTTPRequest() { - screenModelScope.launch { - try { - val resp = ooniProbeClient.doHTTPRequest("https://google.com/humans.txt", 2) - _httpResponse.value = resp.body - } catch (e: Error) { - _httpResponse.value = "error: ${e.message}" - Napier.e("error fetching http ${e.message}") - } - } - } - fun lookupIP() { - screenModelScope.launch { - try { - val ip = ooniProbeClient.getPublicIP() - _publicIP.value = ip - } catch (e: Error) { - _publicIP.value = "error: ${e.message}" - Napier.e("error looking up IP ${e.message}") - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingView.kt b/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingView.kt deleted file mode 100644 index 704189f4..00000000 --- a/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingView.kt +++ /dev/null @@ -1,67 +0,0 @@ -package ui.screens.onboarding - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import cafe.adriel.voyager.core.screen.Screen -import org.koin.compose.koinInject -import org.koin.core.component.KoinComponent - -class OnboardingView( -) : Screen, KoinComponent { - @Composable - override fun Content() { - val screenModel = koinInject() - OnboardingScreenContent( - onClickDone = { - screenModel.onboardingComplete() - } - ) - } -} - -@Composable -private fun OnboardingScreenContent( - onClickDone: () -> Unit -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxHeight() - ) { - Row( - modifier = Modifier - .padding(20.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.Center - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text("Welcome human!", - style = MaterialTheme.typography.headlineLarge, - textAlign = TextAlign.Center - ) - - Button( - onClick = onClickDone, - shape = MaterialTheme.shapes.medium - ) { - Text("Done") - } - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingViewModel.kt b/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingViewModel.kt deleted file mode 100644 index ff2aff51..00000000 --- a/composeApp/src/commonMain/kotlin/ui/screens/onboarding/OnboardingViewModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -package ui.screens.onboarding - -import cafe.adriel.voyager.core.model.ScreenModel - -import core.settings.SettingsStore - -class OnboardingViewModel( - private val settingsStore: SettingsStore, -) : ScreenModel { - fun onboardingComplete() { - settingsStore.saveProbeCredentials("dummy value") - } - -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/Platform.jvm.kt b/composeApp/src/desktopMain/kotlin/Platform.jvm.kt deleted file mode 100644 index f5e7e494..00000000 --- a/composeApp/src/desktopMain/kotlin/Platform.jvm.kt +++ /dev/null @@ -1,5 +0,0 @@ -class JVMPlatform: Platform { - override val name: String = "Java ${System.getProperty("java.version")}" -} - -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/main.kt index 99559b98..7ea0df28 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/main.kt @@ -1,11 +1,13 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import di.Dependencies +import platform.DesktopOONIProbeClientBridge fun main() = application { Window( onCloseRequest = ::exitApplication, title = "OONI Probe", ) { - App() + App(Dependencies(DesktopOONIProbeClientBridge())) } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt b/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt new file mode 100644 index 00000000..92e12d92 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt @@ -0,0 +1,6 @@ +package platform + +class DesktopOONIProbeClientBridge : GoOONIProbeClientBridge { + override fun apiCall(funcName: String) = "Desktop Result" + override fun apiCallWithArgs(funcName: String, args : String) = "" +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/platform/MultiplatformSettings.jvm.kt b/composeApp/src/desktopMain/kotlin/platform/MultiplatformSettings.jvm.kt deleted file mode 100644 index ac011760..00000000 --- a/composeApp/src/desktopMain/kotlin/platform/MultiplatformSettings.jvm.kt +++ /dev/null @@ -1,12 +0,0 @@ -package platform - -import com.russhwolf.settings.PreferencesSettings -import com.russhwolf.settings.Settings -import java.util.prefs.Preferences - -actual class MultiplatformSettings { - actual fun createSettings(): Settings { - val delegate: Preferences = Preferences.userRoot() - return PreferencesSettings(delegate) - } -} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt index fa143d45..7b715a05 100644 --- a/composeApp/src/iosMain/kotlin/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -1,3 +1,5 @@ import androidx.compose.ui.window.ComposeUIViewController +import di.Dependencies +import platform.IosOONIProbeClientBridge -fun MainViewController() = ComposeUIViewController { App() } \ No newline at end of file +fun MainViewController() = ComposeUIViewController { App(Dependencies(IosOONIProbeClientBridge())) } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt b/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt new file mode 100644 index 00000000..140da865 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt @@ -0,0 +1,6 @@ +package platform + +class IosOONIProbeClientBridge : GoOONIProbeClientBridge { + override fun apiCall(funcName: String) = "ios result" + override fun apiCallWithArgs(funcName: String, args : String) = "" +} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/platform/MultiplatformSettings.ios.kt b/composeApp/src/iosMain/kotlin/platform/MultiplatformSettings.ios.kt deleted file mode 100644 index 9a218dd8..00000000 --- a/composeApp/src/iosMain/kotlin/platform/MultiplatformSettings.ios.kt +++ /dev/null @@ -1,12 +0,0 @@ -package platform - -import com.russhwolf.settings.NSUserDefaultsSettings -import com.russhwolf.settings.Settings -import platform.Foundation.NSUserDefaults - -actual class MultiplatformSettings { - actual fun createSettings(): Settings { - val delegate = NSUserDefaults.standardUserDefaults - return NSUserDefaultsSettings(delegate) - } -} \ No newline at end of file From c8e1b50da487ea5281179fdebc24712fcd29f53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 23 Jul 2024 15:58:49 +0100 Subject: [PATCH 2/6] Android and iOS artifacts --- build.gradle.kts | 1 + composeApp/build.gradle.kts | 26 +-- .../src/androidMain/AndroidManifest.xml | 2 +- .../org/ooni/engine/AndroidOoniEngine.kt | 7 + .../ooni/libooniprobe-android/CMakeLists.txt | 31 ---- .../org/ooni/libooniprobe-android/Makefile | 14 -- .../org/ooni/libooniprobe-android/go.mod | 3 - .../org/ooni/libooniprobe-android/go.sum | 0 .../org/ooni/libooniprobe-android/jni.c | 50 ------ .../libooniprobe-android.go | 150 ------------------ .../org/ooni/probe/AndroidApplication.kt | 5 + .../kotlin/org/ooni/probe/Application.kt | 11 -- .../org/ooni/probe/GoOONIProbeClient.kt | 27 ---- .../kotlin/org/ooni/probe/MainActivity.kt | 19 +-- .../src/commonMain/kotlin/di/Dependencies.kt | 12 -- .../kotlin/org/ooni/engine/OoniEngine.kt | 5 + .../kotlin/{ => org/ooni/probe}/App.kt | 8 +- .../kotlin/org/ooni/probe/di/Dependencies.kt | 12 ++ .../kotlin/{ => org/ooni/probe}/ui/Theme.kt | 2 +- .../ooni/probe}/ui/main/MainScreen.kt | 2 +- .../ooni/probe}/ui/main/MainViewModel.kt | 8 +- .../platform/GoOONIProbeClientBridge.kt | 6 - .../org/ooni/engine/DesktopOoniEngine.kt | 7 + .../kotlin/{ => org/ooni/probe}/main.kt | 6 +- .../platform/DesktopOONIProbeClientBridge.kt | 6 - .../src/iosMain/kotlin/MainViewController.kt | 5 - .../org/ooni/probe/MainViewController.kt | 8 + .../platform/IosOONIProbeClientBridge.kt | 6 - gradle/libs.versions.toml | 22 +-- iosApp/Podfile | 16 ++ iosApp/iosApp.xcodeproj/project.pbxproj | 121 +++++++++++++- iosApp/iosApp/ContentView.swift | 2 +- iosApp/iosApp/engine/IosOoniEngine.swift | 8 + 33 files changed, 225 insertions(+), 383 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOoniEngine.kt delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/CMakeLists.txt delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/Makefile delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/go.mod delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/go.sum delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/jni.c delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/libooniprobe-android.go create mode 100644 composeApp/src/androidMain/kotlin/org/ooni/probe/AndroidApplication.kt delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/probe/GoOONIProbeClient.kt delete mode 100644 composeApp/src/commonMain/kotlin/di/Dependencies.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt rename composeApp/src/commonMain/kotlin/{ => org/ooni/probe}/App.kt (81%) create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt rename composeApp/src/commonMain/kotlin/{ => org/ooni/probe}/ui/Theme.kt (99%) rename composeApp/src/commonMain/kotlin/{ => org/ooni/probe}/ui/main/MainScreen.kt (91%) rename composeApp/src/commonMain/kotlin/{ => org/ooni/probe}/ui/main/MainViewModel.kt (68%) delete mode 100644 composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt create mode 100644 composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt rename composeApp/src/desktopMain/kotlin/{ => org/ooni/probe}/main.kt (70%) delete mode 100644 composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt delete mode 100644 composeApp/src/iosMain/kotlin/MainViewController.kt create mode 100644 composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt delete mode 100644 composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt create mode 100644 iosApp/Podfile create mode 100644 iosApp/iosApp/engine/IosOoniEngine.swift diff --git a/build.gradle.kts b/build.gradle.kts index 52cf9fab..dfb28d7f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,5 @@ plugins { alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.jetbrainsCompose) apply false alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.cocoapods) apply false } \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index ed183dcd..8e4990ec 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -4,7 +4,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsCompose) - kotlin("plugin.serialization") version "1.9.22" + alias(libs.plugins.cocoapods) } kotlin { @@ -28,6 +28,20 @@ kotlin { isStatic = true } } + + cocoapods { + ios.deploymentTarget = "9.0" + + version = "1.0" + summary = "Compose App" + homepage = "https://github.com/ooni/probe-multiplatform" + + framework { + baseName = "composeApp" + } + + podfile = project.file("../iosApp/Podfile") + } sourceSets { val desktopMain by getting @@ -35,6 +49,7 @@ kotlin { androidMain.dependencies { implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) + implementation(libs.android.oonimkall) } commonMain.dependencies { implementation(compose.runtime) @@ -44,9 +59,6 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) - - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") - implementation("io.github.aakira:napier:2.7.1") } desktopMain.dependencies { implementation(compose.desktop.currentOs) @@ -67,12 +79,6 @@ android { sourceSets["main"].res.srcDirs("src/androidMain/res") sourceSets["main"].resources.srcDirs("src/commonMain/resources") - externalNativeBuild { - cmake { - path("src/androidMain/kotlin/org/ooni/libooniprobe-android/CMakeLists.txt") - } - } - buildTypes { all { externalNativeBuild { diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index 543d724a..112aadd0 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -3,7 +3,7 @@ -#include -#include - -struct go_string { const char *str; long n; }; -extern char *apiCall(struct go_string fname); -extern char *apiCallWithArgs(struct go_string fname, struct go_string args); - -JNIEXPORT jstring JNICALL Java_org_ooni_probe_GoOONIProbeClient_apiCall(JNIEnv *env, jclass c, jstring fname) -{ - jstring ret; - const char *fname_str = (*env)->GetStringUTFChars(env, fname, 0); - size_t fname_len = (*env)->GetStringUTFLength(env, fname); - char *d = apiCall((struct go_string){ - .str = fname_str, - .n = fname_len - }); - (*env)->ReleaseStringUTFChars(env, fname, fname_str); - if (!d) { - return NULL; - } - ret = (*env)->NewStringUTF(env, d); - free(d); - return ret; -} - -JNIEXPORT jstring JNICALL Java_org_ooni_probe_GoOONIProbeClient_apiCallWithArgs(JNIEnv *env, jclass c, jstring fname, jstring args) -{ - jstring ret; - const char *fname_str = (*env)->GetStringUTFChars(env, fname, 0); - size_t fname_len = (*env)->GetStringUTFLength(env, fname); - - const char *args_str = (*env)->GetStringUTFChars(env, args, 0); - size_t args_len = (*env)->GetStringUTFLength(env, args); - - char *d = apiCallWithArgs((struct go_string){ - .str = fname_str, - .n = fname_len - }, (struct go_string){ - .str = args_str, - .n = args_len - }); - (*env)->ReleaseStringUTFChars(env, fname, fname_str); - if (!d) { - return NULL; - } - ret = (*env)->NewStringUTF(env, d); - free(d); - return ret; -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/libooniprobe-android.go b/composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/libooniprobe-android.go deleted file mode 100644 index 62a59036..00000000 --- a/composeApp/src/androidMain/kotlin/org/ooni/libooniprobe-android/libooniprobe-android.go +++ /dev/null @@ -1,150 +0,0 @@ -package main - -// #cgo LDFLAGS: -llog -// #include - -import "C" -import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" -) - -/** BEGIN API section **/ -// In reality the code in here should live inside of probe-cli -// It represents the API contract that exists between OONI Probe CLI and the apps. - -// pkg/ooniprobe/api/ -func DoHTTPRequest(url string, retryCount int) (string, error) { - fmt.Printf("we don't actually implement %d retries\n", retryCount) - response, err := http.Get(url) - if err != nil { - return "", fmt.Errorf("failed to perform request: %v", err) - } - defer response.Body.Close() - - resp, err := io.ReadAll(response.Body) - if err != nil { - return "", fmt.Errorf("failed to read response: %v", err) - } - - return string(resp), nil -} - -func GetPublicIP() (string, error) { - return DoHTTPRequest("https://api.ipify.org", 1) -} - -// pkg/ooniprobe/mobileapi/ -var ErrUnknownFuncName = errors.New("invalid function name") - -// Any represents a value of any type. -type Any interface{} - -// ParseAnyList parses a JSON string into a list of Any. -func ParseAnyList(s string) ([]Any, error) { - var data []Any - err := json.Unmarshal([]byte(s), &data) - if err != nil { - return nil, err - } - return data, nil -} - -type API struct { -} - -func (a API) Init() { - // Do any initialization needed to get the API running -} - -func (a API) Call(funcName string) (string, error) { - switch funcName { - case "GetPublicIP": - return GetPublicIP() - default: - return "", ErrUnknownFuncName - } -} - -type returnValueHTTPResponse struct { - Body string `json:"body"` -} - -func (a API) CallWithArgs(funcName string, args []Any) (string, error) { - switch funcName { - case "DoHTTPRequest": - if len(args) != 2 { - return "", errors.New("DoHTTPRequest takes exactly 2 arguments") - } - - url, ok := args[0].(string) - if !ok { - return "", errors.New("DoHTTPRequest: args[0](name) must be a string") - } - - retryCount, ok := args[1].(float64) - if !ok { - return "", errors.New("DoHTTPRequest: args[1](count) must be a number") - } - body, err := DoHTTPRequest(url, int(retryCount)) - if err != nil { - return "", err - } - - ret, err := json.Marshal(returnValueHTTPResponse{Body: body}) - return string(ret), err - default: - return "", fmt.Errorf("%s: %v", funcName, ErrUnknownFuncName) - } -} - -/** END API section **/ - -var api API - -type ReturnValue struct { - Value interface{} `json:"return_value"` - Err interface{} `json:"error"` -} - -func SerializeReturnValue(v string, err error) *C.char { - var errVal interface{} - if err != nil { - errVal = fmt.Sprintf("%v", err) - } - rv := ReturnValue{ - Value: v, - Err: errVal, - } - b, err := json.Marshal(rv) - if err != nil { - return nil - } - return C.CString(string(b)) -} - -func init() { - api = API{} - api.Init() -} - -//export apiCall -func apiCall(funcName string) *C.char { - rv, err := api.Call(funcName) - return SerializeReturnValue(rv, err) -} - -//export apiCallWithArgs -func apiCallWithArgs(funcName string, argsJSON string) *C.char { - args, err := ParseAnyList(argsJSON) - if err != nil { - return SerializeReturnValue("", err) - } - rv, err := api.CallWithArgs(funcName, args) - return SerializeReturnValue(rv, err) -} - -func main() {} diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/AndroidApplication.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/AndroidApplication.kt new file mode 100644 index 00000000..e1a28d8b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/AndroidApplication.kt @@ -0,0 +1,5 @@ +package org.ooni.probe + +import android.app.Application + +class AndroidApplication : Application() \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt deleted file mode 100644 index 9483a5f1..00000000 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/Application.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.ooni.probe - -import io.github.aakira.napier.DebugAntilog -import io.github.aakira.napier.Napier - -class Application : android.app.Application() { - override fun onCreate() { - super.onCreate() - Napier.base(DebugAntilog()) - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/GoOONIProbeClient.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/GoOONIProbeClient.kt deleted file mode 100644 index c8d58227..00000000 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/GoOONIProbeClient.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.ooni.probe - -import android.content.Context -import io.github.aakira.napier.Napier - -class GoOONIProbeClient(context: Context) { - init { - Napier.d("loading ooniprobe in appContext=$context") - // System.loadLibrary() is the most basic shared library loading strategy. - // I have seen code like the one in Wireguard make use of many more: - // https://github.com/WireGuard/wireguard-android/blob/master/tunnel/src/main/java/com/wireguard/android/util/SharedLibraryLoader.java - // TODO: do testing of this and unnderstand if these strategies are needed or if it only - // applies to older versions of android and/or loading happening inside of a VPNService - System.loadLibrary("ooniprobe") - } - companion object { - @JvmStatic private external fun apiCall(funcName: String): String - @JvmStatic private external fun apiCallWithArgs(funcName: String, args : String): String - } - fun call(funcName: String) : String{ - return apiCall(funcName) - } - fun callWithArgs(funcName: String, args : String) : String{ - return apiCallWithArgs(funcName, args) - } - -} diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt index 6547b885..d3fbad0b 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt @@ -1,28 +1,17 @@ package org.ooni.probe -import App import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import di.Dependencies -import platform.GoOONIProbeClientBridge +import org.ooni.engine.AndroidOoniEngine +import org.ooni.probe.di.Dependencies class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val client = GoOONIProbeClient(this) - val dependencies = Dependencies(object : GoOONIProbeClientBridge { - override fun apiCall(funcName: String): String { - return client.call(funcName) - } - - override fun apiCallWithArgs(funcName: String, args: String): String { - return client.callWithArgs(funcName, args) - } - }) + val engine = AndroidOoniEngine() + val dependencies = Dependencies(engine) setContent { App(dependencies) diff --git a/composeApp/src/commonMain/kotlin/di/Dependencies.kt b/composeApp/src/commonMain/kotlin/di/Dependencies.kt deleted file mode 100644 index 2e509140..00000000 --- a/composeApp/src/commonMain/kotlin/di/Dependencies.kt +++ /dev/null @@ -1,12 +0,0 @@ -package di - -import platform.GoOONIProbeClientBridge -import ui.main.MainViewModel - -class Dependencies( - private val goOONIProbeClientBridge: GoOONIProbeClientBridge -) { - - val mainViewModel by lazy { MainViewModel(goOONIProbeClientBridge) } - -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt new file mode 100644 index 00000000..9b2e9b32 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt @@ -0,0 +1,5 @@ +package org.ooni.engine + +interface OoniEngine { + fun newUUID4(): String +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt similarity index 81% rename from composeApp/src/commonMain/kotlin/App.kt rename to composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt index dc1f4996..d28fc18c 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt @@ -1,12 +1,14 @@ +package org.ooni.probe + import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.* import androidx.compose.ui.Modifier -import di.Dependencies +import org.ooni.probe.di.Dependencies import org.jetbrains.compose.ui.tooling.preview.Preview -import ui.Theme -import ui.main.MainScreen +import org.ooni.probe.ui.Theme +import org.ooni.probe.ui.main.MainScreen @Composable @Preview diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt new file mode 100644 index 00000000..e8bf71e1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt @@ -0,0 +1,12 @@ +package org.ooni.probe.di + +import org.ooni.engine.OoniEngine +import org.ooni.probe.ui.main.MainViewModel + +class Dependencies( + private val engine: OoniEngine +) { + + val mainViewModel by lazy { MainViewModel(engine) } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/Theme.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/Theme.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/ui/Theme.kt rename to composeApp/src/commonMain/kotlin/org/ooni/probe/ui/Theme.kt index b4bad519..aabcb76d 100644 --- a/composeApp/src/commonMain/kotlin/ui/Theme.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/Theme.kt @@ -1,4 +1,4 @@ -package ui +package org.ooni.probe.ui import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.shape.RoundedCornerShape diff --git a/composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt rename to composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt index 96e2e46d..289cda35 100644 --- a/composeApp/src/commonMain/kotlin/ui/main/MainScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt @@ -1,4 +1,4 @@ -package ui.main +package org.ooni.probe.ui.main import androidx.compose.material.Text import androidx.compose.runtime.Composable diff --git a/composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt similarity index 68% rename from composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt rename to composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt index 5c87be7b..fbe2aa93 100644 --- a/composeApp/src/commonMain/kotlin/ui/main/MainViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt @@ -1,4 +1,4 @@ -package ui.main +package org.ooni.probe.ui.main import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -6,17 +6,17 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import platform.GoOONIProbeClientBridge +import org.ooni.engine.OoniEngine class MainViewModel( - private val goOONIProbeClientBridge: GoOONIProbeClientBridge + private val engine: OoniEngine ) { private val _result = MutableStateFlow(null) val result = _result.asStateFlow() init { CoroutineScope(Dispatchers.IO).launch { - _result.value = goOONIProbeClientBridge.apiCall("GetPublicIP") + _result.value = engine.newUUID4() } } } diff --git a/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt b/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt deleted file mode 100644 index 7b0e9a67..00000000 --- a/composeApp/src/commonMain/kotlin/platform/GoOONIProbeClientBridge.kt +++ /dev/null @@ -1,6 +0,0 @@ -package platform - -interface GoOONIProbeClientBridge { - fun apiCall(funcName: String): String - fun apiCallWithArgs(funcName: String, args : String): String -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt new file mode 100644 index 00000000..fa09692f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt @@ -0,0 +1,7 @@ +package org.ooni.engine + +class DesktopOoniEngine : OoniEngine { + override fun newUUID4(): String { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/main.kt b/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt similarity index 70% rename from composeApp/src/desktopMain/kotlin/main.kt rename to composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt index 7ea0df28..ad609ed2 100644 --- a/composeApp/src/desktopMain/kotlin/main.kt +++ b/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt @@ -1,7 +1,9 @@ +package org.ooni.probe + import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import di.Dependencies -import platform.DesktopOONIProbeClientBridge +import org.ooni.probe.di.Dependencies +import org.ooni.platform.DesktopOONIProbeClientBridge fun main() = application { Window( diff --git a/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt b/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt deleted file mode 100644 index 92e12d92..00000000 --- a/composeApp/src/desktopMain/kotlin/platform/DesktopOONIProbeClientBridge.kt +++ /dev/null @@ -1,6 +0,0 @@ -package platform - -class DesktopOONIProbeClientBridge : GoOONIProbeClientBridge { - override fun apiCall(funcName: String) = "Desktop Result" - override fun apiCallWithArgs(funcName: String, args : String) = "" -} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt deleted file mode 100644 index 7b715a05..00000000 --- a/composeApp/src/iosMain/kotlin/MainViewController.kt +++ /dev/null @@ -1,5 +0,0 @@ -import androidx.compose.ui.window.ComposeUIViewController -import di.Dependencies -import platform.IosOONIProbeClientBridge - -fun MainViewController() = ComposeUIViewController { App(Dependencies(IosOONIProbeClientBridge())) } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt b/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt new file mode 100644 index 00000000..573d9f13 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt @@ -0,0 +1,8 @@ +package org.ooni.probe + +import androidx.compose.ui.window.ComposeUIViewController +import org.ooni.engine.OoniEngine +import org.ooni.probe.di.Dependencies + +fun MainViewController(ooniEngine: OoniEngine) = + ComposeUIViewController { App(Dependencies(ooniEngine)) } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt b/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt deleted file mode 100644 index 140da865..00000000 --- a/composeApp/src/iosMain/kotlin/platform/IosOONIProbeClientBridge.kt +++ /dev/null @@ -1,6 +0,0 @@ -package platform - -class IosOONIProbeClientBridge : GoOONIProbeClientBridge { - override fun apiCall(funcName: String) = "ios result" - override fun apiCallWithArgs(funcName: String, args : String) = "" -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4db653ed..a60d5b20 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,36 +1,24 @@ [versions] agp = "8.2.0" + android-compileSdk = "34" android-minSdk = "24" android-targetSdk = "34" + androidx-activityCompose = "1.9.0" -androidx-appcompat = "1.6.1" -androidx-constraintlayout = "2.1.4" -androidx-core-ktx = "1.13.0" -androidx-espresso-core = "3.5.1" -androidx-material = "1.11.0" -androidx-test-junit = "1.1.5" compose = "1.6.6" compose-plugin = "1.6.2" -junit = "4.13.2" kotlin = "1.9.23" [libraries] -kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } -kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } -junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" } -androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" } -androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" } -androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } +android-oonimkall = { module = "org.ooni:oonimkall", version = "2024.05.22-092559" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } \ No newline at end of file diff --git a/iosApp/Podfile b/iosApp/Podfile new file mode 100644 index 00000000..f3cd2a5a --- /dev/null +++ b/iosApp/Podfile @@ -0,0 +1,16 @@ +platform :ios, '9.0' +use_frameworks! + +target 'iosApp' do + pod 'composeApp', :path => '../composeApp' + + ooni_version = "v3.22.0" + ooni_pods_location = "https://github.com/ooni/probe-cli/releases/download/#{ooni_version}" + + pod "libcrypto", :podspec => "#{ooni_pods_location}/libcrypto.podspec" + pod "libevent", :podspec => "#{ooni_pods_location}/libevent.podspec" + pod "libssl", :podspec => "#{ooni_pods_location}/libssl.podspec" + pod "libtor", :podspec => "#{ooni_pods_location}/libtor.podspec" + pod "libz", :podspec => "#{ooni_pods_location}/libz.podspec" + pod "oonimkall", :podspec => "#{ooni_pods_location}/oonimkall.podspec" +end \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 48efd976..417ef747 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -11,16 +11,22 @@ 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; + 90C537899E1A531A92327A33 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3690801A53F73052EF6710A7 /* Pods_iosApp.framework */; }; + 93E977712C4FCCE3009CCABC /* IosOoniEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 3690801A53F73052EF6710A7 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4C317D1F5DE6C99A874555F9 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; 7555FF7B242A565900829871 /* OONI Probe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OONI Probe.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosOoniEngine.swift; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + B24B7C9C049ADDADBB7C8DCE /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -28,6 +34,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 90C537899E1A531A92327A33 /* Pods_iosApp.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -42,9 +49,19 @@ path = "Preview Content"; sourceTree = ""; }; + 20AA0919DAC14F9E057F989A /* Pods */ = { + isa = PBXGroup; + children = ( + B24B7C9C049ADDADBB7C8DCE /* Pods-iosApp.debug.xcconfig */, + 4C317D1F5DE6C99A874555F9 /* Pods-iosApp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; 42799AB246E5F90AF97AA0EF /* Frameworks */ = { isa = PBXGroup; children = ( + 3690801A53F73052EF6710A7 /* Pods_iosApp.framework */, ); name = Frameworks; sourceTree = ""; @@ -56,6 +73,7 @@ 7555FF7D242A565900829871 /* iosApp */, 7555FF7C242A565900829871 /* Products */, 42799AB246E5F90AF97AA0EF /* Frameworks */, + 20AA0919DAC14F9E057F989A /* Pods */, ); sourceTree = ""; }; @@ -75,10 +93,19 @@ 7555FF8C242A565B00829871 /* Info.plist */, 2152FB032600AC8F00CF470E /* iOSApp.swift */, 058557D7273AAEEB004C7B11 /* Preview Content */, + 93E977722C4FCCF7009CCABC /* engine */, ); path = iosApp; sourceTree = ""; }; + 93E977722C4FCCF7009CCABC /* engine */ = { + isa = PBXGroup; + children = ( + 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */, + ); + path = engine; + sourceTree = ""; + }; AB1DB47929225F7C00F7AF9C /* Configuration */ = { isa = PBXGroup; children = ( @@ -94,18 +121,20 @@ isa = PBXNativeTarget; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( + 2221CAC0E44786DF6A5501B8 /* [CP] Check Pods Manifest.lock */, F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, 7555FF77242A565900829871 /* Sources */, B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, + 93E977732C4FE022009CCABC /* ShellScript */, + 933CE5066A20C80A0F8ABE37 /* [CP] Embed Pods Frameworks */, + CF75C08D9F48FE42E3A941B8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = iosApp; - packageProductDependencies = ( - ); productName = iosApp; productReference = 7555FF7B242A565900829871 /* OONI Probe.app */; productType = "com.apple.product-type.application"; @@ -134,8 +163,6 @@ Base, ); mainGroup = 7555FF72242A565900829871; - packageReferences = ( - ); productRefGroup = 7555FF7C242A565900829871 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -158,6 +185,79 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 2221CAC0E44786DF6A5501B8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 933CE5066A20C80A0F8ABE37 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 93E977732C4FE022009CCABC /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/..\"\n./gradlew embedAndSignAppleFrameworkForXcode\n"; + }; + CF75C08D9F48FE42E3A941B8 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -184,6 +284,7 @@ buildActionMask = 2147483647; files = ( 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 93E977712C4FCCE3009CCABC /* IosOoniEngine.swift in Sources */, 7555FF83242A565900829871 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -311,6 +412,7 @@ }; 7555FFA6242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = B24B7C9C049ADDADBB7C8DCE /* Pods-iosApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; @@ -319,7 +421,9 @@ DEVELOPMENT_TEAM = "${TEAM_ID}"; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(inherited)", ); INFOPLIST_FILE = iosApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.3; @@ -342,6 +446,7 @@ }; 7555FFA7242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4C317D1F5DE6C99A874555F9 /* Pods-iosApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; @@ -350,7 +455,9 @@ DEVELOPMENT_TEAM = "${TEAM_ID}"; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( - "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + "$(inherited)", ); INFOPLIST_FILE = iosApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.3; @@ -395,4 +502,4 @@ /* End XCConfigurationList section */ }; rootObject = 7555FF73242A565900829871 /* Project object */; -} \ No newline at end of file +} diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 3cd5c325..32e7379a 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -4,7 +4,7 @@ import ComposeApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { - MainViewControllerKt.MainViewController() + MainViewControllerKt.MainViewController(ooniEngine: IosOoniEngine()) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} diff --git a/iosApp/iosApp/engine/IosOoniEngine.swift b/iosApp/iosApp/engine/IosOoniEngine.swift new file mode 100644 index 00000000..14fb7867 --- /dev/null +++ b/iosApp/iosApp/engine/IosOoniEngine.swift @@ -0,0 +1,8 @@ +import ComposeApp +import Oonimkall + +class IosOoniEngine : OoniEngine { + func doNewUUID4() -> String { + return OonimkallNewUUID4() + } +} From a3ed3f7aff319b00a3b033737c483fd720ee2c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 23 Jul 2024 17:34:47 +0100 Subject: [PATCH 3/6] Remove default framework config in favor of a cocoapod framework Remove pods from git --- .gitignore | 1 + composeApp/build.gradle.kts | 16 ++---- composeApp/composeApp.podspec | 50 +++++++++++++++++++ iosApp/Podfile.lock | 46 +++++++++++++++++ iosApp/iosApp.xcodeproj/project.pbxproj | 18 ------- .../contents.xcworkspacedata | 10 ++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 +++ iosApp/iosApp/ContentView.swift | 2 +- iosApp/iosApp/engine/IosOoniEngine.swift | 2 +- 9 files changed, 122 insertions(+), 31 deletions(-) create mode 100644 composeApp/composeApp.podspec create mode 100644 iosApp/Podfile.lock create mode 100644 iosApp/iosApp.xcworkspace/contents.xcworkspacedata create mode 100644 iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.gitignore b/.gitignore index 33e4c75d..450ac4df 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ captures !*.xcodeproj/project.xcworkspace/ !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings +/iosApp/Pods/ diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8e4990ec..8b696b05 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -17,17 +17,10 @@ kotlin { } jvm("desktop") - - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { iosTarget -> - iosTarget.binaries.framework { - baseName = "ComposeApp" - isStatic = true - } - } + + iosX64() + iosArm64() + iosSimulatorArm64() cocoapods { ios.deploymentTarget = "9.0" @@ -38,6 +31,7 @@ kotlin { framework { baseName = "composeApp" + isStatic = true } podfile = project.file("../iosApp/Podfile") diff --git a/composeApp/composeApp.podspec b/composeApp/composeApp.podspec new file mode 100644 index 00000000..3a69cea5 --- /dev/null +++ b/composeApp/composeApp.podspec @@ -0,0 +1,50 @@ +Pod::Spec.new do |spec| + spec.name = 'composeApp' + spec.version = '1.0' + spec.homepage = 'https://github.com/ooni/probe-multiplatform' + spec.source = { :http=> ''} + spec.authors = '' + spec.license = '' + spec.summary = 'Compose App' + spec.vendored_frameworks = 'build/cocoapods/framework/composeApp.framework' + spec.libraries = 'c++' + spec.ios.deployment_target = '9.0' + + + if !Dir.exist?('build/cocoapods/framework/composeApp.framework') || Dir.empty?('build/cocoapods/framework/composeApp.framework') + raise " + + Kotlin framework 'composeApp' doesn't exist yet, so a proper Xcode project can't be generated. + 'pod install' should be executed after running ':generateDummyFramework' Gradle task: + + ./gradlew :composeApp:generateDummyFramework + + Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" + end + + spec.pod_target_xcconfig = { + 'KOTLIN_PROJECT_PATH' => ':composeApp', + 'PRODUCT_MODULE_NAME' => 'composeApp', + } + + spec.script_phases = [ + { + :name => 'Build composeApp', + :execution_position => :before_compile, + :shell_path => '/bin/sh', + :script => <<-SCRIPT + if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then + echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"" + exit 0 + fi + set -ev + REPO_ROOT="$PODS_TARGET_SRCROOT" + "$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \ + -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \ + -Pkotlin.native.cocoapods.archs="$ARCHS" \ + -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" + SCRIPT + } + ] + spec.resources = ['build/compose/ios/composeApp/compose-resources'] +end \ No newline at end of file diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock new file mode 100644 index 00000000..8e7dce58 --- /dev/null +++ b/iosApp/Podfile.lock @@ -0,0 +1,46 @@ +PODS: + - composeApp (1.0) + - libcrypto (2024.05.22-093305) + - libevent (2024.05.22-093305) + - libssl (2024.05.22-093305) + - libtor (2024.05.22-093305) + - libz (2024.05.22-093305) + - oonimkall (2024.05.22-093305) + +DEPENDENCIES: + - composeApp (from `../composeApp`) + - libcrypto (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/libcrypto.podspec`) + - libevent (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/libevent.podspec`) + - libssl (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/libssl.podspec`) + - libtor (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/libtor.podspec`) + - libz (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/libz.podspec`) + - oonimkall (from `https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec`) + +EXTERNAL SOURCES: + composeApp: + :path: "../composeApp" + libcrypto: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libcrypto.podspec + libevent: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libevent.podspec + libssl: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libssl.podspec + libtor: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libtor.podspec + libz: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libz.podspec + oonimkall: + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec + +SPEC CHECKSUMS: + composeApp: ce88c37f18ab45a4cfc6b1835b91f60e5cd142cd + libcrypto: 1bb58600c586e28688f5578f4675f5ffa46c8eaf + libevent: 5c8502ca5cc38be31bb510ddade0f238bcc5f0dc + libssl: 170bebcaf567a0285e91a8850b9686137d07c3e1 + libtor: c72b23da6a5d2e16173149784f11cf66156c35be + libz: 83658eb2a0db785623ffdf9ce13407e6b8b5c8f9 + oonimkall: 9768ce9dad18265d45d2ea972c84fb0bd5237cc3 + +PODFILE CHECKSUM: 9f3463701e9fb15f3dded022f7afabede102208e + +COCOAPODS: 1.15.2 diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 417ef747..22781505 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -127,7 +127,6 @@ B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, 93E977732C4FE022009CCABC /* ShellScript */, - 933CE5066A20C80A0F8ABE37 /* [CP] Embed Pods Frameworks */, CF75C08D9F48FE42E3A941B8 /* [CP] Copy Pods Resources */, ); buildRules = ( @@ -207,23 +206,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 933CE5066A20C80A0F8ABE37 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 93E977732C4FE022009CCABC /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..c009e7d7 --- /dev/null +++ b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/iosApp/iosApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 32e7379a..210aea0d 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -1,6 +1,6 @@ import UIKit import SwiftUI -import ComposeApp +import composeApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { diff --git a/iosApp/iosApp/engine/IosOoniEngine.swift b/iosApp/iosApp/engine/IosOoniEngine.swift index 14fb7867..5814a36c 100644 --- a/iosApp/iosApp/engine/IosOoniEngine.swift +++ b/iosApp/iosApp/engine/IosOoniEngine.swift @@ -1,4 +1,4 @@ -import ComposeApp +import composeApp import Oonimkall class IosOoniEngine : OoniEngine { From 3722f51e5aad34249458d4aad5ff3fbd82c8fe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 24 Jul 2024 14:51:22 +0100 Subject: [PATCH 4/6] Run engine task POC --- composeApp/build.gradle.kts | 2 + composeApp/composeApp.podspec | 2 +- .../org/ooni/engine/AndroidOoniEngine.kt | 7 -- .../org/ooni/engine/AndroidOonimkallBridge.kt | 16 +++++ .../kotlin/org/ooni/probe/MainActivity.kt | 6 +- .../kotlin/org/ooni/engine/Engine.kt | 70 +++++++++++++++++++ .../kotlin/org/ooni/engine/EventResult.kt | 29 ++++++++ .../kotlin/org/ooni/engine/OoniEngine.kt | 5 -- .../kotlin/org/ooni/engine/OonimkallBridge.kt | 11 +++ .../kotlin/org/ooni/engine/TaskEvent.kt | 27 +++++++ .../kotlin/org/ooni/engine/TaskSettings.kt | 24 +++++++ .../kotlin/org/ooni/probe/di/Dependencies.kt | 16 ++++- .../org/ooni/probe/ui/main/MainScreen.kt | 25 ++++++- .../org/ooni/probe/ui/main/MainViewModel.kt | 64 ++++++++++++++--- .../org/ooni/engine/DesktopOoniEngine.kt | 7 -- .../org/ooni/engine/DesktopOonimkallBridge.kt | 7 ++ .../desktopMain/kotlin/org/ooni/probe/main.kt | 4 +- .../org/ooni/probe/MainViewController.kt | 7 +- gradle/libs.versions.toml | 6 +- iosApp/Podfile.lock | 2 +- iosApp/iosApp.xcodeproj/project.pbxproj | 26 ++----- iosApp/iosApp/ContentView.swift | 2 +- iosApp/iosApp/engine/IosOoniEngine.swift | 8 --- iosApp/iosApp/engine/IosOonimkallBridge.swift | 19 +++++ 24 files changed, 318 insertions(+), 74 deletions(-) delete mode 100644 composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOoniEngine.kt create mode 100644 composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/EventResult.kt delete mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEvent.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/engine/TaskSettings.kt delete mode 100644 composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt create mode 100644 composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt delete mode 100644 iosApp/iosApp/engine/IosOoniEngine.swift create mode 100644 iosApp/iosApp/engine/IosOonimkallBridge.swift diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8b696b05..fa5951eb 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsCompose) alias(libs.plugins.cocoapods) + alias(libs.plugins.kotlinSerialization) } kotlin { @@ -53,6 +54,7 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + implementation(libs.kotlin.serialization) } desktopMain.dependencies { implementation(compose.desktop.currentOs) diff --git a/composeApp/composeApp.podspec b/composeApp/composeApp.podspec index 3a69cea5..a1a0c555 100644 --- a/composeApp/composeApp.podspec +++ b/composeApp/composeApp.podspec @@ -46,5 +46,5 @@ Pod::Spec.new do |spec| SCRIPT } ] - spec.resources = ['build/compose/ios/composeApp/compose-resources'] + spec.resources = ['build/compose/cocoapods/compose-resources'] end \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOoniEngine.kt b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOoniEngine.kt deleted file mode 100644 index d3d21202..00000000 --- a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOoniEngine.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.ooni.engine - -class AndroidOoniEngine : OoniEngine { - override fun newUUID4(): String { - return oonimkall.Oonimkall.newUUID4() - } -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt new file mode 100644 index 00000000..1d3f9d41 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt @@ -0,0 +1,16 @@ +package org.ooni.engine + +class AndroidOonimkallBridge : OonimkallBridge { + override fun startTask(settingsSerialized: String): OonimkallBridge.Task { + val task = oonimkall.Oonimkall.startTask(settingsSerialized) + return object : OonimkallBridge.Task { + override fun interrupt() { + task.interrupt() + } + + override fun isDone(): Boolean = task.isDone + + override fun waitForNextEvent() = task.waitForNextEvent() + } + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt index d3fbad0b..5034f72c 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt @@ -3,15 +3,15 @@ package org.ooni.probe import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import org.ooni.engine.AndroidOoniEngine +import org.ooni.engine.AndroidOonimkallBridge import org.ooni.probe.di.Dependencies class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val engine = AndroidOoniEngine() - val dependencies = Dependencies(engine) + val bridge = AndroidOonimkallBridge() + val dependencies = Dependencies(bridge, filesDir.absolutePath) setContent { App(dependencies) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt new file mode 100644 index 00000000..e3acc575 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -0,0 +1,70 @@ +package org.ooni.engine + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import kotlin.math.roundToInt + +class Engine( + private val bridge: OonimkallBridge, + private val json: Json, + private val baseFilePath: String +) { + + fun startTask(taskSettings: TaskSettings): Flow = channelFlow { + val finalSettings = taskSettings.copy( + stateDir = baseFilePath, + tunnelDir = baseFilePath, + tempDir = baseFilePath, + assetsDir = baseFilePath, + ) + + val task = bridge.startTask(json.encodeToString(finalSettings)) + + while (!task.isDone()) { + val eventJson = task.waitForNextEvent() + val eventResult = json.decodeFromString(eventJson) + eventResult.toTaskEvent()?.let { send(it) } + } + + invokeOnClose { + if (it is CancellationException) { + task.interrupt() + } + } + } + + private fun EventResult.toTaskEvent(): TaskEvent? = + when (key) { + "status.started" -> TaskEvent.Started + + "status.end" -> TaskEvent.StatusEnd + + "status.progress" -> + value?.percentage?.let { percentageValue -> + TaskEvent.Progress( + percentage = (percentageValue * 100.0).roundToInt(), + message = value?.message + ) + } + + "log" -> value?.message?.let { message -> + TaskEvent.Log( + level = value?.log_level, + message = message + ) + } + + "status.report_create" -> value?.report_id?.let { + TaskEvent.ReportCreate(reportId = it) + } + + "task_terminated" -> TaskEvent.TaskTerminated + + "failure.startup" -> TaskEvent.FailureStartup(message = value?.failure) + + else -> null + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/EventResult.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/EventResult.kt new file mode 100644 index 00000000..e48ca6a6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/EventResult.kt @@ -0,0 +1,29 @@ +package org.ooni.engine + +import kotlinx.serialization.Serializable + +@Serializable +class EventResult { + var key: String? = null + var value: Value? = null + + @Serializable + class Value { + var key: Double = 0.0 + var log_level: String? = null + var message: String? = null + var percentage: Double = 0.0 + var json_str: String? = null + var idx: Int = 0 + var report_id: String? = null + var probe_ip: String? = null + var probe_asn: String? = null + var probe_cc: String? = null + var probe_network_name: String? = null + var downloaded_kb: Double = 0.0 + var uploaded_kb: Double = 0.0 + var input: String? = null + var failure: String? = null + var orig_key: String? = null + } +} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt deleted file mode 100644 index 9b2e9b32..00000000 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/OoniEngine.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.ooni.engine - -interface OoniEngine { - fun newUUID4(): String -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt new file mode 100644 index 00000000..c498c54f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt @@ -0,0 +1,11 @@ +package org.ooni.engine + +interface OonimkallBridge { + fun startTask(settingsSerialized: String): Task + + interface Task { + fun interrupt() + fun isDone(): Boolean + fun waitForNextEvent(): String + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEvent.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEvent.kt new file mode 100644 index 00000000..b4a8c210 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEvent.kt @@ -0,0 +1,27 @@ +package org.ooni.engine + +sealed interface TaskEvent { + data class Log( + val level: String?, + val message: String + ): TaskEvent + + data object Started : TaskEvent + + data class ReportCreate( + val reportId: String + ) : TaskEvent + + data class Progress( + val percentage: Int, + val message: String? + ): TaskEvent + + data object StatusEnd : TaskEvent + + data object TaskTerminated : TaskEvent + + data class FailureStartup( + val message: String? + ): TaskEvent +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskSettings.kt new file mode 100644 index 00000000..5b250811 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskSettings.kt @@ -0,0 +1,24 @@ +package org.ooni.engine + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TaskSettings( + @SerialName("name") val name: String, + @SerialName("inputs") val inputs: List, + @SerialName("version") val version: Int = 1, + @SerialName("log_level") val logLevel: String, + @SerialName("state_dir") val stateDir: String? = null, + @SerialName("temp_dir") val tempDir: String? = null, + @SerialName("tunnel_dir") val tunnelDir: String? = null, + @SerialName("assets_dir") val assetsDir: String? = null, + @SerialName("options") val options: Options = Options() +) { + @Serializable + data class Options( + @SerialName("no_collector") val noCollector: Boolean = true, + @SerialName("software_name") val softwareName: String = "Probe Multiplatform", + @SerialName("software_version") val softwareVersion: String = "1.0" + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt index e8bf71e1..65759bca 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt @@ -1,12 +1,24 @@ package org.ooni.probe.di -import org.ooni.engine.OoniEngine +import kotlinx.serialization.json.Json +import org.ooni.engine.Engine +import org.ooni.engine.OonimkallBridge import org.ooni.probe.ui.main.MainViewModel class Dependencies( - private val engine: OoniEngine + private val oonimkallBridge: OonimkallBridge, + private val baseFileDir: String, ) { + private val json by lazy { + Json { + encodeDefaults = true + ignoreUnknownKeys = true + } + } + + private val engine by lazy { Engine(oonimkallBridge, json, baseFileDir) } + val mainViewModel by lazy { MainViewModel(engine) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt index 289cda35..753f7a87 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainScreen.kt @@ -1,14 +1,35 @@ package org.ooni.probe.ui.main +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier @Composable fun MainScreen( viewModel: MainViewModel ) { - val result by viewModel.result.collectAsState() - Text(result ?: "Waiting result") + val state by viewModel.state.collectAsState() + + Column { + Button( + onClick = { viewModel.onEvent(MainViewModel.Event.StartClick) }, + enabled = !state.isRunning + ) { + Text("Run Test") + } + + Text( + text = state.log, + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) + } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt index fbe2aa93..643dfb32 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/main/MainViewModel.kt @@ -3,20 +3,68 @@ package org.ooni.probe.ui.main import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import org.ooni.engine.OoniEngine +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import org.ooni.engine.Engine +import org.ooni.engine.TaskSettings class MainViewModel( - private val engine: OoniEngine + private val engine: Engine ) { - private val _result = MutableStateFlow(null) - val result = _result.asStateFlow() + private val events = MutableSharedFlow(extraBufferCapacity = 1) + + private val _state = MutableStateFlow(State()) + val state = _state.asStateFlow() init { - CoroutineScope(Dispatchers.IO).launch { - _result.value = engine.newUUID4() - } + events + .flatMapLatest { event -> + when (event) { + Event.StartClick -> { + if (_state.value.isRunning) return@flatMapLatest emptyFlow() + + _state.value = _state.value.copy(isRunning = true) + + engine.startTask(TASK_SETTINGS) + .onEach { taskEvent -> + _state.update { state -> + state.copy(log = state.log + "\n" + taskEvent) + } + } + .onCompletion { + _state.update { it.copy(isRunning = false) } + } + } + } + } + .launchIn(CoroutineScope(Dispatchers.IO)) + } + + fun onEvent(event: Event) { + events.tryEmit(event) + } + + data class State( + val isRunning: Boolean = false, + val log: String = "" + ) + + sealed interface Event { + data object StartClick : Event + } + + companion object { + val TASK_SETTINGS = TaskSettings( + name = "web_connectivity", + inputs = listOf("https://ooni.org"), + logLevel = "DEBUG2" + ) } } diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt deleted file mode 100644 index fa09692f..00000000 --- a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOoniEngine.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.ooni.engine - -class DesktopOoniEngine : OoniEngine { - override fun newUUID4(): String { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt new file mode 100644 index 00000000..5251310f --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt @@ -0,0 +1,7 @@ +package org.ooni.engine + +class DesktopOonimkallBridge : OonimkallBridge { + override fun startTask(settingsSerialized: String): OonimkallBridge.Task { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt b/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt index ad609ed2..a31528ca 100644 --- a/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt +++ b/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt @@ -2,14 +2,14 @@ package org.ooni.probe import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import org.ooni.engine.DesktopOonimkallBridge import org.ooni.probe.di.Dependencies -import org.ooni.platform.DesktopOONIProbeClientBridge fun main() = application { Window( onCloseRequest = ::exitApplication, title = "OONI Probe", ) { - App(Dependencies(DesktopOONIProbeClientBridge())) + App(Dependencies(DesktopOonimkallBridge(), "")) } } \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt b/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt index 573d9f13..f9477233 100644 --- a/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/org/ooni/probe/MainViewController.kt @@ -1,8 +1,9 @@ package org.ooni.probe import androidx.compose.ui.window.ComposeUIViewController -import org.ooni.engine.OoniEngine +import org.ooni.engine.OonimkallBridge import org.ooni.probe.di.Dependencies +import platform.Foundation.NSTemporaryDirectory -fun MainViewController(ooniEngine: OoniEngine) = - ComposeUIViewController { App(Dependencies(ooniEngine)) } \ No newline at end of file +fun MainViewController(bridge: OonimkallBridge) = + ComposeUIViewController { App(Dependencies(bridge, NSTemporaryDirectory())) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a60d5b20..da948952 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,8 @@ android-minSdk = "24" android-targetSdk = "34" androidx-activityCompose = "1.9.0" -compose = "1.6.6" -compose-plugin = "1.6.2" +compose = "1.6.8" +compose-plugin = "1.6.11" kotlin = "1.9.23" [libraries] @@ -15,10 +15,12 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } android-oonimkall = { module = "org.ooni:oonimkall", version = "2024.05.22-092559" } +kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } \ No newline at end of file diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 8e7dce58..44d1d282 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -33,7 +33,7 @@ EXTERNAL SOURCES: :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec SPEC CHECKSUMS: - composeApp: ce88c37f18ab45a4cfc6b1835b91f60e5cd142cd + composeApp: 10255136935d8b5d8b84cf6a830df29add494f6f libcrypto: 1bb58600c586e28688f5578f4675f5ffa46c8eaf libevent: 5c8502ca5cc38be31bb510ddade0f238bcc5f0dc libssl: 170bebcaf567a0285e91a8850b9686137d07c3e1 diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 22781505..123f806b 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -12,7 +12,7 @@ 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; 90C537899E1A531A92327A33 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3690801A53F73052EF6710A7 /* Pods_iosApp.framework */; }; - 93E977712C4FCCE3009CCABC /* IosOoniEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */; }; + 93E977712C4FCCE3009CCABC /* IosOonimkallBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93E977702C4FCCE3009CCABC /* IosOonimkallBridge.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -24,7 +24,7 @@ 7555FF7B242A565900829871 /* OONI Probe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "OONI Probe.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosOoniEngine.swift; sourceTree = ""; }; + 93E977702C4FCCE3009CCABC /* IosOonimkallBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IosOonimkallBridge.swift; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; B24B7C9C049ADDADBB7C8DCE /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -101,7 +101,7 @@ 93E977722C4FCCF7009CCABC /* engine */ = { isa = PBXGroup; children = ( - 93E977702C4FCCE3009CCABC /* IosOoniEngine.swift */, + 93E977702C4FCCE3009CCABC /* IosOonimkallBridge.swift */, ); path = engine; sourceTree = ""; @@ -127,7 +127,6 @@ B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, 93E977732C4FE022009CCABC /* ShellScript */, - CF75C08D9F48FE42E3A941B8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -223,23 +222,6 @@ shellPath = /bin/sh; shellScript = "cd \"$SRCROOT/..\"\n./gradlew embedAndSignAppleFrameworkForXcode\n"; }; - CF75C08D9F48FE42E3A941B8 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -266,7 +248,7 @@ buildActionMask = 2147483647; files = ( 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, - 93E977712C4FCCE3009CCABC /* IosOoniEngine.swift in Sources */, + 93E977712C4FCCE3009CCABC /* IosOonimkallBridge.swift in Sources */, 7555FF83242A565900829871 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 210aea0d..52eb65b6 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -4,7 +4,7 @@ import composeApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { - MainViewControllerKt.MainViewController(ooniEngine: IosOoniEngine()) + MainViewControllerKt.MainViewController(bridge: IosOonimkallBridge()) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} diff --git a/iosApp/iosApp/engine/IosOoniEngine.swift b/iosApp/iosApp/engine/IosOoniEngine.swift deleted file mode 100644 index 5814a36c..00000000 --- a/iosApp/iosApp/engine/IosOoniEngine.swift +++ /dev/null @@ -1,8 +0,0 @@ -import composeApp -import Oonimkall - -class IosOoniEngine : OoniEngine { - func doNewUUID4() -> String { - return OonimkallNewUUID4() - } -} diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift new file mode 100644 index 00000000..fcad04fc --- /dev/null +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -0,0 +1,19 @@ +import composeApp +import Oonimkall + +class IosOonimkallBridge : OonimkallBridge { + func startTask(settingsSerialized: String) -> OonimkallBridgeTask { + var error: NSError? + let task = OonimkallStartTask(settingsSerialized, &error)! + + class Task : OonimkallBridgeTask { + var task: OonimkallTask + init(task: OonimkallTask) { self.task = task } + func isDone() -> Bool { task.isDone() } + func interrupt() { task.interrupt() } + func waitForNextEvent() -> String { task.waitForNextEvent() } + } + + return Task(task: task) + } +} From 5e41094cd2e0b3db81ffef37620455a2eccf1605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 24 Jul 2024 15:19:11 +0100 Subject: [PATCH 5/6] Upgrade to kotlin 2 and update AGP to max compatible --- build.gradle.kts | 1 + composeApp/build.gradle.kts | 50 ++++++++----------------- composeApp/composeApp.podspec | 6 ++- gradle/libs.versions.toml | 7 ++-- iosApp/Podfile.lock | 2 +- iosApp/iosApp.xcodeproj/project.pbxproj | 18 +++++++++ 6 files changed, 44 insertions(+), 40 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index dfb28d7f..f49eedf2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false alias(libs.plugins.jetbrainsCompose) apply false + alias(libs.plugins.jetbrainsComposeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.cocoapods) apply false } \ No newline at end of file diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index fa5951eb..bca19b67 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,19 +1,21 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidApplication) alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.jetbrainsComposeCompiler) alias(libs.plugins.cocoapods) alias(libs.plugins.kotlinSerialization) } kotlin { androidTarget { - compilations.all { - kotlinOptions { - jvmTarget = "11" - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) } } @@ -61,45 +63,20 @@ kotlin { } } + @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { // Common compiler options applied to all Kotlin source sets freeCompilerArgs.add("-Xexpect-actual-classes") } + composeCompiler { + enableStrongSkippingMode = true + } } android { namespace = "org.ooni.probe" compileSdk = libs.versions.android.compileSdk.get().toInt() - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - sourceSets["main"].res.srcDirs("src/androidMain/res") - sourceSets["main"].resources.srcDirs("src/commonMain/resources") - - buildTypes { - all { - externalNativeBuild { - cmake { - targets("libooniprobe.so") - arguments("-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}") - } - } - } - release { - externalNativeBuild { - cmake { - arguments("-DANDROID_PACKAGE_NAME=${namespace}") - } - } - } - debug { - externalNativeBuild { - cmake { - arguments("-DANDROID_PACKAGE_NAME=${namespace}.debug") - } - } - } - } - defaultConfig { applicationId = "org.ooni.probe" minSdk = libs.versions.android.minSdk.get().toInt() @@ -113,13 +90,16 @@ android { } } buildTypes { + getByName("debug") { + applicationIdSuffix = ".debug" + } getByName("release") { isMinifyEnabled = false } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { debugImplementation(libs.compose.ui.tooling) diff --git a/composeApp/composeApp.podspec b/composeApp/composeApp.podspec index a1a0c555..73870841 100644 --- a/composeApp/composeApp.podspec +++ b/composeApp/composeApp.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |spec| spec.summary = 'Compose App' spec.vendored_frameworks = 'build/cocoapods/framework/composeApp.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '9.0' + spec.ios.deployment_target = '9.0' if !Dir.exist?('build/cocoapods/framework/composeApp.framework') || Dir.empty?('build/cocoapods/framework/composeApp.framework') @@ -22,6 +22,10 @@ Pod::Spec.new do |spec| Alternatively, proper pod installation is performed during Gradle sync in the IDE (if Podfile location is set)" end + spec.xcconfig = { + 'ENABLE_USER_SCRIPT_SANDBOXING' => 'NO', + } + spec.pod_target_xcconfig = { 'KOTLIN_PROJECT_PATH' => ':composeApp', 'PRODUCT_MODULE_NAME' => 'composeApp', diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index da948952..f3a3514b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.2.0" +agp = "8.3.2" # Max compatible version https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#version-compatibility android-compileSdk = "34" android-minSdk = "24" @@ -8,19 +8,20 @@ android-targetSdk = "34" androidx-activityCompose = "1.9.0" compose = "1.6.8" compose-plugin = "1.6.11" -kotlin = "1.9.23" +kotlin = "2.0.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } android-oonimkall = { module = "org.ooni:oonimkall", version = "2024.05.22-092559" } -kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } +kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.7.1" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } +jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } \ No newline at end of file diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 44d1d282..2c88a8c9 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -33,7 +33,7 @@ EXTERNAL SOURCES: :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec SPEC CHECKSUMS: - composeApp: 10255136935d8b5d8b84cf6a830df29add494f6f + composeApp: 3f1f4ca4e070c2c0aa528bb1eb838ffc3860f5c0 libcrypto: 1bb58600c586e28688f5578f4675f5ffa46c8eaf libevent: 5c8502ca5cc38be31bb510ddade0f238bcc5f0dc libssl: 170bebcaf567a0285e91a8850b9686137d07c3e1 diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 123f806b..2562ec72 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -127,6 +127,7 @@ B92378962B6B1156000C7307 /* Frameworks */, 7555FF79242A565900829871 /* Resources */, 93E977732C4FE022009CCABC /* ShellScript */, + 3793390471A4D6FCCF24C27E /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -205,6 +206,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 3793390471A4D6FCCF24C27E /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 93E977732C4FE022009CCABC /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; From 7963efca43f109b8a4f8dc83343e3f0db8066bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 24 Jul 2024 15:28:43 +0100 Subject: [PATCH 6/6] Remove desktop target --- .fleet/receipt.json | 5 ----- .gitignore | 1 + composeApp/build.gradle.kts | 20 ------------------- .../org/ooni/engine/DesktopOonimkallBridge.kt | 7 ------- .../desktopMain/kotlin/org/ooni/probe/main.kt | 15 -------------- 5 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt delete mode 100644 composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt diff --git a/.fleet/receipt.json b/.fleet/receipt.json index f1485118..a76ba042 100644 --- a/.fleet/receipt.json +++ b/.fleet/receipt.json @@ -12,11 +12,6 @@ "ui": [ "compose" ] - }, - "desktop": { - "ui": [ - "compose" - ] } } }, diff --git a/.gitignore b/.gitignore index 450ac4df..a968e342 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ captures !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings /iosApp/Pods/ +.kotlin diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index bca19b67..7df336d5 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -18,8 +17,6 @@ kotlin { jvmTarget.set(JvmTarget.JVM_17) } } - - jvm("desktop") iosX64() iosArm64() @@ -41,8 +38,6 @@ kotlin { } sourceSets { - val desktopMain by getting - androidMain.dependencies { implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) @@ -58,9 +53,6 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.kotlin.serialization) } - desktopMain.dependencies { - implementation(compose.desktop.currentOs) - } } @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -105,15 +97,3 @@ android { debugImplementation(libs.compose.ui.tooling) } } - -compose.desktop { - application { - mainClass = "MainKt" - - nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "org.ooni.probe" - packageVersion = "1.0.0" - } - } -} diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt b/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt deleted file mode 100644 index 5251310f..00000000 --- a/composeApp/src/desktopMain/kotlin/org/ooni/engine/DesktopOonimkallBridge.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.ooni.engine - -class DesktopOonimkallBridge : OonimkallBridge { - override fun startTask(settingsSerialized: String): OonimkallBridge.Task { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt b/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt deleted file mode 100644 index a31528ca..00000000 --- a/composeApp/src/desktopMain/kotlin/org/ooni/probe/main.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.ooni.probe - -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application -import org.ooni.engine.DesktopOonimkallBridge -import org.ooni.probe.di.Dependencies - -fun main() = application { - Window( - onCloseRequest = ::exitApplication, - title = "OONI Probe", - ) { - App(Dependencies(DesktopOonimkallBridge(), "")) - } -} \ No newline at end of file