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 }