From 24d99976c85a384375fccd5adaf51abffdc209ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 25 Sep 2024 17:27:24 +0100 Subject: [PATCH] See recent logs --- composeApp/build.gradle.kts | 13 +- composeApp/composeApp.podspec | 4 +- .../values/strings-common.xml | 5 + .../commonMain/kotlin/org/ooni/probe/App.kt | 13 +- .../org/ooni/probe/data/disk/ReadFile.kt | 2 +- .../org/ooni/probe/data/disk/WriteFile.kt | 2 +- .../kotlin/org/ooni/probe/di/Dependencies.kt | 17 ++ .../ooni/probe/shared/monitoring/AppLogger.kt | 82 +++++++++ .../kotlin/org/ooni/probe/ui/log/LogScreen.kt | 158 ++++++++++++++++++ .../org/ooni/probe/ui/log/LogViewModel.kt | 66 ++++++++ .../ooni/probe/ui/navigation/Navigation.kt | 9 + .../probe/ui/results/ResultFilterViews.kt | 36 +--- .../ooni/probe/ui/shared/CustomFilterChip.kt | 40 +++++ .../org/ooni/probe/ui/shared/DateFormats.kt | 17 +- .../org/ooni/probe/ui/theme/CustomColors.kt | 9 + gradle.properties | 2 + iosApp/Podfile | 6 - iosApp/Podfile.lock | 8 +- 18 files changed, 431 insertions(+), 58 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/shared/monitoring/AppLogger.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/CustomFilterChip.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8aec89cec..d0c3a7070 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -88,7 +88,7 @@ kotlin { iosSimulatorArm64() cocoapods { - ios.deploymentTarget = "12.0" + ios.deploymentTarget = "15.3" version = "1.0" summary = "Compose App" @@ -100,6 +100,11 @@ kotlin { binaryOption("bundleId", "composeApp") } + pod("Sentry") { + version = "~> 8.25" + extraOpts += listOf("-compiler-option", "-fmodules") + } + podfile = project.file("../iosApp/Podfile") } @@ -178,7 +183,11 @@ android { versionName = "1.0" resValue("string", "app_name", config.appName) resValue("string", "ooni_run_enabled", config.supportsOoniRun.toString()) - resValue("string", "supported_languages", config.supportedLanguages.joinToString(separator = ",")) + resValue( + "string", + "supported_languages", + config.supportedLanguages.joinToString(separator = ","), + ) resourceConfigurations += config.supportedLanguages } packaging { diff --git a/composeApp/composeApp.podspec b/composeApp/composeApp.podspec index ff72dc219..be8256bba 100644 --- a/composeApp/composeApp.podspec +++ b/composeApp/composeApp.podspec @@ -8,8 +8,8 @@ Pod::Spec.new do |spec| spec.summary = 'Compose App' spec.vendored_frameworks = 'build/cocoapods/framework/composeApp.framework' spec.libraries = 'c++' - spec.ios.deployment_target = '12.0' - + spec.ios.deployment_target = '15.3' + spec.dependency 'Sentry', '~> 8.25' if !Dir.exist?('build/cocoapods/framework/composeApp.framework') || Dir.empty?('build/cocoapods/framework/composeApp.framework') raise " diff --git a/composeApp/src/commonMain/composeResources/values/strings-common.xml b/composeApp/src/commonMain/composeResources/values/strings-common.xml index f2cfe0f30..7173cc599 100644 --- a/composeApp/src/commonMain/composeResources/values/strings-common.xml +++ b/composeApp/src/commonMain/composeResources/values/strings-common.xml @@ -100,6 +100,8 @@ See recent logs Language Setting Storage usage + Delete + Clear Warn when VPN is in use Drugs & Alcohol @@ -253,4 +255,7 @@ Anomaly Correct answer Incorrect answer + Logs + Share Logs + Filter Logs diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt index 2dac20193..271f9c241 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt @@ -21,6 +21,7 @@ import co.touchlab.kermit.Logger import org.jetbrains.compose.ui.tooling.preview.Preview import org.ooni.probe.data.models.DeepLink import org.ooni.probe.di.Dependencies +import org.ooni.probe.shared.PlatformInfo import org.ooni.probe.ui.navigation.BottomNavigationBar import org.ooni.probe.ui.navigation.Navigation import org.ooni.probe.ui.navigation.Screen @@ -78,10 +79,10 @@ fun App( } } - Logger.addLogWriter(dependencies.crashMonitoring.logWriter) - LaunchedEffect(Unit) { - logAppStart(dependencies) + Logger.addLogWriter(dependencies.crashMonitoring.logWriter) + Logger.addLogWriter(dependencies.appLogger.logWriter) + logAppStart(dependencies.platformInfo) } LaunchedEffect(Unit) { dependencies.crashMonitoring.setup() @@ -108,9 +109,9 @@ fun App( } } -private fun logAppStart(dependencies: Dependencies) { - with(dependencies.platformInfo) { - Logger.i( +private fun logAppStart(platformInfo: PlatformInfo) { + with(platformInfo) { + Logger.v( """ ---APP START--- Platform: $platform ($osVersion)" diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/ReadFile.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/ReadFile.kt index ba3f420e4..ac28941f9 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/ReadFile.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/ReadFile.kt @@ -20,7 +20,7 @@ class ReadFileOkio( readUtf8() } } catch (e: IOException) { - Logger.w("Could not read $path", e) + Logger.v("Could not read $path", e) null } } diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/WriteFile.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/WriteFile.kt index d49812cf5..de7753de3 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/WriteFile.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/data/disk/WriteFile.kt @@ -30,7 +30,7 @@ class WriteFileOkio( try { absolutePath.parent?.let { fileSystem.createDirectories(it) } } catch (e: IOException) { - Logger.w("Could not create file $path", e) + Logger.v("Could not create file $path", e) return } 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 249b1ebf9..3c02d5735 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt @@ -65,10 +65,12 @@ import org.ooni.probe.domain.ShouldShowVpnWarning import org.ooni.probe.domain.TestRunStateManager import org.ooni.probe.domain.UploadMissingMeasurements import org.ooni.probe.shared.PlatformInfo +import org.ooni.probe.shared.monitoring.AppLogger import org.ooni.probe.shared.monitoring.CrashMonitoring import org.ooni.probe.ui.dashboard.DashboardViewModel import org.ooni.probe.ui.descriptor.DescriptorViewModel import org.ooni.probe.ui.descriptor.add.AddDescriptorViewModel +import org.ooni.probe.ui.log.LogViewModel import org.ooni.probe.ui.onboarding.OnboardingViewModel import org.ooni.probe.ui.result.ResultViewModel import org.ooni.probe.ui.results.ResultsViewModel @@ -122,6 +124,14 @@ class Dependencies( // Monitoring val crashMonitoring by lazy { CrashMonitoring(preferenceRepository) } + val appLogger by lazy { + AppLogger( + readFile = readFile, + writeFile = writeFile, + deleteFiles = deleteFiles, + backgroundDispatcher = backgroundDispatcher, + ) + } // Engine @@ -322,6 +332,13 @@ class Dependencies( deleteTestDescriptor = deleteTestDescriptor::invoke, ) + fun logViewModel(onBack: () -> Unit) = + LogViewModel( + onBack = onBack, + readLog = appLogger::read, + clearLog = appLogger::clear, + ) + fun onboardingViewModel( goToDashboard: () -> Unit, goToSettings: () -> Unit, diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/monitoring/AppLogger.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/monitoring/AppLogger.kt new file mode 100644 index 000000000..bc0172ca3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/shared/monitoring/AppLogger.kt @@ -0,0 +1,82 @@ +package org.ooni.probe.shared.monitoring + +import co.touchlab.kermit.LogWriter +import co.touchlab.kermit.Severity +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.datetime.LocalDateTime +import okio.Path.Companion.toPath +import org.ooni.probe.data.disk.DeleteFiles +import org.ooni.probe.data.disk.ReadFile +import org.ooni.probe.data.disk.WriteFile +import org.ooni.probe.shared.now +import org.ooni.probe.ui.shared.logFormat + +class AppLogger( + private val readFile: ReadFile, + private val writeFile: WriteFile, + private val deleteFiles: DeleteFiles, + private val backgroundDispatcher: CoroutineDispatcher, +) { + private val log = MutableStateFlow(emptyList()) + + fun read(severity: Severity?): Flow> = + log + .onStart { + if (log.value.isEmpty()) { + log.value = readFile(FILE_PATH).orEmpty().lines() + } + } + .map { lines -> + if (severity == null) { + lines + } else { + lines.filter { line -> + line.contains(": ${severity.name.uppercase()} :") + } + } + } + + suspend fun clear() { + withContext(backgroundDispatcher) { + log.value = emptyList() + deleteFiles(FILE_PATH) + } + } + + val logWriter = object : LogWriter() { + override fun isLoggable( + tag: String, + severity: Severity, + ): Boolean = severity != Severity.Verbose + + override fun log( + severity: Severity, + message: String, + tag: String, + throwable: Throwable?, + ) { + CoroutineScope(backgroundDispatcher).launch { + val logMessage = + "${LocalDateTime.now().logFormat()} : ${severity.name.uppercase()} : $message" + log.update { lines -> + val newLines = (lines + logMessage).takeLast(MAX_LINES) + writeFile(FILE_PATH, newLines.joinToString("\n"), append = false) + newLines + } + } + } + } + + companion object { + private val FILE_PATH = "Log/logger.txt".toPath() + private const val MAX_LINES = 1000 + } +} 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 new file mode 100644 index 000000000..ddfa42cdc --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt @@ -0,0 +1,158 @@ +package org.ooni.probe.ui.log + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.Res +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 org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.ooni.probe.ui.shared.CustomFilterChip +import org.ooni.probe.ui.theme.LocalCustomColors + +@Composable +fun LogScreen( + state: LogViewModel.State, + onEvent: (LogViewModel.Event) -> Unit, +) { + Column { + TopAppBar( + title = { Text(stringResource(Res.string.logs)) }, + navigationIcon = { + IconButton(onClick = { onEvent(LogViewModel.Event.BackClicked) }) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = stringResource(Res.string.back), + ) + } + }, + actions = { + IconButton(onClick = { onEvent(LogViewModel.Event.ClearClicked) }) { + Icon( + painterResource(Res.drawable.ic_delete_all), + contentDescription = stringResource(Res.string.Settings_Storage_Delete), + ) + } + }, + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .padding(bottom = 8.dp), + ) { + Text( + stringResource(Res.string.filter_logs), + modifier = Modifier.weight(2f), + ) + SeverityFilter( + current = state.filter, + onFilterChanged = { onEvent(LogViewModel.Event.FilterChanged(it)) }, + modifier = Modifier.weight(1f), + ) + } + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues( + start = 16.dp, + end = 16.dp, + bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding(), + ), + ) { + items(state.log) { line -> + Text( + line, + style = MaterialTheme.typography.labelMedium, + color = when { + line.contains(": WARN : ") -> LocalCustomColors.current.logWarn + line.contains(": ERROR : ") -> LocalCustomColors.current.logError + line.contains(": INFO : ") -> LocalCustomColors.current.logInfo + else -> LocalCustomColors.current.logDebug + }, + ) + } + } + } +} + +@Composable +fun SeverityFilter( + current: Severity?, + onFilterChanged: (Severity?) -> Unit, + modifier: Modifier = Modifier, +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + modifier = modifier.width(IntrinsicSize.Min), + ) { + CustomFilterChip( + text = current.label(), + selected = current != null, + onClick = { expanded = true }, + ) + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + SEVERITY_OPTIONS.forEach { option -> + DropdownMenuItem( + text = { Text(option.label()) }, + onClick = { + onFilterChanged(option) + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + ) + } + } + } +} + +private fun Severity?.label() = this?.name?.uppercase() ?: "ALL" + +private val SEVERITY_OPTIONS = listOf( + null, + Severity.Error, + Severity.Warn, + Severity.Info, + Severity.Debug, +) diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogViewModel.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogViewModel.kt new file mode 100644 index 000000000..6227f5180 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogViewModel.kt @@ -0,0 +1,66 @@ +package org.ooni.probe.ui.log + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import co.touchlab.kermit.Severity +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update + +class LogViewModel( + onBack: () -> Unit, + readLog: (Severity?) -> Flow>, + clearLog: suspend () -> Unit, +) : ViewModel() { + private val events = MutableSharedFlow(extraBufferCapacity = 1) + + private val _state = MutableStateFlow(State()) + val state = _state.asStateFlow() + + init { + state + .map { it.filter } + .flatMapLatest { filter -> readLog(filter) } + .onEach { log -> _state.update { state -> state.copy(log = log) } } + .launchIn(viewModelScope) + + events + .filterIsInstance() + .onEach { onBack() } + .launchIn(viewModelScope) + + events + .filterIsInstance() + .onEach { clearLog() } + .launchIn(viewModelScope) + + events + .filterIsInstance() + .onEach { event -> _state.update { it.copy(filter = event.severity) } } + .launchIn(viewModelScope) + } + + fun onEvent(event: Event) { + events.tryEmit(event) + } + + data class State( + val log: List = emptyList(), + val filter: Severity? = null, + ) + + sealed interface Event { + data object BackClicked : Event + + data object ClearClicked : Event + + data class FilterChanged(val severity: Severity?) : Event + } +} 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 7e8395b2e..f4bba7ba3 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 @@ -21,6 +21,7 @@ import org.ooni.probe.shared.decodeUrlFromBase64 import org.ooni.probe.ui.dashboard.DashboardScreen import org.ooni.probe.ui.descriptor.DescriptorScreen import org.ooni.probe.ui.descriptor.add.AddDescriptorScreen +import org.ooni.probe.ui.log.LogScreen import org.ooni.probe.ui.measurement.MeasurementScreen import org.ooni.probe.ui.onboarding.OnboardingScreen import org.ooni.probe.ui.result.ResultScreen @@ -157,6 +158,14 @@ fun Navigation( ProxyScreen(state, viewModel::onEvent) } + PreferenceCategoryKey.SEE_RECENT_LOGS.value -> { + val viewModel = viewModel { + dependencies.logViewModel(onBack = { navController.popBackStack() }) + } + val state by viewModel.state.collectAsState() + LogScreen(state, viewModel::onEvent) + } + else -> { val viewModel = viewModel { dependencies.settingsCategoryViewModel( 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 e61aaeede..a556ac65f 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 @@ -3,11 +3,7 @@ package org.ooni.probe.ui.results import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuBoxScope import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.FilterChip -import androidx.compose.material3.FilterChipDefaults -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -15,8 +11,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -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 @@ -26,6 +20,7 @@ import org.jetbrains.compose.resources.stringResource import org.ooni.engine.models.TaskOrigin import org.ooni.probe.data.models.Descriptor import org.ooni.probe.data.models.ResultFilter +import org.ooni.probe.ui.shared.CustomFilterChip @Composable fun DescriptorFilter( @@ -45,6 +40,7 @@ fun DescriptorFilter( text = current.label(), selected = current != ResultFilter.Type.All, onClick = { expanded = true }, + modifier = Modifier.fillMaxWidth(), ) ExposedDropdownMenu( expanded = expanded, @@ -82,6 +78,7 @@ fun OriginFilter( text = current.name(), selected = current != ResultFilter.Type.All, onClick = { expanded = true }, + modifier = Modifier.fillMaxWidth(), ) ExposedDropdownMenu( expanded = expanded, @@ -101,33 +98,6 @@ fun OriginFilter( } } -@Composable -private fun ExposedDropdownMenuBoxScope.CustomFilterChip( - text: String, - selected: Boolean, - onClick: () -> Unit, -) { - FilterChip( - selected = selected, - onClick = { onClick() }, - label = { - Text( - text, - textAlign = TextAlign.Center, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.fillMaxWidth(), - ) - }, - colors = FilterChipDefaults.filterChipColors( - selectedContainerColor = MaterialTheme.colorScheme.surfaceVariant, - ), - modifier = Modifier - .fillMaxWidth() - .menuAnchor(), - ) -} - @Composable private fun ResultFilter.Type.label() = when (this) { diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/CustomFilterChip.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/CustomFilterChip.kt new file mode 100644 index 000000000..c9fde3a86 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/shared/CustomFilterChip.kt @@ -0,0 +1,40 @@ +package org.ooni.probe.ui.shared + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.ExposedDropdownMenuBoxScope +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow + +@Composable +fun ExposedDropdownMenuBoxScope.CustomFilterChip( + text: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + FilterChip( + selected = selected, + onClick = { onClick() }, + label = { + Text( + text, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + ) + }, + colors = FilterChipDefaults.filterChipColors( + selectedContainerColor = MaterialTheme.colorScheme.surfaceVariant, + ), + modifier = modifier + .fillMaxWidth() + .menuAnchor(), + ) +} 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 c4d5f6cf8..740e1db09 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 @@ -20,7 +20,7 @@ import org.jetbrains.compose.resources.stringResource import org.ooni.probe.shared.today import kotlin.time.Duration -private val dateTimeFormat = LocalDateTime.Format { +private val longDateTimeFormat = LocalDateTime.Format { date(LocalDate.Formats.ISO) char(' ') hour() @@ -28,6 +28,16 @@ private val dateTimeFormat = LocalDateTime.Format { minute() } +private val logDateTimeFormat = LocalDateTime.Format { + date(LocalDate.Formats.ISO) + char(' ') + hour() + char(':') + minute() + char(':') + second() +} + @Composable fun LocalDateTime.relativeDateTime(): String = if (date == LocalDate.today()) { @@ -44,8 +54,9 @@ fun LocalDateTime.relativeDateTime(): String = longFormat() } -@Composable -fun LocalDateTime.longFormat(): String = format(dateTimeFormat) +fun LocalDateTime.longFormat(): String = format(longDateTimeFormat) + +fun LocalDateTime.logFormat(): String = format(logDateTimeFormat) @Composable fun Duration.shortFormat(): String = diff --git a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/theme/CustomColors.kt b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/theme/CustomColors.kt index 9aeb6253f..3fc25b408 100644 --- a/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/theme/CustomColors.kt +++ b/composeApp/src/commonMain/kotlin/org/ooni/probe/ui/theme/CustomColors.kt @@ -32,6 +32,11 @@ val onQuizWarningColor = Color.White val quizWarningBackColor = Color(0xFF343a40) val onQuizWarningBackColor = Color.White +val logDebugColor = Color.Unspecified +val logInfoColor = Color(0xFF2b8a3e) +val logWarnColor = Color(0xFFd9480f) +val logErrorColor = Color(0xFFc92a2a) + data class CustomColors( val success: Color, val onSuccess: Color, @@ -52,6 +57,10 @@ data class CustomColors( val onQuizWarning: Color = onQuizWarningColor, val quizWarningBack: Color = quizWarningBackColor, val onQuizWarningBack: Color = onQuizWarningBackColor, + val logDebug: Color = logDebugColor, + val logInfo: Color = logInfoColor, + val logWarn: Color = logWarnColor, + val logError: Color = logErrorColor, ) val customColorsLight = CustomColors( diff --git a/gradle.properties b/gradle.properties index f1cb0cd02..3a9e04382 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,3 +10,5 @@ android.useAndroidX=true #MPP kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.enableCInteropCommonization=true + +kotlin.apple.deprecated.allowUsingEmbedAndSignWithCocoaPodsDependencies=true diff --git a/iosApp/Podfile b/iosApp/Podfile index db9387490..1f40e8503 100644 --- a/iosApp/Podfile +++ b/iosApp/Podfile @@ -9,12 +9,6 @@ def shared_pods pod 'sqlite3', '~> 3.42.0' - pod("Sentry") { - version = "~> 8.25" - linkOnly = true - extraOpts += listOf("-compiler-option", "-fmodules") - } - pod "libcrypto", :podspec => "#{ooni_pods_location}/libcrypto.podspec" pod "libevent", :podspec => "#{ooni_pods_location}/libevent.podspec" pod "libssl", :podspec => "#{ooni_pods_location}/libssl.podspec" diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index a0fe2b75f..f63234a27 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -1,5 +1,6 @@ PODS: - - composeApp (1.0) + - composeApp (1.0): + - Sentry (~> 8.25) - libcrypto (2024.05.22-093305) - libevent (2024.05.22-093305) - libssl (2024.05.22-093305) @@ -21,7 +22,6 @@ DEPENDENCIES: - 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`) - - Sentry - sqlite3 (~> 3.42.0) SPEC REPOS: @@ -46,7 +46,7 @@ EXTERNAL SOURCES: :podspec: https://github.com/ooni/probe-cli/releases/download/v3.22.0/oonimkall.podspec SPEC CHECKSUMS: - composeApp: be6187105a7697f3cb0799a21bbbfddc5e65b077 + composeApp: df1a7904b08876a9465714c2293f6e472f9fdd55 libcrypto: 1bb58600c586e28688f5578f4675f5ffa46c8eaf libevent: 5c8502ca5cc38be31bb510ddade0f238bcc5f0dc libssl: 170bebcaf567a0285e91a8850b9686137d07c3e1 @@ -56,6 +56,6 @@ SPEC CHECKSUMS: Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57 sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c -PODFILE CHECKSUM: b49ba8d6a4e50f7a2ff0d0e0311fe97c8511e6e9 +PODFILE CHECKSUM: b06fce1e10c83d22148bcb93b3b75ca57d3bff2b COCOAPODS: 1.15.2