feat(ui/features): better rule list management

This commit is contained in:
rhunk
2024-08-13 14:58:31 +02:00
parent 324eae8bfa
commit 4325e512ca
6 changed files with 248 additions and 7 deletions

View File

@@ -179,3 +179,9 @@ fun AppDatabase.getRuleIds(type: String): MutableList<String> {
}
}
fun AppDatabase.clearRuleIds(type: String) {
executeAsync {
database.execSQL("DELETE FROM rules WHERE type = ?", arrayOf(type))
}
}

View File

@@ -26,6 +26,7 @@ import me.rhunk.snapenhance.ui.manager.pages.social.MessagingPreview
import me.rhunk.snapenhance.ui.manager.pages.social.SocialRootSection
import me.rhunk.snapenhance.ui.manager.pages.theming.EditThemeSection
import me.rhunk.snapenhance.ui.manager.pages.ManageReposSection
import me.rhunk.snapenhance.ui.manager.pages.features.ManageRuleFeature
import me.rhunk.snapenhance.ui.manager.pages.theming.ThemingRoot
import me.rhunk.snapenhance.ui.manager.pages.tracker.EditRule
import me.rhunk.snapenhance.ui.manager.pages.tracker.FriendTrackerManagerRoot
@@ -52,6 +53,7 @@ class Routes(
val tasks = route(RouteInfo("tasks", icon = Icons.Default.TaskAlt, primary = true), TasksRootSection())
val features = route(RouteInfo("features", icon = Icons.Default.Stars, primary = true), FeaturesRootSection())
val manageRuleFeature = route(RouteInfo("manage_rule_feature/?rule_type={rule_type}"), ManageRuleFeature()).parent(features)
val home = route(RouteInfo("home", icon = Icons.Default.Home, primary = true), HomeRootSection())
val settings = route(RouteInfo("home_settings"), HomeSettings()).parent(home)

View File

@@ -5,7 +5,6 @@ import android.net.Uri
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -19,7 +18,6 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
@@ -33,7 +31,6 @@ import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.github.skydoves.colorpicker.compose.AlphaTile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -444,6 +441,14 @@ class FeaturesRootSection : Routes.Route() {
verticalAlignment = Alignment.CenterVertically
) {
PropertyAction(property, registerClickCallback = { callback ->
if (property.key.propertyTranslationPath().startsWith("rules.properties")) {
clickCallback = {
routes.manageRuleFeature.navigate {
put("rule_type", property.key.name)
}
}
return@PropertyAction clickCallback!!
}
clickCallback = callback
callback
})

View File

@@ -0,0 +1,215 @@
package me.rhunk.snapenhance.ui.manager.pages.features
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavBackStackEntry
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.common.data.MessagingRuleType
import me.rhunk.snapenhance.common.data.RuleState
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
import me.rhunk.snapenhance.common.ui.rememberAsyncUpdateDispatcher
import me.rhunk.snapenhance.storage.clearRuleIds
import me.rhunk.snapenhance.storage.getRuleIds
import me.rhunk.snapenhance.storage.setRule
import me.rhunk.snapenhance.ui.manager.Routes
import me.rhunk.snapenhance.ui.manager.pages.social.AddFriendDialog
import me.rhunk.snapenhance.ui.manager.pages.social.AddFriendDialog.Actions
import me.rhunk.snapenhance.ui.util.AlertDialogs
import me.rhunk.snapenhance.ui.util.Dialog
class ManageRuleFeature : Routes.Route() {
@Composable
fun SelectRuleTypeRadio(
checked: Boolean,
text: String,
onStateChanged: (Boolean) -> Unit,
selectedBlock: @Composable () -> Unit = {},
) {
Box(modifier = Modifier.clickable {
onStateChanged(!checked)
}) {
Column(
modifier = Modifier
.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(selected = checked, onClick = null)
Text(text)
}
if (checked) {
Column(modifier = Modifier
.offset(x = 15.dp)
.padding(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
selectedBlock()
}
}
}
}
}
override val content: @Composable (NavBackStackEntry) -> Unit = content@{ navBackStackEntry ->
val currentRuleType = navBackStackEntry.arguments?.getString("rule_type")?.let {
MessagingRuleType.getByName(it)
} ?: return@content
var ruleState by remember {
mutableStateOf(context.config.root.rules.getRuleState(currentRuleType))
}
val propertyKeyPair = remember {
context.config.root.rules.getPropertyPair(currentRuleType.key)
}
val updateDispatcher = rememberAsyncUpdateDispatcher()
val currentRuleIds by rememberAsyncMutableState(defaultValue = mutableListOf(), updateDispatcher = updateDispatcher) {
context.database.getRuleIds(currentRuleType.key)
}
fun setRuleState(newState: RuleState?) {
ruleState = newState
propertyKeyPair.value.setAny(newState?.key)
context.coroutineScope.launch {
context.config.writeConfig(dispatchConfigListener = false)
}
}
var addFriendDialog by remember { mutableStateOf(null as AddFriendDialog?) }
LaunchedEffect(addFriendDialog) {
if (addFriendDialog == null) {
updateDispatcher.dispatch()
}
}
fun showAddFriendDialog() {
addFriendDialog = AddFriendDialog(
context = context,
pinnedIds = currentRuleIds,
actionHandler = Actions(
onFriendState = { friend, state ->
context.database.setRule(friend.userId, currentRuleType.key, state)
if (state) {
currentRuleIds.add(friend.userId)
} else {
currentRuleIds.remove(friend.userId)
}
},
onGroupState = { group, state ->
context.database.setRule(group.conversationId, currentRuleType.key, state)
if (state) {
currentRuleIds.add(group.conversationId)
} else {
currentRuleIds.remove(group.conversationId)
}
},
getFriendState = { friend ->
currentRuleIds.contains(friend.userId)
},
getGroupState = { group ->
currentRuleIds.contains(group.conversationId)
}
)
)
}
if (addFriendDialog != null) {
addFriendDialog?.Content {
addFriendDialog = null
}
}
Column(
modifier = Modifier.fillMaxSize()
) {
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = remember {
context.translation[propertyKeyPair.key.propertyName()]
},
fontSize = 20.sp,
)
Text(
text = remember {
context.translation[propertyKeyPair.key.propertyDescription()]
},
fontWeight = FontWeight.Light,
fontSize = 12.sp,
lineHeight = 16.sp,
)
}
SelectRuleTypeRadio(checked = ruleState == null, text = translation["disable_state_option"], onStateChanged = {
setRuleState(null)
}) {
Text(text = translation["disable_state_subtext"], fontWeight = FontWeight.Light, fontSize = 12.sp)
}
SelectRuleTypeRadio(checked = ruleState == RuleState.WHITELIST, text = translation["whitelist_state_option"], onStateChanged = {
setRuleState(RuleState.WHITELIST)
}) {
Text(text = translation.format("whitelist_state_subtext", "count" to currentRuleIds.size.toString()), fontWeight = FontWeight.Light, fontSize = 12.sp)
OutlinedButton(onClick = {
showAddFriendDialog()
}) {
Text(text = translation["whitelist_state_button"])
}
}
SelectRuleTypeRadio(checked = ruleState == RuleState.BLACKLIST, text = translation["blacklist_state_option"], onStateChanged = {
setRuleState(RuleState.BLACKLIST)
}) {
Text(text = translation.format("blacklist_state_subtext", "count" to currentRuleIds.size.toString()), fontWeight = FontWeight.Light, fontSize = 12.sp)
OutlinedButton(onClick = { showAddFriendDialog() }) {
Text(text = translation["blacklist_state_button"])
}
}
Row(
modifier = Modifier.fillMaxWidth().padding(5.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
var confirmationDialog by remember { mutableStateOf(false) }
if (confirmationDialog) {
Dialog(onDismissRequest = {
confirmationDialog = false
}) {
remember { AlertDialogs(context.translation) }.ConfirmDialog(
title = translation["dialog_clear_confirmation_text"],
onDismiss = { confirmationDialog = false },
onConfirm = {
context.database.clearRuleIds(currentRuleType.key)
context.coroutineScope.launch(context.database.executor.asCoroutineDispatcher()) {
updateDispatcher.dispatch()
}
confirmationDialog = false
}
)
}
}
Button(onClick = { confirmationDialog = true }) {
Text(text = translation["clear_list_button"])
}
}
}
}
}

View File

@@ -28,6 +28,7 @@
"routes": {
"tasks": "Tasks",
"features": "Features",
"manage_rule_feature": "Manage Rule Feature",
"home": "Home",
"home_settings": "Settings",
"home_logs": "Logs",
@@ -95,6 +96,18 @@
"config_export_failure_toast": "Failed to export config {error}",
"saved_config_snackbar": "Config saved"
},
"manage_rule_feature": {
"disable_state_option": "Disabled",
"disable_state_subtext": "No friends/groups will be affected",
"whitelist_state_option": "No one except ...",
"whitelist_state_subtext": "Only {count} friends/groups will be affected by this rule",
"whitelist_state_button": "Select allowed friends/groups",
"blacklist_state_option": "Everyone except ...",
"blacklist_state_subtext": "Everyone except {count} friends/groups will be affected by this rule",
"blacklist_state_button": "Select excluded friends/groups",
"clear_list_button": "Clear friends/groups list",
"dialog_clear_confirmation_text": "Are you sure you want to clear the list?"
},
"social": {
"friends_tab": "Friends",
"groups_tab": "Groups",

View File

@@ -70,11 +70,11 @@ class ModConfig(
}
}
fun writeConfig() {
writeConfigObject(root)
fun writeConfig(dispatchConfigListener: Boolean = true) {
writeConfigObject(root, dispatchConfigListener)
}
private fun writeConfigObject(config: RootConfig) {
private fun writeConfigObject(config: RootConfig, dispatchConfigListener: Boolean = true) {
var shouldRestart = false
var shouldCleanCache = false
var configChanged = false
@@ -113,7 +113,7 @@ class ModConfig(
val oldConfig = runCatching { fileWrapper.readBytes().toString(Charsets.UTF_8) }.getOrNull()
fileWrapper.writeBytes(exportToString(config = config).toByteArray(Charsets.UTF_8))
configStateListener?.takeIf { it.asBinder().pingBinder() }?.also {
configStateListener?.takeIf { dispatchConfigListener && it.asBinder().pingBinder() }?.also {
runCatching {
compareDiff(createRootConfig().apply {
fromJson(gson.fromJson(oldConfig ?: return@runCatching, JsonObject::class.java))