fix: add ugly hacks because reflection breaks it

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-11-01 04:28:02 +01:00
parent 6be963154d
commit 88faa795ba
5 changed files with 357 additions and 51 deletions

View File

@ -81,4 +81,30 @@
public static *** i(...);
public static *** w(...);
public static *** e(...);
}
-dontwarn kotlin.**
-dontwarn org.w3c.dom.events.*
-dontwarn org.jetbrains.kotlin.di.InjectorForRuntimeDescriptorLoader
-keep class kotlin.** { *; }
-keep class org.jetbrains.kotlin.** { *; }
-keepclassmembers,allowoptimization enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
**[] $VALUES;
public *;
}
-keepattributes InnerClasses
-keep class androidx.compose.ui.graphics.ColorKt { *; }
-keep class androidx.compose.material3.ColorScheme { *; }
-keep class androidx.compose.material3.ColorSchemeKt { *; }
-keep class androidx.compose.material3.ColorSchemeKt$* { *; }
-keepclassmembers class androidx.compose.material3.ColorSchemeKt {
public static final <fields>;
public <fields>;
}

View File

@ -1,5 +1,6 @@
package chat.revolt.api.schemas
import chat.revolt.ui.theme.OverridableColourScheme
import kotlinx.serialization.Serializable
@Serializable
@ -18,5 +19,5 @@ data class AndroidSpecificSettings(
* Colour overrides.
* Map of `primary, onPrimary, primaryContainer, onPrimaryContainer, inversePrimary, secondary, onSecondary, secondaryContainer, onSecondaryContainer, tertiary, onTertiary, tertiaryContainer, onTertiaryContainer, background, onBackground, surface, onSurface, surfaceVariant, onSurfaceVariant, surfaceTint, inverseSurface, inverseOnSurface, error, onError, errorContainer, onErrorContainer, outline, outlineVariant, scrim` to int colours.
*/
var colourOverrides: Map<String, Int>? = null,
var colourOverrides: OverridableColourScheme? = null,
)

View File

@ -31,7 +31,6 @@ import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material3.Button
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -65,12 +64,15 @@ import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import chat.revolt.R
import chat.revolt.api.RevoltCbor
import chat.revolt.api.RevoltJson
import chat.revolt.api.settings.GlobalState
import chat.revolt.api.settings.SyncedSettings
import chat.revolt.components.generic.PageHeader
import chat.revolt.components.screens.settings.appearance.ColourChip
import chat.revolt.ui.theme.ClearRippleTheme
import chat.revolt.ui.theme.OverridableColourScheme
import chat.revolt.ui.theme.Theme
import chat.revolt.ui.theme.getFieldByName
import chat.revolt.ui.theme.systemSupportsDynamicColors
import com.github.skydoves.colorpicker.compose.AlphaSlider
import com.github.skydoves.colorpicker.compose.BrightnessSlider
@ -85,8 +87,6 @@ import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import java.io.File
import javax.inject.Inject
import kotlin.reflect.KVisibility
import kotlin.reflect.full.memberProperties
@HiltViewModel
@Suppress("StaticFieldLeak")
@ -110,7 +110,17 @@ class AppearanceSettingsScreenViewModel @Inject constructor(
val overrides = SyncedSettings.android.copy().colourOverrides
if (overrides != null) {
val mutOverrides = overrides.toMutableMap()
// Yes, this looks stupid. Please see the comments in OverridableColourScheme.kt regarding this.
val json = RevoltJson.encodeToString(
OverridableColourScheme.serializer(),
overrides
)
val asMap = RevoltJson.decodeFromString(
MapSerializer(String.serializer(), Int.serializer()),
json
)
val mutOverrides = asMap.toMutableMap()
if (value == null) {
mutOverrides.remove(fieldName)
} else {
@ -119,35 +129,31 @@ class AppearanceSettingsScreenViewModel @Inject constructor(
SyncedSettings.updateAndroid(
SyncedSettings.android.copy(
colourOverrides = mutOverrides
colourOverrides = OverridableColourScheme()
.applyFromKeyValueMap(mutOverrides)
)
)
} else if (value != null) {
SyncedSettings.updateAndroid(
SyncedSettings.android.copy(
colourOverrides = mapOf(
fieldName to value
)
colourOverrides = OverridableColourScheme()
.applyFromKeyValueMap(
mapOf(fieldName to value)
)
)
)
}
}
}
private fun validOverrideKey(key: String): Boolean {
return ColorScheme::class.memberProperties.any { it.name == key }
}
private fun applyBulkOverrides(overrides: Map<String, Int>) {
val existingOverrides = SyncedSettings.android.colourOverrides ?: mapOf()
val newOverrides = existingOverrides.toMutableMap()
newOverrides.putAll(overrides.filterKeys { validOverrideKey(it) })
val existingOverrides = SyncedSettings.android.colourOverrides ?: OverridableColourScheme()
viewModelScope.launch {
SyncedSettings.updateAndroid(
SyncedSettings.android.copy(
colourOverrides = newOverrides
colourOverrides = existingOverrides
.applyFromKeyValueMap(overrides.filterKeys { it in OverridableColourScheme.fieldNames })
)
)
}
@ -186,8 +192,8 @@ class AppearanceSettingsScreenViewModel @Inject constructor(
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
outputStream.write(
RevoltCbor.encodeToByteArray(
MapSerializer(String.serializer(), Int.serializer()),
SyncedSettings.android.colourOverrides ?: mapOf()
OverridableColourScheme.serializer(),
SyncedSettings.android.colourOverrides ?: OverridableColourScheme()
)
)
}
@ -452,28 +458,23 @@ fun AppearanceSettingsScreen(
Spacer(modifier = Modifier.height(10.dp))
ColorScheme::class.memberProperties.forEach { member ->
if (member.visibility != KVisibility.PUBLIC) return@forEach
val name = member.name
val value = member.getter.call(MaterialTheme.colorScheme) as Color
OverridableColourScheme.fieldNames.forEach { fieldName ->
val value =
SyncedSettings.android.colourOverrides?.getFieldByName(fieldName)
?: MaterialTheme.colorScheme.getFieldByName(fieldName)
ColourChip(
color = value,
text = try {
R.string::class.java.getField("settings_appearance_colour_overrides_${name.toSnakeCase()}")
.getInt(null)
.let { context.getString(it) }
} catch (e: Exception) {
name
},
color = Color(value ?: 0),
text = OverridableColourScheme.fieldNameToResource[fieldName]
?.let { context.getString(it) }
?: fieldName,
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp)
.testTag("set_colour_override_$name")
.testTag("set_colour_override_$fieldName")
) {
viewModel.selectedOverrideName = name
viewModel.selectedOverrideInitialValue = value.toArgb()
viewModel.selectedOverrideName = fieldName
viewModel.selectedOverrideInitialValue = value
viewModel.overridePickerSheetVisible = true
}
}

View File

@ -0,0 +1,286 @@
package chat.revolt.ui.theme
import androidx.compose.material3.ColorScheme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import chat.revolt.R
import kotlinx.serialization.Serializable
// Word of warning, this file is ugly, because I've had to fight a bit with the Compose compiler,
// namely native Kotlin (not Java) reflection seems to consistently break it.
// So I've had to resort to... methods like this. I'm sorry.
// If you've been linked to this file, I promise the rest of the codebase is not like this.
// Original comments during research and development preserved.
@Serializable
data class OverridableColourScheme(
val primary: Int? = null,
val onPrimary: Int? = null,
val primaryContainer: Int? = null,
val onPrimaryContainer: Int? = null,
val inversePrimary: Int? = null,
val secondary: Int? = null,
val onSecondary: Int? = null,
val secondaryContainer: Int? = null,
val onSecondaryContainer: Int? = null,
val tertiary: Int? = null,
val onTertiary: Int? = null,
val tertiaryContainer: Int? = null,
val onTertiaryContainer: Int? = null,
val background: Int? = null,
val onBackground: Int? = null,
val surface: Int? = null,
val onSurface: Int? = null,
val surfaceVariant: Int? = null,
val onSurfaceVariant: Int? = null,
val surfaceTint: Int? = null,
val inverseSurface: Int? = null,
val inverseOnSurface: Int? = null,
val error: Int? = null,
val onError: Int? = null,
val errorContainer: Int? = null,
val onErrorContainer: Int? = null,
val outline: Int? = null,
val outlineVariant: Int? = null,
val scrim: Int? = null
) {
fun applyTo(colorScheme: ColorScheme): ColorScheme {
var newScheme = colorScheme.copy()
// This is SLOW. It is also STUPID. But using reflection breaks the Compose compiler.
// Another piece of trash from Google. This company should go bankrupt already, what a
// joke.
if (primary != null) newScheme = newScheme.copy(primary = Color(primary))
if (onPrimary != null) newScheme = newScheme.copy(onPrimary = Color(onPrimary))
if (primaryContainer != null) newScheme =
newScheme.copy(primaryContainer = Color(primaryContainer))
if (onPrimaryContainer != null) newScheme =
newScheme.copy(onPrimaryContainer = Color(onPrimaryContainer))
if (inversePrimary != null) newScheme =
newScheme.copy(inversePrimary = Color(inversePrimary))
if (secondary != null) newScheme = newScheme.copy(secondary = Color(secondary))
if (onSecondary != null) newScheme = newScheme.copy(onSecondary = Color(onSecondary))
if (secondaryContainer != null) newScheme =
newScheme.copy(secondaryContainer = Color(secondaryContainer))
if (onSecondaryContainer != null) newScheme =
newScheme.copy(onSecondaryContainer = Color(onSecondaryContainer))
if (tertiary != null) newScheme = newScheme.copy(tertiary = Color(tertiary))
if (onTertiary != null) newScheme = newScheme.copy(onTertiary = Color(onTertiary))
if (tertiaryContainer != null) newScheme =
newScheme.copy(tertiaryContainer = Color(tertiaryContainer))
if (onTertiaryContainer != null) newScheme =
newScheme.copy(onTertiaryContainer = Color(onTertiaryContainer))
if (background != null) newScheme = newScheme.copy(background = Color(background))
if (onBackground != null) newScheme = newScheme.copy(onBackground = Color(onBackground))
if (surface != null) newScheme = newScheme.copy(surface = Color(surface))
if (onSurface != null) newScheme = newScheme.copy(onSurface = Color(onSurface))
if (surfaceVariant != null) newScheme =
newScheme.copy(surfaceVariant = Color(surfaceVariant))
if (onSurfaceVariant != null) newScheme =
newScheme.copy(onSurfaceVariant = Color(onSurfaceVariant))
if (surfaceTint != null) newScheme = newScheme.copy(surfaceTint = Color(surfaceTint))
if (inverseSurface != null) newScheme =
newScheme.copy(inverseSurface = Color(inverseSurface))
if (inverseOnSurface != null) newScheme =
newScheme.copy(inverseOnSurface = Color(inverseOnSurface))
if (error != null) newScheme = newScheme.copy(error = Color(error))
if (onError != null) newScheme = newScheme.copy(onError = Color(onError))
if (errorContainer != null) newScheme =
newScheme.copy(errorContainer = Color(errorContainer))
if (onErrorContainer != null) newScheme =
newScheme.copy(onErrorContainer = Color(onErrorContainer))
if (outline != null) newScheme = newScheme.copy(outline = Color(outline))
if (outlineVariant != null) newScheme =
newScheme.copy(outlineVariant = Color(outlineVariant))
if (scrim != null) newScheme = newScheme.copy(scrim = Color(scrim))
return newScheme
}
fun applyFromKeyValueMap(map: Map<String, Int>): OverridableColourScheme {
var newScheme = this
map.filterKeys { it in fieldNames }.forEach { (key, value) ->
when (key) {
"primary" -> newScheme = newScheme.copy(primary = value)
"onPrimary" -> newScheme = newScheme.copy(onPrimary = value)
"primaryContainer" -> newScheme = newScheme.copy(primaryContainer = value)
"onPrimaryContainer" -> newScheme =
newScheme.copy(onPrimaryContainer = value)
"inversePrimary" -> newScheme = newScheme.copy(inversePrimary = (value))
"secondary" -> newScheme = newScheme.copy(secondary = (value))
"onSecondary" -> newScheme = newScheme.copy(onSecondary = (value))
"secondaryContainer" -> newScheme =
newScheme.copy(secondaryContainer = (value))
"onSecondaryContainer" -> newScheme =
newScheme.copy(onSecondaryContainer = (value))
"tertiary" -> newScheme = newScheme.copy(tertiary = (value))
"onTertiary" -> newScheme = newScheme.copy(onTertiary = (value))
"tertiaryContainer" -> newScheme = newScheme.copy(tertiaryContainer = (value))
"onTertiaryContainer" -> newScheme =
newScheme.copy(onTertiaryContainer = (value))
"background" -> newScheme = newScheme.copy(background = (value))
"onBackground" -> newScheme = newScheme.copy(onBackground = (value))
"surface" -> newScheme = newScheme.copy(surface = (value))
"onSurface" -> newScheme = newScheme.copy(onSurface = (value))
"surfaceVariant" -> newScheme = newScheme.copy(surfaceVariant = (value))
"onSurfaceVariant" -> newScheme = newScheme.copy(onSurfaceVariant = (value))
"surfaceTint" -> newScheme = newScheme.copy(surfaceTint = (value))
"inverseSurface" -> newScheme = newScheme.copy(inverseSurface = (value))
"inverseOnSurface" -> newScheme = newScheme.copy(inverseOnSurface = (value))
"error" -> newScheme = newScheme.copy(error = (value))
"onError" -> newScheme = newScheme.copy(onError = (value))
"errorContainer" -> newScheme = newScheme.copy(errorContainer = (value))
"onErrorContainer" -> newScheme = newScheme.copy(onErrorContainer = (value))
"outline" -> newScheme = newScheme.copy(outline = (value))
"outlineVariant" -> newScheme = newScheme.copy(outlineVariant = (value))
"scrim" -> newScheme = newScheme.copy(scrim = (value))
}
}
return newScheme
}
fun getFieldByName(name: String): Int? {
return when (name) {
"primary" -> primary
"onPrimary" -> onPrimary
"primaryContainer" -> primaryContainer
"onPrimaryContainer" -> onPrimaryContainer
"inversePrimary" -> inversePrimary
"secondary" -> secondary
"onSecondary" -> onSecondary
"secondaryContainer" -> secondaryContainer
"onSecondaryContainer" -> onSecondaryContainer
"tertiary" -> tertiary
"onTertiary" -> onTertiary
"tertiaryContainer" -> tertiaryContainer
"onTertiaryContainer" -> onTertiaryContainer
"background" -> background
"onBackground" -> onBackground
"surface" -> surface
"onSurface" -> onSurface
"surfaceVariant" -> surfaceVariant
"onSurfaceVariant" -> onSurfaceVariant
"surfaceTint" -> surfaceTint
"inverseSurface" -> inverseSurface
"inverseOnSurface" -> inverseOnSurface
"error" -> error
"onError" -> onError
"errorContainer" -> errorContainer
"onErrorContainer" -> onErrorContainer
"outline" -> outline
"outlineVariant" -> outlineVariant
"scrim" -> scrim
else -> null
}
}
companion object {
// I am genuinely going to go to Google's office and hand them this code and tell them
// to fix their garbage Gradle plugin
val fieldNames = listOf(
"primary",
"onPrimary",
"primaryContainer",
"onPrimaryContainer",
"inversePrimary",
"secondary",
"onSecondary",
"secondaryContainer",
"onSecondaryContainer",
"tertiary",
"onTertiary",
"tertiaryContainer",
"onTertiaryContainer",
"background",
"onBackground",
"surface",
"onSurface",
"surfaceVariant",
"onSurfaceVariant",
"surfaceTint",
"inverseSurface",
"inverseOnSurface",
"error",
"onError",
"errorContainer",
"onErrorContainer",
"outline",
"outlineVariant",
"scrim"
)
// See above comment HOLY SHIT i am genuinely going insane
val fieldNameToResource = mapOf(
"primary" to R.string.settings_appearance_colour_overrides_primary,
"onPrimary" to R.string.settings_appearance_colour_overrides_on_primary,
"primaryContainer" to R.string.settings_appearance_colour_overrides_primary_container,
"onPrimaryContainer" to R.string.settings_appearance_colour_overrides_on_primary_container,
"inversePrimary" to R.string.settings_appearance_colour_overrides_inverse_primary,
"secondary" to R.string.settings_appearance_colour_overrides_secondary,
"onSecondary" to R.string.settings_appearance_colour_overrides_on_secondary,
"secondaryContainer" to R.string.settings_appearance_colour_overrides_secondary_container,
"onSecondaryContainer" to R.string.settings_appearance_colour_overrides_on_secondary_container,
"tertiary" to R.string.settings_appearance_colour_overrides_tertiary,
"onTertiary" to R.string.settings_appearance_colour_overrides_on_tertiary,
"tertiaryContainer" to R.string.settings_appearance_colour_overrides_tertiary_container,
"onTertiaryContainer" to R.string.settings_appearance_colour_overrides_on_tertiary_container,
"background" to R.string.settings_appearance_colour_overrides_background,
"onBackground" to R.string.settings_appearance_colour_overrides_on_background,
"surface" to R.string.settings_appearance_colour_overrides_surface,
"onSurface" to R.string.settings_appearance_colour_overrides_on_surface,
"surfaceVariant" to R.string.settings_appearance_colour_overrides_surface_variant,
"onSurfaceVariant" to R.string.settings_appearance_colour_overrides_on_surface_variant,
"surfaceTint" to R.string.settings_appearance_colour_overrides_surface_tint,
"inverseSurface" to R.string.settings_appearance_colour_overrides_inverse_surface,
"inverseOnSurface" to R.string.settings_appearance_colour_overrides_inverse_on_surface,
"error" to R.string.settings_appearance_colour_overrides_error,
"onError" to R.string.settings_appearance_colour_overrides_on_error,
"errorContainer" to R.string.settings_appearance_colour_overrides_error_container,
"onErrorContainer" to R.string.settings_appearance_colour_overrides_on_error_container,
"outline" to R.string.settings_appearance_colour_overrides_outline,
"outlineVariant" to R.string.settings_appearance_colour_overrides_outline_variant,
"scrim" to R.string.settings_appearance_colour_overrides_scrim
)
}
}
fun ColorScheme.getFieldByName(name: String): Int? {
return when (name) {
"primary" -> primary.toArgb()
"onPrimary" -> onPrimary.toArgb()
"primaryContainer" -> primaryContainer.toArgb()
"onPrimaryContainer" -> onPrimaryContainer.toArgb()
"inversePrimary" -> inversePrimary.toArgb()
"secondary" -> secondary.toArgb()
"onSecondary" -> onSecondary.toArgb()
"secondaryContainer" -> secondaryContainer.toArgb()
"onSecondaryContainer" -> onSecondaryContainer.toArgb()
"tertiary" -> tertiary.toArgb()
"onTertiary" -> onTertiary.toArgb()
"tertiaryContainer" -> tertiaryContainer.toArgb()
"onTertiaryContainer" -> onTertiaryContainer.toArgb()
"background" -> background.toArgb()
"onBackground" -> onBackground.toArgb()
"surface" -> surface.toArgb()
"onSurface" -> onSurface.toArgb()
"surfaceVariant" -> surfaceVariant.toArgb()
"onSurfaceVariant" -> onSurfaceVariant.toArgb()
"surfaceTint" -> surfaceTint.toArgb()
"inverseSurface" -> inverseSurface.toArgb()
"inverseOnSurface" -> inverseOnSurface.toArgb()
"error" -> error.toArgb()
"onError" -> onError.toArgb()
"errorContainer" -> errorContainer.toArgb()
"onErrorContainer" -> onErrorContainer.toArgb()
"outline" -> outline.toArgb()
"outlineVariant" -> outlineVariant.toArgb()
"scrim" -> scrim.toArgb()
else -> null
}
}

View File

@ -19,8 +19,6 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
val RevoltColorScheme = darkColorScheme(
primary = Color(0xffda4e5b),
@ -68,7 +66,10 @@ enum class Theme {
}
@Composable
fun getColorScheme(requestedTheme: Theme, colourOverrides: Map<String, Int>? = null): ColorScheme {
fun getColorScheme(
requestedTheme: Theme,
colourOverrides: OverridableColourScheme? = null
): ColorScheme {
val context = LocalContext.current
val systemInDarkTheme = isSystemInDarkTheme()
@ -112,24 +113,15 @@ fun getColorScheme(requestedTheme: Theme, colourOverrides: Map<String, Int>? = n
}
}
colorScheme::class.memberProperties.forEach {
if (it is KMutableProperty<*>) {
val name = it.name
val value = colourOverrides?.get(name)
if (value != null) {
it.setter.call(colorScheme, Color(value))
}
}
}
return colorScheme
if (colourOverrides == null) return colorScheme
return colourOverrides.applyTo(colorScheme)
}
@SuppressLint("NewApi")
@Composable
fun RevoltTheme(
requestedTheme: Theme,
colourOverrides: Map<String, Int>?,
colourOverrides: OverridableColourScheme? = null,
content: @Composable () -> Unit
) {
val colorScheme = getColorScheme(requestedTheme, colourOverrides)