diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts
index 09cba321..e77fd2a0 100644
--- a/composeApp/build.gradle.kts
+++ b/composeApp/build.gradle.kts
@@ -67,6 +67,7 @@ kotlin {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.android.oonimkall)
+ implementation(libs.android.activity)
}
commonMain.dependencies {
implementation(compose.runtime)
@@ -84,7 +85,10 @@ kotlin {
}
}
all {
- languageSettings.optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
+ languageSettings {
+ optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
+ optIn("androidx.compose.material3.ExperimentalMaterial3Api")
+ }
}
}
diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt
index 8d31aaa9..84d37a28 100644
--- a/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt
+++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/MainActivity.kt
@@ -3,11 +3,13 @@ package org.ooni.probe
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
class MainActivity : ComponentActivity() {
private val app get() = applicationContext as AndroidApplication
override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
App(app.dependencies)
diff --git a/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml
deleted file mode 100644
index c0bcfb28..00000000
--- a/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_dashboard.xml b/composeApp/src/commonMain/composeResources/drawable/ic_dashboard.xml
new file mode 100644
index 00000000..d5dacdbd
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_dashboard.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_history.xml b/composeApp/src/commonMain/composeResources/drawable/ic_history.xml
new file mode 100644
index 00000000..9f0b77f1
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_history.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_settings.xml b/composeApp/src/commonMain/composeResources/drawable/ic_settings.xml
new file mode 100644
index 00000000..109e06f8
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_settings.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml
index 4392043c..3c51645b 100644
--- a/composeApp/src/commonMain/composeResources/values/strings-common.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml
@@ -1,3 +1,7 @@
Run Test
+ Back
+ Dashboard
+ Test Results
+ Settings
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
index 3f115e9a..3cfd2417 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
@@ -12,6 +12,7 @@ import co.touchlab.kermit.Logger
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.ooni.probe.di.Dependencies
import org.ooni.probe.ui.AppTheme
+import org.ooni.probe.ui.navigation.BottomNavigationBar
import org.ooni.probe.ui.navigation.Navigation
@Composable
@@ -24,7 +25,11 @@ fun App(dependencies: Dependencies) {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
- Scaffold {
+ Scaffold(
+ bottomBar = {
+ BottomNavigationBar(navController)
+ },
+ ) {
Navigation(
navController = navController,
dependencies = dependencies,
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/TestResult.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/TestResult.kt
new file mode 100644
index 00000000..e492626f
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/TestResult.kt
@@ -0,0 +1,7 @@
+package org.ooni.probe.data.models
+
+data class TestResult(
+ val id: Id,
+) {
+ data class Id(val value: String)
+}
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 23a7ac45..0eb54d1c 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
@@ -3,8 +3,11 @@ package org.ooni.probe.di
import kotlinx.serialization.json.Json
import org.ooni.engine.Engine
import org.ooni.engine.OonimkallBridge
+import org.ooni.probe.data.models.TestResult
import org.ooni.probe.shared.PlatformInfo
import org.ooni.probe.ui.dashboard.DashboardViewModel
+import org.ooni.probe.ui.result.ResultViewModel
+import org.ooni.probe.ui.results.ResultsViewModel
class Dependencies(
val platformInfo: PlatformInfo,
@@ -12,6 +15,7 @@ class Dependencies(
private val baseFileDir: String,
) {
// Data
+
private val json by lazy {
Json {
encodeDefaults = true
@@ -20,8 +24,17 @@ class Dependencies(
}
// Engine
+
private val engine by lazy { Engine(oonimkallBridge, json, baseFileDir) }
// ViewModels
+
val dashboardViewModel get() = DashboardViewModel(engine)
+
+ fun resultsViewModel(goToResult: (TestResult.Id) -> Unit) = ResultsViewModel(goToResult)
+
+ fun resultViewModel(
+ resultId: TestResult.Id,
+ onBack: () -> Unit,
+ ) = ResultViewModel(resultId, onBack)
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
index c19d58a6..6e80b7fa 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/DashboardScreen.kt
@@ -1,15 +1,11 @@
package org.ooni.probe.ui.dashboard
import androidx.compose.foundation.Image
-import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
@@ -24,43 +20,33 @@ import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.ooni.probe.ui.AppTheme
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DashboardScreen(
state: DashboardViewModel.State,
onEvent: (DashboardViewModel.Event) -> Unit,
) {
- Scaffold(
- topBar = {
- TopAppBar(
- title = {
- Text(stringResource(Res.string.app_name))
- },
- )
- },
- ) { contentPadding ->
+ Column {
+ TopAppBar(
+ title = { Text(stringResource(Res.string.app_name)) },
+ )
- Column(
- modifier = Modifier.padding(contentPadding),
- verticalArrangement = Arrangement.Center,
- ) {
- Button(
- onClick = { onEvent(DashboardViewModel.Event.StartClick) },
- enabled = !state.isRunning,
- ) {
- Text(stringResource(Res.string.run_tests))
- }
+ Image(
+ painterResource(Res.drawable.logo),
+ contentDescription = stringResource(Res.string.app_name),
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ )
- Image(
- painterResource(Res.drawable.logo),
- contentDescription = "OONI Probe Logo",
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
- Text(
- text = state.log,
- modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
- )
+ Button(
+ onClick = { onEvent(DashboardViewModel.Event.StartClick) },
+ enabled = !state.isRunning,
+ ) {
+ Text(stringResource(Res.string.run_tests))
}
+
+ Text(
+ text = state.log,
+ modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
+ )
}
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/BottomNavigationBar.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/BottomNavigationBar.kt
new file mode 100644
index 00000000..dedb9c6c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/BottomNavigationBar.kt
@@ -0,0 +1,81 @@
+package org.ooni.probe.ui.navigation
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.navigation.NavController
+import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.compose.currentBackStackEntryAsState
+import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.dashboard
+import ooniprobe.composeapp.generated.resources.ic_dashboard
+import ooniprobe.composeapp.generated.resources.ic_history
+import ooniprobe.composeapp.generated.resources.ic_settings
+import ooniprobe.composeapp.generated.resources.settings
+import ooniprobe.composeapp.generated.resources.test_results
+import org.jetbrains.compose.resources.painterResource
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+fun BottomNavigationBar(navController: NavController) {
+ val entry by navController.currentBackStackEntryAsState()
+ val currentRoute = entry?.destination?.route ?: return
+
+ // Only show the bottom app on the main screens
+ if (!MAIN_NAVIGATION_SCREENS.map { it.route }.contains(currentRoute)) return
+
+ NavigationBar {
+ MAIN_NAVIGATION_SCREENS.forEach { screen ->
+ NavigationBarItem(
+ icon = {
+ Icon(
+ painterResource(screen.iconRes),
+ contentDescription = stringResource(screen.titleRes),
+ )
+ },
+ label = { Text(stringResource(screen.titleRes)) },
+ selected = currentRoute == screen.route,
+ onClick = {
+ navController.navigate(screen.route) {
+ // Pop up to the start destination of the graph to
+ // avoid building up a large stack of destinations
+ // on the back stack as users select items
+ navController.graph.findStartDestination().route?.let {
+ popUpTo(it) {
+ saveState = true
+ }
+ }
+ // Avoid multiple copies of the same destination when
+ // re-selecting the same item
+ launchSingleTop = true
+ // Restore state when re-selecting a previously selected item
+ restoreState = true
+ }
+ },
+ )
+ }
+ }
+}
+
+private val Screen.titleRes
+ get() =
+ when (this) {
+ Screen.Dashboard -> Res.string.dashboard
+ Screen.Results -> Res.string.test_results
+ Screen.Settings -> Res.string.settings
+ else -> throw IllegalArgumentException("Only main screens allowed in bottom navigation")
+ }
+
+private val Screen.iconRes
+ get() =
+ when (this) {
+ Screen.Dashboard -> Res.drawable.ic_dashboard
+ Screen.Results -> Res.drawable.ic_history
+ Screen.Settings -> Res.drawable.ic_settings
+ else -> throw IllegalArgumentException("Only main screens allowed in bottom navigation")
+ }
+
+private val MAIN_NAVIGATION_SCREENS = listOf(Screen.Dashboard, Screen.Results, Screen.Settings)
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Navigation.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Navigation.kt
index 80911a2b..569b0cd8 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Navigation.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Navigation.kt
@@ -9,8 +9,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
+import org.ooni.probe.data.models.TestResult
import org.ooni.probe.di.Dependencies
import org.ooni.probe.ui.dashboard.DashboardScreen
+import org.ooni.probe.ui.result.ResultScreen
+import org.ooni.probe.ui.results.ResultsScreen
+import org.ooni.probe.ui.settings.SettingsScreen
@Composable
fun Navigation(
@@ -23,12 +27,40 @@ fun Navigation(
modifier = Modifier.fillMaxSize(),
) {
composable(route = Screen.Dashboard.route) {
+ val viewModel = viewModel { dependencies.dashboardViewModel }
+ val state by viewModel.state.collectAsState()
+ DashboardScreen(state, viewModel::onEvent)
+ }
+
+ composable(route = Screen.Results.route) {
val viewModel =
viewModel {
- dependencies.dashboardViewModel
+ dependencies.resultsViewModel(
+ goToResult = { navController.navigate(Screen.Result(it).route) },
+ )
}
val state by viewModel.state.collectAsState()
- DashboardScreen(state, viewModel::onEvent)
+ ResultsScreen(state, viewModel::onEvent)
+ }
+
+ composable(route = Screen.Settings.route) {
+ SettingsScreen()
+ }
+
+ composable(
+ route = Screen.Result.NAV_ROUTE,
+ arguments = Screen.Result.ARGUMENTS,
+ ) { entry ->
+ val resultId = entry.arguments?.getString("resultId") ?: return@composable
+ val viewModel =
+ viewModel {
+ dependencies.resultViewModel(
+ resultId = TestResult.Id(resultId),
+ onBack = { navController.navigateUp() },
+ )
+ }
+ val state by viewModel.state.collectAsState()
+ ResultScreen(state, viewModel::onEvent)
}
}
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Screen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Screen.kt
index aa1f9de5..41e94fe4 100644
--- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Screen.kt
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/Screen.kt
@@ -1,5 +1,9 @@
package org.ooni.probe.ui.navigation
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import org.ooni.probe.data.models.TestResult
+
sealed class Screen(
val route: String,
) {
@@ -8,4 +12,11 @@ sealed class Screen(
data object Results : Screen("results")
data object Settings : Screen("settings")
+
+ data class Result(val resultId: TestResult.Id) : Screen("results/${resultId.value}") {
+ companion object {
+ const val NAV_ROUTE = "results/{resultId}"
+ val ARGUMENTS = listOf(navArgument("resultId") { type = NavType.StringType })
+ }
+ }
}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt
new file mode 100644
index 00000000..a18e4f1c
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt
@@ -0,0 +1,38 @@
+package org.ooni.probe.ui.result
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.back
+import ooniprobe.composeapp.generated.resources.test_results
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+fun ResultScreen(
+ state: ResultViewModel.State,
+ onEvent: (ResultViewModel.Event) -> Unit,
+) {
+ Column {
+ TopAppBar(
+ title = {
+ Text(stringResource(Res.string.test_results))
+ },
+ navigationIcon = {
+ IconButton(onClick = { onEvent(ResultViewModel.Event.BackClicked) }) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringResource(Res.string.back),
+ )
+ }
+ },
+ )
+
+ Text(state.result.id.value)
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt
new file mode 100644
index 00000000..32a30aa1
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt
@@ -0,0 +1,40 @@
+package org.ooni.probe.ui.result
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.ooni.probe.data.models.TestResult
+
+class ResultViewModel(
+ resultId: TestResult.Id,
+ onBack: () -> Unit,
+) : ViewModel() {
+ private val events = MutableSharedFlow(extraBufferCapacity = 1)
+
+ private val _state = MutableStateFlow(State(TestResult(resultId)))
+ val state = _state.asStateFlow()
+
+ init {
+ events
+ .filterIsInstance()
+ .onEach { onBack() }
+ .launchIn(viewModelScope)
+ }
+
+ fun onEvent(event: Event) {
+ events.tryEmit(event)
+ }
+
+ data class State(
+ val result: TestResult,
+ )
+
+ sealed interface Event {
+ data object BackClicked : Event
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt
new file mode 100644
index 00000000..53f706ff
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt
@@ -0,0 +1,30 @@
+package org.ooni.probe.ui.results
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.test_results
+import org.jetbrains.compose.resources.stringResource
+
+@Composable
+fun ResultsScreen(
+ state: ResultsViewModel.State,
+ onEvent: (ResultsViewModel.Event) -> Unit,
+) {
+ Column {
+ TopAppBar(
+ title = {
+ Text(stringResource(Res.string.test_results))
+ },
+ )
+
+ state.results.forEach { result ->
+ Button(onClick = { onEvent(ResultsViewModel.Event.ResultClick(result)) }) {
+ Text(result.id.value)
+ }
+ }
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsViewModel.kt
new file mode 100644
index 00000000..db708d7d
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsViewModel.kt
@@ -0,0 +1,42 @@
+package org.ooni.probe.ui.results
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.ooni.probe.data.models.TestResult
+
+class ResultsViewModel(
+ goToResult: (TestResult.Id) -> Unit,
+) : ViewModel() {
+ private val events = MutableSharedFlow(extraBufferCapacity = 1)
+
+ private val _state =
+ MutableStateFlow(
+ State(results = listOf(TestResult(TestResult.Id("123456")))),
+ )
+ val state = _state.asStateFlow()
+
+ init {
+ events
+ .filterIsInstance()
+ .onEach { goToResult(it.result.id) }
+ .launchIn(viewModelScope)
+ }
+
+ fun onEvent(event: Event) {
+ events.tryEmit(event)
+ }
+
+ data class State(
+ val results: List,
+ )
+
+ sealed interface Event {
+ data class ResultClick(val result: TestResult) : Event
+ }
+}
diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/SettingsScreen.kt
new file mode 100644
index 00000000..a5ff4139
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/SettingsScreen.kt
@@ -0,0 +1,30 @@
+package org.ooni.probe.ui.settings
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import ooniprobe.composeapp.generated.resources.Res
+import ooniprobe.composeapp.generated.resources.settings
+import org.jetbrains.compose.resources.stringResource
+import org.jetbrains.compose.ui.tooling.preview.Preview
+import org.ooni.probe.ui.AppTheme
+
+@Composable
+fun SettingsScreen() {
+ Column {
+ TopAppBar(
+ title = {
+ Text(stringResource(Res.string.settings))
+ },
+ )
+ }
+}
+
+@Preview
+@Composable
+fun SettingsScreenPreview() {
+ AppTheme {
+ SettingsScreen()
+ }
+}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5cfad43e..1b69c83c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -20,6 +20,7 @@ ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.1.1" }
[libraries]
# UI
+android-activity = { module = "androidx.activity:activity-ktx", version = "1.9.1" }
lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.0" }
navigation = { module = "org.jetbrains.androidx.navigation:navigation-compose", version = "2.7.0-alpha07" }
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index bad33e7c..41631f95 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -155,7 +155,7 @@
B92378962B6B1156000C7307 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
93E977732C4FE022009CCABC /* ShellScript */,
- C15EB4D3556522B7CE6B1638 /* [CP] Copy Pods Resources */,
+ F57E468EACCE9B29FB4C68FC /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -176,7 +176,7 @@
79FBD0102C5A70AF004E041C /* Frameworks */,
79FBD0122C5A70AF004E041C /* Resources */,
79FBD0152C5A70AF004E041C /* ShellScript */,
- E83A3E9E7BCDBBDBB4891740 /* [CP] Copy Pods Resources */,
+ 06E00FBBFEA4B57236F98854 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -243,6 +243,23 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
+ 06E00FBBFEA4B57236F98854 /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
2221CAC0E44786DF6A5501B8 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -339,58 +356,41 @@
shellPath = /bin/sh;
shellScript = "cd \"$SRCROOT/..\"\n./gradlew copyBrandingToCommonResources embedAndSignAppleFrameworkForXcode -Porganization=ooni\n";
};
- C15EB4D3556522B7CE6B1638 /* [CP] Copy Pods Resources */ = {
+ F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Copy Pods Resources";
+ inputPaths = (
+ );
+ name = "Compile Kotlin Framework";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources.sh\"\n";
- showEnvVarsInLog = 0;
+ shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew copyBrandingToCommonResources :composeApp:embedAndSignAppleFrameworkForXcode -Porganization=ooni\n";
};
- E83A3E9E7BCDBBDBB4891740 /* [CP] Copy Pods Resources */ = {
+ F57E468EACCE9B29FB4C68FC /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-NewsMediaScan/Pods-NewsMediaScan-resources.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OONIProbe/Pods-OONIProbe-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "Compile Kotlin Framework";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew copyBrandingToCommonResources :composeApp:embedAndSignAppleFrameworkForXcode -Porganization=ooni\n";
- };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */