From 2027530cdfe980c6789e242de9de3c48d17245ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 12:00:56 +0000 Subject: [PATCH 01/22] Improve DescriptorUpdateWorker exception handling to avoid misleading reports --- .../probe/background/DescriptorUpdateWorker.kt | 18 +++++++++++++----- .../org/ooni/probe/domain/FetchDescriptor.kt | 15 +++++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt index afac9b82..054849b1 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt @@ -12,8 +12,10 @@ import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import androidx.work.workDataOf import co.touchlab.kermit.Logger +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first +import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running import ooniprobe.composeapp.generated.resources.Res @@ -41,11 +43,14 @@ class DescriptorUpdateWorker( } override suspend fun doWork(): Result { - return coroutineScope { - val descriptors = getDescriptors() ?: return@coroutineScope Result.failure() - if (descriptors.isEmpty()) return@coroutineScope Result.success(buildWorkData(descriptors.map { it.id })) + try { + val descriptors = getDescriptors() ?: return Result.failure() + if (descriptors.isEmpty()) return Result.success(buildWorkData(descriptors.map { it.id })) dependencies.getDescriptorUpdate.invoke(descriptors) - return@coroutineScope Result.success(buildWorkData(descriptors.map { it.id })) + return Result.success(buildWorkData(descriptors.map { it.id })) + } catch (e: CancellationException) { + Logger.w("DescriptorUpdateWorker: cancelled", e) + return Result.failure() } } @@ -55,7 +60,10 @@ class DescriptorUpdateWorker( try { val ids = json.decodeFromString>(descriptorsJson) return testDescriptorRepository.selectByRunIds(ids).first() - } catch (e: Exception) { + } catch (e: SerializationException) { + Logger.w("Could not start update worker: invalid configuration", e) + return null + }catch (e: IllegalArgumentException) { Logger.w("Could not start update worker: invalid configuration", e) return null } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/FetchDescriptor.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/FetchDescriptor.kt index 48830c06..41cc162e 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/FetchDescriptor.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/FetchDescriptor.kt @@ -1,6 +1,7 @@ package org.ooni.probe.domain import co.touchlab.kermit.Logger +import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import org.ooni.engine.Engine.MkException import org.ooni.engine.models.OONIRunDescriptor @@ -26,8 +27,11 @@ class FetchDescriptor( json.decodeFromString(it).toModel().copy( revisions = fetchRevisions(descriptorId).get()?.revisions, ) - } catch (e: Throwable) { - Logger.e(e) { "Failed to decode descriptor" } + } catch (e: SerializationException) { + Logger.e(e) { "Failed to decode descriptor $descriptorId" } + null + } catch (e: IllegalArgumentException) { + Logger.e(e) { "Failed to decode descriptor $descriptorId" } null } } ?: throw MkException(Throwable("Failed to fetch descriptor")) @@ -43,8 +47,11 @@ class FetchDescriptor( result?.let { try { json.decodeFromString(it) - } catch (e: Throwable) { - Logger.e(e) { "Failed to decode revisions" } + } catch (e: SerializationException) { + Logger.e(e) { "Failed to decode descriptor revisions $descriptorId" } + null + } catch (e: IllegalArgumentException) { + Logger.e(e) { "Failed to decode descriptor revisions $descriptorId" } null } } ?: throw MkException(Throwable("Failed to fetch revision")) From 9dbec55f30f9def966b00f4e891fcf160f8bb475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 13:30:37 +0000 Subject: [PATCH 02/22] Don't report ResolverLookupFailures to Sentry --- .../org/ooni/probe/domain/RunNetTest.kt | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt index f6f27216..c79eb421 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt @@ -221,7 +221,7 @@ class RunNetTest( is TaskEvent.StartupFailure, is TaskEvent.ResolverLookupFailure, - -> { + -> { val message = when (event) { is TaskEvent.StartupFailure -> event.message is TaskEvent.ResolverLookupFailure -> event.message @@ -232,22 +232,24 @@ class RunNetTest( updateResult { it.copy( failureMessage = - if (it.failureMessage != null) { - "${it.failureMessage}\n$message" - } else { - message - }, + if (it.failureMessage != null) { + "${it.failureMessage}\n$message" + } else { + message + }, ) } } - val value = when (event) { - is TaskEvent.StartupFailure -> event.value - is TaskEvent.ResolverLookupFailure -> event.value - else -> null - } ?: return + when (event) { + is TaskEvent.StartupFailure -> + Logger.w(message ?: "StartupFailure", Failure(event.value)) + + is TaskEvent.ResolverLookupFailure -> + Logger.i(message ?: "ResolverLookupFailure", Failure(event.value)) - Logger.w(message ?: "Failure", Failure(value)) + else -> Unit + } } is TaskEvent.BugJsonDump -> { From 14b4de0c5988c47ac18731dfd6b0a0e15e132157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 15:13:08 +0000 Subject: [PATCH 03/22] Skip failures from cancelling test --- .../config/OrganizationConfigInterface.kt | 6 +-- .../kotlin/org/ooni/engine/Engine.kt | 8 +++- .../kotlin/org/ooni/engine/TaskEventMapper.kt | 7 +++- .../org/ooni/engine/models/TaskEvent.kt | 9 ++++ .../org/ooni/probe/domain/RunNetTest.kt | 41 ++++++++++++++----- 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt index 1d38bf95..3ec9094d 100644 --- a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt @@ -5,13 +5,13 @@ interface OrganizationConfigInterface { val baseSoftwareName: String val ooniApiBaseUrl: String - get() = "https://api.ooni.org" + get() = "https://api.dev.ooni.io" val ooniRunDomain: String - get() = "run.ooni.org" + get() = "run.test.ooni.org" val ooniRunDashboardUrl: String - get() = "https://run.ooni.org" + get() = "https://run.test.ooni.org" val explorerUrl: String get() = "https://explorer.test.ooni.org" diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index f06299af..3e0a1638 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -52,15 +52,19 @@ class Engine( val settingsSerialized = json.encodeToString(taskSettings) var task: OonimkallBridge.Task? = null + var isCancelled = false try { task = bridge.startTask(settingsSerialized) - addRunCancelListener { task.interrupt() } + addRunCancelListener { + isCancelled = true + task.interrupt() + } while (!task.isDone() && isActive) { val eventJson = task.waitForNextEvent() val taskEventResult = json.decodeFromString(eventJson) - taskEventMapper(taskEventResult)?.let { send(it) } + taskEventMapper(taskEventResult, isCancelled)?.let { send(it) } } } catch (e: Exception) { Logger.d("Error while running task", e) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEventMapper.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEventMapper.kt index 09a4ba99..71d2fd02 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEventMapper.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/TaskEventMapper.kt @@ -9,7 +9,10 @@ class TaskEventMapper( private val networkTypeFinder: NetworkTypeFinder, private val json: Json, ) { - operator fun invoke(result: TaskEventResult): TaskEvent? { + operator fun invoke( + result: TaskEventResult, + isCancelled: Boolean = false, + ): TaskEvent? { val key = result.key val value = result.value @@ -33,6 +36,7 @@ class TaskEventMapper( TaskEvent.ResolverLookupFailure( message = value.failure, value = value, + isCancelled = isCancelled, ) } ?: run { Logger.d("Task Event $key missing 'value'") @@ -44,6 +48,7 @@ class TaskEventMapper( TaskEvent.StartupFailure( message = value.failure, value = value, + isCancelled = isCancelled, ) } ?: run { Logger.d("Task Event $key missing 'value'") diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskEvent.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskEvent.kt index 3b35dfa0..12d62280 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskEvent.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/models/TaskEvent.kt @@ -59,6 +59,7 @@ sealed interface TaskEvent { data class ResolverLookupFailure( val message: String?, val value: TaskEventResult.Value, + val isCancelled: Boolean, ) : TaskEvent data object Started : TaskEvent @@ -66,9 +67,17 @@ sealed interface TaskEvent { data class StartupFailure( val message: String?, val value: TaskEventResult.Value, + val isCancelled: Boolean, ) : TaskEvent data class TaskTerminated( val index: Int, ) : TaskEvent + + fun isCancelled() = + when (this) { + is StartupFailure -> isCancelled + is ResolverLookupFailure -> isCancelled + else -> null + } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt index c79eb421..562b9594 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunNetTest.kt @@ -221,7 +221,7 @@ class RunNetTest( is TaskEvent.StartupFailure, is TaskEvent.ResolverLookupFailure, - -> { + -> { val message = when (event) { is TaskEvent.StartupFailure -> event.message is TaskEvent.ResolverLookupFailure -> event.message @@ -232,28 +232,33 @@ class RunNetTest( updateResult { it.copy( failureMessage = - if (it.failureMessage != null) { - "${it.failureMessage}\n$message" - } else { - message - }, + if (it.failureMessage != null) { + "${it.failureMessage}\n$message" + } else { + message + }, ) } } + if (event.isCancelled() == true) return + when (event) { is TaskEvent.StartupFailure -> - Logger.w(message ?: "StartupFailure", Failure(event.value)) + Logger.w("StartupFailure", StartupFailure(message, event.value)) is TaskEvent.ResolverLookupFailure -> - Logger.i(message ?: "ResolverLookupFailure", Failure(event.value)) + Logger.i( + "ResolverLookupFailure", + ResolverLookupFailure(message, event.value), + ) else -> Unit } } is TaskEvent.BugJsonDump -> { - Logger.w("BugJsonDump", Failure(event.value)) + Logger.w("BugJsonDump", BugJsonDump(event.value)) } is TaskEvent.TaskTerminated -> Unit @@ -293,5 +298,21 @@ class RunNetTest( ) } - inner class Failure(value: TaskEventResult.Value) : Exception(json.encodeToString(value)) + open inner class Failure(message: String?, value: TaskEventResult.Value?) : Exception( + if (message != null && value != null) { + message + "\n" + json.encodeToString(value) + } else if (value != null) { + json.encodeToString(value) + } else { + message ?: "" + }, + ) + + inner class StartupFailure(message: String?, value: TaskEventResult.Value?) : + Failure(message, value) + + inner class ResolverLookupFailure(message: String?, value: TaskEventResult.Value?) : + Failure(message, value) + + inner class BugJsonDump(value: TaskEventResult.Value?) : Failure(null, value) } From f4e31dfaa133cc5291da331506703578f20e736f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 15:22:13 +0000 Subject: [PATCH 04/22] Report error requesting permission exception --- .../kotlin/org/ooni/probe/ui/onboarding/OnboardingScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingScreen.kt index 94d5e8ba..139d882b 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingScreen.kt @@ -372,7 +372,7 @@ fun ColumnScope.RequestPermissionStep(onEvent: (OnboardingViewModel.Event) -> Un Logger.i("Permission request cancelled") // Nothing to do here } catch (e: Exception) { - Logger.e("Error requesting permission") + Logger.e("Error requesting permission", e) } } }, From 2142e773f9020c463fb017876482a9482b017bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 15:27:14 +0000 Subject: [PATCH 05/22] Fix kotlin lint --- .../kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt index 054849b1..0957d22f 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt @@ -13,7 +13,6 @@ import androidx.work.WorkerParameters import androidx.work.workDataOf import co.touchlab.kermit.Logger import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString @@ -63,7 +62,7 @@ class DescriptorUpdateWorker( } catch (e: SerializationException) { Logger.w("Could not start update worker: invalid configuration", e) return null - }catch (e: IllegalArgumentException) { + } catch (e: IllegalArgumentException) { Logger.w("Could not start update worker: invalid configuration", e) return null } From 94eca749bc8e9ee5a081e807697e267407cbb0eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 16:19:26 +0000 Subject: [PATCH 06/22] Fix stunReachability test --- .../kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt | 7 ++----- .../org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt | 7 ++----- .../org/ooni/probe/config/OrganizationConfigInterface.kt | 6 +++--- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt index 358f934c..358c9fc9 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt @@ -30,7 +30,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.ooni.probe.data.models.SettingsKey -import org.ooni.probe.uitesting.helpers.checkLinkInsideWebView +import org.ooni.probe.uitesting.helpers.checkTextAnywhereInsideWebView import org.ooni.probe.uitesting.helpers.checkSummaryInsideWebView import org.ooni.probe.uitesting.helpers.clickOnText import org.ooni.probe.uitesting.helpers.isNewsMediaScan @@ -137,10 +137,7 @@ class RunningTestsTest { clickOnText(Res.string.Test_Experimental_Fullname) compose.onAllNodesWithText("stunreachability")[0].performClick() wait { onNodeWithText(Res.string.measurement).isDisplayed() } - checkLinkInsideWebView( - "https://ooni.org/nettest/http-requests/", - "STUN Reachability", - ) + checkTextAnywhereInsideWebView("stunreachability") } } diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt index af34dff2..7e979284 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/helpers/ComposeTestHelpers.kt @@ -91,13 +91,10 @@ fun ComposeTestRule.checkSummaryInsideWebView(text: String) { } } -fun ComposeTestRule.checkLinkInsideWebView( - link: String, - text: String, -) { +fun ComposeTestRule.checkTextAnywhereInsideWebView(text: String) { waitAssertion(WEBSITE_WAIT_TIMEOUT) { onWebView() - .withElement(findElement(Locator.CSS_SELECTOR, "a[href=\"$link\"")) + .withElement(findElement(Locator.CSS_SELECTOR, "*")) .check(webMatches(getText(), containsString(text))) } } diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt index 1d38bf95..3ec9094d 100644 --- a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt @@ -5,13 +5,13 @@ interface OrganizationConfigInterface { val baseSoftwareName: String val ooniApiBaseUrl: String - get() = "https://api.ooni.org" + get() = "https://api.dev.ooni.io" val ooniRunDomain: String - get() = "run.ooni.org" + get() = "run.test.ooni.org" val ooniRunDashboardUrl: String - get() = "https://run.ooni.org" + get() = "https://run.test.ooni.org" val explorerUrl: String get() = "https://explorer.test.ooni.org" From 315ea665efa1e40fee6f1db3c1661989321bcfe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 25 Nov 2024 17:41:45 +0000 Subject: [PATCH 07/22] Fix first descriptor update on NewsMediaScan --- .../kotlin/org/ooni/probe/background/AppWorkerManager.kt | 1 + .../org/ooni/probe/background/DescriptorUpdateWorker.kt | 8 ++++++-- composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt | 6 ++---- .../kotlin/org/ooni/probe/domain/OrganizationSettings.kt | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/AppWorkerManager.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/AppWorkerManager.kt index be6a9963..6e2f98ca 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/AppWorkerManager.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/AppWorkerManager.kt @@ -62,6 +62,7 @@ class AppWorkerManager( suspend fun configureDescriptorAutoUpdate(): Boolean { return withContext(backgroundDispatcher) { val request = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS) + .setInitialDelay(1, TimeUnit.DAYS) // avoid immediate start .build() workManager.enqueueUniquePeriodicWork( DescriptorUpdateWorker.AutoUpdateWorkerName, diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt index 0957d22f..198bb07a 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt @@ -44,7 +44,10 @@ class DescriptorUpdateWorker( override suspend fun doWork(): Result { try { val descriptors = getDescriptors() ?: return Result.failure() - if (descriptors.isEmpty()) return Result.success(buildWorkData(descriptors.map { it.id })) + if (descriptors.isEmpty()) { + Logger.i("Skipping DescriptorUpdateWorker: no descriptors to update") + return Result.success(buildWorkData(descriptors.map { it.id })) + } dependencies.getDescriptorUpdate.invoke(descriptors) return Result.success(buildWorkData(descriptors.map { it.id })) } catch (e: CancellationException) { @@ -57,7 +60,8 @@ class DescriptorUpdateWorker( val descriptorsJson = inputData.getString(DATA_KEY_DESCRIPTORS) if (descriptorsJson != null) { try { - val ids = json.decodeFromString>(descriptorsJson) + val ids = + json.decodeFromString>(descriptorsJson) return testDescriptorRepository.selectByRunIds(ids).first() } catch (e: SerializationException) { Logger.w("Could not start update worker: invalid configuration", e) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt index 884d4273..dcfcc190 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt @@ -97,6 +97,8 @@ fun App( LaunchedEffect(Unit) { dependencies.bootstrapTestDescriptors() dependencies.bootstrapPreferences() + dependencies.configureDescriptorAutoUpdate() + dependencies.fetchDescriptorUpdate(null) } LaunchedEffect(Unit) { dependencies.finishInProgressData() @@ -104,10 +106,6 @@ fun App( LaunchedEffect(Unit) { dependencies.observeAndConfigureAutoRun() } - LaunchedEffect(Unit) { - dependencies.configureDescriptorAutoUpdate() - dependencies.fetchDescriptorUpdate(null) - } LaunchedEffect(deepLink) { when (deepLink) { diff --git a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt index 05538846..98fb257b 100644 --- a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt +++ b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt @@ -11,6 +11,6 @@ fun webConnectivityPreferences( return emptyList() } -fun preferenceDefaults(): List> { +fun preferenceDefaults(): List> { return emptyList() } From 1d4c2bef2f32bc3e702a7e6bc861976363b43f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 26 Nov 2024 11:11:32 +0000 Subject: [PATCH 08/22] Add CI badges to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 63806833..cabbf0d7 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Multiplatform (Android and iOS currently) version of the Probe app. +![Validate](https://github.com/ooni/probe-multiplatform/actions/workflows/validate.yml/badge.svg) +![Android Instrumented Tests](https://github.com/ooni/probe-multiplatform/actions/workflows/instrumented-tests.yml/badge.svg) + ## Project structure * `composeApp` is for code that will be shared across your Compose Multiplatform applications. From ed1f11c3dccc3df99aafb600209ccd16e84db946 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Tue, 26 Nov 2024 12:53:09 +0100 Subject: [PATCH 09/22] chore: upgrade engine to `v3.22.0` --- gradle/libs.versions.toml | 2 +- iosApp/Podfile | 2 +- iosApp/Podfile.lock | 50 +++++++++++++++++++-------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3249c1f6..e2f4d604 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -43,7 +43,7 @@ androidx-datastore-core-okio = { group = "androidx.datastore", name = "datastore androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "dataStoreVersion" } # Engine -android-oonimkall = { module = "org.ooni:oonimkall", version = "2024.08.08-091959" } +android-oonimkall = { module = "org.ooni:oonimkall", version = "2024.11.26-053000" } # Logging kermit = { module = "co.touchlab:kermit", version = "2.0.4" } diff --git a/iosApp/Podfile b/iosApp/Podfile index 2e032086..8e9e141c 100644 --- a/iosApp/Podfile +++ b/iosApp/Podfile @@ -2,7 +2,7 @@ platform :ios, '14.0' use_frameworks! def shared_pods - ooni_version = "v3.22.0" + ooni_version = "v3.24.0" ooni_pods_location = "https://github.com/ooni/probe-cli/releases/download/#{ooni_version}" pod 'composeApp', :path => '../composeApp' diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index 74e7870f..e6910a28 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -1,12 +1,12 @@ PODS: - composeApp (1.0): - Sentry (~> 8.38.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) + - libcrypto (2024.11.26-053946) + - libevent (2024.11.26-053946) + - libssl (2024.11.26-053946) + - libtor (2024.11.26-053946) + - libz (2024.11.26-053946) + - oonimkall (2024.11.26-053946) - Sentry (8.38.0): - Sentry/Core (= 8.38.0) - Sentry/Core (8.38.0) @@ -16,12 +16,12 @@ PODS: 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`) + - libcrypto (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/libcrypto.podspec`) + - libevent (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/libevent.podspec`) + - libssl (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/libssl.podspec`) + - libtor (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/libtor.podspec`) + - libz (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/libz.podspec`) + - oonimkall (from `https://github.com/ooni/probe-cli/releases/download/v3.24.0/oonimkall.podspec`) - sqlite3 (~> 3.42.0) SPEC REPOS: @@ -33,29 +33,29 @@ EXTERNAL SOURCES: composeApp: :path: "../composeApp" libcrypto: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libcrypto.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/libcrypto.podspec libevent: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libevent.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/libevent.podspec libssl: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libssl.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/libssl.podspec libtor: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libtor.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/libtor.podspec libz: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/libz.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/libz.podspec oonimkall: - :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec + :podspec: https://github.com/ooni/probe-cli/releases/download/v3.24.0/oonimkall.podspec SPEC CHECKSUMS: composeApp: 675ce506dcd05e76ecb4fdd0f73a10d4af635af1 - libcrypto: 1bb58600c586e28688f5578f4675f5ffa46c8eaf - libevent: 5c8502ca5cc38be31bb510ddade0f238bcc5f0dc - libssl: 170bebcaf567a0285e91a8850b9686137d07c3e1 - libtor: c72b23da6a5d2e16173149784f11cf66156c35be - libz: 83658eb2a0db785623ffdf9ce13407e6b8b5c8f9 - oonimkall: 9768ce9dad18265d45d2ea972c84fb0bd5237cc3 + libcrypto: 7fb50209305641e986f6b1a1e4680ec4d71602e3 + libevent: b9f2a7f3b682f7a072f4d21edf360052d3a591dc + libssl: 115acb9ff8f7d20f437ff8e112f1d21f210dad94 + libtor: efd4a639943c12a239094c2927891578cb4c7fcc + libz: 8e83c9433082438bce962f836f845d835f26b850 + oonimkall: 935a4263650a75bef0d8b487f7d349094b4c2b73 Sentry: 205813e7e758b53df157cedb8c55b31a14300645 sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c -PODFILE CHECKSUM: 7ccceb3a8043e0a52e0aaf77c23a4e93dbe689e7 +PODFILE CHECKSUM: 59b6c2ae5c19ca87fa0a3f359deffa5e78551529 COCOAPODS: 1.16.2 From f50313658c1a3af0e3699b476a43b163d0ce0172 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Tue, 26 Nov 2024 12:59:50 +0100 Subject: [PATCH 10/22] call session close --- .../kotlin/org/ooni/engine/AndroidOonimkallBridge.kt | 3 +++ iosApp/iosApp/engine/IosOonimkallBridge.swift | 1 + 2 files changed, 4 insertions(+) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt index f568dfab..b28d86f0 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt @@ -38,6 +38,9 @@ class AndroidOonimkallBridge : OonimkallBridge { override fun checkIn(config: OonimkallBridge.CheckInConfig): OonimkallBridge.CheckInResults { val context = session.newContextWithTimeout(CONTEXT_TIMEOUT) val info = session.checkIn(context, config.toMk()) + runCatching { + session.close() + } return OonimkallBridge.CheckInResults( reportId = info.webConnectivity?.reportID, urls = info.webConnectivity.getUrlInfos(), diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 3c101686..4f5f5455 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -88,6 +88,7 @@ class IosOonimkallBridge: OonimkallBridge { } } + try ses?.close() return OonimkallBridgeCheckInResults( reportId: info?.webConnectivity?.reportID, urls: responseUrls From 5e01945ae276d7cc2d81d442679970e99b04004e Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Tue, 26 Nov 2024 18:14:26 +0100 Subject: [PATCH 11/22] chore: add logger --- .../kotlin/org/ooni/engine/AndroidOonimkallBridge.kt | 2 ++ iosApp/iosApp/engine/IosOonimkallBridge.swift | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt index b28d86f0..19afcad8 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt @@ -40,6 +40,8 @@ class AndroidOonimkallBridge : OonimkallBridge { val info = session.checkIn(context, config.toMk()) runCatching { session.close() + }.onFailure { + co.touchlab.kermit.Logger.w(it) { "Failed to close session" } } return OonimkallBridge.CheckInResults( reportId = info.webConnectivity?.reportID, diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 4f5f5455..0e4d9b1a 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -88,7 +88,9 @@ class IosOonimkallBridge: OonimkallBridge { } } - try ses?.close() + do { + try ses?.close() + } catch {} return OonimkallBridgeCheckInResults( reportId: info?.webConnectivity?.reportID, urls: responseUrls From 3e4808072ab66184868578145a899648b130d022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 26 Nov 2024 17:46:40 +0000 Subject: [PATCH 12/22] Delegate session close to the engine --- .../org/ooni/engine/AndroidOonimkallBridge.kt | 9 +- .../kotlin/org/ooni/engine/Engine.kt | 36 ++++--- .../kotlin/org/ooni/engine/OonimkallBridge.kt | 4 + .../org/ooni/engine/TestOonimkallBridge.kt | 2 + .../ooni/probe/domain/OrganizationSettings.kt | 2 +- iosApp/iosApp/engine/IosOonimkallBridge.swift | 100 ++++++++---------- 6 files changed, 79 insertions(+), 74 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt index 19afcad8..3bc19da3 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/engine/AndroidOonimkallBridge.kt @@ -38,11 +38,6 @@ class AndroidOonimkallBridge : OonimkallBridge { override fun checkIn(config: OonimkallBridge.CheckInConfig): OonimkallBridge.CheckInResults { val context = session.newContextWithTimeout(CONTEXT_TIMEOUT) val info = session.checkIn(context, config.toMk()) - runCatching { - session.close() - }.onFailure { - co.touchlab.kermit.Logger.w(it) { "Failed to close session" } - } return OonimkallBridge.CheckInResults( reportId = info.webConnectivity?.reportID, urls = info.webConnectivity.getUrlInfos(), @@ -54,6 +49,10 @@ class AndroidOonimkallBridge : OonimkallBridge { val response = session.httpDo(context, request.toMk()) return OonimkallBridge.HTTPResponse(body = response.body) } + + override fun close() { + session.close() + } } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt index f06299af..09cf70b5 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/Engine.kt @@ -81,22 +81,32 @@ class Engine( session(sessionConfig).submitMeasurement(measurement) }.mapError { MkException(it) } - suspend fun checkIn(taskOrigin: TaskOrigin): Result = - resultOf(backgroundContext) { + suspend fun checkIn(taskOrigin: TaskOrigin): Result { + return resultOf(backgroundContext) { val preferences = getEnginePreferences() val sessionConfig = buildSessionConfig(taskOrigin, preferences) - session(sessionConfig).checkIn( - OonimkallBridge.CheckInConfig( - charging = isBatteryCharging(), - onWiFi = networkTypeFinder() == NetworkType.Wifi, - platform = platformInfo.platform.value, - runType = taskOrigin.value, - softwareName = sessionConfig.softwareName, - softwareVersion = sessionConfig.softwareVersion, - webConnectivityCategories = preferences.enabledWebCategories, - ), - ) + val session = session(sessionConfig) + try { + session.checkIn( + OonimkallBridge.CheckInConfig( + charging = isBatteryCharging(), + onWiFi = networkTypeFinder() == NetworkType.Wifi, + platform = platformInfo.platform.value, + runType = taskOrigin.value, + softwareName = sessionConfig.softwareName, + softwareVersion = sessionConfig.softwareVersion, + webConnectivityCategories = preferences.enabledWebCategories, + ), + ) + } finally { + try { + session.close() + } catch (e: Exception) { + Logger.w("Error closing session", e) + } + } }.mapError { MkException(it) } + } suspend fun httpDo( method: String, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt index 99624984..7a8e2d23 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/engine/OonimkallBridge.kt @@ -45,6 +45,10 @@ interface OonimkallBridge { @Throws(Exception::class) fun httpDo(request: HTTPRequest): HTTPResponse + + // Needs to be called after `checkIn` + @Throws(Exception::class) + fun close() } data class SubmitMeasurementResults( diff --git a/composeApp/src/commonTest/kotlin/org/ooni/engine/TestOonimkallBridge.kt b/composeApp/src/commonTest/kotlin/org/ooni/engine/TestOonimkallBridge.kt index 250dd1b1..dff97e4d 100644 --- a/composeApp/src/commonTest/kotlin/org/ooni/engine/TestOonimkallBridge.kt +++ b/composeApp/src/commonTest/kotlin/org/ooni/engine/TestOonimkallBridge.kt @@ -46,5 +46,7 @@ class TestOonimkallBridge : OonimkallBridge { override fun checkIn(config: OonimkallBridge.CheckInConfig): OonimkallBridge.CheckInResults = checkInMock!!(config) override fun httpDo(request: OonimkallBridge.HTTPRequest): OonimkallBridge.HTTPResponse = httpDoMock!!(request) + + override fun close() {} } } diff --git a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt index 05538846..98fb257b 100644 --- a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt +++ b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt @@ -11,6 +11,6 @@ fun webConnectivityPreferences( return emptyList() } -fun preferenceDefaults(): List> { +fun preferenceDefaults(): List> { return emptyList() } diff --git a/iosApp/iosApp/engine/IosOonimkallBridge.swift b/iosApp/iosApp/engine/IosOonimkallBridge.swift index 0e4d9b1a..0b5668d8 100644 --- a/iosApp/iosApp/engine/IosOonimkallBridge.swift +++ b/iosApp/iosApp/engine/IosOonimkallBridge.swift @@ -33,51 +33,52 @@ class IosOonimkallBridge: OonimkallBridge { } func doNewSession(sessionConfig: OonimkallBridgeSessionConfig) throws -> OonimkallBridgeSession { - class IosSession: OonimkallBridgeSession { - private let sessionConfig: OonimkallSessionConfig + func error(_ message: String, code: Int = 0, domain: String = "IosOonimkallBridge", function: String = #function, file: String = #file, line: Int = #line) -> NSError { - init(sessionConfig: OonimkallSessionConfig) { - self.sessionConfig = sessionConfig - } + let functionKey = "\(domain).function" + let fileKey = "\(domain).file" + let lineKey = "\(domain).line" - func error(_ message: String, code: Int = 0, domain: String = "IosOonimkallBridge", function: String = #function, file: String = #file, line: Int = #line) -> NSError { + let error = NSError(domain: domain, code: code, userInfo: [ + message: message, + functionKey: function, + fileKey: file, + lineKey: line + ]) - let functionKey = "\(domain).function" - let fileKey = "\(domain).file" - let lineKey = "\(domain).line" + return error + } - let error = NSError(domain: domain, code: code, userInfo: [ - message: message, - functionKey: function, - fileKey: file, - lineKey: line - ]) + class IosSession: OonimkallBridgeSession { + private let session: OonimkallSession - return error + init(sessionConfig: OonimkallSessionConfig) throws { + var sessionError: NSError? + guard let session = OonimkallNewSession(sessionConfig, &sessionError) else { + throw error("Unable to create session") + } + // throw error if any + if sessionError != nil { + throw sessionError! + } + self.session = session } func checkIn(config: OonimkallBridgeCheckInConfig) throws -> OonimkallBridgeCheckInResults { - var error: NSError? - let ses = OonimkallNewSession(sessionConfig, &error) - // throw error if any - if error != nil { - throw error! - } - guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw self.error("Unable to create context") + guard let context = session.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw error("Unable to create context") } do { - let info = try ses?.check(in: context, config: config.toMk()) - - + let info = try session.check(in: context, config: config.toMk()) + var responseUrls = [OonimkallBridgeUrlInfo]() - let size = info?.webConnectivity?.size() ?? 0 + let size = info.webConnectivity?.size() ?? 0 for i in 0.. OonimkallBridgeHTTPResponse { - var error: NSError? - let ses = OonimkallNewSession(sessionConfig, &error) - // throw error if any - if error != nil { - throw error! - } - guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw self.error("Unable to create context") + guard let context = session.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw error("Unable to create context") } do { - let response = try ses?.httpDo(context, jreq: request.toMk()) - return OonimkallBridgeHTTPResponse(body: response?.body) + let response = try session.httpDo(context, jreq: request.toMk()) + return OonimkallBridgeHTTPResponse(body: response.body) } catch { throw error } } func submitMeasurement(measurement: String) throws -> OonimkallBridgeSubmitMeasurementResults { - var error: NSError? - let ses = OonimkallNewSession(sessionConfig, &error) - // throw error if any - if error != nil { - throw error! - } - guard let context = ses?.newContext(withTimeout: CONTEXT_TIMEOUT) else { - throw self.error("Unable to create context") + guard let context = session.newContext(withTimeout: CONTEXT_TIMEOUT) else { + throw error("Unable to create context") } do { - let result: OonimkallSubmitMeasurementResults? = try ses?.submit(context, measurement: measurement) + let result: OonimkallSubmitMeasurementResults? = try session.submit(context, measurement: measurement) return OonimkallBridgeSubmitMeasurementResults( updatedMeasurement: result?.updatedMeasurement, updatedReportId: result?.updatedReportID ?? "" @@ -139,9 +125,13 @@ class IosOonimkallBridge: OonimkallBridge { throw error } } + + func close() throws { + try session.close() + } } - return IosSession(sessionConfig: sessionConfig.toMk()) + return try IosSession(sessionConfig: sessionConfig.toMk()) } } From 85005d27161cd8754cd67902b6cffe47c61f5032 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Tue, 26 Nov 2024 19:04:25 +0100 Subject: [PATCH 13/22] update explorer url --- .../kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt | 2 +- .../kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt index 1d38bf95..54c7dc20 100644 --- a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt @@ -14,7 +14,7 @@ interface OrganizationConfigInterface { get() = "https://run.ooni.org" val explorerUrl: String - get() = "https://explorer.test.ooni.org" + get() = "https://explorer.ooni.org" val testDisplayMode: TestDisplayMode diff --git a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt index 79889cd9..6838b82d 100644 --- a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt @@ -13,7 +13,7 @@ interface OrganizationConfigInterface { get() = "https://run.ooni.org" val explorerUrl: String - get() = "https://explorer.test.ooni.org" + get() = "https://explorer.ooni.org" val testDisplayMode: TestDisplayMode From 2a5616d52c21cc65098b8bd67db3ddb32b435ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 26 Nov 2024 16:31:42 +0000 Subject: [PATCH 14/22] Improve test options --- .../values/strings-common.xml | 6 +- .../ooni/probe/config/BuildTypesDefaults.kt | 8 ++ .../config/OrganizationConfigInterface.kt | 26 ---- .../ooni/probe/config/OrganizationConfig.kt | 21 +++ .../ooni/probe/data/models/PreferenceItem.kt | 76 ++++++++++ .../{PreferenceModel.kt => SettingsKey.kt} | 72 ---------- .../ooni/probe/domain/BootstrapPreferences.kt | 2 +- .../org/ooni/probe/domain/GetSettings.kt | 135 ++++++++++++++---- .../ooni/probe/ui/navigation/Navigation.kt | 12 +- .../category/SettingsCategoryScreen.kt | 122 ++++++++-------- .../ooni/probe/config/BuildTypeDefaults.kt | 8 ++ .../config/OrganizationConfigInterface.kt | 25 ---- .../ooni/probe/config/OrganizationConfig.kt | 1 + .../domain/OrganizationPreferenceDefaults.kt | 8 ++ .../ooni/probe/domain/OrganizationSettings.kt | 16 --- .../ooni/probe/config/OrganizationConfig.kt | 1 + .../domain/OrganizationPreferenceDefaults.kt | 13 ++ .../ooni/probe/domain/OrganizationSettings.kt | 73 ---------- 18 files changed, 318 insertions(+), 307 deletions(-) create mode 100644 composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt delete mode 100644 composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceItem.kt rename composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/{PreferenceModel.kt => SettingsKey.kt} (50%) create mode 100644 composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt delete mode 100644 composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt create mode 100644 composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt delete mode 100644 composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt create mode 100644 composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt delete mode 100644 composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index 39dc3d3e..0650d913 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -76,8 +76,8 @@ Only while charging By enabling automatic testing, OONI Probe tests will run automatically multiple times per day. Your test results will automatically get published on OONI Explorer: https://explorer.ooni.org/ \n\nImportant: If you have a VPN enabled, OONI Probe will not run tests automatically. Please turn off your VPN for automated OONI Probe testing. Learn more: https://ooni.org/support/faq/#can-i-run-ooni-probe-over-a-vpn - Limit test duration - Test duration + Limit Websites test duration + Maximum Websites test duration Website categories to test %1$s categories enabled @@ -291,4 +291,6 @@ Only the last %1$d results are shown Skip after this amount of results failed to upload Unsupported URL + Results are automatically uploaded to OONI explorer + Tests will run every hour in the background diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt new file mode 100644 index 00000000..a255ad36 --- /dev/null +++ b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt @@ -0,0 +1,8 @@ +package org.ooni.probe.config + +object BuildTypesDefaults : BuildTypesDefaultsInterface { + override val ooniApiBaseUrl = "https://api.dev.ooni.io" + override val ooniRunDomain = "run.test.ooni.org" + override val ooniRunDashboardUrl = "https://run.test.ooni.org" + override val explorerUrl = "https://explorer.test.ooni.org" +} diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt deleted file mode 100644 index 9024c2ac..00000000 --- a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.ooni.probe.config - - -interface OrganizationConfigInterface { - val baseSoftwareName: String - - val ooniApiBaseUrl: String - get() = "https://api.dev.ooni.io" - - val ooniRunDomain: String - get() = "run.test.ooni.org" - - val ooniRunDashboardUrl: String - get() = "https://run.test.ooni.org" - - val explorerUrl: String - get() = "https://explorer.ooni.org" - - val testDisplayMode: TestDisplayMode - - val autorunTaskId: String - - val onboardingImages: OnboardingImages - - val updateDescriptorTaskId: String -} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt index f722bf11..ffe14918 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt @@ -2,6 +2,27 @@ package org.ooni.probe.config import org.jetbrains.compose.resources.DrawableResource +interface OrganizationConfigInterface { + val baseSoftwareName: String + val testDisplayMode: TestDisplayMode + val autorunTaskId: String + val onboardingImages: OnboardingImages + val updateDescriptorTaskId: String + val hasWebsitesDescriptor: Boolean + + val ooniApiBaseUrl get() = BuildTypesDefaults.ooniApiBaseUrl + val ooniRunDomain get() = BuildTypesDefaults.ooniRunDomain + val ooniRunDashboardUrl get() = BuildTypesDefaults.ooniRunDashboardUrl + val explorerUrl get() = BuildTypesDefaults.explorerUrl +} + +interface BuildTypesDefaultsInterface { + val ooniApiBaseUrl: String + val ooniRunDomain: String + val ooniRunDashboardUrl: String + val explorerUrl: String +} + data class OnboardingImages( val image1: DrawableResource, val image2: DrawableResource, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceItem.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceItem.kt new file mode 100644 index 00000000..a0b93168 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceItem.kt @@ -0,0 +1,76 @@ +package org.ooni.probe.data.models + +import androidx.compose.runtime.Composable +import org.jetbrains.compose.resources.DrawableResource +import org.jetbrains.compose.resources.StringResource + +open class PreferenceItem( + open val title: StringResource, + open val icon: DrawableResource? = null, + open val type: PreferenceItemType, + open val key: SettingsKey, + open val supportingContent: @Composable (() -> Unit)? = null, + open val trailingContent: @Composable (() -> Unit)? = null, + open val enabled: Boolean = true, + open val indentation: Int = 0, +) + +data class SettingsItem( + override val icon: DrawableResource? = null, + override val title: StringResource, + override val type: PreferenceItemType, + override val key: SettingsKey, + override val supportingContent: @Composable (() -> Unit)? = null, + override val trailingContent: @Composable (() -> Unit)? = null, + override val enabled: Boolean = true, + override val indentation: Int = 0, +) : PreferenceItem( + title = title, + icon = icon, + supportingContent = supportingContent, + type = type, + key = key, + enabled = enabled, + ) + +data class SettingsCategoryItem( + override val icon: DrawableResource? = null, + override val title: StringResource, + val route: PreferenceCategoryKey, + val settings: List? = emptyList(), + override val supportingContent: @Composable (() -> Unit)? = null, + val footerContent: @Composable (() -> Unit)? = null, + override val indentation: Int = 0, +) : PreferenceItem( + title = title, + icon = icon, + supportingContent = supportingContent, + type = PreferenceItemType.ROUTE, + key = SettingsKey.ROUTE, + ) + +enum class PreferenceItemType { + SWITCH, + INT, + BUTTON, + SELECT, + ROUTE, +} + +enum class PreferenceCategoryKey(val value: String) { + NOTIFICATIONS("notifications"), + TEST_OPTIONS("test_options"), + PRIVACY("privacy"), + PROXY("proxy"), + ADVANCED("advanced"), + SEND_EMAIL("send_email"), + ABOUT_OONI("about_ooni"), + + WEBSITES_CATEGORIES("websites_categories"), + SEE_RECENT_LOGS("see_recent_logs"), + ; + + companion object { + fun fromValue(value: String?) = value?.let { entries.firstOrNull { it.value == value } } + } +} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/SettingsKey.kt similarity index 50% rename from composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceModel.kt rename to composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/SettingsKey.kt index 7a4e3d95..b87c3884 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/PreferenceModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/SettingsKey.kt @@ -1,77 +1,5 @@ package org.ooni.probe.data.models -import androidx.compose.runtime.Composable -import org.jetbrains.compose.resources.DrawableResource -import org.jetbrains.compose.resources.StringResource - -open class PreferenceItem( - open val title: StringResource, - open val icon: DrawableResource? = null, - open val type: PreferenceItemType, - open val key: SettingsKey, - open val supportingContent: @Composable (() -> Unit)? = null, - open val trailingContent: @Composable (() -> Unit)? = null, - open val enabled: Boolean = true, -) - -data class SettingsItem( - override val icon: DrawableResource? = null, - override val title: StringResource, - override val type: PreferenceItemType, - override val key: SettingsKey, - override val supportingContent: @Composable (() -> Unit)? = null, - override val trailingContent: @Composable (() -> Unit)? = null, - override val enabled: Boolean = true, -) : PreferenceItem( - title = title, - icon = icon, - supportingContent = supportingContent, - type = type, - key = key, - enabled = enabled, - ) - -data class SettingsCategoryItem( - override val icon: DrawableResource? = null, - override val title: StringResource, - val route: PreferenceCategoryKey, - val settings: List? = emptyList(), - override val supportingContent: @Composable (() -> Unit)? = null, - val footerContent: @Composable (() -> Unit)? = null, -) : PreferenceItem( - title = title, - icon = icon, - supportingContent = supportingContent, - type = PreferenceItemType.ROUTE, - key = SettingsKey.ROUTE, - ) - -enum class PreferenceItemType { - SWITCH, - INT, - BUTTON, - SELECT, - ROUTE, -} - -enum class PreferenceCategoryKey(val value: String) { - NOTIFICATIONS("notifications"), - TEST_OPTIONS("test_options"), - PRIVACY("privacy"), - PROXY("proxy"), - ADVANCED("advanced"), - SEND_EMAIL("send_email"), - ABOUT_OONI("about_ooni"), - - WEBSITES_CATEGORIES("websites_categories"), - SEE_RECENT_LOGS("see_recent_logs"), - ; - - companion object { - fun fromValue(value: String) = entries.first { it.value == value } - } -} - enum class SettingsKey(val value: String) { // Notifications NOTIFICATIONS_ENABLED("notifications_enabled"), diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/BootstrapPreferences.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/BootstrapPreferences.kt index 91dfbdf7..d54523d9 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/BootstrapPreferences.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/BootstrapPreferences.kt @@ -33,7 +33,7 @@ class BootstrapPreferences( SettingsKey.AUTOMATED_TESTING_CHARGING to true, SettingsKey.AUTOMATED_TESTING_NOT_UPLOADED_LIMIT to NOT_UPLOADED_LIMIT_DEFAULT, ) + - preferenceDefaults(), + organizationPreferenceDefaults(), ) } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt index 3ef7f165..11eb869e 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt @@ -28,6 +28,7 @@ import ooniprobe.composeapp.generated.resources.Settings_Advanced_Label import ooniprobe.composeapp.generated.resources.Settings_Advanced_RecentLogs import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_ChargingOnly +import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_Description import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_Footer import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_WiFiOnly import ooniprobe.composeapp.generated.resources.Settings_Notifications_Enabled @@ -37,10 +38,15 @@ import ooniprobe.composeapp.generated.resources.Settings_Privacy_SendCrashReport import ooniprobe.composeapp.generated.resources.Settings_Proxy_Label import ooniprobe.composeapp.generated.resources.Settings_SendEmail_Label import ooniprobe.composeapp.generated.resources.Settings_Sharing_UploadResults +import ooniprobe.composeapp.generated.resources.Settings_Sharing_UploadResults_Description import ooniprobe.composeapp.generated.resources.Settings_Storage_Clear import ooniprobe.composeapp.generated.resources.Settings_Storage_Label import ooniprobe.composeapp.generated.resources.Settings_TestOptions_Label import ooniprobe.composeapp.generated.resources.Settings_WarmVPNInUse_Label +import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Description +import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label +import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime +import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled import ooniprobe.composeapp.generated.resources.advanced import ooniprobe.composeapp.generated.resources.auto_test_not_uploaded_limit import ooniprobe.composeapp.generated.resources.ic_settings @@ -51,6 +57,7 @@ import ooniprobe.composeapp.generated.resources.proxy import ooniprobe.composeapp.generated.resources.send_email import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.WebConnectivityCategory +import org.ooni.probe.config.OrganizationConfig import org.ooni.probe.data.models.PreferenceCategoryKey import org.ooni.probe.data.models.PreferenceItemType import org.ooni.probe.data.models.SettingsCategoryItem @@ -59,6 +66,8 @@ import org.ooni.probe.data.models.SettingsKey import org.ooni.probe.data.repositories.PreferenceRepository import org.ooni.probe.ui.settings.category.SettingsDescription import org.ooni.probe.ui.shared.formatDataUsage +import org.ooni.probe.ui.shared.shortFormat +import kotlin.time.Duration.Companion.seconds class GetSettings( private val preferencesRepository: PreferenceRepository, @@ -82,6 +91,7 @@ class GetSettings( val enabledCategoriesCount = WebConnectivityCategory.entries.count { preferences[it.settingsKey] == true } buildSettings( + hasWebsitesDescriptor = OrganizationConfig.hasWebsitesDescriptor, uploadResultsEnabled = preferences[SettingsKey.UPLOAD_RESULTS] == true, autoRunEnabled = preferences[SettingsKey.AUTOMATED_TESTING_ENABLED] == true, autoRunNotUploadedLimit = preferences[SettingsKey.AUTOMATED_TESTING_NOT_UPLOADED_LIMIT] as? Int, @@ -95,6 +105,7 @@ class GetSettings( } private fun buildSettings( + hasWebsitesDescriptor: Boolean, uploadResultsEnabled: Boolean, autoRunEnabled: Boolean, autoRunNotUploadedLimit: Int?, @@ -131,43 +142,111 @@ class GetSettings( title = Res.string.Settings_Sharing_UploadResults, key = SettingsKey.UPLOAD_RESULTS, type = PreferenceItemType.SWITCH, + supportingContent = { + Text( + stringResource(Res.string.Settings_Sharing_UploadResults_Description), + style = MaterialTheme.typography.labelLarge, + ) + }, ), SettingsItem( title = Res.string.Settings_AutomatedTesting_RunAutomatically, key = SettingsKey.AUTOMATED_TESTING_ENABLED, type = PreferenceItemType.SWITCH, enabled = uploadResultsEnabled, - ), - SettingsItem( - title = Res.string.Settings_AutomatedTesting_RunAutomatically_WiFiOnly, - key = SettingsKey.AUTOMATED_TESTING_WIFIONLY, - type = PreferenceItemType.SWITCH, - enabled = autoRunEnabled && uploadResultsEnabled, - ), - SettingsItem( - title = Res.string.Settings_AutomatedTesting_RunAutomatically_ChargingOnly, - key = SettingsKey.AUTOMATED_TESTING_CHARGING, - type = PreferenceItemType.SWITCH, - enabled = autoRunEnabled && uploadResultsEnabled, - ), - SettingsItem( - title = Res.string.auto_test_not_uploaded_limit, - key = SettingsKey.AUTOMATED_TESTING_NOT_UPLOADED_LIMIT, - type = PreferenceItemType.INT, - enabled = autoRunEnabled && uploadResultsEnabled, supportingContent = { - val value = ( - autoRunNotUploadedLimit - ?: BootstrapPreferences.NOT_UPLOADED_LIMIT_DEFAULT - ).coerceAtLeast(1) - Text(value.toString()) + Text( + stringResource(Res.string.Settings_AutomatedTesting_RunAutomatically_Description), + style = MaterialTheme.typography.labelLarge, + ) }, ), - ) + webConnectivityPreferences( - enabledCategoriesCount, - maxRuntimeEnabled, - maxRuntime, - ), + ) + if (autoRunEnabled) { + listOf( + SettingsItem( + title = Res.string.Settings_AutomatedTesting_RunAutomatically_WiFiOnly, + key = SettingsKey.AUTOMATED_TESTING_WIFIONLY, + type = PreferenceItemType.SWITCH, + enabled = autoRunEnabled && uploadResultsEnabled, + indentation = 1, + ), + SettingsItem( + title = Res.string.Settings_AutomatedTesting_RunAutomatically_ChargingOnly, + key = SettingsKey.AUTOMATED_TESTING_CHARGING, + type = PreferenceItemType.SWITCH, + enabled = autoRunEnabled && uploadResultsEnabled, + indentation = 1, + ), + SettingsItem( + title = Res.string.auto_test_not_uploaded_limit, + key = SettingsKey.AUTOMATED_TESTING_NOT_UPLOADED_LIMIT, + type = PreferenceItemType.INT, + enabled = autoRunEnabled && uploadResultsEnabled, + supportingContent = { + val value = ( + autoRunNotUploadedLimit + ?: BootstrapPreferences.NOT_UPLOADED_LIMIT_DEFAULT + ).coerceAtLeast(1) + Text(value.toString()) + }, + indentation = 1, + ), + ) + if (hasWebsitesDescriptor) { + listOfNotNull( + SettingsItem( + title = Res.string.Settings_Websites_MaxRuntimeEnabled, + key = SettingsKey.MAX_RUNTIME_ENABLED, + type = PreferenceItemType.SWITCH, + indentation = 1, + ), + if (maxRuntimeEnabled) { + SettingsItem( + title = Res.string.Settings_Websites_MaxRuntime, + key = SettingsKey.MAX_RUNTIME, + type = PreferenceItemType.INT, + supportingContent = { + maxRuntime?.let { + Text(it.coerceAtLeast(0).seconds.shortFormat()) + } + }, + indentation = 2, + ) + } else { + null + }, + ) + } else { + emptyList() + } + } else { + emptyList() + } + if (hasWebsitesDescriptor) { + listOf( + SettingsCategoryItem( + title = Res.string.Settings_Websites_Categories_Label, + route = PreferenceCategoryKey.WEBSITES_CATEGORIES, + supportingContent = { + Text( + stringResource( + Res.string.Settings_Websites_Categories_Description, + enabledCategoriesCount, + ), + ) + }, + settings = WebConnectivityCategory.entries.mapNotNull { cat -> + SettingsItem( + icon = cat.icon, + title = cat.title, + supportingContent = { Text(stringResource(cat.description)) }, + key = cat.settingsKey ?: return@mapNotNull null, + type = PreferenceItemType.SWITCH, + ) + }, + ), + ) + } else { + emptyList() + }, footerContent = { SettingsDescription( Res.string.Settings_AutomatedTesting_RunAutomatically_Footer, 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 a3064879..f39b19c6 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 @@ -161,9 +161,11 @@ fun Navigation( route = Screen.SettingsCategory.NAV_ROUTE, arguments = Screen.SettingsCategory.ARGUMENTS, ) { entry -> - val category = entry.arguments?.getString("category") ?: return@composable + val category = PreferenceCategoryKey.fromValue( + entry.arguments?.getString("category"), + ) ?: return@composable when (category) { - PreferenceCategoryKey.ABOUT_OONI.value -> { + PreferenceCategoryKey.ABOUT_OONI -> { val viewModel = viewModel { dependencies.aboutViewModel(onBack = { navController.goBack() }) } @@ -174,7 +176,7 @@ fun Navigation( ) } - PreferenceCategoryKey.PROXY.value -> { + PreferenceCategoryKey.PROXY -> { val viewModel = viewModel { dependencies.proxyViewModel(onBack = { navController.goBack() }) } @@ -182,7 +184,7 @@ fun Navigation( ProxyScreen(state, viewModel::onEvent) } - PreferenceCategoryKey.SEE_RECENT_LOGS.value -> { + PreferenceCategoryKey.SEE_RECENT_LOGS -> { val viewModel = viewModel { dependencies.logViewModel(onBack = { navController.goBack() }) } @@ -193,7 +195,7 @@ fun Navigation( else -> { val viewModel = viewModel { dependencies.settingsCategoryViewModel( - categoryKey = category, + categoryKey = category.value, goToSettingsForCategory = { navController.safeNavigate(Screen.SettingsCategory(it)) }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt index e0c07bcb..f685ef84 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt @@ -78,69 +78,73 @@ fun SettingsCategoryScreen( val category = state.category ?: return Column { category.settings?.forEach { preferenceItem -> - when (preferenceItem.type) { - PreferenceItemType.SWITCH -> - SwitchSettingsView( - icon = preferenceItem.icon, - title = preferenceItem.title, - key = preferenceItem.key, - checked = state.preferences[preferenceItem.key] == true, - enabled = preferenceItem.enabled, - supportingContent = preferenceItem.supportingContent, - onCheckedChange = { key, value -> - onEvent( - SettingsCategoryViewModel.Event.CheckedChangeClick( - key, - value, - ), - ) - }, - ) + Box( + modifier = Modifier.padding(start = 32.dp * preferenceItem.indentation), + ) { + when (preferenceItem.type) { + PreferenceItemType.SWITCH -> + SwitchSettingsView( + icon = preferenceItem.icon, + title = preferenceItem.title, + key = preferenceItem.key, + checked = state.preferences[preferenceItem.key] == true, + enabled = preferenceItem.enabled, + supportingContent = preferenceItem.supportingContent, + onCheckedChange = { key, value -> + onEvent( + SettingsCategoryViewModel.Event.CheckedChangeClick( + key, + value, + ), + ) + }, + ) - PreferenceItemType.INT -> - NumberPickerItem( - title = preferenceItem.title, - supportingContent = preferenceItem.supportingContent, - enabled = preferenceItem.enabled, - value = state.preferences[preferenceItem.key] as? Int, - onChanged = { - onEvent( - SettingsCategoryViewModel.Event.IntChanged( - preferenceItem.key, - it, - ), - ) - }, - ) + PreferenceItemType.INT -> + NumberPickerItem( + title = preferenceItem.title, + supportingContent = preferenceItem.supportingContent, + enabled = preferenceItem.enabled, + value = state.preferences[preferenceItem.key] as? Int, + onChanged = { + onEvent( + SettingsCategoryViewModel.Event.IntChanged( + preferenceItem.key, + it, + ), + ) + }, + ) - PreferenceItemType.BUTTON -> - RouteSettingsView( - title = preferenceItem.title, - supportingContent = preferenceItem.supportingContent, - trailingContent = preferenceItem.trailingContent, - ) + PreferenceItemType.BUTTON -> + RouteSettingsView( + title = preferenceItem.title, + supportingContent = preferenceItem.supportingContent, + trailingContent = preferenceItem.trailingContent, + ) - PreferenceItemType.ROUTE -> - RouteSettingsView( - title = preferenceItem.title, - supportingContent = preferenceItem.supportingContent, - modifier = - Modifier.clickable { - if (preferenceItem is SettingsCategoryItem) { - onEvent( - SettingsCategoryViewModel.Event.SettingsCategoryClick( - preferenceItem.route, - ), - ) - } - }, - ) + PreferenceItemType.ROUTE -> + RouteSettingsView( + title = preferenceItem.title, + supportingContent = preferenceItem.supportingContent, + modifier = + Modifier.clickable { + if (preferenceItem is SettingsCategoryItem) { + onEvent( + SettingsCategoryViewModel.Event.SettingsCategoryClick( + preferenceItem.route, + ), + ) + } + }, + ) - PreferenceItemType.SELECT -> - RouteSettingsView( - title = preferenceItem.title, - supportingContent = preferenceItem.supportingContent, - ) + PreferenceItemType.SELECT -> + RouteSettingsView( + title = preferenceItem.title, + supportingContent = preferenceItem.supportingContent, + ) + } } } category.footerContent?.let { diff --git a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt new file mode 100644 index 00000000..f36060c0 --- /dev/null +++ b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt @@ -0,0 +1,8 @@ +package org.ooni.probe.config + +object BuildTypesDefaults : BuildTypesDefaultsInterface { + override val ooniApiBaseUrl = "https://api.ooni.org" + override val ooniRunDomain = "run.ooni.org" + override val ooniRunDashboardUrl = "https://run.ooni.org" + override val explorerUrl = "https://explorer.test.ooni.org" +} diff --git a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt deleted file mode 100644 index 6838b82d..00000000 --- a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/OrganizationConfigInterface.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.ooni.probe.config - -interface OrganizationConfigInterface { - val baseSoftwareName: String - - val ooniApiBaseUrl: String - get() = "https://api.ooni.org" - - val ooniRunDomain: String - get() = "run.ooni.org" - - val ooniRunDashboardUrl: String - get() = "https://run.ooni.org" - - val explorerUrl: String - get() = "https://explorer.ooni.org" - - val testDisplayMode: TestDisplayMode - - val autorunTaskId: String - - val onboardingImages: OnboardingImages - - val updateDescriptorTaskId: String -} diff --git a/composeApp/src/dwMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt b/composeApp/src/dwMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt index 6237f0cf..fa7326fb 100644 --- a/composeApp/src/dwMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt +++ b/composeApp/src/dwMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt @@ -13,4 +13,5 @@ object OrganizationConfig : OrganizationConfigInterface { image2 = Res.drawable.onboarding, image3 = Res.drawable.onboarding, ) + override val hasWebsitesDescriptor = false } diff --git a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt new file mode 100644 index 00000000..284516ed --- /dev/null +++ b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt @@ -0,0 +1,8 @@ +package org.ooni.probe.domain + +import org.ooni.probe.data.models.PreferenceItem +import org.ooni.probe.data.models.SettingsKey + +fun organizationPreferenceDefaults(): List> { + return emptyList() +} diff --git a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt b/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt deleted file mode 100644 index 98fb257b..00000000 --- a/composeApp/src/dwMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.ooni.probe.domain - -import org.ooni.probe.data.models.PreferenceItem -import org.ooni.probe.data.models.SettingsKey - -fun webConnectivityPreferences( - enabledCategoriesCount: Int, - maxRuntimeEnabled: Boolean, - maxRuntime: Int?, -): List { - return emptyList() -} - -fun preferenceDefaults(): List> { - return emptyList() -} diff --git a/composeApp/src/ooniMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt b/composeApp/src/ooniMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt index e8ec35c7..87dc2ff9 100644 --- a/composeApp/src/ooniMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt +++ b/composeApp/src/ooniMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt @@ -15,4 +15,5 @@ object OrganizationConfig : OrganizationConfigInterface { image2 = Res.drawable.onboarding2, image3 = Res.drawable.onboarding3, ) + override val hasWebsitesDescriptor = true } diff --git a/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt b/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt new file mode 100644 index 00000000..31de858b --- /dev/null +++ b/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationPreferenceDefaults.kt @@ -0,0 +1,13 @@ +package org.ooni.probe.domain + +import org.ooni.engine.models.WebConnectivityCategory +import org.ooni.probe.data.models.SettingsKey + +fun organizationPreferenceDefaults(): List> { + return listOf( + SettingsKey.MAX_RUNTIME_ENABLED to true, + SettingsKey.MAX_RUNTIME to 90, + ) + WebConnectivityCategory.entries + .mapNotNull { it.settingsKey } + .map { it to true } +} diff --git a/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt b/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt deleted file mode 100644 index d6112600..00000000 --- a/composeApp/src/ooniMain/kotlin/org/ooni/probe/domain/OrganizationSettings.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.ooni.probe.domain - -import androidx.compose.material3.Text -import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Description -import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label -import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime -import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled -import org.jetbrains.compose.resources.stringResource -import org.ooni.engine.models.WebConnectivityCategory -import org.ooni.probe.data.models.PreferenceCategoryKey -import org.ooni.probe.data.models.PreferenceItem -import org.ooni.probe.data.models.PreferenceItemType -import org.ooni.probe.data.models.SettingsCategoryItem -import org.ooni.probe.data.models.SettingsItem -import org.ooni.probe.data.models.SettingsKey -import org.ooni.probe.ui.shared.shortFormat -import kotlin.time.Duration.Companion.seconds - -fun webConnectivityPreferences( - enabledCategoriesCount: Int, - maxRuntimeEnabled: Boolean, - maxRuntime: Int?, -): List { - return listOf( - SettingsCategoryItem( - title = Res.string.Settings_Websites_Categories_Label, - route = PreferenceCategoryKey.WEBSITES_CATEGORIES, - supportingContent = { - Text( - stringResource( - Res.string.Settings_Websites_Categories_Description, - enabledCategoriesCount, - ), - ) - }, - settings = WebConnectivityCategory.entries.mapNotNull { cat -> - SettingsItem( - icon = cat.icon, - title = cat.title, - supportingContent = { Text(stringResource(cat.description)) }, - key = cat.settingsKey ?: return@mapNotNull null, - type = PreferenceItemType.SWITCH, - ) - }, - ), - SettingsItem( - title = Res.string.Settings_Websites_MaxRuntimeEnabled, - key = SettingsKey.MAX_RUNTIME_ENABLED, - type = PreferenceItemType.SWITCH, - ), - SettingsItem( - title = Res.string.Settings_Websites_MaxRuntime, - key = SettingsKey.MAX_RUNTIME, - type = PreferenceItemType.INT, - enabled = maxRuntimeEnabled, - supportingContent = { - maxRuntime?.let { - Text(it.coerceAtLeast(0).seconds.shortFormat()) - } - }, - ), - ) -} - -fun preferenceDefaults(): List> { - return listOf( - SettingsKey.MAX_RUNTIME_ENABLED to true, - SettingsKey.MAX_RUNTIME to 90, - ) + WebConnectivityCategory.entries - .mapNotNull { it.settingsKey } - .map { it to true } -} From 261f9acc49de8b1fb2d8882a279f06a4801d65f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Tue, 26 Nov 2024 18:58:18 +0000 Subject: [PATCH 15/22] Fix limit website --- .../values/strings-common.xml | 1 + .../org/ooni/probe/domain/GetSettings.kt | 57 +++++++++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index 0650d913..299e38db 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -293,4 +293,5 @@ Unsupported URL Results are automatically uploaded to OONI explorer Tests will run every hour in the background + Only for manual runs diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt index 11eb869e..2db3816c 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt @@ -47,6 +47,7 @@ import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Des import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled +import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled_Description import ooniprobe.composeapp.generated.resources.advanced import ooniprobe.composeapp.generated.resources.auto_test_not_uploaded_limit import ooniprobe.composeapp.generated.resources.ic_settings @@ -191,33 +192,39 @@ class GetSettings( }, indentation = 1, ), - ) + if (hasWebsitesDescriptor) { - listOfNotNull( - SettingsItem( - title = Res.string.Settings_Websites_MaxRuntimeEnabled, - key = SettingsKey.MAX_RUNTIME_ENABLED, - type = PreferenceItemType.SWITCH, - indentation = 1, - ), - if (maxRuntimeEnabled) { - SettingsItem( - title = Res.string.Settings_Websites_MaxRuntime, - key = SettingsKey.MAX_RUNTIME, - type = PreferenceItemType.INT, - supportingContent = { - maxRuntime?.let { - Text(it.coerceAtLeast(0).seconds.shortFormat()) - } - }, - indentation = 2, + ) + } else { + emptyList() + } + if (hasWebsitesDescriptor) { + listOfNotNull( + SettingsItem( + title = Res.string.Settings_Websites_MaxRuntimeEnabled, + key = SettingsKey.MAX_RUNTIME_ENABLED, + type = PreferenceItemType.SWITCH, + supportingContent = { + Text( + stringResource(Res.string.Settings_Websites_MaxRuntimeEnabled_Description), + style = MaterialTheme.typography.labelLarge, ) - } else { - null }, - ) - } else { - emptyList() - } + indentation = 0, + ), + if (maxRuntimeEnabled) { + SettingsItem( + title = Res.string.Settings_Websites_MaxRuntime, + key = SettingsKey.MAX_RUNTIME, + type = PreferenceItemType.INT, + supportingContent = { + maxRuntime?.let { + Text(it.coerceAtLeast(0).seconds.shortFormat()) + } + }, + indentation = 1, + ) + } else { + null + }, + ) } else { emptyList() } + if (hasWebsitesDescriptor) { From 63e2baaa15f5ae1bfdffe0faf768643d631458d0 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Wed, 27 Nov 2024 12:11:10 +0100 Subject: [PATCH 16/22] fix: `mailto` on android (#311) --- .../src/commonMain/composeResources/values/untraslatable.xml | 2 +- .../kotlin/org/ooni/probe/domain/SendSupportEmail.kt | 1 - .../src/iosMain/kotlin/org/ooni/probe/SetupDependencies.kt | 5 ++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/values/untraslatable.xml b/composeApp/src/commonMain/composeResources/values/untraslatable.xml index 9bd0951e..d4ff1224 100644 --- a/composeApp/src/commonMain/composeResources/values/untraslatable.xml +++ b/composeApp/src/commonMain/composeResources/values/untraslatable.xml @@ -1,5 +1,5 @@ - mailto:bugs@openobservatory.org + bugs@openobservatory.org [bug-report] OONI Probe %1$s %1$s: %2$s diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/SendSupportEmail.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/SendSupportEmail.kt index 4289f140..29b4cd34 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/SendSupportEmail.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/SendSupportEmail.kt @@ -14,7 +14,6 @@ class SendSupportEmail( private val launchAction: (PlatformAction) -> Boolean, ) { suspend operator fun invoke() { - getString(Res.string.shareEmailTo) val supportEmail = getString(Res.string.shareEmailTo) val subject = getString(Res.string.shareSubject, platformInfo.version) val chooserTitle = getString(Res.string.Settings_SendEmail_Label) diff --git a/composeApp/src/iosMain/kotlin/org/ooni/probe/SetupDependencies.kt b/composeApp/src/iosMain/kotlin/org/ooni/probe/SetupDependencies.kt index 308d98bc..0ef4c7ff 100644 --- a/composeApp/src/iosMain/kotlin/org/ooni/probe/SetupDependencies.kt +++ b/composeApp/src/iosMain/kotlin/org/ooni/probe/SetupDependencies.kt @@ -162,7 +162,6 @@ class SetupDependencies( private fun sendMail(action: PlatformAction.Mail): Boolean { MFMailComposeViewController.canSendMail().let { canSendMail -> - val email = action.to.removePrefix("mailto:") if (canSendMail) { MFMailComposeViewController().apply { mailComposeDelegate = object : @@ -176,7 +175,7 @@ class SetupDependencies( controller.dismissViewControllerAnimated(true, null) } } - setToRecipients(listOf(email)) + setToRecipients(listOf(action.to)) setSubject(action.subject) setMessageBody(action.body, isHTML = false) }.let { @@ -188,7 +187,7 @@ class SetupDependencies( } return true } else { - UIPasteboard.generalPasteboard.string = email + UIPasteboard.generalPasteboard.string = action.to return false } } From d8194cb589fc3f3469880e9a7ba2f4b6f0880634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 27 Nov 2024 10:33:56 +0000 Subject: [PATCH 17/22] Use new string resources --- .../composeResources/values/strings-common.xml | 6 ++++-- .../{BuildTypesDefaults.kt => BuildTypeDefaults.kt} | 4 ++-- .../kotlin/org/ooni/probe/config/OrganizationConfig.kt | 10 +++++----- .../kotlin/org/ooni/probe/domain/GetSettings.kt | 8 ++++---- .../kotlin/org/ooni/probe/config/BuildTypeDefaults.kt | 4 ++-- 5 files changed, 17 insertions(+), 15 deletions(-) rename composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/{BuildTypesDefaults.kt => BuildTypeDefaults.kt} (63%) diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index 299e38db..2199b650 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -76,8 +76,8 @@ Only while charging By enabling automatic testing, OONI Probe tests will run automatically multiple times per day. Your test results will automatically get published on OONI Explorer: https://explorer.ooni.org/ \n\nImportant: If you have a VPN enabled, OONI Probe will not run tests automatically. Please turn off your VPN for automated OONI Probe testing. Learn more: https://ooni.org/support/faq/#can-i-run-ooni-probe-over-a-vpn - Limit Websites test duration - Maximum Websites test duration + Limit test duration + Test duration Website categories to test %1$s categories enabled @@ -292,6 +292,8 @@ Skip after this amount of results failed to upload Unsupported URL Results are automatically uploaded to OONI explorer + Limit Websites test duration + Maximum Websites test duration Tests will run every hour in the background Only for manual runs diff --git a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt similarity index 63% rename from composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt rename to composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt index a255ad36..4aca2826 100644 --- a/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypesDefaults.kt +++ b/composeApp/src/commonMain/debug/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt @@ -1,8 +1,8 @@ package org.ooni.probe.config -object BuildTypesDefaults : BuildTypesDefaultsInterface { +object BuildTypeDefaults : BuildTypeDefaultsInterface { override val ooniApiBaseUrl = "https://api.dev.ooni.io" override val ooniRunDomain = "run.test.ooni.org" override val ooniRunDashboardUrl = "https://run.test.ooni.org" - override val explorerUrl = "https://explorer.test.ooni.org" + override val explorerUrl = "https://explorer.ooni.org" } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt index ffe14918..4393c783 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/config/OrganizationConfig.kt @@ -10,13 +10,13 @@ interface OrganizationConfigInterface { val updateDescriptorTaskId: String val hasWebsitesDescriptor: Boolean - val ooniApiBaseUrl get() = BuildTypesDefaults.ooniApiBaseUrl - val ooniRunDomain get() = BuildTypesDefaults.ooniRunDomain - val ooniRunDashboardUrl get() = BuildTypesDefaults.ooniRunDashboardUrl - val explorerUrl get() = BuildTypesDefaults.explorerUrl + val ooniApiBaseUrl get() = BuildTypeDefaults.ooniApiBaseUrl + val ooniRunDomain get() = BuildTypeDefaults.ooniRunDomain + val ooniRunDashboardUrl get() = BuildTypeDefaults.ooniRunDashboardUrl + val explorerUrl get() = BuildTypeDefaults.explorerUrl } -interface BuildTypesDefaultsInterface { +interface BuildTypeDefaultsInterface { val ooniApiBaseUrl: String val ooniRunDomain: String val ooniRunDashboardUrl: String diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt index 2db3816c..b73a1ce4 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt @@ -45,9 +45,9 @@ import ooniprobe.composeapp.generated.resources.Settings_TestOptions_Label import ooniprobe.composeapp.generated.resources.Settings_WarmVPNInUse_Label import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Description import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label -import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime -import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled_Description +import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled_New +import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime_New import ooniprobe.composeapp.generated.resources.advanced import ooniprobe.composeapp.generated.resources.auto_test_not_uploaded_limit import ooniprobe.composeapp.generated.resources.ic_settings @@ -198,7 +198,7 @@ class GetSettings( } + if (hasWebsitesDescriptor) { listOfNotNull( SettingsItem( - title = Res.string.Settings_Websites_MaxRuntimeEnabled, + title = Res.string.Settings_Websites_MaxRuntimeEnabled_New, key = SettingsKey.MAX_RUNTIME_ENABLED, type = PreferenceItemType.SWITCH, supportingContent = { @@ -211,7 +211,7 @@ class GetSettings( ), if (maxRuntimeEnabled) { SettingsItem( - title = Res.string.Settings_Websites_MaxRuntime, + title = Res.string.Settings_Websites_MaxRuntime_New, key = SettingsKey.MAX_RUNTIME, type = PreferenceItemType.INT, supportingContent = { diff --git a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt index f36060c0..ce360acd 100644 --- a/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt +++ b/composeApp/src/commonMain/release/kotlin/org/ooni/probe/config/BuildTypeDefaults.kt @@ -1,8 +1,8 @@ package org.ooni.probe.config -object BuildTypesDefaults : BuildTypesDefaultsInterface { +object BuildTypeDefaults : BuildTypeDefaultsInterface { override val ooniApiBaseUrl = "https://api.ooni.org" override val ooniRunDomain = "run.ooni.org" override val ooniRunDashboardUrl = "https://run.ooni.org" - override val explorerUrl = "https://explorer.test.ooni.org" + override val explorerUrl = "https://explorer.ooni.org" } From 763a80a7f09d11bdbe53e7c36cf9eb2c8ad7594c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Thu, 28 Nov 2024 06:41:50 +0000 Subject: [PATCH 18/22] Upload missing results on start (#315) --- .../org/ooni/probe/background/RunWorker.kt | 4 +- .../values/strings-common.xml | 1 + .../commonMain/kotlin/org/ooni/probe/App.kt | 2 + .../probe/background/RunBackgroundTask.kt | 65 ++++++++++--------- .../probe/data/models/RunSpecification.kt | 16 +++-- .../kotlin/org/ooni/probe/di/Dependencies.kt | 2 +- .../probe/domain/GetAutoRunSpecification.kt | 4 +- .../probe/domain/GetTestDescriptorsBySpec.kt | 4 +- .../org/ooni/probe/domain/RunDescriptors.kt | 6 +- .../choosewebsites/ChooseWebsitesViewModel.kt | 2 +- .../ui/dashboard/RunBackgroundStateSection.kt | 4 +- .../ooni/probe/ui/result/ResultViewModel.kt | 2 +- .../org/ooni/probe/ui/run/RunViewModel.kt | 2 +- .../ui/choosewebsites/ChooseWebsitesTest.kt | 2 +- 14 files changed, 65 insertions(+), 51 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt index cb4e5a98..becd5f52 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt @@ -22,9 +22,9 @@ import kotlinx.serialization.encodeToString import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title -import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading import ooniprobe.composeapp.generated.resources.Notification_StopTest import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.UploadingMissingResults import ooniprobe.composeapp.generated.resources.notification_channel_name import org.jetbrains.compose.resources.getString import org.ooni.probe.AndroidApplication @@ -143,7 +143,7 @@ class RunWorker( val progress = state.uploaded + state.failedToUpload + 1 setContentText( getString( - Res.string.Modal_ResultsNotUploaded_Uploading, + Res.string.UploadingMissingResults, "$progress/${state.total}", ), ) diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index 2199b650..54499155 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -296,4 +296,5 @@ Maximum Websites test duration Tests will run every hour in the background Only for manual runs + Uploading missing results %1$s diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt index dcfcc190..cafd010c 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt @@ -24,6 +24,7 @@ import ooniprobe.composeapp.generated.resources.Res import org.jetbrains.compose.resources.getString import org.jetbrains.compose.ui.tooling.preview.Preview import org.ooni.probe.data.models.DeepLink +import org.ooni.probe.data.models.RunSpecification import org.ooni.probe.di.Dependencies import org.ooni.probe.shared.PlatformInfo import org.ooni.probe.ui.navigation.BottomNavigationBar @@ -99,6 +100,7 @@ fun App( dependencies.bootstrapPreferences() dependencies.configureDescriptorAutoUpdate() dependencies.fetchDescriptorUpdate(null) + dependencies.startSingleRunInner(RunSpecification.OnlyUploadMissingResults) } LaunchedEffect(Unit) { dependencies.finishInProgressData() diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt index 62889795..8a30e959 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt @@ -28,8 +28,8 @@ class RunBackgroundTask( private val uploadMissingMeasurements: (ResultModel.Id?) -> Flow, private val checkSkipAutoRunNotUploadedLimit: () -> Flow, private val getNetworkType: () -> NetworkType, - private val getAutoRunSpecification: suspend () -> RunSpecification, - private val runDescriptors: suspend (RunSpecification) -> Unit, + private val getAutoRunSpecification: suspend () -> RunSpecification.Full, + private val runDescriptors: suspend (RunSpecification.Full) -> Unit, private val setRunBackgroundState: ((RunBackgroundState) -> RunBackgroundState) -> Unit, private val getRunBackgroundState: () -> Flow, private val addRunCancelListener: (() -> Unit) -> Unit, @@ -44,52 +44,57 @@ class RunBackgroundTask( return@channelFlow } - val uploadCancelled = uploadMissingResults(isAutoRun = spec == null) - if (uploadCancelled) return@channelFlow + val isAutoRun = spec == null + if (isAutoRun || spec is RunSpecification.OnlyUploadMissingResults) { + val uploadCancelled = uploadMissingResults() + if (uploadCancelled) return@channelFlow + } + + if (spec is RunSpecification.OnlyUploadMissingResults) { + setRunBackgroundState { RunBackgroundState.Idle() } + return@channelFlow + } - runTests(spec) + runTests(spec as? RunSpecification.Full) // When a test is cancelled, sometimes the last measurement isn't uploaded getLatestResult().first()?.id.let { latestResultId -> val idleState = getRunBackgroundState().first() - uploadMissingResults(isAutoRun = spec == null, resultId = latestResultId) + uploadMissingResults(resultId = latestResultId) updateState(idleState) } }.onCompletion { clearRunCancelListeners() } - private suspend fun ProducerScope.uploadMissingResults( - isAutoRun: Boolean, - resultId: ResultModel.Id? = null, - ): Boolean { + private suspend fun ProducerScope.uploadMissingResults(resultId: ResultModel.Id? = null): Boolean { val autoUpload = getPreferenceValueByKey(SettingsKey.UPLOAD_RESULTS).first() == true - var isCancelled = false + if (!autoUpload) return false - if ((isAutoRun || resultId != null) && autoUpload) { - coroutineScope { - val uploadJob = async { - uploadMissingMeasurements(resultId) - .collectLatest { uploadState -> - updateState(RunBackgroundState.UploadingMissingResults(uploadState)) - } - } + var isCancelled = false - addRunCancelListener { - isCancelled = true - if (uploadJob.isActive) uploadJob.cancel() - CoroutineScope(Dispatchers.Default).launch { - updateState(RunBackgroundState.Stopping) + coroutineScope { + val uploadJob = async { + uploadMissingMeasurements(resultId) + .collectLatest { uploadState -> + updateState(RunBackgroundState.UploadingMissingResults(uploadState)) } - } + } - try { - uploadJob.await() - } catch (e: CancellationException) { - Logger.i("Upload Missing Results (result=$resultId): cancelled") + addRunCancelListener { + isCancelled = true + if (uploadJob.isActive) uploadJob.cancel() + CoroutineScope(Dispatchers.Default).launch { + updateState(RunBackgroundState.Stopping) } } + + try { + uploadJob.await() + } catch (e: CancellationException) { + Logger.i("Upload Missing Results (result=$resultId): cancelled") + } } if (isCancelled) { @@ -100,7 +105,7 @@ class RunBackgroundTask( return false } - private suspend fun ProducerScope.runTests(spec: RunSpecification?) { + private suspend fun ProducerScope.runTests(spec: RunSpecification.Full?) { if (checkSkipAutoRunNotUploadedLimit().first()) { Logger.i("Skipping auto-run tests: too many not-uploaded results") return diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt index 6b705764..3c2ffd18 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/RunSpecification.kt @@ -4,11 +4,17 @@ import kotlinx.serialization.Serializable import org.ooni.engine.models.TaskOrigin @Serializable -data class RunSpecification( - val tests: List, - val taskOrigin: TaskOrigin, - val isRerun: Boolean, -) { +sealed interface RunSpecification { + @Serializable + data object OnlyUploadMissingResults : RunSpecification + + @Serializable + data class Full( + val tests: List, + val taskOrigin: TaskOrigin, + val isRerun: Boolean, + ) : RunSpecification + @Serializable data class Test( val source: Source, 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 1867688d..1e5a9952 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt @@ -106,7 +106,7 @@ class Dependencies( @VisibleForTesting val buildDataStore: () -> DataStore, private val isBatteryCharging: () -> Boolean, - private val startSingleRunInner: (RunSpecification) -> Unit, + val startSingleRunInner: (RunSpecification) -> Unit, private val configureAutoRun: suspend (AutoRunParameters) -> Unit, val configureDescriptorAutoUpdate: suspend () -> Boolean, val fetchDescriptorUpdate: suspend (List?) -> Unit, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt index 4f5eaa7f..47455162 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetAutoRunSpecification.kt @@ -11,10 +11,10 @@ class GetAutoRunSpecification( private val getDescriptors: GetTestDescriptors, private val preferenceRepository: PreferenceRepository, ) { - suspend operator fun invoke(): RunSpecification { + suspend operator fun invoke(): RunSpecification.Full { val descriptors = getDescriptors().first().filterForAutoRun() - return RunSpecification( + return RunSpecification.Full( tests = descriptors.map { descriptor -> RunSpecification.Test( source = RunSpecification.Test.Source.fromDescriptor(descriptor), diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt index 4ec91cd5..89017c85 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetTestDescriptorsBySpec.kt @@ -9,7 +9,7 @@ import org.ooni.probe.data.models.RunSpecification class GetTestDescriptorsBySpec( private val getTestDescriptors: () -> Flow>, ) { - suspend operator fun invoke(spec: RunSpecification): List = + suspend operator fun invoke(spec: RunSpecification.Full): List = getTestDescriptors() .first() .filterNot { it.isExpired } @@ -30,7 +30,7 @@ class GetTestDescriptorsBySpec( } // Is this descriptor contained in the RunSpecification's list of tests - private fun RunSpecification.forDescriptor(descriptor: Descriptor) = + private fun RunSpecification.Full.forDescriptor(descriptor: Descriptor) = tests.firstOrNull { specTest -> when (descriptor.source) { is Descriptor.Source.Default -> { diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt index 529a47d1..58447056 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/RunDescriptors.kt @@ -20,7 +20,7 @@ import org.ooni.probe.shared.now import kotlin.time.Duration class RunDescriptors( - private val getTestDescriptorsBySpec: suspend (RunSpecification) -> List, + private val getTestDescriptorsBySpec: suspend (RunSpecification.Full) -> List, private val downloadUrls: suspend (TaskOrigin) -> Result, MkException>, private val storeResult: suspend (ResultModel) -> ResultModel.Id, private val markResultAsDone: suspend (ResultModel.Id) -> Unit, @@ -32,7 +32,7 @@ class RunDescriptors( private val getEnginePreferences: suspend () -> EnginePreferences, private val finishInProgressData: suspend () -> Unit, ) { - suspend operator fun invoke(spec: RunSpecification) { + suspend operator fun invoke(spec: RunSpecification.Full) { setRunBackgroundState { RunBackgroundState.RunningTests() } val descriptors = getTestDescriptorsBySpec(spec) @@ -55,7 +55,7 @@ class RunDescriptors( private suspend fun runDescriptorsCancellable( descriptors: List, - spec: RunSpecification, + spec: RunSpecification.Full, ) { addRunCancelListener { setRunBackgroundState { RunBackgroundState.Stopping } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt index 88cc5369..349f2ab1 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesViewModel.kt @@ -101,7 +101,7 @@ class ChooseWebsitesViewModel( } startBackgroundRun( - RunSpecification( + RunSpecification.Full( tests = listOf( RunSpecification.Test( source = RunSpecification.Test.Source.Default("websites"), diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt index 600af177..b73e0075 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt @@ -27,9 +27,9 @@ import ooniprobe.composeapp.generated.resources.Dashboard_Running_EstimatedTimeL import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title -import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading import ooniprobe.composeapp.generated.resources.OONIRun_Run import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.UploadingMissingResults import ooniprobe.composeapp.generated.resources.ic_timer import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -104,7 +104,7 @@ fun RunBackgroundStateSection( val progress = uploadState.uploaded + uploadState.failedToUpload + 1 Text( text = stringResource( - Res.string.Modal_ResultsNotUploaded_Uploading, + Res.string.UploadingMissingResults, "$progress/${uploadState.total}", ), style = MaterialTheme.typography.bodyLarge, 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 index 7c877b9d..d481da49 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultViewModel.kt @@ -103,7 +103,7 @@ class ResultViewModel( private fun getRerunSpecification(): RunSpecification? { val item = _state.value.result ?: return null - return RunSpecification( + return RunSpecification.Full( tests = listOf( RunSpecification.Test( source = RunSpecification.Test.Source.fromDescriptor(item.descriptor), diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt index 86941bb1..d37e8c4d 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunViewModel.kt @@ -224,7 +224,7 @@ class RunViewModel( .filter { it.isSelected } .map { it.item } } - return RunSpecification( + return RunSpecification.Full( tests = selectedTests.map { (descriptor, tests) -> RunSpecification.Test( diff --git a/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt b/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt index dcafeca0..7738b96a 100644 --- a/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt +++ b/composeApp/src/commonTest/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesTest.kt @@ -45,7 +45,7 @@ class ChooseWebsitesTest { } onNodeWithText("Test ${websites.size} URLs").performClick() - val spec = runSpec + val spec = runSpec as? RunSpecification.Full assertNotNull(spec) assertEquals(false, spec.isRerun) assertEquals(TaskOrigin.OoniRun, spec.taskOrigin) From 858421d8551d713ed4fdefa00a70ccd7319e9b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Thu, 28 Nov 2024 06:42:10 +0000 Subject: [PATCH 19/22] Fix run with VPN (#317) --- .../probe/background/RunBackgroundTask.kt | 2 +- .../probe/background/RunBackgroundTaskTest.kt | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt index 8a30e959..734422a4 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/background/RunBackgroundTask.kt @@ -111,7 +111,7 @@ class RunBackgroundTask( return } - if (getNetworkType() == NetworkType.VPN) { + if (getNetworkType() == NetworkType.VPN && spec == null) { Logger.i("Skipping auto-run tests: VPN enabled") return } diff --git a/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt b/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt new file mode 100644 index 00000000..c43dfc99 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt @@ -0,0 +1,101 @@ +package org.ooni.probe.background + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.ooni.engine.models.NetworkType +import org.ooni.engine.models.TaskOrigin +import org.ooni.probe.data.models.ResultModel +import org.ooni.probe.data.models.RunBackgroundState +import org.ooni.probe.data.models.RunSpecification +import org.ooni.probe.data.models.SettingsKey +import org.ooni.probe.domain.UploadMissingMeasurements +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class RunBackgroundTaskTest { + @Test + fun skipAutoRunIfVpnIsEnabled() = + runTest { + var wasRunDescriptorsCalled = false + val state = MutableStateFlow(RunBackgroundState.Idle()) + val subject = buildSubject( + getNetworkType = { NetworkType.VPN }, + getRunBackgroundState = { state }, + runDescriptors = { + wasRunDescriptorsCalled = true + state.value = RunBackgroundState.RunningTests() + delay(100) + state.value = RunBackgroundState.Idle() + }, + ) + + subject(null).collect() + + assertFalse(wasRunDescriptorsCalled) + } + + @Test + fun runManualRunIfVpnIsEnabled() = + runTest { + var wasRunDescriptorsCalled = false + val state = MutableStateFlow(RunBackgroundState.Idle()) + val subject = buildSubject( + getNetworkType = { NetworkType.VPN }, + getRunBackgroundState = { state }, + runDescriptors = { + wasRunDescriptorsCalled = true + state.value = RunBackgroundState.RunningTests() + delay(100) + state.value = RunBackgroundState.Idle() + }, + ) + + subject( + RunSpecification( + tests = emptyList(), + taskOrigin = TaskOrigin.OoniRun, + isRerun = false, + ), + ).collect() + + assertTrue(wasRunDescriptorsCalled) + } + + private fun buildSubject( + getPreferenceValueByKey: (SettingsKey) -> Flow = { flowOf(true) }, + uploadMissingMeasurements: (ResultModel.Id?) -> Flow = { emptyFlow() }, + checkSkipAutoRunNotUploadedLimit: () -> Flow = { flowOf(false) }, + getNetworkType: () -> NetworkType = { NetworkType.Wifi }, + getAutoRunSpecification: suspend () -> RunSpecification = { + RunSpecification( + tests = emptyList(), + taskOrigin = TaskOrigin.AutoRun, + isRerun = false, + ) + }, + runDescriptors: suspend (RunSpecification) -> Unit = {}, + setRunBackgroundState: ((RunBackgroundState) -> RunBackgroundState) -> Unit = {}, + getRunBackgroundState: () -> Flow = { flowOf(RunBackgroundState.Idle()) }, + addRunCancelListener: (() -> Unit) -> Unit = {}, + clearRunCancelListeners: () -> Unit = {}, + getLatestResult: () -> Flow = { flowOf(null) }, + ) = RunBackgroundTask( + getPreferenceValueByKey = getPreferenceValueByKey, + uploadMissingMeasurements = uploadMissingMeasurements, + checkSkipAutoRunNotUploadedLimit = checkSkipAutoRunNotUploadedLimit, + getNetworkType = getNetworkType, + getAutoRunSpecification = getAutoRunSpecification, + runDescriptors = runDescriptors, + setRunBackgroundState = setRunBackgroundState, + getRunBackgroundState = getRunBackgroundState, + addRunCancelListener = addRunCancelListener, + clearRunCancelListeners = clearRunCancelListeners, + getLatestResult = getLatestResult, + ) +} From 6c4d485a02a9952b31eaec0da2c31d0755503b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 27 Nov 2024 17:48:38 +0000 Subject: [PATCH 20/22] Prepare new strings to translate --- .../ooni/probe/uitesting/DescriptorsTest.kt | 1 - .../background/DescriptorUpdateWorker.kt | 4 +- .../org/ooni/probe/background/RunWorker.kt | 8 +- .../values/strings-common.xml | 90 ++++++++++--------- .../models/InstalledTestDescriptorModel.kt | 18 ++-- .../org/ooni/probe/domain/GetSettings.kt | 4 +- .../org/ooni/probe/domain/ShareLogFile.kt | 4 +- .../ui/choosewebsites/ChooseWebsitesScreen.kt | 4 +- .../ui/dashboard/RunBackgroundStateSection.kt | 4 +- .../probe/ui/descriptor/DescriptorScreen.kt | 4 +- .../kotlin/org/ooni/probe/ui/log/LogScreen.kt | 20 ++--- .../probe/ui/measurement/MeasurementScreen.kt | 12 +-- .../probe/ui/onboarding/OnboardingQuiz.kt | 8 +- .../probe/ui/result/ResultMeasurementCell.kt | 12 +-- .../org/ooni/probe/ui/result/ResultScreen.kt | 8 +- .../org/ooni/probe/ui/results/ResultCell.kt | 12 +-- .../probe/ui/results/ResultFilterViews.kt | 16 ++-- .../ooni/probe/ui/results/ResultsScreen.kt | 8 +- .../kotlin/org/ooni/probe/ui/run/RunScreen.kt | 16 ++-- .../ooni/probe/ui/running/RunningScreen.kt | 4 +- .../probe/ui/settings/about/AboutScreen.kt | 4 +- .../category/SettingsCategoryScreen.kt | 4 +- .../probe/ui/settings/proxy/ProxyScreen.kt | 4 +- .../org/ooni/probe/ui/shared/DateFormats.kt | 42 ++++++--- .../probe/background/RunBackgroundTaskTest.kt | 6 +- 25 files changed, 169 insertions(+), 148 deletions(-) diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt index 8ebf9bf4..1122f81b 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/DescriptorsTest.kt @@ -7,7 +7,6 @@ import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.junit4.createEmptyComposeRule -import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onLast import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt index 198bb07a..23b4825c 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/DescriptorUpdateWorker.kt @@ -17,8 +17,8 @@ import kotlinx.coroutines.flow.first import kotlinx.serialization.SerializationException import kotlinx.serialization.encodeToString import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running +import ooniprobe.composeapp.generated.resources.Notification_ChannelName import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.notification_channel_name import org.jetbrains.compose.resources.getString import org.ooni.probe.AndroidApplication import org.ooni.probe.R @@ -79,7 +79,7 @@ class DescriptorUpdateWorker( notificationManager.createNotificationChannel( NotificationChannel( NOTIFICATION_CHANNEL_ID, - getString(Res.string.notification_channel_name), + getString(Res.string.Notification_ChannelName), NotificationManager.IMPORTANCE_DEFAULT, ), ) diff --git a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt index becd5f52..2223fed2 100644 --- a/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt +++ b/composeApp/src/androidMain/kotlin/org/ooni/probe/background/RunWorker.kt @@ -22,10 +22,10 @@ import kotlinx.serialization.encodeToString import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notice import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title +import ooniprobe.composeapp.generated.resources.Notification_ChannelName import ooniprobe.composeapp.generated.resources.Notification_StopTest import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.UploadingMissingResults -import ooniprobe.composeapp.generated.resources.notification_channel_name +import ooniprobe.composeapp.generated.resources.Results_UploadingMissing import org.jetbrains.compose.resources.getString import org.ooni.probe.AndroidApplication import org.ooni.probe.MainActivity @@ -130,7 +130,7 @@ class RunWorker( notificationManager.createNotificationChannel( NotificationChannel( NOTIFICATION_CHANNEL_ID, - getString(Res.string.notification_channel_name), + getString(Res.string.Notification_ChannelName), NotificationManager.IMPORTANCE_LOW, ), ) @@ -143,7 +143,7 @@ class RunWorker( val progress = state.uploaded + state.failedToUpload + 1 setContentText( getString( - Res.string.UploadingMissingResults, + Res.string.Results_UploadingMissing, "$progress/${state.total}", ), ) diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index 54499155..efc266b3 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -232,15 +232,24 @@ OONI Probe cannot run automatically without battery optimization. Do you want to try again? - Last updated %1$s - Back - refresh - Measurement - - %1$d measurement - %1$d measurements + + Back + refresh + Collapse + Expand + %1$s ago + + %1$d minute + %1$d minutes + + + %1$d hour + %1$d hours - + %1$dh + %1$dm + %1$ds + January February March @@ -254,47 +263,46 @@ November December - %1$s ago - - %1$d minute - %1$d minutes - - - %1$d hour - %1$d hours - - %1$dh - %1$dm - %1$ds - Testing - Manual Run - Auto Run - Collapse - Expand + + Correct answer + Incorrect answer + + Last updated %1$s Run %1$d test Run %1$d tests - All Types - All Sources - VPN - Go to Settings > General > VPN and disconnect from your VPN. - Failed - OK - Anomaly - Correct answer - Incorrect answer - Logs - Share Logs - Error sharing logs - Filter Logs - Only the last %1$d results are shown - Skip after this amount of results failed to upload + Unsupported URL + + Measurement + + %1$d measurement + %1$d measurements + + Failed + OK + Anomaly + + All Types + All Sources + Only the last %1$d results are shown + Uploading missing results %1$s + + Logs + Share Logs + Error sharing logs + Filter Logs + Go to Settings > General > VPN and disconnect from your VPN. + Skip after this amount of results failed to upload Results are automatically uploaded to OONI explorer Limit Websites test duration Maximum Websites test duration Tests will run every hour in the background Only for manual runs - Uploading missing results %1$s + + Testing + Manual Run + Auto Run + VPN diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/InstalledTestDescriptorModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/InstalledTestDescriptorModel.kt index 9986f5ee..15d1a7a1 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/InstalledTestDescriptorModel.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/models/InstalledTestDescriptorModel.kt @@ -2,24 +2,24 @@ package org.ooni.probe.data.models import co.touchlab.kermit.Logger import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalDateTime.Companion.Format +import kotlinx.datetime.format +import kotlinx.datetime.format.MonthNames +import kotlinx.datetime.format.char import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import ooniprobe.composeapp.generated.resources.Common_Months import ooniprobe.composeapp.generated.resources.Dashboard_Runv2_Overview_Description +import ooniprobe.composeapp.generated.resources.Dashboard_Runv2_Overview_LastUpdated import ooniprobe.composeapp.generated.resources.Res -import org.jetbrains.compose.resources.stringResource -import kotlinx.datetime.LocalDateTime.Companion.Format -import kotlinx.datetime.format -import kotlinx.datetime.format.MonthNames -import kotlinx.datetime.format.char -import ooniprobe.composeapp.generated.resources.Dashboard_Runv2_Overview_LastUpdatd -import ooniprobe.composeapp.generated.resources.months import ooniprobe.composeapp.generated.resources.test_circumvention import ooniprobe.composeapp.generated.resources.test_experimental import ooniprobe.composeapp.generated.resources.test_instant_messaging import ooniprobe.composeapp.generated.resources.test_performance import ooniprobe.composeapp.generated.resources.test_websites import org.jetbrains.compose.resources.stringArrayResource +import org.jetbrains.compose.resources.stringResource import org.ooni.probe.data.TestDescriptor import org.ooni.probe.shared.InstalledDescriptorIcons import org.ooni.probe.shared.hexToColor @@ -65,7 +65,7 @@ fun InstalledTestDescriptorModel.toDescriptor(updateStatus: UpdateStatus = Updat shortDescription = { shortDescriptionIntl?.getCurrent() ?: shortDescription }, description = { descriptionIntl?.getCurrent() ?: description }, metadata = { - val monthNames = stringArrayResource(Res.array.months) + val monthNames = stringArrayResource(Res.array.Common_Months) val formattedDate = { date: LocalDateTime? -> date?.format(dateTimeFormat(monthNames)) } formattedDate(dateCreated)?.let { formattedDateCreated -> stringResource( @@ -73,7 +73,7 @@ fun InstalledTestDescriptorModel.toDescriptor(updateStatus: UpdateStatus = Updat author.orEmpty(), formattedDateCreated, ) + ". " + formattedDate(dateUpdated)?.let { - stringResource(Res.string.Dashboard_Runv2_Overview_LastUpdatd, it) + stringResource(Res.string.Dashboard_Runv2_Overview_LastUpdated, it) } } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt index b73a1ce4..b03b1f9b 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/GetSettings.kt @@ -26,6 +26,7 @@ import ooniprobe.composeapp.generated.resources.Settings_About_Label import ooniprobe.composeapp.generated.resources.Settings_Advanced_DebugLogs import ooniprobe.composeapp.generated.resources.Settings_Advanced_Label import ooniprobe.composeapp.generated.resources.Settings_Advanced_RecentLogs +import ooniprobe.composeapp.generated.resources.Settings_AutoTest_NotUploadedLimit import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_ChargingOnly import ooniprobe.composeapp.generated.resources.Settings_AutomatedTesting_RunAutomatically_Description @@ -49,7 +50,6 @@ import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnab import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntimeEnabled_New import ooniprobe.composeapp.generated.resources.Settings_Websites_MaxRuntime_New import ooniprobe.composeapp.generated.resources.advanced -import ooniprobe.composeapp.generated.resources.auto_test_not_uploaded_limit import ooniprobe.composeapp.generated.resources.ic_settings import ooniprobe.composeapp.generated.resources.notifications import ooniprobe.composeapp.generated.resources.outline_info @@ -179,7 +179,7 @@ class GetSettings( indentation = 1, ), SettingsItem( - title = Res.string.auto_test_not_uploaded_limit, + title = Res.string.Settings_AutoTest_NotUploadedLimit, key = SettingsKey.AUTOMATED_TESTING_NOT_UPLOADED_LIMIT, type = PreferenceItemType.INT, enabled = autoRunEnabled && uploadResultsEnabled, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/ShareLogFile.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/ShareLogFile.kt index 6835155b..b07833cb 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/ShareLogFile.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/domain/ShareLogFile.kt @@ -2,7 +2,7 @@ package org.ooni.probe.domain import okio.Path import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.logs +import ooniprobe.composeapp.generated.resources.Settings_Logs import org.jetbrains.compose.resources.getString import org.ooni.probe.data.models.PlatformAction @@ -13,7 +13,7 @@ class ShareLogFile( suspend operator fun invoke(): Boolean = shareFile( PlatformAction.FileSharing( - title = getString(Res.string.logs), + title = getString(Res.string.Settings_Logs), filePath = getAppLoggerFile(), ), ) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesScreen.kt index cad7e5a5..515aa9ea 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/choosewebsites/ChooseWebsitesScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.CustomWebsites_Fab_Text import ooniprobe.composeapp.generated.resources.Modal_Cancel import ooniprobe.composeapp.generated.resources.Modal_CustomURL_NotSaved @@ -39,7 +40,6 @@ import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Settings_Websites_CustomURL_Add import ooniprobe.composeapp.generated.resources.Settings_Websites_CustomURL_Title import ooniprobe.composeapp.generated.resources.Settings_Websites_CustomURL_URL -import ooniprobe.composeapp.generated.resources.back import ooniprobe.composeapp.generated.resources.ic_add import ooniprobe.composeapp.generated.resources.ic_cancel import ooniprobe.composeapp.generated.resources.ic_timer @@ -63,7 +63,7 @@ fun ChooseWebsitesScreen( IconButton(onClick = { onEvent(ChooseWebsitesViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt index b73e0075..80329586 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/dashboard/RunBackgroundStateSection.kt @@ -29,7 +29,7 @@ import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notic import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title import ooniprobe.composeapp.generated.resources.OONIRun_Run import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.UploadingMissingResults +import ooniprobe.composeapp.generated.resources.Results_UploadingMissing import ooniprobe.composeapp.generated.resources.ic_timer import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -104,7 +104,7 @@ fun RunBackgroundStateSection( val progress = uploadState.uploaded + uploadState.failedToUpload + 1 Text( text = stringResource( - Res.string.UploadingMissingResults, + Res.string.Results_UploadingMissing, "$progress/${uploadState.total}", ), style = MaterialTheme.typography.bodyLarge, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/descriptor/DescriptorScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/descriptor/DescriptorScreen.kt index 3c6e5c08..2e214413 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/descriptor/DescriptorScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/descriptor/DescriptorScreen.kt @@ -39,13 +39,13 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import ooniprobe.composeapp.generated.resources.AddDescriptor_AutoRun import ooniprobe.composeapp.generated.resources.AddDescriptor_Settings +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Dashboard_Overview_ChooseWebsites import ooniprobe.composeapp.generated.resources.Dashboard_Overview_Estimated import ooniprobe.composeapp.generated.resources.Dashboard_Overview_LastRun_Never import ooniprobe.composeapp.generated.resources.Dashboard_Overview_LatestTest import ooniprobe.composeapp.generated.resources.Dashboard_Runv2_Overview_ReviewUpdates import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.back import ooniprobe.composeapp.generated.resources.ooni_empty_state import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -91,7 +91,7 @@ fun DescriptorScreen( IconButton(onClick = { onEvent(DescriptorViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt index 8599de96..ee6588eb 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt @@ -34,14 +34,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import co.touchlab.kermit.Severity +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.Settings_FilterLogs +import ooniprobe.composeapp.generated.resources.Settings_Logs +import ooniprobe.composeapp.generated.resources.Settings_ShareLogs +import ooniprobe.composeapp.generated.resources.Settings_ShareLogs_Error import ooniprobe.composeapp.generated.resources.Settings_Storage_Delete -import ooniprobe.composeapp.generated.resources.back -import ooniprobe.composeapp.generated.resources.filter_logs import ooniprobe.composeapp.generated.resources.ic_delete_all -import ooniprobe.composeapp.generated.resources.logs -import ooniprobe.composeapp.generated.resources.share_logs -import ooniprobe.composeapp.generated.resources.share_logs_error import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -57,12 +57,12 @@ fun LogScreen( ) { Column(Modifier.background(MaterialTheme.colorScheme.background)) { TopBar( - title = { Text(stringResource(Res.string.logs)) }, + title = { Text(stringResource(Res.string.Settings_Logs)) }, navigationIcon = { IconButton(onClick = { onEvent(LogViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, @@ -76,7 +76,7 @@ fun LogScreen( IconButton(onClick = { onEvent(LogViewModel.Event.ShareClicked) }) { Icon( Icons.Default.Share, - contentDescription = stringResource(Res.string.share_logs), + contentDescription = stringResource(Res.string.Settings_ShareLogs), ) } }, @@ -90,7 +90,7 @@ fun LogScreen( .padding(bottom = 8.dp), ) { Text( - stringResource(Res.string.filter_logs), + stringResource(Res.string.Settings_FilterLogs), modifier = Modifier.weight(2f), ) SeverityFilter( @@ -129,7 +129,7 @@ fun LogScreen( snackbarHostState.showSnackbar( getString( when (error) { - LogViewModel.Error.Share -> Res.string.share_logs_error + LogViewModel.Error.Share -> Res.string.Settings_ShareLogs_Error }, ), ) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt index ac4e6bed..2e5e8310 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/measurement/MeasurementScreen.kt @@ -28,10 +28,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back +import ooniprobe.composeapp.generated.resources.Common_Refresh +import ooniprobe.composeapp.generated.resources.Measurement_Title import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.back -import ooniprobe.composeapp.generated.resources.measurement -import ooniprobe.composeapp.generated.resources.refresh import org.intellij.markdown.html.urlEncode import org.jetbrains.compose.resources.stringResource import org.ooni.probe.config.OrganizationConfig @@ -56,13 +56,13 @@ fun MeasurementScreen( Box { TopBar( title = { - Text(stringResource(Res.string.measurement)) + Text(stringResource(Res.string.Measurement_Title)) }, navigationIcon = { IconButton(onClick = { onBack() }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, @@ -93,7 +93,7 @@ fun MeasurementScreen( ) { Icon( Icons.Default.Refresh, - contentDescription = stringResource(Res.string.refresh), + contentDescription = stringResource(Res.string.Common_Refresh), ) } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingQuiz.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingQuiz.kt index 76247808..e822a1c7 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingQuiz.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/onboarding/OnboardingQuiz.kt @@ -39,9 +39,9 @@ import ooniprobe.composeapp.generated.resources.Onboarding_PopQuiz_Title import ooniprobe.composeapp.generated.resources.Onboarding_PopQuiz_True import ooniprobe.composeapp.generated.resources.Onboarding_PopQuiz_Wrong_Button_Back import ooniprobe.composeapp.generated.resources.Onboarding_PopQuiz_Wrong_Button_Continue +import ooniprobe.composeapp.generated.resources.Onboarding_QuizAnswer_Correct +import ooniprobe.composeapp.generated.resources.Onboarding_QuizAnswer_Incorrect import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.quiz_answer_correct -import ooniprobe.composeapp.generated.resources.quiz_answer_incorrect import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.ooni.probe.data.models.Animation @@ -200,9 +200,9 @@ private fun ResultAnimation( animation = if (isCorrect) Animation.QuizCorrect else Animation.QuizIncorrect, contentDescription = stringResource( if (isCorrect) { - Res.string.quiz_answer_correct + Res.string.Onboarding_QuizAnswer_Correct } else { - Res.string.quiz_answer_incorrect + Res.string.Onboarding_QuizAnswer_Incorrect }, ), restartOnPlay = false, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultMeasurementCell.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultMeasurementCell.kt index 70a09210..829d52ab 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultMeasurementCell.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultMeasurementCell.kt @@ -17,6 +17,9 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Measurements_Anomaly +import ooniprobe.composeapp.generated.resources.Measurements_Failed +import ooniprobe.composeapp.generated.resources.Measurements_Ok import ooniprobe.composeapp.generated.resources.Modal_UploadFailed_Title import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Snackbar_ResultsNotUploaded_Text @@ -24,9 +27,6 @@ import ooniprobe.composeapp.generated.resources.ic_cloud_off import ooniprobe.composeapp.generated.resources.ic_measurement_anomaly import ooniprobe.composeapp.generated.resources.ic_measurement_failed import ooniprobe.composeapp.generated.resources.ic_measurement_ok -import ooniprobe.composeapp.generated.resources.measurement_anomaly -import ooniprobe.composeapp.generated.resources.measurement_failed -import ooniprobe.composeapp.generated.resources.measurement_ok import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.TestType @@ -111,9 +111,9 @@ fun ResultMeasurementCell( ), contentDescription = stringResource( when { - isFailed -> Res.string.measurement_failed - measurement.isAnomaly -> Res.string.measurement_anomaly - else -> Res.string.measurement_ok + isFailed -> Res.string.Measurements_Failed + measurement.isAnomaly -> Res.string.Measurements_Anomaly + else -> Res.string.Measurements_Ok }, ), tint = Color.Unspecified, 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 index 3127dc6b..3d2429a4 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/result/ResultScreen.kt @@ -41,10 +41,12 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Modal_Cancel import ooniprobe.composeapp.generated.resources.Modal_ReRun_Title import ooniprobe.composeapp.generated.resources.Modal_ReRun_Websites_Run import ooniprobe.composeapp.generated.resources.Modal_ReRun_Websites_Title +import ooniprobe.composeapp.generated.resources.NetworkType_Vpn import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.TestResults_NotAvailable import ooniprobe.composeapp.generated.resources.TestResults_Overview_Hero_DataUsage @@ -58,12 +60,10 @@ import ooniprobe.composeapp.generated.resources.TestResults_Summary_Hero_Runtime import ooniprobe.composeapp.generated.resources.TestResults_Summary_Hero_WiFi import ooniprobe.composeapp.generated.resources.TestResults_Summary_Performance_Hero_Download import ooniprobe.composeapp.generated.resources.TestResults_Summary_Performance_Hero_Upload -import ooniprobe.composeapp.generated.resources.back import ooniprobe.composeapp.generated.resources.ic_download import ooniprobe.composeapp.generated.resources.ic_replay import ooniprobe.composeapp.generated.resources.ic_upload import ooniprobe.composeapp.generated.resources.ooni_bw -import ooniprobe.composeapp.generated.resources.vpn import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.NetworkType @@ -94,7 +94,7 @@ fun ResultScreen( IconButton(onClick = { onEvent(ResultViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, @@ -316,7 +316,7 @@ private fun NetworkType.label(): String = when (this) { NetworkType.Mobile -> Res.string.TestResults_Summary_Hero_Mobile NetworkType.NoInternet -> Res.string.TestResults_Summary_Hero_NoInternet - NetworkType.VPN -> Res.string.vpn + NetworkType.VPN -> Res.string.NetworkType_Vpn NetworkType.Wifi -> Res.string.TestResults_Summary_Hero_WiFi is NetworkType.Unknown -> Res.string.TestResults_NotAvailable }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt index 3ae7627a..f8dcdb1d 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt @@ -18,14 +18,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Measurements_Count import ooniprobe.composeapp.generated.resources.Modal_UploadFailed_Title import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Snackbar_ResultsNotUploaded_Text +import ooniprobe.composeapp.generated.resources.TaskOrigin_AutoRun +import ooniprobe.composeapp.generated.resources.TaskOrigin_Manual import ooniprobe.composeapp.generated.resources.TestResults_UnknownASN import ooniprobe.composeapp.generated.resources.ic_cloud_off -import ooniprobe.composeapp.generated.resources.measurements_count -import ooniprobe.composeapp.generated.resources.task_origin_auto_run -import ooniprobe.composeapp.generated.resources.task_origin_manual import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource @@ -106,8 +106,8 @@ fun ResultCell( Text( stringResource( when (item.result.taskOrigin) { - TaskOrigin.AutoRun -> Res.string.task_origin_auto_run - TaskOrigin.OoniRun -> Res.string.task_origin_manual + TaskOrigin.AutoRun -> Res.string.TaskOrigin_AutoRun + TaskOrigin.OoniRun -> Res.string.TaskOrigin_Manual }, ), style = MaterialTheme.typography.labelLarge, @@ -116,7 +116,7 @@ fun ResultCell( if (!hasError) { Text( pluralStringResource( - Res.plurals.measurements_count, + Res.plurals.Measurements_Count, item.measurementsCount.toInt(), item.measurementsCount, ), diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultFilterViews.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultFilterViews.kt index 6c9b0b88..611c2a25 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultFilterViews.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultFilterViews.kt @@ -13,10 +13,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.task_origin_all -import ooniprobe.composeapp.generated.resources.task_origin_auto_run -import ooniprobe.composeapp.generated.resources.task_origin_manual -import ooniprobe.composeapp.generated.resources.test_type_all +import ooniprobe.composeapp.generated.resources.Results_TaskOrigin_All +import ooniprobe.composeapp.generated.resources.Results_TestType_All +import ooniprobe.composeapp.generated.resources.TaskOrigin_AutoRun +import ooniprobe.composeapp.generated.resources.TaskOrigin_Manual import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.TaskOrigin import org.ooni.probe.data.models.Descriptor @@ -108,16 +108,16 @@ fun OriginFilter( @Composable private fun ResultFilter.Type.label() = when (this) { - ResultFilter.Type.All -> stringResource(Res.string.test_type_all) + ResultFilter.Type.All -> stringResource(Res.string.Results_TestType_All) is ResultFilter.Type.One -> value.title() } @Composable private fun ResultFilter.Type.name() = when (this) { - ResultFilter.Type.All -> stringResource(Res.string.task_origin_all) + ResultFilter.Type.All -> stringResource(Res.string.Results_TaskOrigin_All) is ResultFilter.Type.One -> when (value) { - TaskOrigin.AutoRun -> stringResource(Res.string.task_origin_auto_run) - TaskOrigin.OoniRun -> stringResource(Res.string.task_origin_manual) + TaskOrigin.AutoRun -> stringResource(Res.string.TaskOrigin_AutoRun) + TaskOrigin.OoniRun -> stringResource(Res.string.TaskOrigin_Manual) } } 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 index ebe9d25a..59d10032 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt @@ -42,10 +42,12 @@ import kotlinx.datetime.LocalDate.Companion.Format import kotlinx.datetime.format import kotlinx.datetime.format.MonthNames import kotlinx.datetime.format.char +import ooniprobe.composeapp.generated.resources.Common_Months import ooniprobe.composeapp.generated.resources.Modal_Cancel import ooniprobe.composeapp.generated.resources.Modal_Delete import ooniprobe.composeapp.generated.resources.Modal_DoYouWantToDeleteAllTests import ooniprobe.composeapp.generated.resources.Res +import ooniprobe.composeapp.generated.resources.Results_LimitedNotice import ooniprobe.composeapp.generated.resources.Snackbar_ResultsSomeNotUploaded_Text import ooniprobe.composeapp.generated.resources.Snackbar_ResultsSomeNotUploaded_UploadAll import ooniprobe.composeapp.generated.resources.TestResults_Overview_FilterTests @@ -59,9 +61,7 @@ import ooniprobe.composeapp.generated.resources.TestResults_Summary_Performance_ import ooniprobe.composeapp.generated.resources.ic_delete_all import ooniprobe.composeapp.generated.resources.ic_download import ooniprobe.composeapp.generated.resources.ic_upload -import ooniprobe.composeapp.generated.resources.months import ooniprobe.composeapp.generated.resources.ooni_empty_state -import ooniprobe.composeapp.generated.resources.results_limited_notice import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringArrayResource import org.jetbrains.compose.resources.stringResource @@ -151,7 +151,7 @@ fun ResultsScreen( item("limited") { Text( text = stringResource( - Res.string.results_limited_notice, + Res.string.Results_LimitedNotice, state.filter.limit, ), style = MaterialTheme.typography.labelLarge, @@ -316,7 +316,7 @@ private fun Summary(summary: ResultsViewModel.Summary?) { @Composable private fun ResultDateHeader(date: LocalDate) { - val monthNames = stringArrayResource(Res.array.months) + val monthNames = stringArrayResource(Res.array.Common_Months) Text( date.format( Format { diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt index 336d359c..151def90 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt @@ -43,6 +43,9 @@ import androidx.compose.ui.platform.testTag import androidx.compose.ui.semantics.Role import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back +import ooniprobe.composeapp.generated.resources.Common_Collapse +import ooniprobe.composeapp.generated.resources.Common_Expand import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_Description import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_SelectAll @@ -55,10 +58,7 @@ import ooniprobe.composeapp.generated.resources.Modal_DisableVPN_Title import ooniprobe.composeapp.generated.resources.Modal_OK import ooniprobe.composeapp.generated.resources.Modal_RunAnyway import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.back -import ooniprobe.composeapp.generated.resources.collapse -import ooniprobe.composeapp.generated.resources.disable_vpn_instructions -import ooniprobe.composeapp.generated.resources.expand +import ooniprobe.composeapp.generated.resources.Settings_DisableVpnInstructions import ooniprobe.composeapp.generated.resources.ic_keyboard_arrow_down import ooniprobe.composeapp.generated.resources.ic_keyboard_arrow_up import ooniprobe.composeapp.generated.resources.ic_timer @@ -91,7 +91,7 @@ fun RunScreen( IconButton(onClick = { onEvent(RunViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, @@ -291,9 +291,9 @@ private fun DescriptorItem( ), contentDescription = stringResource( if (descriptorItem.isExpanded) { - Res.string.collapse + Res.string.Common_Collapse } else { - Res.string.expand + Res.string.Common_Expand }, ), ) @@ -380,7 +380,7 @@ private fun DisableVpnInstructionsDialog(onDismiss: () -> Unit) { AlertDialog( onDismissRequest = { onDismiss() }, title = { Text(stringResource(Res.string.Modal_DisableVPN)) }, - text = { Text(stringResource(Res.string.disable_vpn_instructions)) }, + text = { Text(stringResource(Res.string.Settings_DisableVpnInstructions)) }, confirmButton = { TextButton(onClick = { onDismiss() }) { Text(stringResource(Res.string.Modal_OK)) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/running/RunningScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/running/RunningScreen.kt index be11f3ca..4f6298cc 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/running/RunningScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/running/RunningScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Dashboard_Running_EstimatedTimeLeft import ooniprobe.composeapp.generated.resources.Dashboard_Running_ProxyInUse import ooniprobe.composeapp.generated.resources.Dashboard_Running_Running @@ -41,7 +42,6 @@ import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Notic import ooniprobe.composeapp.generated.resources.Dashboard_Running_Stopping_Title import ooniprobe.composeapp.generated.resources.Notification_StopTest import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.back import ooniprobe.composeapp.generated.resources.ooni_empty_state import ooniprobe.composeapp.generated.resources.test_circumvention import org.jetbrains.compose.resources.painterResource @@ -73,7 +73,7 @@ fun RunningScreen( IconButton(onClick = { onEvent(RunningViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/about/AboutScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/about/AboutScreen.kt index c26cd225..0d367611 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/about/AboutScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/about/AboutScreen.kt @@ -26,9 +26,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Settings_About_Content_Paragraph -import ooniprobe.composeapp.generated.resources.back import ooniprobe.composeapp.generated.resources.version import org.jetbrains.compose.resources.stringResource import org.ooni.probe.ui.shared.MarkdownViewer @@ -53,7 +53,7 @@ fun AboutScreen( IconButton(onClick = { onEvent(AboutViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt index f685ef84..6d04f733 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/category/SettingsCategoryScreen.kt @@ -38,10 +38,10 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Modal_Cancel import ooniprobe.composeapp.generated.resources.Modal_OK import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.back import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.painterResource @@ -65,7 +65,7 @@ fun SettingsCategoryScreen( IconButton(onClick = { onEvent(SettingsCategoryViewModel.Event.BackClicked) }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyScreen.kt index ea271a8a..1e496573 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/settings/proxy/ProxyScreen.kt @@ -35,12 +35,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Settings_Proxy_Custom_Hostname import ooniprobe.composeapp.generated.resources.Settings_Proxy_Custom_Port import ooniprobe.composeapp.generated.resources.Settings_Proxy_Custom_Protocol import ooniprobe.composeapp.generated.resources.Settings_Proxy_Enabled -import ooniprobe.composeapp.generated.resources.back import org.jetbrains.compose.resources.stringResource import org.ooni.probe.data.models.ProxyProtocol import org.ooni.probe.data.models.ProxyType @@ -62,7 +62,7 @@ fun ProxyScreen( }) { Icon( Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = stringResource(Res.string.back), + contentDescription = stringResource(Res.string.Common_Back), ) } }, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt index 740e1db0..15eccc41 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt @@ -8,13 +8,13 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.format import kotlinx.datetime.format.char import kotlinx.datetime.toInstant +import ooniprobe.composeapp.generated.resources.Common_Ago +import ooniprobe.composeapp.generated.resources.Common_Hours +import ooniprobe.composeapp.generated.resources.Common_Hours_Abbreviated +import ooniprobe.composeapp.generated.resources.Common_Minutes +import ooniprobe.composeapp.generated.resources.Common_Minutes_Abbreviated +import ooniprobe.composeapp.generated.resources.Common_Seconds_Abbreviated import ooniprobe.composeapp.generated.resources.Res -import ooniprobe.composeapp.generated.resources.ago -import ooniprobe.composeapp.generated.resources.hours -import ooniprobe.composeapp.generated.resources.hours_abbreviated -import ooniprobe.composeapp.generated.resources.minutes -import ooniprobe.composeapp.generated.resources.minutes_abbreviated -import ooniprobe.composeapp.generated.resources.seconds_abbreviated import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource import org.ooni.probe.shared.today @@ -44,12 +44,12 @@ fun LocalDateTime.relativeDateTime(): String = val diff = (Clock.System.now() - toInstant(TimeZone.currentSystemDefault())) val diffString = diff.toComponents { hours, minutes, _, _ -> if (hours > 0) { - pluralStringResource(Res.plurals.hours, hours.toInt(), hours.toInt()) + pluralStringResource(Res.plurals.Common_Hours, hours.toInt(), hours.toInt()) } else { - pluralStringResource(Res.plurals.minutes, minutes, minutes) + pluralStringResource(Res.plurals.Common_Minutes, minutes, minutes) } } - stringResource(Res.string.ago, diffString) + stringResource(Res.string.Common_Ago, diffString) } else { longFormat() } @@ -61,9 +61,23 @@ fun LocalDateTime.logFormat(): String = format(logDateTimeFormat) @Composable fun Duration.shortFormat(): String = toComponents { hours, minutes, seconds, _ -> - (if (hours > 0) stringResource(Res.string.hours_abbreviated, hours.toInt()) else "") + - " " + - (if (minutes > 0) stringResource(Res.string.minutes_abbreviated, minutes) else "") + - " " + - (if (seconds > 0) stringResource(Res.string.seconds_abbreviated, seconds) else "") + ( + if (hours > 0) { + stringResource(Res.string.Common_Hours_Abbreviated, hours.toInt()) + } else { + "" + } + ) + " " + ( + if (minutes > 0) { + stringResource(Res.string.Common_Minutes_Abbreviated, minutes) + } else { + "" + } + ) + " " + ( + if (seconds > 0) { + stringResource(Res.string.Common_Seconds_Abbreviated, seconds) + } else { + "" + } + ) }.trimStart() diff --git a/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt b/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt index c43dfc99..d77295fa 100644 --- a/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt +++ b/composeApp/src/commonTest/kotlin/org/ooni/probe/background/RunBackgroundTaskTest.kt @@ -57,7 +57,7 @@ class RunBackgroundTaskTest { ) subject( - RunSpecification( + RunSpecification.Full( tests = emptyList(), taskOrigin = TaskOrigin.OoniRun, isRerun = false, @@ -72,8 +72,8 @@ class RunBackgroundTaskTest { uploadMissingMeasurements: (ResultModel.Id?) -> Flow = { emptyFlow() }, checkSkipAutoRunNotUploadedLimit: () -> Flow = { flowOf(false) }, getNetworkType: () -> NetworkType = { NetworkType.Wifi }, - getAutoRunSpecification: suspend () -> RunSpecification = { - RunSpecification( + getAutoRunSpecification: suspend () -> RunSpecification.Full = { + RunSpecification.Full( tests = emptyList(), taskOrigin = TaskOrigin.AutoRun, isRerun = false, From eda2d1eecee8ba943d4351a4826e4e02d68f8500 Mon Sep 17 00:00:00 2001 From: Norbel AMBANUMBEN Date: Thu, 28 Nov 2024 20:30:01 +0100 Subject: [PATCH 21/22] chore: update translation strings --- .../values/strings-common.xml | 54 +++++++-------- .../composeResources/values/untraslatable.xml | 34 ++++++++++ .../org/ooni/probe/shared/ResourceExt.kt | 67 +++++++++++++++++++ .../org/ooni/probe/ui/results/ResultCell.kt | 4 +- .../ooni/probe/ui/results/ResultsScreen.kt | 5 +- .../kotlin/org/ooni/probe/ui/run/RunScreen.kt | 4 +- .../org/ooni/probe/ui/shared/DateFormats.kt | 6 +- 7 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index efc266b3..da685069 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -238,48 +238,42 @@ Collapse Expand %1$s ago - - %1$d minute - %1$d minutes - - - %1$d hour - %1$d hours - + %1$d minute + %1$d minutes + %1$d hour + %1$d hours %1$dh %1$dm %1$ds - - January - February - March - April - May - June - July - August - September - October - November - December - + + January + February + March + April + May + June + July + August + September + October + November + December Correct answer Incorrect answer Last updated %1$s - - Run %1$d test - Run %1$d tests - + + Run %1$d test + Run %1$d tests Unsupported URL Measurement - - %1$d measurement - %1$d measurements - + + %1$d measurement + %1$d measurements + Failed OK Anomaly diff --git a/composeApp/src/commonMain/composeResources/values/untraslatable.xml b/composeApp/src/commonMain/composeResources/values/untraslatable.xml index d4ff1224..fea55471 100644 --- a/composeApp/src/commonMain/composeResources/values/untraslatable.xml +++ b/composeApp/src/commonMain/composeResources/values/untraslatable.xml @@ -2,4 +2,38 @@ bugs@openobservatory.org [bug-report] OONI Probe %1$s %1$s: %2$s + + + @string/Common_Minutes_One + @string/Common_Minutes_Other + + + @string/Common_Hour_One + @string/Common_Hour_Other + + + + @string/Dashboard_RunTests_RunButton_Label_One + @string/Dashboard_RunTests_RunButton_Label_Other + + + + @string/Measurements_Count_One + @string/Measurements_Count_Other + + + + @string/Common_Months_January + @string/Common_Months_February + @string/Common_Months_March + @string/Common_Months_April + @string/Common_Months_May + @string/Common_Months_June + @string/Common_Months_July + @string/Common_Months_August + @string/Common_Months_September + @string/Common_Months_October + @string/Common_Months_November + @string/Common_Months_December + diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt new file mode 100644 index 00000000..5453e18b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt @@ -0,0 +1,67 @@ +package org.ooni.probe.shared + +import androidx.compose.runtime.Composable +import ooniprobe.composeapp.generated.resources.Common_Hour_One +import ooniprobe.composeapp.generated.resources.Common_Hour_Other +import ooniprobe.composeapp.generated.resources.Common_Minutes_One +import ooniprobe.composeapp.generated.resources.Common_Minutes_Other +import ooniprobe.composeapp.generated.resources.Common_Months_April +import ooniprobe.composeapp.generated.resources.Common_Months_August +import ooniprobe.composeapp.generated.resources.Common_Months_December +import ooniprobe.composeapp.generated.resources.Common_Months_February +import ooniprobe.composeapp.generated.resources.Common_Months_January +import ooniprobe.composeapp.generated.resources.Common_Months_July +import ooniprobe.composeapp.generated.resources.Common_Months_June +import ooniprobe.composeapp.generated.resources.Common_Months_March +import ooniprobe.composeapp.generated.resources.Common_Months_May +import ooniprobe.composeapp.generated.resources.Common_Months_November +import ooniprobe.composeapp.generated.resources.Common_Months_October +import ooniprobe.composeapp.generated.resources.Common_Months_September +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label_One +import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label_Other +import ooniprobe.composeapp.generated.resources.Measurements_Count_One +import ooniprobe.composeapp.generated.resources.Measurements_Count_Other +import ooniprobe.composeapp.generated.resources.Res +import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.pluralStringResource +import org.jetbrains.compose.resources.stringResource + +val stringMap = mapOf( + "@string/Common_Minutes_One" to Res.string.Common_Minutes_One, + "@string/Common_Minutes_Other" to Res.string.Common_Minutes_Other, + "@string/Common_Hour_One" to Res.string.Common_Hour_One, + "@string/Common_Hour_Other" to Res.string.Common_Hour_Other, + "@string/Dashboard_RunTests_RunButton_Label_One" to Res.string.Dashboard_RunTests_RunButton_Label_One, + "@string/Dashboard_RunTests_RunButton_Label_Other" to Res.string.Dashboard_RunTests_RunButton_Label_Other, + "@string/Measurements_Count_One" to Res.string.Measurements_Count_One, + "@string/Measurements_Count_Other" to Res.string.Measurements_Count_Other, +) + +@Composable +fun stringMonthArrayResource(): List { + return listOf( + stringResource(Res.string.Common_Months_January), + stringResource(Res.string.Common_Months_February), + stringResource(Res.string.Common_Months_March), + stringResource(Res.string.Common_Months_April), + stringResource(Res.string.Common_Months_May), + stringResource(Res.string.Common_Months_June), + stringResource(Res.string.Common_Months_July), + stringResource(Res.string.Common_Months_August), + stringResource(Res.string.Common_Months_September), + stringResource(Res.string.Common_Months_October), + stringResource(Res.string.Common_Months_November), + stringResource(Res.string.Common_Months_December), + ) +} + +@Composable +fun pluralStringResourceItem( + resource: PluralStringResource, + quantity: Int, + vararg formatArgs: Any, +): String { + return stringMap[pluralStringResource(resource, quantity, formatArgs)]?.let { + return stringResource(it, *formatArgs) + } ?: "" +} diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt index f8dcdb1d..0ed90f10 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultCell.kt @@ -27,10 +27,10 @@ import ooniprobe.composeapp.generated.resources.TaskOrigin_Manual import ooniprobe.composeapp.generated.resources.TestResults_UnknownASN import ooniprobe.composeapp.generated.resources.ic_cloud_off import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.TaskOrigin import org.ooni.probe.data.models.ResultListItem +import org.ooni.probe.shared.pluralStringResourceItem import org.ooni.probe.ui.dashboard.TestDescriptorLabel import org.ooni.probe.ui.shared.relativeDateTime @@ -115,7 +115,7 @@ fun ResultCell( ) if (!hasError) { Text( - pluralStringResource( + pluralStringResourceItem( Res.plurals.Measurements_Count, item.measurementsCount.toInt(), item.measurementsCount, 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 index 59d10032..3053b1ea 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/results/ResultsScreen.kt @@ -42,7 +42,6 @@ import kotlinx.datetime.LocalDate.Companion.Format import kotlinx.datetime.format import kotlinx.datetime.format.MonthNames import kotlinx.datetime.format.char -import ooniprobe.composeapp.generated.resources.Common_Months import ooniprobe.composeapp.generated.resources.Modal_Cancel import ooniprobe.composeapp.generated.resources.Modal_Delete import ooniprobe.composeapp.generated.resources.Modal_DoYouWantToDeleteAllTests @@ -63,8 +62,8 @@ import ooniprobe.composeapp.generated.resources.ic_download import ooniprobe.composeapp.generated.resources.ic_upload import ooniprobe.composeapp.generated.resources.ooni_empty_state import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringArrayResource import org.jetbrains.compose.resources.stringResource +import org.ooni.probe.shared.stringMonthArrayResource import org.ooni.probe.ui.shared.TopBar import org.ooni.probe.ui.shared.formatDataUsage import org.ooni.probe.ui.shared.isHeightCompact @@ -316,7 +315,7 @@ private fun Summary(summary: ResultsViewModel.Summary?) { @Composable private fun ResultDateHeader(date: LocalDate) { - val monthNames = stringArrayResource(Res.array.Common_Months) + val monthNames = stringMonthArrayResource() Text( date.format( Format { diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt index 151def90..054ba640 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/run/RunScreen.kt @@ -63,13 +63,13 @@ import ooniprobe.composeapp.generated.resources.ic_keyboard_arrow_down import ooniprobe.composeapp.generated.resources.ic_keyboard_arrow_up import ooniprobe.composeapp.generated.resources.ic_timer import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.TestType import org.ooni.probe.config.OrganizationConfig import org.ooni.probe.config.TestDisplayMode import org.ooni.probe.data.models.Descriptor import org.ooni.probe.data.models.NetTest +import org.ooni.probe.shared.pluralStringResourceItem import org.ooni.probe.ui.dashboard.TestDescriptorLabel import org.ooni.probe.ui.dashboard.TestDescriptorSection import org.ooni.probe.ui.shared.ParentSelectableItem @@ -189,7 +189,7 @@ fun RunScreen( .padding(WindowInsets.navigationBars.asPaddingValues()), ) { Text( - text = pluralStringResource( + text = pluralStringResourceItem( Res.plurals.Dashboard_RunTests_RunButton_Label, selectedTestsCount, selectedTestsCount, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt index 15eccc41..e37f1172 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/DateFormats.kt @@ -15,8 +15,8 @@ import ooniprobe.composeapp.generated.resources.Common_Minutes import ooniprobe.composeapp.generated.resources.Common_Minutes_Abbreviated import ooniprobe.composeapp.generated.resources.Common_Seconds_Abbreviated import ooniprobe.composeapp.generated.resources.Res -import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource +import org.ooni.probe.shared.pluralStringResourceItem import org.ooni.probe.shared.today import kotlin.time.Duration @@ -44,9 +44,9 @@ fun LocalDateTime.relativeDateTime(): String = val diff = (Clock.System.now() - toInstant(TimeZone.currentSystemDefault())) val diffString = diff.toComponents { hours, minutes, _, _ -> if (hours > 0) { - pluralStringResource(Res.plurals.Common_Hours, hours.toInt(), hours.toInt()) + pluralStringResourceItem(Res.plurals.Common_Hours, hours.toInt(), hours.toInt()) } else { - pluralStringResource(Res.plurals.Common_Minutes, minutes, minutes) + pluralStringResourceItem(Res.plurals.Common_Minutes, minutes, minutes) } } stringResource(Res.string.Common_Ago, diffString) From 11ccc9808d852b27b15039272c8abd4be99e9750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Mon, 2 Dec 2024 12:46:29 +0000 Subject: [PATCH 22/22] Fix instrumentation tests --- .../ooni/probe/uitesting/RunningTestsTest.kt | 37 ++++++++++++------- .../org/ooni/probe/uitesting/SettingsTest.kt | 4 +- .../ooni/probe/uitesting/UploadResultTest.kt | 6 +-- .../org/ooni/probe/shared/ResourceExt.kt | 12 ++++++ 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt index 358c9fc9..ffff25fd 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/RunningTestsTest.kt @@ -2,6 +2,7 @@ package org.ooni.probe.uitesting import androidx.compose.ui.test.hasText import androidx.compose.ui.test.isDisplayed +import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithTag @@ -14,6 +15,7 @@ import kotlinx.coroutines.test.runTest import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_SelectNone import ooniprobe.composeapp.generated.resources.Dashboard_RunV2_RunFinished +import ooniprobe.composeapp.generated.resources.Measurement_Title import ooniprobe.composeapp.generated.resources.OONIRun_Run import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Test_Circumvention_Fullname @@ -22,16 +24,15 @@ import ooniprobe.composeapp.generated.resources.Test_InstantMessaging_Fullname import ooniprobe.composeapp.generated.resources.Test_Performance_Fullname import ooniprobe.composeapp.generated.resources.Test_Psiphon_Fullname import ooniprobe.composeapp.generated.resources.Test_Signal_Fullname -import ooniprobe.composeapp.generated.resources.measurement -import org.jetbrains.compose.resources.getPluralString import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.ooni.probe.data.models.SettingsKey -import org.ooni.probe.uitesting.helpers.checkTextAnywhereInsideWebView +import org.ooni.probe.shared.getPluralStringResourceItem import org.ooni.probe.uitesting.helpers.checkSummaryInsideWebView +import org.ooni.probe.uitesting.helpers.checkTextAnywhereInsideWebView import org.ooni.probe.uitesting.helpers.clickOnText import org.ooni.probe.uitesting.helpers.isNewsMediaScan import org.ooni.probe.uitesting.helpers.isOoni @@ -64,13 +65,13 @@ class RunningTestsTest { clickOnText(Res.string.Dashboard_RunTests_SelectNone) clickOnText(Res.string.Test_Signal_Fullname) - clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + clickOnRunButton(1) clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) clickOnText(Res.string.Test_InstantMessaging_Fullname) clickOnText(Res.string.Test_Signal_Fullname) - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("Signal") } } @@ -84,13 +85,13 @@ class RunningTestsTest { clickOnText(Res.string.Dashboard_RunTests_SelectNone) clickOnText(Res.string.Test_Psiphon_Fullname) - clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + clickOnRunButton(1) clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) clickOnText(Res.string.Test_Circumvention_Fullname) clickOnText(Res.string.Test_Psiphon_Fullname) - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("Psiphon") } } @@ -107,13 +108,13 @@ class RunningTestsTest { .performScrollToNode(hasText("HTTP Header", substring = true)) .performTouchInput { swipeUp() } clickOnText("HTTP Header", substring = true) - clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + clickOnRunButton(1) clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) clickOnText(Res.string.Test_Performance_Fullname) clickOnText("HTTP Header", substring = true) - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("middleboxes") } } @@ -130,13 +131,13 @@ class RunningTestsTest { .performScrollToNode(hasText("stunreachability")) .performTouchInput { swipeUp() } clickOnText("stunreachability", substring = true) - clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + clickOnRunButton(1) clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) clickOnText(Res.string.Test_Experimental_Fullname) compose.onAllNodesWithText("stunreachability")[0].performClick() - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkTextAnywhereInsideWebView("stunreachability") } } @@ -151,17 +152,27 @@ class RunningTestsTest { clickOnText(Res.string.Dashboard_RunTests_SelectNone) clickOnText("Trusted International Media") - clickOnText(getPluralString(Res.plurals.Dashboard_RunTests_RunButton_Label, 1, 1)) + clickOnRunButton(1) clickOnText(Res.string.Dashboard_RunV2_RunFinished, timeout = TEST_WAIT_TIMEOUT) clickOnText("Trusted International Media") clickOnText("https://www.dw.com") - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("https://www.dw.com") } } + private suspend fun ComposeTestRule.clickOnRunButton(quantity: Int) { + clickOnText( + getPluralStringResourceItem( + Res.plurals.Dashboard_RunTests_RunButton_Label, + quantity, + quantity, + ), + ) + } + companion object { private val TEST_WAIT_TIMEOUT = 3.minutes } diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt index 5746055b..8664f18b 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/SettingsTest.kt @@ -7,6 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import ooniprobe.composeapp.generated.resources.CategoryCode_ANON_Name +import ooniprobe.composeapp.generated.resources.Common_Back import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Settings_Advanced_DebugLogs import ooniprobe.composeapp.generated.resources.Settings_Advanced_Label @@ -25,7 +26,6 @@ import ooniprobe.composeapp.generated.resources.Settings_Title import ooniprobe.composeapp.generated.resources.Settings_WarmVPNInUse_Label import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Description import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label -import ooniprobe.composeapp.generated.resources.back import org.jetbrains.compose.resources.getString import org.junit.Assert.assertTrue import org.junit.Before @@ -126,7 +126,7 @@ class SettingsTest { clickOnText(Res.string.Settings_Websites_Categories_Label) clickOnText(Res.string.CategoryCode_ANON_Name) - clickOnContentDescription(Res.string.back) + clickOnContentDescription(Res.string.Common_Back) onNodeWithText(getString(Res.string.Settings_Websites_Categories_Description, 1)) .assertIsDisplayed() diff --git a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt index e319b44b..46592b4e 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/org/ooni/probe/uitesting/UploadResultTest.kt @@ -9,13 +9,13 @@ import kotlinx.coroutines.test.runTest import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_RunButton_Label import ooniprobe.composeapp.generated.resources.Dashboard_RunTests_SelectNone import ooniprobe.composeapp.generated.resources.Dashboard_RunV2_RunFinished +import ooniprobe.composeapp.generated.resources.Measurement_Title import ooniprobe.composeapp.generated.resources.Modal_ResultsNotUploaded_Uploading import ooniprobe.composeapp.generated.resources.OONIRun_Run import ooniprobe.composeapp.generated.resources.Res import ooniprobe.composeapp.generated.resources.Snackbar_ResultsSomeNotUploaded_UploadAll import ooniprobe.composeapp.generated.resources.Test_InstantMessaging_Fullname import ooniprobe.composeapp.generated.resources.Test_Signal_Fullname -import ooniprobe.composeapp.generated.resources.measurement import org.jetbrains.compose.resources.getPluralString import org.jetbrains.compose.resources.getString import org.junit.Before @@ -75,7 +75,7 @@ class UploadResultTest { clickOnText(Res.string.Test_Signal_Fullname) - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("Signal") } } @@ -105,7 +105,7 @@ class UploadResultTest { clickOnText("https://www.dw.com") - wait { onNodeWithText(Res.string.measurement).isDisplayed() } + wait { onNodeWithText(Res.string.Measurement_Title).isDisplayed() } checkSummaryInsideWebView("https://www.dw.com") } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt index 5453e18b..6579c836 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/ResourceExt.kt @@ -23,6 +23,8 @@ import ooniprobe.composeapp.generated.resources.Measurements_Count_One import ooniprobe.composeapp.generated.resources.Measurements_Count_Other import ooniprobe.composeapp.generated.resources.Res import org.jetbrains.compose.resources.PluralStringResource +import org.jetbrains.compose.resources.getPluralString +import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.pluralStringResource import org.jetbrains.compose.resources.stringResource @@ -65,3 +67,13 @@ fun pluralStringResourceItem( return stringResource(it, *formatArgs) } ?: "" } + +suspend fun getPluralStringResourceItem( + resource: PluralStringResource, + quantity: Int, + vararg formatArgs: Any, +): String { + return stringMap[getPluralString(resource, quantity, formatArgs)]?.let { + return getString(it, *formatArgs) + } ?: "" +}