From 910be40d791213f5ca220be2d66b6944fb85ccfd Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Mon, 26 May 2025 10:01:36 -0700 Subject: [PATCH] feat(IconOpts): colored icon preview --- .../ui/components/CustomDiscordAppIcon.kt | 82 +++++++++++++++++++ .../ui/screens/iconopts/IconOptionsScreen.kt | 34 +++++++- .../ui/screens/patchopts/PatchOptions.kt | 4 +- .../manager/ui/util/ThrottledStateEffect.kt | 34 ++++++++ 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt create mode 100644 app/src/main/kotlin/com/aliucord/manager/ui/util/ThrottledStateEffect.kt diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt b/app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt new file mode 100644 index 0000000..0dc3391 --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt @@ -0,0 +1,82 @@ +package com.aliucord.manager.ui.components + +import android.graphics.Paint +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.InsetDrawable +import android.widget.ImageView +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.core.graphics.applyCanvas +import androidx.core.graphics.createBitmap +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import com.aliucord.manager.R + +@Composable +fun discordIconDrawable( + backgroundColor: Color, + size: Dp = 24.dp, +): Drawable { + val density = LocalDensity.current + val context = LocalContext.current + val size = with(density) { size.roundToPx() } + + val bitmap = remember(size) { createBitmap(size, size) } + val drawable = remember(context) { + InsetDrawable( + /* drawable = */ ContextCompat.getDrawable(context, R.drawable.ic_discord)!!, + /* inset = */ (size * .17f).toInt(), + ) + } + + DisposableEffect(size) { + onDispose { + bitmap.recycle() + } + } + + DisposableEffect(backgroundColor, bitmap, drawable) { + bitmap.applyCanvas { + val paint = Paint().apply { + style = Paint.Style.FILL + setColor(backgroundColor.toArgb()) + } + + drawRect(Rect(0, 0, width, height), paint) + drawable.setBounds(0, 0, width, height) + drawable.draw(this) + } + + onDispose {} + } + + return remember(context, bitmap) { + RoundedBitmapDrawableFactory.create(context.resources, bitmap).apply { + isCircular = true + setAntiAlias(true) + } + } +} + +@Composable +fun ColoredDiscordAppIcon( + drawable: Drawable, + modifier: Modifier = Modifier, +) { + AndroidView( + factory = { context -> + ImageView(context).apply { + setImageDrawable(drawable) + } + }, + modifier = modifier, + ) +} diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/iconopts/IconOptionsScreen.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/iconopts/IconOptionsScreen.kt index bc8ac32..0b16073 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/iconopts/IconOptionsScreen.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/iconopts/IconOptionsScreen.kt @@ -31,8 +31,10 @@ import coil3.compose.AsyncImage import com.aliucord.manager.R import com.aliucord.manager.ui.components.* import com.aliucord.manager.ui.screens.iconopts.components.* +import com.aliucord.manager.ui.screens.patchopts.PatchOptions import com.aliucord.manager.ui.screens.patchopts.PatchOptionsScreen import com.aliucord.manager.ui.util.ColorSaver +import com.aliucord.manager.ui.util.throttledState import dev.zt64.compose.pipette.CircularColorPicker import dev.zt64.compose.pipette.HsvColor import kotlinx.parcelize.IgnoredOnParcel @@ -88,7 +90,37 @@ fun IconOptionsScreenContent( .padding(paddingValues) .padding(horizontal = 20.dp, vertical = 20.dp) ) { - // TODO: live icon preview + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + val color = when (mode) { + IconOptionsMode.Original -> PatchOptions.IconReplacement.Blurple.color // TODO: old vs new discord color? + IconOptionsMode.Aliucord -> PatchOptions.IconReplacement.Aliucord.color + IconOptionsMode.CustomColor -> selectedColor.toColor() + IconOptionsMode.CustomImage -> Color.Black // TODO: custom image icon preview + } + val throttledColor by throttledState(value = color, throttleMs = 75) + + val drawable = discordIconDrawable( + backgroundColor = throttledColor, + size = 72.dp, + ) + + ColoredDiscordAppIcon( + drawable = drawable, + modifier = Modifier.size(24.dp), + ) + ColoredDiscordAppIcon( + drawable = drawable, + modifier = Modifier.size(48.dp), + ) + ColoredDiscordAppIcon( + drawable = drawable, + modifier = Modifier.size(72.dp), + ) + } HorizontalDivider(Modifier.padding(vertical = 16.dp)) diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptions.kt b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptions.kt index f5a7b54..d1d0c67 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptions.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/screens/patchopts/PatchOptions.kt @@ -82,9 +82,9 @@ data class PatchOptions( val Aliucord = CustomColor(Color(0xFF00C853)) /** - * The Discord icon color. + * The new Discord blurple. */ - val Discord = CustomColor(Color(0xFF7289DA)) + val Blurple = CustomColor(Color(0xFF5865F2)) } } diff --git a/app/src/main/kotlin/com/aliucord/manager/ui/util/ThrottledStateEffect.kt b/app/src/main/kotlin/com/aliucord/manager/ui/util/ThrottledStateEffect.kt new file mode 100644 index 0000000..311338d --- /dev/null +++ b/app/src/main/kotlin/com/aliucord/manager/ui/util/ThrottledStateEffect.kt @@ -0,0 +1,34 @@ +package com.aliucord.manager.ui.util + +import androidx.compose.runtime.* +import kotlinx.coroutines.* + +@Composable +fun throttledState( + value: T, + throttleMs: Long, +): State { + val scope = rememberCoroutineScope() + val state = remember { mutableStateOf(value) } + var lastEmitTime by remember { mutableLongStateOf(0L) } + var trailingJob by remember { mutableStateOf(null) } + + LaunchedEffect(value) { + val now = System.currentTimeMillis() + val sinceLastEmit = now - lastEmitTime + + if (sinceLastEmit >= throttleMs) { + lastEmitTime = now + state.value = value + } else { + trailingJob?.cancel() + trailingJob = scope.launch { + delay(throttleMs - sinceLastEmit) + lastEmitTime = System.currentTimeMillis() + state.value = value + } + } + } + + return state +}