Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class FloconDatastorePreference(
// no op
}

override suspend fun remove(columnName: String) {
// no op
}

override suspend fun columns(): List<String> {
return emptyList() // no op
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ class FloconDatastorePreference(
}
}

override suspend fun remove(columnName: String) {
try {
dataStore.edit { preferences ->
val key = preferences.asMap().keys.find { it.name == columnName } ?: return@edit
preferences.remove(key)
}
} catch (t: Throwable) {
FloconLogger.logError(t.message ?: "cannot remove datastore preference", t)
}
}

override suspend fun columns(): List<String> {
return try {
dataStore.data.first().asMap().map { it.key.name }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ object Protocol {
const val GetSharedPreferences = "get_shared_preferences"
const val GetSharedPreferenceValue = "get_shared_preference_value"
const val SetSharedPreferenceValue = "set_shared_preference_value"
const val RemoveSharedPreferenceValue = "remove_shared_preference_value"
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,23 @@ object SamplePreferences : FloconPreference {

private val values = mutableMapOf<String, FloconPreferenceValue>()

// default keys deleted from the desktop app, until they are set again
private val removed = mutableSetOf<String>()

override suspend fun set(columnName: String, value: FloconPreferenceValue) {
if (columnName == COUNTER) value.intValue?.let { counter = it }
values[columnName] = value
removed -= columnName
}

override suspend fun remove(columnName: String) {
if (columnName == COUNTER) counter = 0
values -= columnName
removed += columnName
}

override suspend fun columns(): List<String> = (listOf(COUNTER, "username") + values.keys).distinct()
override suspend fun columns(): List<String> =
(listOf(COUNTER, "username") + values.keys).distinct() - removed

override suspend fun get(columnName: String): FloconPreferenceValue? = when (columnName) {
COUNTER -> FloconPreferenceValue(intValue = counter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ interface FloconPreference {
value: FloconPreferenceValue,
)

/**
* Removes the key, when deleted from the desktop app.
* Does nothing by default, override it to support deleting a key.
*/
suspend fun remove(columnName: String) = Unit

suspend fun columns(): List<String>

suspend fun get(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ data class FloconSharedPreference(
editor.apply()
}

override suspend fun remove(columnName: String) {
sharedPreferences.edit().remove(columnName).apply()
}

override suspend fun columns(): List<String> {
return sharedPreferences.all.keys.toList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.github.openflocon.flocon.plugins.sharedprefs.model.fromdevice.Preferen
import io.github.openflocon.flocon.plugins.sharedprefs.model.fromdevice.SharedPreferenceValueResultDataModel
import io.github.openflocon.flocon.plugins.sharedprefs.model.todevice.ToDeviceEditSharedPreferenceValueMessage
import io.github.openflocon.flocon.plugins.sharedprefs.model.todevice.ToDeviceGetSharedPreferenceValueMessage
import io.github.openflocon.flocon.plugins.sharedprefs.model.todevice.ToDeviceRemoveSharedPreferenceValueMessage
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

Expand Down Expand Up @@ -110,6 +111,23 @@ internal class FloconSharedPrefsPluginImpl(
FloconLogger.logError("SharedPreferences edit error", t)
}
}

Protocol.ToDevice.SharedPreferences.Method.RemoveSharedPreferenceValue -> {
val message = encoder.decode<ToDeviceRemoveSharedPreferenceValueMessage>(body) ?: return
val preference = preferences.value[message.sharedPreferenceName] ?: return

try {
preference.remove(columnName = message.key)

// then send the shared pref content
sendPreferenceValues(
preference = preference,
requestId = message.requestId,
)
} catch (t: Throwable) {
FloconLogger.logError("SharedPreferences remove error", t)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ interface FloconPreference {
value: FloconPreferenceValue,
)

/**
* Removes the key, when deleted from the desktop app.
* Does nothing by default, override it to support deleting a key.
*/
suspend fun remove(columnName: String) = Unit

suspend fun columns(): List<String>

suspend fun get(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.github.openflocon.flocon.plugins.sharedprefs.model.todevice

import kotlinx.serialization.Serializable

@Serializable
internal data class ToDeviceRemoveSharedPreferenceValueMessage(
val requestId: String,
val sharedPreferenceName: String,
val key: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ private class UserDefaultsPreference(
?: return
defaults.setObject(newValue, forKey = columnName)
}

override suspend fun remove(columnName: String) {
defaults.removeObjectForKey(columnName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFalse

class FloconSharedPrefsPluginTest {

Expand All @@ -26,10 +27,16 @@ class FloconSharedPrefsPluginTest {
override val name: String,
override val type: String = FloconPreferenceType.Custom,
) : FloconPreference {
val values = mutableMapOf("age" to FloconPreferenceValue(intValue = 34))
val values = mutableMapOf(
"age" to FloconPreferenceValue(intValue = 34),
"token" to FloconPreferenceValue(stringValue = "abc"),
)
override suspend fun set(columnName: String, value: FloconPreferenceValue) {
values[columnName] = value
}
override suspend fun remove(columnName: String) {
values -= columnName
}
override suspend fun columns(): List<String> = values.keys.toList()
override suspend fun get(columnName: String): FloconPreferenceValue? = values[columnName]
}
Expand Down Expand Up @@ -91,6 +98,40 @@ class FloconSharedPrefsPluginTest {
assertContains(sender.messages.last().third, "{\"key\":\"age\",\"intValue\":35}")
}

@Test
fun `removeSharedPreferenceValue removes the key and sends the values`() = runTest {
plugin.register(preference)

plugin.onMessageReceived(
method = Protocol.ToDevice.SharedPreferences.Method.RemoveSharedPreferenceValue,
body = "{\"requestId\":\"1\",\"sharedPreferenceName\":\"user\",\"key\":\"token\"}",
)

assertFalse("token" in preference.values)
val (_, method, body) = sender.messages.last()
assertEquals(Protocol.FromDevice.SharedPreferences.Method.GetSharedPreferenceValue, method)
assertContains(body, "{\"key\":\"age\",\"intValue\":34}")
assertFalse(body.contains("token"))
}

@Test
fun `removeSharedPreferenceValue keeps the key when remove is not implemented`() = runTest {
val readOnly = object : FloconPreference {
override val name: String = "readOnly"
override suspend fun set(columnName: String, value: FloconPreferenceValue) = Unit
override suspend fun columns(): List<String> = listOf("age")
override suspend fun get(columnName: String) = FloconPreferenceValue(intValue = 34)
}
plugin.register(readOnly)

plugin.onMessageReceived(
method = Protocol.ToDevice.SharedPreferences.Method.RemoveSharedPreferenceValue,
body = "{\"requestId\":\"1\",\"sharedPreferenceName\":\"readOnly\",\"key\":\"age\"}",
)

assertContains(sender.messages.last().third, "{\"key\":\"age\",\"intValue\":34}")
}

@Test
fun `key is the plugin name sent by the desktop`() {
assertEquals(Protocol.ToDevice.SharedPreferences.Plugin, plugin.key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@
<string name="preference_cancel">Annuler</string>
<string name="preference_edit">Modifier</string>
<string name="preference_copy">Copier</string>
<string name="preference_delete">Supprimer</string>
<string name="preference_delete_title">Supprimer « %1$s » ?</string>
<string name="preference_delete_message">La clé est supprimée de l'app. Cette action est définitive.</string>
<string name="preference_read_only">lecture seule</string>
<string name="preferences_file_empty_title">%1$s est vide</string>
<string name="preferences_file_empty_subtitle">L'application n'a encore rien écrit dans ce fichier.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,9 @@
<string name="preference_cancel">Cancel</string>
<string name="preference_edit">Edit</string>
<string name="preference_copy">Copy</string>
<string name="preference_delete">Delete</string>
<string name="preference_delete_title">Delete "%1$s"?</string>
<string name="preference_delete_message">The key is removed from the app. This can't be undone.</string>
<string name="preference_read_only">read only</string>
<string name="preferences_file_empty_title">%1$s is empty</string>
<string name="preferences_file_empty_subtitle">The app hasn't written anything in this file yet.</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.github.openflocon.domain.sharedpreference.models.SharedPreferenceRowDo
import io.github.openflocon.domain.sharedpreference.usecase.EditSharedPrefFieldUseCase
import io.github.openflocon.domain.sharedpreference.usecase.GetCurrentDeviceSharedPreferenceValuesUseCase
import io.github.openflocon.domain.sharedpreference.usecase.ObserveCurrentDeviceSharedPreferenceValuesUseCase
import io.github.openflocon.domain.sharedpreference.usecase.RemoveSharedPrefFieldUseCase
import io.github.openflocon.flocondesktop.features.sharedpreferences.delegate.SharedPrefSelectorDelegate
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.DeviceSharedPrefUiModel
import io.github.openflocon.flocondesktop.features.sharedpreferences.model.SharedPreferencesRowUiModel
Expand Down Expand Up @@ -39,6 +40,7 @@ class SharedPreferencesViewModel(
private val sharedPrefSelectorDelegate: SharedPrefSelectorDelegate,
private val observeCurrentDeviceSharedPreferenceValuesUseCase: ObserveCurrentDeviceSharedPreferenceValuesUseCase,
private val editSharedPrefFieldUseCase: EditSharedPrefFieldUseCase,
private val removeSharedPrefFieldUseCase: RemoveSharedPrefFieldUseCase,
private val getCurrentDeviceSharedPrefValuesUseCase: GetCurrentDeviceSharedPreferenceValuesUseCase,
private val navigationState: MainFloconNavigationState,
) : ViewModel() {
Expand Down Expand Up @@ -194,6 +196,13 @@ class SharedPreferencesViewModel(
navigationState.navigate(SharedPreferencesRoutes.Edit(row.key))
}

// the device answers with the refreshed values, like after an edit
fun onDeleteClicked(row: SharedPreferencesRowUiModel) {
viewModelScope.launch(dispatcherProvider.viewModel) {
removeSharedPrefFieldUseCase(row.key)
}
}

fun onCopyClicked(row: SharedPreferencesRowUiModel) {
viewModelScope.launch {
copyToClipboard(row.value.asString())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.github.openflocon.flocondesktop.features.sharedpreferences.view

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import flocondesktop.composeapp.generated.resources.Res
import flocondesktop.composeapp.generated.resources.preference_cancel
import flocondesktop.composeapp.generated.resources.preference_delete
import flocondesktop.composeapp.generated.resources.preference_delete_message
import flocondesktop.composeapp.generated.resources.preference_delete_title
import io.github.openflocon.library.designsystem.FloconTheme
import io.github.openflocon.library.designsystem.components.FloconButton
import io.github.openflocon.library.designsystem.components.FloconDialog
import io.github.openflocon.library.designsystem.components.FloconDialogHeader
import io.github.openflocon.library.designsystem.components.FloconOutlinedButton
import org.jetbrains.compose.resources.stringResource

@Composable
fun SharedPreferenceDeleteDialog(
key: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
FloconDialog(
onDismissRequest = onDismiss,
modifier = modifier,
) {
SharedPreferenceDeleteContent(
key = key,
onConfirm = onConfirm,
onDismiss = onDismiss,
)
}
}

@Composable
private fun SharedPreferenceDeleteContent(
key: String,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
) {
Column {
FloconDialogHeader(
title = stringResource(Res.string.preference_delete_title, key),
modifier = Modifier.fillMaxWidth(),
)
Column(
verticalArrangement = Arrangement.spacedBy(FloconTheme.spacing.medium),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = FloconTheme.spacing.large)
.padding(bottom = FloconTheme.spacing.medium),
) {
Text(
text = stringResource(Res.string.preference_delete_message),
style = FloconTheme.typography.bodyMedium,
color = FloconTheme.colors.inkMuted,
)
Row(
horizontalArrangement = Arrangement.spacedBy(FloconTheme.spacing.small, Alignment.End),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
FloconOutlinedButton(onClick = onDismiss) {
Text(stringResource(Res.string.preference_cancel))
}
FloconButton(
onClick = onConfirm,
containerColor = FloconTheme.colors.statusError,
) {
Text(stringResource(Res.string.preference_delete))
}
}
}
}
}

@Preview
@Composable
private fun SharedPreferenceDeleteContentPreview() {
FloconTheme {
SharedPreferenceDeleteContent(
key = "user_token",
onConfirm = {},
onDismiss = {},
)
}
}
Loading
Loading