HomeSettings.kt (12709B) - 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 RowTitle(title = translation["message_logger_title"]) 158 ShiftedRow { 159 Column( 160 verticalArrangement = Arrangement.spacedBy(4.dp), 161 ) { 162 var storedMessagesCount by rememberAsyncMutableState(defaultValue = 0) { 163 context.messageLogger.getStoredMessageCount() 164 } 165 var storedStoriesCount by rememberAsyncMutableState(defaultValue = 0) { 166 context.messageLogger.getStoredStoriesCount() 167 } 168 Row( 169 horizontalArrangement = Arrangement.spacedBy(10.dp), 170 verticalAlignment = Alignment.CenterVertically, 171 modifier = Modifier 172 .fillMaxWidth() 173 .padding(5.dp) 174 ) { 175 Column( 176 modifier = Modifier.weight(1f), 177 verticalArrangement = Arrangement.spacedBy(2.dp), 178 ) { 179 Text( 180 translation.format("message_logger_summary", 181 "messageCount" to storedMessagesCount.toString(), 182 "storyCount" to storedStoriesCount.toString() 183 ), maxLines = 2) 184 } 185 Button(onClick = { 186 runCatching { 187 activityLauncherHelper.saveFile("message_logger.db", "application/octet-stream") { uri -> 188 context.androidContext.contentResolver.openOutputStream(uri.toUri())?.use { outputStream -> 189 context.messageLogger.databaseFile.inputStream().use { inputStream -> 190 inputStream.copyTo(outputStream) 191 } 192 } 193 } 194 }.onFailure { 195 context.log.error("Failed to export database", it) 196 context.longToast("Failed to export database! ${it.localizedMessage}") 197 } 198 }) { 199 Text(text = translation["export_button"]) 200 } 201 Button(onClick = { 202 runCatching { 203 context.messageLogger.purgeAll() 204 storedMessagesCount = 0 205 storedStoriesCount = 0 206 }.onFailure { 207 context.log.error("Failed to clear messages", it) 208 context.longToast("Failed to clear messages! ${it.localizedMessage}") 209 }.onSuccess { 210 context.shortToast(translation["success_toast"]) 211 } 212 }) { 213 Text(text = translation["clear_button"]) 214 } 215 } 216 OutlinedButton( 217 modifier = Modifier 218 .fillMaxWidth() 219 .padding(5.dp), 220 onClick = { 221 routes.loggerHistory.navigate() 222 } 223 ) { 224 Text(translation["view_logger_history_button"]) 225 } 226 } 227 } 228 229 RowTitle(title = translation["debug_title"]) 230 Row( 231 horizontalArrangement = Arrangement.SpaceBetween, 232 verticalAlignment = Alignment.CenterVertically, 233 ) { 234 var selectedFileType by remember { mutableStateOf(InternalFileHandleType.entries.first()) } 235 Box( 236 modifier = Modifier 237 .weight(1f) 238 .padding(start = 26.dp) 239 ) { 240 var expanded by remember { mutableStateOf(false) } 241 242 ExposedDropdownMenuBox( 243 expanded = expanded, 244 onExpandedChange = { expanded = it }, 245 modifier = Modifier.fillMaxWidth(0.7f) 246 ) { 247 TextField( 248 value = selectedFileType.fileName, 249 onValueChange = {}, 250 readOnly = true, 251 modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable) 252 ) 253 254 ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { 255 InternalFileHandleType.entries.forEach { fileType -> 256 DropdownMenuItem(onClick = { 257 expanded = false 258 selectedFileType = fileType 259 }, text = { 260 Text(text = fileType.fileName) 261 }) 262 } 263 } 264 } 265 } 266 Button(onClick = { 267 runCatching { 268 context.coroutineScope.launch { 269 selectedFileType.resolve(context.androidContext).delete() 270 } 271 }.onFailure { 272 context.log.error("Failed to clear file", it) 273 context.longToast("Failed to clear file! ${it.localizedMessage}") 274 }.onSuccess { 275 context.shortToast(translation["success_toast"]) 276 } 277 }) { 278 Text(translation["clear_button"]) 279 } 280 } 281 ShiftedRow { 282 Column( 283 verticalArrangement = Arrangement.spacedBy(4.dp), 284 ) { 285 PreferenceToggle(context.sharedPreferences, key = "test_mode", text = "Test Mode (FOR DEBUGGING ONLY)") 286 PreferenceToggle(context.sharedPreferences, key = "disable_feature_loading", text = "Disable Feature Loading") 287 PreferenceToggle(context.sharedPreferences, key = "disable_mapper", text = "Disable Auto Mapper") 288 } 289 } 290 Spacer(modifier = Modifier.height(50.dp)) 291 } 292 } 293 }