From 85b1d708c3f491be59d29861e2e262d7eb2e9fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 31 Jul 2024 15:59:12 +0100 Subject: [PATCH] Navigation main screens skeleton --- composeApp/build.gradle.kts | 6 +- .../kotlin/org/ooni/probe/MainActivity.kt | 2 + .../drawable/compose-multiplatform.xml | 36 --------- .../drawable/ic_dashboard.xml | 13 +++ .../composeResources/drawable/ic_history.xml | 13 +++ .../composeResources/drawable/ic_settings.xml | 13 +++ .../values/strings-common.xml | 4 + .../commonMain/kotlin/org/ooni/probe/App.kt | 7 +- .../org/ooni/probe/data/models/TestResult.kt | 7 ++ .../kotlin/org/ooni/probe/di/Dependencies.kt | 13 +++ .../probe/ui/dashboard/DashboardScreen.kt | 52 +++++------- .../ui/navigation/BottomNavigationBar.kt | 81 +++++++++++++++++++ .../ooni/probe/ui/navigation/Navigation.kt | 36 ++++++++- .../org/ooni/probe/ui/navigation/Screen.kt | 11 +++ .../org/ooni/probe/ui/result/ResultScreen.kt | 38 +++++++++ .../ooni/probe/ui/result/ResultViewModel.kt | 40 +++++++++ .../ooni/probe/ui/results/ResultsScreen.kt | 30 +++++++ .../ooni/probe/ui/results/ResultsViewModel.kt | 42 ++++++++++ .../ooni/probe/ui/settings/SettingsScreen.kt | 30 +++++++ gradle/libs.versions.toml | 1 + iosApp/iosApp.xcodeproj/project.pbxproj | 60 +++++++------- 21 files changed, 432 insertions(+), 103 deletions(-) delete mode 100644 composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_dashboard.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_history.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_settings.xml create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/TestResult.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/navigation/BottomNavigationBar.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/SettingsScreen.kt 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 */