Convert debug menu to Jetpack Compose

This commit is contained in:
Jobobby04 2022-05-08 21:09:02 -04:00
parent 7403709ecd
commit 5fedef2ccb
3 changed files with 190 additions and 59 deletions

View File

@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
@ -40,8 +41,11 @@ fun PreferenceRow(
onLongClick: () -> Unit = {}, onLongClick: () -> Unit = {},
subtitle: String? = null, subtitle: String? = null,
action: @Composable (() -> Unit)? = null, action: @Composable (() -> Unit)? = null,
// SY -->
subtitleAnnotated: AnnotatedString? = null
// SY <--
) { ) {
val height = if (subtitle != null) 72.dp else 56.dp val height = if (subtitle != null /* SY --> */ || subtitleAnnotated != null/* SY <-- */) 72.dp else 56.dp
val titleTextStyle = MaterialTheme.typography.bodyLarge val titleTextStyle = MaterialTheme.typography.bodyLarge
val subtitleTextStyle = MaterialTheme.typography.bodyMedium.copy( val subtitleTextStyle = MaterialTheme.typography.bodyMedium.copy(
@ -84,6 +88,15 @@ fun PreferenceRow(
style = subtitleTextStyle, style = subtitleTextStyle,
) )
} }
// SY -->
if (subtitleAnnotated != null) {
Text(
modifier = Modifier.padding(top = 4.dp),
text = subtitleAnnotated,
style = subtitleTextStyle,
)
}
// SY <--
} }
if (action != null) { if (action != null) {
Box(Modifier.widthIn(min = 56.dp)) { Box(Modifier.widthIn(min = 56.dp)) {
@ -100,6 +113,9 @@ fun SwitchPreference(
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
painter: Painter? = null, painter: Painter? = null,
// SY -->
subtitleAnnotated: AnnotatedString? = null
// SY <--
) { ) {
PreferenceRow( PreferenceRow(
modifier = modifier, modifier = modifier,
@ -112,5 +128,8 @@ fun SwitchPreference(
Text(preference.value.toString()) Text(preference.value.toString())
}, },
onClick = { preference.value = !preference.value }, onClick = { preference.value = !preference.value },
// SY -->
subtitleAnnotated = subtitleAnnotated
// SY <--
) )
} }

View File

@ -1,6 +1,8 @@
package exh.debug package exh.debug
import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Locale import java.util.Locale
@ -20,7 +22,7 @@ enum class DebugToggles(val default: Boolean) {
// Pretend that all galleries only have a single version // Pretend that all galleries only have a single version
INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS(false); INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS(false);
val prefKey = "eh_debug_toggle_${name.lowercase(Locale.US)}" private val prefKey = "eh_debug_toggle_${name.lowercase(Locale.US)}"
var enabled: Boolean var enabled: Boolean
get() = prefs.flowPrefs.getBoolean(prefKey, default).get() get() = prefs.flowPrefs.getBoolean(prefKey, default).get()
@ -28,6 +30,8 @@ enum class DebugToggles(val default: Boolean) {
prefs.flowPrefs.getBoolean(prefKey).set(value) prefs.flowPrefs.getBoolean(prefKey).set(value)
} }
fun asPref(scope: CoroutineScope) = PreferenceMutableState(prefs.flowPrefs.getBoolean(prefKey, default), scope)
companion object { companion object {
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
} }

View File

@ -1,74 +1,186 @@
package exh.debug package exh.debug
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.util.Log import android.util.Log
import android.widget.TextView import androidx.compose.animation.AnimatedVisibility
import androidx.core.text.HtmlCompat import androidx.compose.animation.fadeIn
import androidx.preference.PreferenceScreen import androidx.compose.animation.fadeOut
import com.google.android.material.dialog.MaterialAlertDialogBuilder import androidx.compose.foundation.background
import eu.kanade.tachiyomi.ui.setting.SettingsController import androidx.compose.foundation.gestures.forEachGesture
import eu.kanade.tachiyomi.util.preference.defaultValue import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsBottomHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.unit.dp
import eu.kanade.core.prefs.PreferenceMutableState
import eu.kanade.presentation.components.Divider
import eu.kanade.presentation.components.PreferenceRow
import eu.kanade.presentation.components.SwitchPreference
import eu.kanade.tachiyomi.ui.base.controller.BasicComposeController
import eu.kanade.tachiyomi.util.preference.onClick import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.switchPreference
import exh.util.capitalize import exh.util.capitalize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale import java.util.Locale
import kotlin.reflect.KFunction
import kotlin.reflect.KVisibility import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.declaredFunctions
class SettingsDebugController : SettingsController() { class SettingsDebugController : BasicComposeController() {
@SuppressLint("SetTextI18n")
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
title = "DEBUG MENU"
preferenceCategory { override fun getTitle(): String {
title = "Functions" return "DEBUG MENU"
}
DebugFunctions::class.declaredFunctions.filter { data class DebugToggle(val name: String, val pref: PreferenceMutableState<Boolean>, val default: Boolean)
it.visibility == KVisibility.PUBLIC
}.forEach {
preference {
title = it.name.replace("(.)(\\p{Upper})".toRegex(), "$1 $2").lowercase(Locale.getDefault()).capitalize(Locale.getDefault())
isPersistent = false
onClick { @Composable
try { override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
val result = it.call(DebugFunctions) val functions by produceState<List<Pair<KFunction<*>, String>>?>(initialValue = null) {
val text = "Function returned result:\n\n$result" value = withContext(Dispatchers.Default) {
MaterialAlertDialogBuilder(context) DebugFunctions::class.declaredFunctions.filter {
.setTitle(title.toString()) it.visibility == KVisibility.PUBLIC
.setMessage(text) }.map {
.create() it to it.name.replace("(.)(\\p{Upper})".toRegex(), "$1 $2")
} catch (t: Throwable) { .lowercase(Locale.getDefault()).capitalize(Locale.getDefault())
val text = "Function threw exception:\n\n${Log.getStackTraceString(t)}"
MaterialAlertDialogBuilder(context)
.setMessage(text)
.create()
}.also { dialog ->
dialog.setOnShowListener {
dialog.findViewById<TextView>(android.R.id.message)?.apply {
setTextIsSelectable(true)
}
}
}.show()
}
} }
} }
} }
val toggles by produceState(initialValue = emptyList()) {
preferenceCategory { value = withContext(Dispatchers.Default) {
title = "Toggles" DebugToggles.values().map { DebugToggle(it.name, it.asPref(viewScope), it.default) }
}
DebugToggles.values().forEach { }
switchPreference { if (functions != null) {
title = it.name.replace('_', ' ').lowercase(Locale.getDefault()).capitalize(Locale.getDefault()) val scope = rememberCoroutineScope()
key = it.prefKey Box(
defaultValue = it.default Modifier
summaryOn = if (it.default) "" else MODIFIED_TEXT .fillMaxSize()
summaryOff = if (it.default) MODIFIED_TEXT else "" .nestedScroll(nestedScrollInterop)
) {
var running by remember { mutableStateOf(false) }
var result by remember { mutableStateOf<Pair<String, String>?>(null) }
LazyColumn(Modifier.fillMaxSize()) {
item {
Text(
text = "Functions",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(16.dp)
)
}
items(functions.orEmpty()) { (func, name) ->
PreferenceRow(
title = name,
onClick = {
scope.launch(Dispatchers.Default) {
val text = try {
running = true
"Function returned result:\n\n${func.call(DebugFunctions)}"
} catch (e: Exception) {
"Function threw exception:\n\n${Log.getStackTraceString(e)}"
} finally {
running = false
}
result = name to text
}
},
)
}
item {
Divider()
}
item {
Text(
text = "Toggles",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(16.dp)
)
}
items(toggles) { (name, pref, default) ->
SwitchPreference(
preference = pref,
title = name.replace('_', ' ')
.lowercase(Locale.getDefault())
.capitalize(Locale.getDefault()),
subtitleAnnotated = if (pref.value != default) {
AnnotatedString("MODIFIED", SpanStyle(color = Color.Red))
} else null
)
}
item {
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
}
} }
AnimatedVisibility(
running && result == null,
enter = fadeIn(),
exit = fadeOut(),
modifier = Modifier.fillMaxSize()
) {
Box(
Modifier
.fillMaxSize()
.background(color = Color.White.copy(alpha = 0.3F))
.pointerInput(running && result == null) {
forEachGesture {
awaitPointerEventScope {
waitForUpOrCancellation()?.consume()
}
}
},
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
if (result != null) {
AlertDialog(
onDismissRequest = { result = null },
title = {
Text(text = result?.first.orEmpty())
},
confirmButton = {},
text = {
SelectionContainer {
Text(text = result?.second.orEmpty())
}
}
)
}
}
} else {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
} }
} }
} }
@ -77,8 +189,4 @@ class SettingsDebugController : SettingsController() {
super.onActivityStopped(activity) super.onActivityStopped(activity)
router.popCurrentController() router.popCurrentController()
} }
companion object {
private val MODIFIED_TEXT = HtmlCompat.fromHtml("<font color='red'>MODIFIED</font>", HtmlCompat.FROM_HTML_MODE_LEGACY)
}
} }