Convert debug menu to Jetpack Compose
This commit is contained in:
parent
7403709ecd
commit
5fedef2ccb
@ -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 <--
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DebugToggle(val name: String, val pref: PreferenceMutableState<Boolean>, val default: Boolean)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ComposeContent(nestedScrollInterop: NestedScrollConnection) {
|
||||||
|
val functions by produceState<List<Pair<KFunction<*>, String>>?>(initialValue = null) {
|
||||||
|
value = withContext(Dispatchers.Default) {
|
||||||
DebugFunctions::class.declaredFunctions.filter {
|
DebugFunctions::class.declaredFunctions.filter {
|
||||||
it.visibility == KVisibility.PUBLIC
|
it.visibility == KVisibility.PUBLIC
|
||||||
}.forEach {
|
}.map {
|
||||||
preference {
|
it to it.name.replace("(.)(\\p{Upper})".toRegex(), "$1 $2")
|
||||||
title = it.name.replace("(.)(\\p{Upper})".toRegex(), "$1 $2").lowercase(Locale.getDefault()).capitalize(Locale.getDefault())
|
.lowercase(Locale.getDefault()).capitalize(Locale.getDefault())
|
||||||
isPersistent = false
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
try {
|
|
||||||
val result = it.call(DebugFunctions)
|
|
||||||
val text = "Function returned result:\n\n$result"
|
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(title.toString())
|
|
||||||
.setMessage(text)
|
|
||||||
.create()
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
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()) {
|
||||||
|
value = withContext(Dispatchers.Default) {
|
||||||
|
DebugToggles.values().map { DebugToggle(it.name, it.asPref(viewScope), it.default) }
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
title = "Toggles"
|
|
||||||
|
|
||||||
DebugToggles.values().forEach {
|
|
||||||
switchPreference {
|
|
||||||
title = it.name.replace('_', ' ').lowercase(Locale.getDefault()).capitalize(Locale.getDefault())
|
|
||||||
key = it.prefKey
|
|
||||||
defaultValue = it.default
|
|
||||||
summaryOn = if (it.default) "" else MODIFIED_TEXT
|
|
||||||
summaryOff = if (it.default) MODIFIED_TEXT else ""
|
|
||||||
}
|
}
|
||||||
|
if (functions != null) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user