enh : refactor carousel imp

This commit is contained in:
MrSluffy
2025-01-10 12:14:16 +08:00
parent f5fdbdb954
commit e154c33da9

View File

@@ -1,17 +1,15 @@
package app.lawnchair.ui.popup
import android.animation.LayoutTransition
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.WallpaperManager
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.AttributeSet
import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -46,82 +44,68 @@ class WallpaperCarouselView @JvmOverloads constructor(
private var currentItemIndex = 0
private val iconFrame = IconFrame(context).apply {
setIcon(R.drawable.ic_tick)
setBackgroundWithRadius(bgColor = Themes.getColorAccent(context), cornerRadius = 100F)
setBackgroundWithRadius(Themes.getColorAccent(context), 100F)
}
private val loadingView: ProgressBar = ProgressBar(context).apply { isIndeterminate = true }
private val loadingView = ProgressBar(context).apply { isIndeterminate = true }
init {
orientation = HORIZONTAL
setLayoutTransition(
LayoutTransition().apply {
enableTransitionType(LayoutTransition.CHANGING)
setDuration(200L)
},
)
addView(loadingView)
val factory = WallpaperViewModelFactory(context)
viewModel = ViewModelProvider(context as ViewModelStoreOwner, factory)[WallpaperViewModel::class.java]
viewModel = ViewModelProvider(
context as ViewModelStoreOwner,
WallpaperViewModelFactory(context),
)[WallpaperViewModel::class.java]
observeWallpapers()
}
private fun observeWallpapers() {
viewModel.wallpapers.observe(context as LifecycleOwner) { wallpapers ->
if (wallpapers.isEmpty()) {
visibility = GONE
loadingView.visibility = GONE
} else {
visibility = VISIBLE
displayWallpapers(wallpapers)
}
visibility = if (wallpapers.isEmpty()) GONE else VISIBLE
loadingView.visibility = if (wallpapers.isEmpty()) GONE else VISIBLE
if (wallpapers.isNotEmpty()) displayWallpapers(wallpapers)
}
}
private fun displayWallpapers(wallpapers: List<Wallpaper>) {
removeAllViews()
val isLandscape = deviceProfile.isLandscape || deviceProfile.isTablet
val totalWidth = width.takeIf { it > 0 } ?: (deviceProfile.widthPx * if (isLandscape) 0.5 else 0.8).toInt()
val totalWidth = calculateTotalWidth()
val firstItemWidth = totalWidth * 0.4
val remainingWidth = totalWidth - firstItemWidth
val marginBetweenItems = totalWidth * 0.03
val itemWidth = (remainingWidth - (marginBetweenItems * (wallpapers.size - 1))) / (wallpapers.size - 1)
val itemWidth = calculateItemWidth(totalWidth, wallpapers.size, firstItemWidth)
val margin = (totalWidth * 0.03).toInt()
wallpapers.forEachIndexed { index, wallpaper ->
val cardView = createCardView(index, firstItemWidth, itemWidth, marginBetweenItems, wallpaper)
val cardView = createCardView(index, firstItemWidth, itemWidth, margin, wallpaper)
addView(cardView)
// Load the wallpaper image only if necessary
loadWallpaperImage(wallpaper, cardView, index == currentItemIndex)
}
loadingView.visibility = GONE
}
private fun calculateTotalWidth(): Int {
return width.takeIf { it > 0 } ?: (deviceProfile.widthPx * if (deviceProfile.isLandscape || deviceProfile.isTablet) 0.5 else 0.8).toInt()
}
private fun calculateItemWidth(totalWidth: Int, itemCount: Int, firstItemWidth: Double): Double {
val remainingWidth = totalWidth - firstItemWidth
val marginBetweenItems = totalWidth * 0.03
return (remainingWidth - (marginBetweenItems * (itemCount - 1))) / (itemCount - 1)
}
@SuppressLint("ClickableViewAccessibility")
private fun createCardView(
index: Int,
firstItemWidth: Double,
itemWidth: Double,
marginBetweenItems: Double,
margin: Int,
wallpaper: Wallpaper,
): CardView {
return CardView(context).apply {
radius = Themes.getDialogCornerRadius(context) / 2
layoutParams = LayoutParams(
when (index) {
currentItemIndex -> firstItemWidth.toInt()
else -> itemWidth.toInt()
},
if (index == currentItemIndex) firstItemWidth.toInt() else itemWidth.toInt(),
LayoutParams.MATCH_PARENT,
).apply {
setMargins(
if (index > 0) marginBetweenItems.toInt() else 0,
0,
0,
0,
)
}
).apply { setMargins(if (index > 0) margin else 0, 0, 0, 0) }
setOnTouchListener { _, _ ->
if (index != currentItemIndex) {
@@ -136,128 +120,98 @@ class WallpaperCarouselView @JvmOverloads constructor(
private fun loadWallpaperImage(wallpaper: Wallpaper, cardView: CardView, isCurrent: Boolean) {
CoroutineScope(Dispatchers.IO).launch {
val wallpaperFile = File(wallpaper.imagePath)
val bitmap = if (wallpaperFile.exists()) {
BitmapFactory.decodeFile(wallpaper.imagePath)
} else {
null
}
val bitmap = File(wallpaper.imagePath).takeIf { it.exists() }?.let { BitmapFactory.decodeFile(it.path) }
withContext(Dispatchers.Main) { addImageView(cardView, bitmap, isCurrent) }
}
}
withContext(Dispatchers.Main) {
val imageView = ImageView(context).apply {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder))
scaleType = ImageView.ScaleType.CENTER_CROP
alpha = 0f
}
cardView.addView(imageView)
if (bitmap != null) {
imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null)
imageView.setImageBitmap(bitmap)
imageView.animate()
.alpha(1f)
.setDuration(200L)
.setInterpolator(AccelerateDecelerateInterpolator())
.withEndAction {
imageView.setLayerType(View.LAYER_TYPE_NONE, null)
if (isCurrent) addIconFrameToCenter(cardView)
}
.start()
} else {
Log.e("WallpaperCarouselView", "File not found: ${wallpaper.imagePath}")
imageView.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder))
}
}
private fun addImageView(cardView: CardView, bitmap: Bitmap?, isCurrent: Boolean) {
val imageView = ImageView(context).apply {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder))
scaleType = ImageView.ScaleType.CENTER_CROP
alpha = 0f
}
cardView.addView(imageView)
if (bitmap != null) {
imageView.setImageBitmap(bitmap)
imageView.animate().alpha(1f).setDuration(200L).withEndAction {
if (isCurrent) addIconFrameToCenter(cardView)
}.start()
}
}
private fun setWallpaper(wallpaper: Wallpaper) {
val currentCardView = getChildAt(currentItemIndex) as CardView
val loadingSpinner = ProgressBar(context).apply {
isIndeterminate = true
layoutParams = FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
}
val spinner = createLoadingSpinner()
currentCardView.removeView(iconFrame)
currentCardView.addView(loadingSpinner)
currentCardView.addView(spinner)
CoroutineScope(Dispatchers.IO).launch {
try {
val wallpaperManager = WallpaperManager.getInstance(context)
val bitmap = BitmapFactory.decodeFile(wallpaper.imagePath)
wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM)
WallpaperManager.getInstance(context).setBitmap(
BitmapFactory.decodeFile(wallpaper.imagePath),
null,
true,
WallpaperManager.FLAG_SYSTEM,
)
viewModel.updateWallpaperRank(wallpaper)
withContext(Dispatchers.Main) {
currentCardView.removeView(loadingSpinner)
addIconFrameToCenter(currentCardView)
}
} catch (e: Exception) {
Log.e("WallpaperCarouselView", "Failed to set wallpaper: ${e.message}")
} finally {
withContext(Dispatchers.Main) {
currentCardView.removeView(loadingSpinner)
currentCardView.removeView(spinner)
addIconFrameToCenter(currentCardView)
}
}
}
}
private fun addIconFrameToCenter(cardView: CardView) {
if (iconFrame.parent != null) {
(iconFrame.parent as ViewGroup).removeView(iconFrame)
}
val params = FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
).apply {
private fun createLoadingSpinner() = ProgressBar(context).apply {
isIndeterminate = true
layoutParams = FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
}
}
cardView.addView(iconFrame, params)
private fun addIconFrameToCenter(cardView: CardView? = getChildAt(currentItemIndex) as CardView) {
(iconFrame.parent as? ViewGroup)?.removeView(iconFrame)
cardView?.addView(
iconFrame,
FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
gravity = Gravity.CENTER
},
)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
if (viewModel.wallpapers.value?.isNotEmpty() == true) {
displayWallpapers(viewModel.wallpapers.value!!)
}
viewModel.wallpapers.value?.let { displayWallpapers(it) }
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val isLandscape = deviceProfile.isLandscape || deviceProfile.isTablet
val valWidth = if (isLandscape) (deviceProfile.widthPx * 0.5).toInt() else (deviceProfile.widthPx * 0.8).toInt()
val width = MeasureSpec.makeMeasureSpec(valWidth, MeasureSpec.EXACTLY)
super.onMeasure(width, heightMeasureSpec)
super.onMeasure(
MeasureSpec.makeMeasureSpec(calculateTotalWidth(), MeasureSpec.EXACTLY),
heightMeasureSpec,
)
}
private fun animateWidthTransition(
newIndex: Int,
firstItemWidth: Double,
itemWidth: Double,
) {
private fun animateWidthTransition(newIndex: Int, firstItemWidth: Double, itemWidth: Double) {
currentItemIndex = newIndex
for (i in 0 until childCount) {
val cardView = getChildAt(i) as? CardView ?: continue
val targetWidth = if (i == currentItemIndex) firstItemWidth.toInt() else itemWidth.toInt()
if (cardView.layoutParams.width != targetWidth) {
val animator = ValueAnimator.ofInt(cardView.layoutParams.width, targetWidth).apply {
duration = 300L
addUpdateListener { animation ->
val animatedValue = animation.animatedValue as Int
cardView.layoutParams = cardView.layoutParams.apply { width = animatedValue }
cardView.requestLayout()
(getChildAt(i) as? CardView)?.let { cardView ->
val targetWidth = if (i == currentItemIndex) firstItemWidth.toInt() else itemWidth.toInt()
if (cardView.layoutParams.width != targetWidth) {
ValueAnimator.ofInt(cardView.layoutParams.width, targetWidth).apply {
duration = 300L
addUpdateListener {
cardView.layoutParams.width = it.animatedValue as Int
cardView.requestLayout()
}
start()
}
}
animator.start()
}
if (i == currentItemIndex) {
addIconFrameToCenter(cardView)
if (i == currentItemIndex) addIconFrameToCenter(cardView)
}
}
}