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