feat(IconOpts): colored icon preview
This commit is contained in:
parent
b89a529afa
commit
910be40d79
4 changed files with 151 additions and 3 deletions
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -31,8 +31,10 @@ import coil3.compose.AsyncImage
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.ui.components.*
|
import com.aliucord.manager.ui.components.*
|
||||||
import com.aliucord.manager.ui.screens.iconopts.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.screens.patchopts.PatchOptionsScreen
|
||||||
import com.aliucord.manager.ui.util.ColorSaver
|
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.CircularColorPicker
|
||||||
import dev.zt64.compose.pipette.HsvColor
|
import dev.zt64.compose.pipette.HsvColor
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
|
@ -88,7 +90,37 @@ fun IconOptionsScreenContent(
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.padding(horizontal = 20.dp, vertical = 20.dp)
|
.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))
|
HorizontalDivider(Modifier.padding(vertical = 16.dp))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,9 +82,9 @@ data class PatchOptions(
|
||||||
val Aliucord = CustomColor(Color(0xFF00C853))
|
val Aliucord = CustomColor(Color(0xFF00C853))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Discord icon color.
|
* The new Discord blurple.
|
||||||
*/
|
*/
|
||||||
val Discord = CustomColor(Color(0xFF7289DA))
|
val Blurple = CustomColor(Color(0xFF5865F2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.aliucord.manager.ui.util
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> throttledState(
|
||||||
|
value: T,
|
||||||
|
throttleMs: Long,
|
||||||
|
): State<T> {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val state = remember { mutableStateOf(value) }
|
||||||
|
var lastEmitTime by remember { mutableLongStateOf(0L) }
|
||||||
|
var trailingJob by remember { mutableStateOf<Job?>(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
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue