From 71bf74c88045c2ba634c9c907591ba36d33dcba0 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Sat, 31 May 2025 12:10:05 -0700 Subject: [PATCH] feat(IconOptions): custom image preview --- .../ui/components/CustomDiscordAppIcon.kt | 43 +++++-- .../ui/screens/iconopts/IconOptionsScreen.kt | 116 +++++++++++++----- app/src/main/res/values/strings.xml | 4 +- 3 files changed, 117 insertions(+), 46 deletions(-) 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 index c0cb97c..5f6c749 100644 --- a/app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt +++ b/app/src/main/kotlin/com/aliucord/manager/ui/components/CustomDiscordAppIcon.kt @@ -1,10 +1,10 @@ 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.graphics.* +import android.graphics.drawable.* +import android.os.Build import android.widget.ImageView +import androidx.annotation.RequiresApi import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -17,8 +17,9 @@ 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 androidx.core.graphics.drawable.toDrawable import com.aliucord.manager.R +import com.aliucord.manager.ui.screens.patchopts.PatchOptions.IconReplacement @Composable fun discordIconDrawable( @@ -30,7 +31,6 @@ fun discordIconDrawable( val context = LocalContext.current val size = with(density) { size.roundToPx() } - val bitmap = remember(size) { createBitmap(size, size) } val drawable = remember(context, oldLogo) { val drawableId = when (oldLogo) { false -> R.drawable.ic_discord @@ -45,6 +45,7 @@ fun discordIconDrawable( } } + val bitmap = remember(size) { createBitmap(size, size) } DisposableEffect(size) { onDispose { bitmap.recycle() @@ -57,8 +58,8 @@ fun discordIconDrawable( style = Paint.Style.FILL setColor(backgroundColor.toArgb()) } - drawRect(Rect(0, 0, width, height), paint) + drawable.setBounds(0, 0, width, height) drawable.draw(this) } @@ -67,15 +68,30 @@ fun discordIconDrawable( } return remember(context, bitmap) { - RoundedBitmapDrawableFactory.create(context.resources, bitmap).apply { - isCircular = true - setAntiAlias(true) - } + bitmap.toDrawable(context.resources) + } +} + +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun customIconDrawable( + foregroundIcon: ByteArray, + backgroundColor: Color = IconReplacement.BlurpleColor, +): Drawable { + val context = LocalContext.current + + return remember(context, foregroundIcon) { + val foregroundIconBitmap = BitmapFactory.decodeByteArray(foregroundIcon, 0, foregroundIcon.size) + + AdaptiveIconDrawable( + /* backgroundDrawable = */ backgroundColor.toArgb().toDrawable(), + /* foregroundDrawable = */ foregroundIconBitmap.toDrawable(context.resources), + ) } } @Composable -fun ColoredDiscordAppIcon( +fun Drawable( drawable: Drawable, modifier: Modifier = Modifier, ) { @@ -85,6 +101,9 @@ fun ColoredDiscordAppIcon( setImageDrawable(drawable) } }, + update = { imageView -> + imageView.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 e97b96e..70a8f61 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 @@ -10,6 +10,8 @@ import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -90,39 +92,11 @@ fun IconOptionsScreenContent( .padding(paddingValues) .padding(horizontal = 20.dp, vertical = 20.dp) ) { - Row( - horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - val color = when (mode) { - IconOptionsMode.Original -> PatchOptions.IconReplacement.BlurpleColor - IconOptionsMode.OldDiscord -> PatchOptions.IconReplacement.OldBlurpleColor - IconOptionsMode.Aliucord -> PatchOptions.IconReplacement.AliucordColor - 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, - oldLogo = mode == IconOptionsMode.OldDiscord, - 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), - ) - } + IconPreview( + mode = mode, + selectedColor = selectedColor, + selectedImage = selectedImage, + ) HorizontalDivider(Modifier.padding(vertical = 16.dp)) @@ -186,6 +160,82 @@ fun IconOptionsScreenContent( } } +@Composable +private fun IconPreview( + mode: IconOptionsMode, + selectedColor: HsvColor, + selectedImage: () -> ByteArray?, +) { + Label( + name = stringResource(R.string.iconopts_icon_preview_title), + description = stringResource(R.string.iconopts_icon_preview_desc), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 14.dp), + ) { + val drawable = if (mode == IconOptionsMode.CustomImage) { + val image = selectedImage() + + if (image != null && Build.VERSION.SDK_INT >= 26) { + customIconDrawable( + foregroundIcon = image, + ) + } else { + discordIconDrawable( + backgroundColor = Color.Black, + oldLogo = false, + size = 72.dp, + ) + } + } else { + val color = when (mode) { + IconOptionsMode.Original -> PatchOptions.IconReplacement.BlurpleColor + IconOptionsMode.OldDiscord -> PatchOptions.IconReplacement.OldBlurpleColor + IconOptionsMode.Aliucord -> PatchOptions.IconReplacement.AliucordColor + IconOptionsMode.CustomColor -> selectedColor.toColor() + IconOptionsMode.CustomImage -> error("unreachable") + } + val throttledColor by throttledState(value = color, throttleMs = 75) + + discordIconDrawable( + backgroundColor = throttledColor, + oldLogo = mode == IconOptionsMode.OldDiscord, + size = 72.dp, + ) + } + + Drawable( + drawable = drawable, + modifier = Modifier + .size(24.dp) + .clip(CircleShape), + ) + Drawable( + drawable = drawable, + modifier = Modifier + .size(48.dp) + .clip(CircleShape), + ) + Drawable( + drawable = drawable, + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(4.dp)), + ) + Drawable( + drawable = drawable, + modifier = Modifier + .size(72.dp) + .clip(CircleShape), + ) + } + } +} + @Composable private fun CustomColorOptions( color: HsvColor, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 654aa7e..36d18e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,8 +189,10 @@ Image size exceeds maximum of 256KiB Select Image Example image + Icon Preview + A preview of the selected options, cropped and scaled to show how the icon will appear on your device Preview - Uncropped preview of the selected image + A full, uncropped preview of the selected image. No image selected Log