Skip to content

Commit

Permalink
Select/deselect all website categories
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos committed Dec 3, 2024
1 parent 1e9c2bc commit 6b51b5c
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 15 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 @@ -237,6 +237,8 @@
<string name="Common_Refresh">refresh</string>
<string name="Common_Collapse">Collapse</string>
<string name="Common_Expand">Expand</string>
<string name="Common_SelectAll">Select all</string>
<string name="Common_DeselectAll">Deselect all</string>
<string name="Common_Ago">%1$s ago</string>
<string name="Common_Minutes_One">%1$d minute</string>
<string name="Common_Minutes_Other">%1$d minutes</string>
Expand Down
20 changes: 14 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 @@ -530,6 +531,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 +568,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 @@ -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 @@ -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
@@ -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.Common_DeselectAll
import ooniprobe.composeapp.generated.resources.Common_SelectAll
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_Websites_Categories_Label
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.Common_SelectAll),
)
}
IconButton(
onClick = { onEvent(WebCategoriesViewModel.Event.DeselectAllClicked) },
enabled = state.deselectAllEnabled,
) {
Icon(
painterResource(Res.drawable.ic_deselect),
stringResource(Res.string.Common_DeselectAll),
)
}
},
)

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))
},
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package org.ooni.probe.ui.settings.webcategories

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import org.ooni.engine.models.WebConnectivityCategory
import org.ooni.probe.data.models.SettingsKey
import org.ooni.probe.ui.shared.SelectableItem

class WebCategoriesViewModel(
onBack: () -> Unit,
getPreferencesByKeys: (List<SettingsKey>) -> Flow<Map<SettingsKey, Any?>>,
setPreferenceValuesByKeys: suspend (List<Pair<SettingsKey, Any?>>) -> Unit,
) : ViewModel() {
private val events = MutableSharedFlow<Event>(extraBufferCapacity = 1)

private val _state = MutableStateFlow(State())
val state = _state.asStateFlow()

init {
val categories = WebConnectivityCategory.entries.filter { it.settingsKey != null }

getPreferencesByKeys(categories.mapNotNull { it.settingsKey })
.onEach { preferences ->
_state.update {
it.copy(
items = categories.map { category ->
SelectableItem(
item = category,
isSelected = preferences[category.settingsKey] == true,
)
},
)
}
}
.launchIn(viewModelScope)

events.filterIsInstance<Event.PreferenceChanged>()
.onEach {
setPreferenceValuesByKeys(
listOf((it.category.settingsKey ?: return@onEach) to it.value),
)
}
.launchIn(viewModelScope)

events.filterIsInstance<Event.SelectAllClicked>()
.onEach {
setPreferenceValuesByKeys(
categories.map { (it.settingsKey ?: return@onEach) to true },
)
}
.launchIn(viewModelScope)

events.filterIsInstance<Event.DeselectAllClicked>()
.onEach {
setPreferenceValuesByKeys(
categories.map { (it.settingsKey ?: return@onEach) to false },
)
}
.launchIn(viewModelScope)

events.filterIsInstance<Event.BackClicked>()
.onEach { onBack() }
.launchIn(viewModelScope)
}

fun onEvent(event: Event) {
events.tryEmit(event)
}

data class State(
val items: List<SelectableItem<WebConnectivityCategory>> = emptyList(),
) {
val selectAllEnabled get() = items.any { !it.isSelected }
val deselectAllEnabled get() = items.any { it.isSelected }
}

sealed interface Event {
data class PreferenceChanged(
val category: WebConnectivityCategory,
val value: Boolean,
) : Event

data object SelectAllClicked : Event

data object DeselectAllClicked : Event

data object BackClicked : Event
}
}

0 comments on commit 6b51b5c

Please sign in to comment.