Skip to content

Commit

Permalink
Merge pull request #328 from ooni/select-categories
Browse files Browse the repository at this point in the history
Select/deselect all website categories
  • Loading branch information
sdsantos authored Dec 4, 2024
2 parents 9c8fd59 + f586d96 commit 94c190e
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M791,904L567,680L280,680L280,393L56,169L112,112L848,848L791,904ZM360,600L487,600L360,473L360,600ZM680,567L600,487L600,360L473,360L393,280L680,280L680,567ZM200,760L200,840Q167,840 143.5,816.5Q120,793 120,760L200,760ZM120,680L120,600L200,600L200,680L120,680ZM120,520L120,440L200,440L200,520L120,520ZM120,360L120,280L200,280L200,360L120,360ZM280,840L280,760L360,760L360,840L280,840ZM280,200L280,120L360,120L360,200L280,200ZM440,840L440,760L520,760L520,840L440,840ZM440,200L440,120L520,120L520,200L440,200ZM600,840L600,760L680,760L680,840L600,840ZM600,200L600,120L680,120L680,200L600,200ZM760,680L760,600L840,600L840,680L760,680ZM760,520L760,440L840,440L840,520L760,520ZM760,360L760,280L840,280L840,360L760,360ZM760,200L760,120Q793,120 816.5,143.5Q840,167 840,200L760,200Z"/>
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M280,680L280,280L680,280L680,680L280,680ZM360,600L600,600L600,360L360,360L360,600ZM200,760L200,840Q167,840 143.5,816.5Q120,793 120,760L200,760ZM120,680L120,600L200,600L200,680L120,680ZM120,520L120,440L200,440L200,520L120,520ZM120,360L120,280L200,280L200,360L120,360ZM200,200L120,200Q120,167 143.5,143.5Q167,120 200,120L200,200ZM280,840L280,760L360,760L360,840L280,840ZM280,200L280,120L360,120L360,200L280,200ZM440,840L440,760L520,760L520,840L440,840ZM440,200L440,120L520,120L520,200L440,200ZM600,840L600,760L680,760L680,840L600,840ZM600,200L600,120L680,120L680,200L600,200ZM760,840L760,760L840,760Q840,793 816.5,816.5Q793,840 760,840ZM760,680L760,600L840,600L840,680L760,680ZM760,520L760,440L840,440L840,520L760,520ZM760,360L760,280L840,280L840,360L760,360ZM760,200L760,120Q793,120 816.5,143.5Q840,167 840,200L760,200Z"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@
<string name="Settings_Websites_MaxRuntime_New">Maximum Websites test duration</string>
<string name="Settings_AutomatedTesting_RunAutomatically_Description">Tests will run every hour in the background</string>
<string name="Settings_Websites_MaxRuntimeEnabled_Description">Only for manual runs</string>
<string name="Settings_Websites_Categories_Selection_None">Deselect All</string>
<string name="Settings_Websites_Categories_Selection_All">Select All</string>

<string name="Notification_ChannelName">Testing</string>
<string name="TaskOrigin_Manual">Manual Run</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ data class Descriptor(
val longRunningTests: List<NetTest> = emptyList(),
val source: Source,
val updateStatus: UpdateStatus,
val enabled: Boolean = true,
) {
sealed interface Source {
data class Default(val value: DefaultTestDescriptor) : Source
Expand Down
21 changes: 15 additions & 6 deletions composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import org.ooni.probe.ui.settings.SettingsViewModel
import org.ooni.probe.ui.settings.about.AboutViewModel
import org.ooni.probe.ui.settings.category.SettingsCategoryViewModel
import org.ooni.probe.ui.settings.proxy.ProxyViewModel
import org.ooni.probe.ui.settings.webcategories.WebCategoriesViewModel
import org.ooni.probe.ui.upload.UploadMeasurementsViewModel
import kotlin.coroutines.CoroutineContext

Expand Down Expand Up @@ -291,6 +292,7 @@ class Dependencies(
getDefaultTestDescriptors = getDefaultTestDescriptors::invoke,
listInstalledTestDescriptors = testDescriptorRepository::list,
descriptorUpdates = getDescriptorUpdate::observeAvailableUpdatesState,
getPreferenceValues = preferenceRepository::allSettings,
)
}
private val getTestDescriptorsBySpec by lazy {
Expand Down Expand Up @@ -530,6 +532,15 @@ class Dependencies(
shareUrl = { launchAction(PlatformAction.Share(it)) },
)

fun reviewUpdatesViewModel(onBack: () -> Unit): ReviewUpdatesViewModel {
return ReviewUpdatesViewModel(
onBack = onBack,
createOrUpdate = testDescriptorRepository::createOrUpdate,
cancelUpdates = getDescriptorUpdate::cancelUpdates,
observeAvailableUpdatesState = getDescriptorUpdate::observeAvailableUpdatesState,
)
}

fun settingsCategoryViewModel(
categoryKey: String,
goToSettingsForCategory: (PreferenceCategoryKey) -> Unit,
Expand Down Expand Up @@ -558,14 +569,12 @@ class Dependencies(
uploadMissingMeasurements = uploadMissingMeasurements::invoke,
)

fun reviewUpdatesViewModel(onBack: () -> Unit): ReviewUpdatesViewModel {
return ReviewUpdatesViewModel(
fun webCategoriesViewModel(onBack: () -> Unit) =
WebCategoriesViewModel(
onBack = onBack,
createOrUpdate = testDescriptorRepository::createOrUpdate,
cancelUpdates = getDescriptorUpdate::cancelUpdates,
observeAvailableUpdatesState = getDescriptorUpdate::observeAvailableUpdatesState,
getPreferencesByKeys = preferenceRepository::allSettings,
setPreferenceValuesByKeys = preferenceRepository::setValuesByKey,
)
}

companion object {
@VisibleForTesting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class GetAutoRunSpecification(
}

private suspend fun List<Descriptor>.filterForAutoRun() =
filter { it.isEnabledForAutoRun() }
filter { it.enabled && it.isEnabledForAutoRun() }
.map { descriptor ->
descriptor.copy(
netTests = descriptor.netTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,6 @@ class GetSettings(
),
)
},
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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,42 @@ import kotlinx.coroutines.flow.map
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_TestOptions_LongRunningTest
import org.jetbrains.compose.resources.stringResource
import org.ooni.engine.models.WebConnectivityCategory
import org.ooni.probe.data.models.DefaultTestDescriptor
import org.ooni.probe.data.models.Descriptor
import org.ooni.probe.data.models.DescriptorUpdatesStatus
import org.ooni.probe.data.models.InstalledTestDescriptorModel
import org.ooni.probe.data.models.SettingsKey
import org.ooni.probe.data.models.UpdateStatus
import org.ooni.probe.data.models.toDescriptor

class GetTestDescriptors(
private val getDefaultTestDescriptors: () -> List<DefaultTestDescriptor>,
private val listInstalledTestDescriptors: () -> Flow<List<InstalledTestDescriptorModel>>,
private val descriptorUpdates: () -> Flow<DescriptorUpdatesStatus>,
private val getPreferenceValues: (List<SettingsKey>) -> Flow<Map<SettingsKey, Any?>>,
) {
operator fun invoke(): Flow<List<Descriptor>> {
return combine(
listInstalledTestDescriptors(),
descriptorUpdates(),
flowOf(getDefaultTestDescriptors()),
) { installedDescriptors, descriptorUpdates, defaultDescriptors ->
isWebsitesDescriptorEnabled(),
) { installedDescriptors, descriptorUpdates, defaultDescriptors, isWebsitesEnabled ->
val updatedDescriptors = installedDescriptors.map { item ->
item.toDescriptor(updateStatus = descriptorUpdates.getStatusOf(item.id))
}
return@combine defaultDescriptors
.map { it.toDescriptor() } + updatedDescriptors
val allDescriptors = defaultDescriptors.map { it.toDescriptor() } + updatedDescriptors
return@combine allDescriptors.map {
it.copy(enabled = it.name != "websites" || isWebsitesEnabled)
}
}
}

private fun isWebsitesDescriptorEnabled() =
getPreferenceValues(WebConnectivityCategory.entries.mapNotNull { it.settingsKey })
.map { preferences -> preferences.any { it.value == true } }

private fun DefaultTestDescriptor.toDescriptor() =
Descriptor(
name = label,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.ooni.probe.ui.settings.SettingsScreen
import org.ooni.probe.ui.settings.about.AboutScreen
import org.ooni.probe.ui.settings.category.SettingsCategoryScreen
import org.ooni.probe.ui.settings.proxy.ProxyScreen
import org.ooni.probe.ui.settings.webcategories.WebCategoriesScreen
import org.ooni.probe.ui.upload.UploadMeasurementsDialog

private val START_SCREEN = Screen.Dashboard
Expand Down Expand Up @@ -165,6 +166,14 @@ fun Navigation(
entry.arguments?.getString("category"),
) ?: return@composable
when (category) {
PreferenceCategoryKey.WEBSITES_CATEGORIES -> {
val viewModel = viewModel {
dependencies.webCategoriesViewModel(onBack = { navController.goBack() })
}
val state by viewModel.state.collectAsState()
WebCategoriesScreen(state, viewModel::onEvent)
}

PreferenceCategoryKey.ABOUT_OONI -> {
val viewModel = viewModel {
dependencies.aboutViewModel(onBack = { navController.goBack() })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ private fun LazyListScope.regularTestItems(
items(testItems, key = { "${descriptor.key}_${it.item.test.name}" }) { testItem ->
TestItem(
testItem = testItem,
enabled = descriptor.enabled,
onChecked = {
onEvent(
RunViewModel.Event.NetTestChecked(
Expand Down Expand Up @@ -271,11 +272,13 @@ private fun DescriptorItem(
state = descriptorItem.state,
onClick = { onChecked(descriptorItem.state != ToggleableState.On) },
role = Role.Checkbox,
enabled = descriptor.enabled,
)
.padding(horizontal = 16.dp),
) {
TriStateCheckbox(
state = descriptorItem.state,
enabled = descriptor.enabled,
onClick = null,
modifier = Modifier.padding(end = 24.dp),
)
Expand Down Expand Up @@ -304,6 +307,7 @@ private fun DescriptorItem(
@Composable
fun TestItem(
testItem: SelectableItem<NetTest>,
enabled: Boolean = true,
onChecked: (Boolean) -> Unit,
) {
val test = testItem.item
Expand All @@ -315,11 +319,13 @@ fun TestItem(
value = testItem.isSelected,
onValueChange = { onChecked(it) },
role = Role.Checkbox,
enabled = enabled,
)
.padding(horizontal = 16.dp, vertical = 10.dp),
) {
Checkbox(
checked = testItem.isSelected,
enabled = enabled,
onCheckedChange = null,
modifier = Modifier.padding(end = 24.dp),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ 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.DescriptorType
import org.ooni.probe.data.models.PlatformAction
import org.ooni.probe.data.models.NetTest
import org.ooni.probe.data.models.PlatformAction
import org.ooni.probe.data.models.RunSpecification
import org.ooni.probe.data.models.SettingsKey
import org.ooni.probe.data.repositories.PreferenceRepository
Expand Down Expand Up @@ -62,9 +62,9 @@ class RunViewModel(

ParentSelectableItem(
item = descriptor,
state = when (selectedTestsCount) {
0 -> ToggleableState.Off
tests.size -> ToggleableState.On
state = when {
!descriptor.enabled || selectedTestsCount == 0 -> ToggleableState.Off
selectedTestsCount == tests.size -> ToggleableState.On
else -> ToggleableState.Indeterminate
},
isExpanded = when (OrganizationConfig.testDisplayMode) {
Expand All @@ -75,7 +75,7 @@ class RunViewModel(
) to tests.map { test ->
SelectableItem(
item = test,
isSelected = preferences[descriptor to test] == true,
isSelected = descriptor.enabled && preferences[descriptor to test] == true,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.ooni.probe.ui.settings.webcategories

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.navigationBars
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.Icon
import androidx.compose.material3.IconButton
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.TextOverflow
import androidx.compose.ui.unit.sp
import ooniprobe.composeapp.generated.resources.Common_Back
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label
import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Selection_All
import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Selection_None
import ooniprobe.composeapp.generated.resources.ic_deselect
import ooniprobe.composeapp.generated.resources.ic_select_all
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.ooni.probe.ui.settings.category.SwitchSettingsView
import org.ooni.probe.ui.shared.TopBar

@Composable
fun WebCategoriesScreen(
state: WebCategoriesViewModel.State,
onEvent: (WebCategoriesViewModel.Event) -> Unit,
) {
Column(Modifier.background(MaterialTheme.colorScheme.background)) {
TopBar(
title = {
Text(
stringResource(Res.string.Settings_Websites_Categories_Label),
style = MaterialTheme.typography.headlineSmall.copy(fontSize = 18.sp),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
navigationIcon = {
IconButton(onClick = { onEvent(WebCategoriesViewModel.Event.BackClicked) }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(Res.string.Common_Back),
)
}
},
actions = {
IconButton(
onClick = { onEvent(WebCategoriesViewModel.Event.SelectAllClicked) },
enabled = state.selectAllEnabled,
) {
Icon(
painterResource(Res.drawable.ic_select_all),
stringResource(Res.string.Settings_Websites_Categories_Selection_All),
)
}
IconButton(
onClick = { onEvent(WebCategoriesViewModel.Event.DeselectAllClicked) },
enabled = state.deselectAllEnabled,
) {
Icon(
painterResource(Res.drawable.ic_deselect),
stringResource(Res.string.Settings_Websites_Categories_Selection_None),
)
}
},
)

LazyColumn(
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
) {
items(state.items, key = { it.item.settingsKey!! }) { item ->
SwitchSettingsView(
icon = item.item.icon,
title = item.item.title,
key = item.item.settingsKey!!,
checked = item.isSelected,
enabled = true,
supportingContent = { Text(stringResource(item.item.description)) },
onCheckedChange = { _, value ->
onEvent(WebCategoriesViewModel.Event.PreferenceChanged(item.item, value))
},
)
}
}
}
}
Loading

0 comments on commit 94c190e

Please sign in to comment.