HomeSettings.kt (12846B) - raw
1 package me.rhunk.snapenhance.ui.manager.pages.home 2 3 import android.content.SharedPreferences 4 import androidx.compose.foundation.clickable 5 import androidx.compose.foundation.layout.* 6 import androidx.compose.foundation.rememberScrollState 7 import androidx.compose.foundation.verticalScroll 8 import androidx.compose.material.icons.Icons 9 import androidx.compose.material.icons.automirrored.filled.OpenInNew 10 import androidx.compose.material3.* 11 import androidx.compose.runtime.* 12 import androidx.compose.ui.Alignment 13 import androidx.compose.ui.Modifier 14 import androidx.compose.ui.text.font.FontWeight 15 import androidx.compose.ui.unit.dp 16 import androidx.compose.ui.unit.sp 17 import androidx.compose.ui.window.Dialog 18 import androidx.core.content.edit 19 import androidx.core.net.toUri 20 import androidx.navigation.NavBackStackEntry 21 import kotlinx.coroutines.launch 22 import me.rhunk.snapenhance.common.action.EnumAction 23 import me.rhunk.snapenhance.common.bridge.InternalFileHandleType 24 import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState 25 import me.rhunk.snapenhance.ui.manager.Routes 26 import me.rhunk.snapenhance.ui.setup.Requirements 27 import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper 28 import me.rhunk.snapenhance.ui.util.AlertDialogs 29 import me.rhunk.snapenhance.ui.util.saveFile 30 31 class HomeSettings : Routes.Route() { 32 private lateinit var activityLauncherHelper: ActivityLauncherHelper 33 private val dialogs by lazy { AlertDialogs(context.translation) } 34 35 override val init: () -> Unit = { 36 activityLauncherHelper = ActivityLauncherHelper(context.activity!!) 37 } 38 39 @Composable 40 private fun RowTitle(title: String) { 41 Text(text = title, modifier = Modifier.padding(16.dp), fontSize = 20.sp, fontWeight = FontWeight.Bold) 42 } 43 44 @Composable 45 private fun PreferenceToggle(sharedPreferences: SharedPreferences, key: String, text: String) { 46 val realKey = "debug_$key" 47 var value by remember { mutableStateOf(sharedPreferences.getBoolean(realKey, false)) } 48 49 Row( 50 modifier = Modifier 51 .fillMaxWidth() 52 .heightIn(min = 55.dp) 53 .clickable { 54 value = !value 55 sharedPreferences 56 .edit() { 57 putBoolean(realKey, value) 58 } 59 }, 60 horizontalArrangement = Arrangement.SpaceBetween, 61 verticalAlignment = Alignment.CenterVertically 62 ) { 63 Text(text = text, modifier = Modifier.padding(end = 16.dp), fontSize = 14.sp) 64 Switch(checked = value, onCheckedChange = { 65 value = it 66 sharedPreferences.edit().putBoolean(realKey, it).apply() 67 }, modifier = Modifier.padding(end = 26.dp)) 68 } 69 } 70 71 @Composable 72 private fun RowAction(key: String, requireConfirmation: Boolean = false, action: () -> Unit) { 73 var confirmationDialog by remember { 74 mutableStateOf(false) 75 } 76 77 fun takeAction() { 78 if (requireConfirmation) { 79 confirmationDialog = true 80 } else { 81 action() 82 } 83 } 84 85 if (requireConfirmation && confirmationDialog) { 86 Dialog(onDismissRequest = { confirmationDialog = false }) { 87 dialogs.ConfirmDialog(title = context.translation["manager.dialogs.action_confirm.title"], onConfirm = { 88 action() 89 confirmationDialog = false 90 }, onDismiss = { 91 confirmationDialog = false 92 }) 93 } 94 } 95 96 ShiftedRow( 97 modifier = Modifier 98 .fillMaxWidth() 99 .heightIn(min = 55.dp) 100 .clickable { 101 takeAction() 102 }, 103 horizontalArrangement = Arrangement.SpaceBetween, 104 verticalAlignment = Alignment.CenterVertically 105 ) { 106 Column( 107 modifier = Modifier.weight(1f), 108 ) { 109 Text(text = context.translation["actions.$key.name"], fontSize = 16.sp, fontWeight = FontWeight.Bold, lineHeight = 20.sp) 110 context.translation.getOrNull("actions.$key.description")?.let { Text(text = it, fontSize = 12.sp, fontWeight = FontWeight.Light, lineHeight = 15.sp) } 111 } 112 IconButton(onClick = { takeAction() }, 113 modifier = Modifier.padding(end = 2.dp) 114 ) { 115 Icon( 116 imageVector = Icons.AutoMirrored.Filled.OpenInNew, 117 contentDescription = null, 118 modifier = Modifier.size(24.dp) 119 ) 120 } 121 } 122 } 123 124 @Composable 125 private fun ShiftedRow( 126 modifier: Modifier = Modifier, 127 horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, 128 verticalAlignment: Alignment.Vertical = Alignment.Top, 129 content: @Composable RowScope.() -> Unit 130 ) { 131 Row( 132 modifier = modifier.padding(start = 26.dp), 133 horizontalArrangement = horizontalArrangement, 134 verticalAlignment = verticalAlignment 135 ) { content(this) } 136 } 137 138 @OptIn(ExperimentalMaterial3Api::class) 139 override val content: @Composable (NavBackStackEntry) -> Unit = { 140 Column( 141 modifier = Modifier 142 .fillMaxSize() 143 .verticalScroll(rememberScrollState()) 144 ) { 145 RowTitle(title = translation["actions_title"]) 146 EnumAction.entries.forEach { enumAction -> 147 RowAction(key = enumAction.key) { 148 context.launchActionIntent(enumAction) 149 } 150 } 151 RowAction(key = "regen_mappings") { 152 context.checkForRequirements(Requirements.MAPPINGS) 153 } 154 RowAction(key = "change_language") { 155 context.checkForRequirements(Requirements.LANGUAGE) 156 } 157 RowAction(key = "security_features") { 158 context.checkForRequirements(Requirements.SIF) 159 } 160 RowTitle(title = translation["message_logger_title"]) 161 ShiftedRow { 162 Column( 163 verticalArrangement = Arrangement.spacedBy(4.dp), 164 ) { 165 var storedMessagesCount by rememberAsyncMutableState(defaultValue = 0) { 166 context.messageLogger.getStoredMessageCount() 167 } 168 var storedStoriesCount by rememberAsyncMutableState(defaultValue = 0) { 169 context.messageLogger.getStoredStoriesCount() 170 } 171 Row( 172 horizontalArrangement = Arrangement.spacedBy(10.dp), 173 verticalAlignment = Alignment.CenterVertically, 174 modifier = Modifier 175 .fillMaxWidth() 176 .padding(5.dp) 177 ) { 178 Column( 179 modifier = Modifier.weight(1f), 180 verticalArrangement = Arrangement.spacedBy(2.dp), 181 ) { 182 Text( 183 translation.format("message_logger_summary", 184 "messageCount" to storedMessagesCount.toString(), 185 "storyCount" to storedStoriesCount.toString() 186 ), maxLines = 2) 187 } 188 Button(onClick = { 189 runCatching { 190 activityLauncherHelper.saveFile("message_logger.db", "application/octet-stream") { uri -> 191 context.androidContext.contentResolver.openOutputStream(uri.toUri())?.use { outputStream -> 192 context.messageLogger.databaseFile.inputStream().use { inputStream -> 193 inputStream.copyTo(outputStream) 194 } 195 } 196 } 197 }.onFailure { 198 context.log.error("Failed to export database", it) 199 context.longToast("Failed to export database! ${it.localizedMessage}") 200 } 201 }) { 202 Text(text = translation["export_button"]) 203 } 204 Button(onClick = { 205 runCatching { 206 context.messageLogger.purgeAll() 207 storedMessagesCount = 0 208 storedStoriesCount = 0 209 }.onFailure { 210 context.log.error("Failed to clear messages", it) 211 context.longToast("Failed to clear messages! ${it.localizedMessage}") 212 }.onSuccess { 213 context.shortToast(translation["success_toast"]) 214 } 215 }) { 216 Text(text = translation["clear_button"]) 217 } 218 } 219 OutlinedButton( 220 modifier = Modifier 221 .fillMaxWidth() 222 .padding(5.dp), 223 onClick = { 224 routes.loggerHistory.navigate() 225 } 226 ) { 227 Text(translation["view_logger_history_button"]) 228 } 229 } 230 } 231 232 RowTitle(title = translation["debug_title"]) 233 Row( 234 horizontalArrangement = Arrangement.SpaceBetween, 235 verticalAlignment = Alignment.CenterVertically, 236 ) { 237 var selectedFileType by remember { mutableStateOf(InternalFileHandleType.entries.first()) } 238 Box( 239 modifier = Modifier 240 .weight(1f) 241 .padding(start = 26.dp) 242 ) { 243 var expanded by remember { mutableStateOf(false) } 244 245 ExposedDropdownMenuBox( 246 expanded = expanded, 247 onExpandedChange = { expanded = it }, 248 modifier = Modifier.fillMaxWidth(0.7f) 249 ) { 250 TextField( 251 value = selectedFileType.fileName, 252 onValueChange = {}, 253 readOnly = true, 254 modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable) 255 ) 256 257 ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { 258 InternalFileHandleType.entries.forEach { fileType -> 259 DropdownMenuItem(onClick = { 260 expanded = false 261 selectedFileType = fileType 262 }, text = { 263 Text(text = fileType.fileName) 264 }) 265 } 266 } 267 } 268 } 269 Button(onClick = { 270 runCatching { 271 context.coroutineScope.launch { 272 selectedFileType.resolve(context.androidContext).delete() 273 } 274 }.onFailure { 275 context.log.error("Failed to clear file", it) 276 context.longToast("Failed to clear file! ${it.localizedMessage}") 277 }.onSuccess { 278 context.shortToast(translation["success_toast"]) 279 } 280 }) { 281 Text(translation["clear_button"]) 282 } 283 } 284 ShiftedRow { 285 Column( 286 verticalArrangement = Arrangement.spacedBy(4.dp), 287 ) { 288 PreferenceToggle(context.sharedPreferences, key = "enable_security_features", text = "Enable Security Features") 289 PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading") 290 PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper") 291 } 292 } 293 Spacer(modifier = Modifier.height(50.dp)) 294 } 295 } 296 }