diff --git a/lawnchair/res/layout/wallpaper_options_popup.xml b/lawnchair/res/layout/wallpaper_options_popup.xml
new file mode 100644
index 0000000000..e9ebdbc05a
--- /dev/null
+++ b/lawnchair/res/layout/wallpaper_options_popup.xml
@@ -0,0 +1,21 @@
+
+
diff --git a/lawnchair/res/layout/wallpaper_options_popup_item.xml b/lawnchair/res/layout/wallpaper_options_popup_item.xml
new file mode 100644
index 0000000000..9a218d11e4
--- /dev/null
+++ b/lawnchair/res/layout/wallpaper_options_popup_item.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/lawnchair/res/values/dimens.xml b/lawnchair/res/values/dimens.xml
index ece4192fb3..4060aa256c 100644
--- a/lawnchair/res/values/dimens.xml
+++ b/lawnchair/res/values/dimens.xml
@@ -105,4 +105,6 @@
64dp
18dp
+ 120dp
+
diff --git a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
index 94c9d6f126..23c947ea46 100644
--- a/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
+++ b/lawnchair/src/app/lawnchair/LawnchairLauncher.kt
@@ -47,6 +47,7 @@ import app.lawnchair.theme.ThemeProvider
import app.lawnchair.ui.popup.LawnchairShortcut
import app.lawnchair.util.getThemedIconPacksInstalled
import app.lawnchair.util.unsafeLazy
+import app.lawnchair.wallpaper.service.WallpaperDatabase
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.BaseActivity
import com.android.launcher3.BubbleTextView
@@ -227,6 +228,8 @@ class LawnchairLauncher : QuickstepLauncher() {
showQuickstepWarningIfNecessary()
reloadIconsIfNeeded()
+
+ WallpaperDatabase.INSTANCE.get(this).checkpointSync()
}
override fun collectStateHandlers(out: MutableList>) {
diff --git a/lawnchair/src/app/lawnchair/ui/popup/LawnchairPopupDialog.kt b/lawnchair/src/app/lawnchair/ui/popup/LawnchairPopupDialog.kt
new file mode 100644
index 0000000000..19fe1eb6c0
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/ui/popup/LawnchairPopupDialog.kt
@@ -0,0 +1,16 @@
+package app.lawnchair.ui.popup
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.launcher3.R
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.views.OptionsPopupView
+
+class LawnchairPopupDialog {
+ class LawnchairOptionsPopUp(context: T, attributeSet: AttributeSet) : OptionsPopupView(context, attributeSet)
+ where T : Context, T : ActivityContext {
+
+ override fun isShortcutOrWrapper(view: View): Boolean = view.id == R.id.wallpaper_container || super.isShortcutOrWrapper(view)
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt
new file mode 100644
index 0000000000..6e5e8cd6ef
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/ui/popup/WallpaperCarouselView.kt
@@ -0,0 +1,227 @@
+package app.lawnchair.ui.popup
+
+import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.WallpaperManager
+import android.content.Context
+import android.graphics.BitmapFactory
+import android.util.AttributeSet
+import android.util.Log
+import android.view.Gravity
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import androidx.cardview.widget.CardView
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelStoreOwner
+import app.lawnchair.LawnchairLauncher
+import app.lawnchair.views.component.IconFrame
+import app.lawnchair.wallpaper.model.WallpaperViewModel
+import app.lawnchair.wallpaper.model.WallpaperViewModelFactory
+import app.lawnchair.wallpaper.service.Wallpaper
+import com.android.launcher3.R
+import com.android.launcher3.util.Themes
+import com.android.launcher3.views.ActivityContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class WallpaperCarouselView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : LinearLayout(context, attrs, defStyleAttr) {
+
+ private val viewModel: WallpaperViewModel
+
+ private val deviceProfile = ActivityContext.lookupContext(context).deviceProfile
+
+ private var currentItemIndex = 0
+ private val iconFrame = IconFrame(context).apply {
+ setIcon(R.drawable.ic_tick)
+ setBackgroundWithRadius(
+ bgColor = Themes.getColorAccent(context),
+ cornerRadius = 100F,
+ )
+ }
+
+ private val loadingView: ProgressBar = ProgressBar(context).apply {
+ isIndeterminate = true
+ visibility = VISIBLE
+ }
+
+ init {
+ orientation = HORIZONTAL
+ addView(loadingView)
+ val factory = WallpaperViewModelFactory(context)
+ viewModel = ViewModelProvider(context as ViewModelStoreOwner, factory)[WallpaperViewModel::class.java]
+
+ observeWallpapers()
+ }
+
+ private fun observeWallpapers() {
+ viewModel.wallpapers.observe(context as LifecycleOwner) { wallpapers ->
+ if (wallpapers.isEmpty()) {
+ visibility = GONE
+ loadingView.visibility = GONE
+ } else {
+ try {
+ visibility = VISIBLE
+ displayWallpapers(wallpapers)
+ } catch (e: Exception) {
+ Log.e("WallpaperCarouselView", "Error displaying wallpapers: ${e.message}")
+ }
+ }
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun displayWallpapers(wallpapers: List) {
+ removeAllViews()
+ val totalWidth = width.takeIf { it > 0 } ?: (deviceProfile.widthPx * 0.8).toInt()
+
+ val firstItemWidth = totalWidth * 0.5
+ val remainingWidth = totalWidth - firstItemWidth
+
+ val marginBetweenItems = totalWidth * 0.02
+ val itemWidth = (remainingWidth - (marginBetweenItems * (wallpapers.size - 1))) / (wallpapers.size - 1)
+
+ wallpapers.forEachIndexed { index, wallpaper ->
+ val cardView = CardView(context).apply {
+ radius = Themes.getDialogCornerRadius(context) / 2
+
+ layoutParams = LayoutParams(
+ when (index) {
+ currentItemIndex -> firstItemWidth.toInt()
+ else -> itemWidth.toInt()
+ },
+ LayoutParams.MATCH_PARENT,
+ ).apply {
+ setMargins(
+ if (index > 0) marginBetweenItems.toInt() else 0,
+ 0,
+ 0,
+ 0,
+ )
+ }
+
+ setOnTouchListener { _, _ ->
+ if (index != currentItemIndex) {
+ animateWidthTransition(index, firstItemWidth, itemWidth)
+ } else {
+ setWallpaper(wallpaper)
+ }
+ true
+ }
+ }
+
+ val placeholderImageView = ImageView(context).apply {
+ setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_deepshortcut_placeholder))
+ scaleType = ImageView.ScaleType.CENTER_CROP
+ }
+
+ cardView.addView(placeholderImageView)
+ addView(cardView)
+
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = BitmapFactory.decodeFile(wallpaper.imagePath)
+ withContext(Dispatchers.Main) {
+ (cardView.getChildAt(0) as? ImageView)?.apply {
+ setImageBitmap(bitmap)
+ }
+ if (index == currentItemIndex) {
+ addIconFrameToCenter(cardView)
+ }
+ }
+ }
+ }
+
+ loadingView.visibility = GONE
+ }
+
+ private fun setWallpaper(wallpaper: Wallpaper) {
+ val loadingSpinner = ProgressBar(context).apply {
+ isIndeterminate = true
+ layoutParams = FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
+ gravity = Gravity.CENTER
+ }
+ }
+
+ val currentCardView = getChildAt(currentItemIndex) as CardView
+ currentCardView.removeView(iconFrame)
+ currentCardView.addView(loadingSpinner)
+
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val wallpaperManager = WallpaperManager.getInstance(context)
+ val bitmap = BitmapFactory.decodeFile(wallpaper.imagePath)
+
+ wallpaperManager.setBitmap(bitmap, null, true, WallpaperManager.FLAG_SYSTEM)
+
+ withContext(Dispatchers.Main) {
+ currentCardView.removeView(loadingSpinner)
+ addIconFrameToCenter(currentCardView)
+ }
+ } catch (e: Exception) {
+ Log.e("WallpaperCarouselView", "Failed to set wallpaper: ${e.message}")
+ withContext(Dispatchers.Main) {
+ currentCardView.removeView(loadingSpinner)
+ 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 {
+ gravity = Gravity.CENTER
+ }
+
+ cardView.addView(iconFrame, params)
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val valWidth = (deviceProfile.widthPx * 0.8).toInt()
+ val width = MeasureSpec.makeMeasureSpec(valWidth, MeasureSpec.EXACTLY)
+ super.onMeasure(width, heightMeasureSpec)
+ }
+
+ 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()
+ }
+ }
+ animator.start()
+ }
+ if (i == currentItemIndex) {
+ addIconFrameToCenter(cardView)
+ }
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt b/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt
index 7c25e6092c..42bf46f69c 100644
--- a/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt
+++ b/lawnchair/src/app/lawnchair/util/LawnchairUtils.kt
@@ -53,6 +53,7 @@ import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Themes
import com.android.systemui.shared.system.QuickStepContract
import com.patrykmichalik.opto.core.firstBlocking
+import java.io.ByteArrayOutputStream
import java.util.concurrent.Callable
import java.util.concurrent.ExecutionException
import kotlin.math.max
@@ -254,6 +255,12 @@ fun Size.scaleDownTo(maxSize: Int): Size {
}
}
+fun bitmapToByteArray(bitmap: Bitmap): ByteArray {
+ val stream = ByteArrayOutputStream()
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
+ return stream.toByteArray()
+}
+
fun Context.isDefaultLauncher(): Boolean = getDefaultLauncherPackageName() == packageName
fun Context.getDefaultLauncherPackageName(): String? = runCatching { getDefaultResolveInfo()?.activityInfo?.packageName }.getOrNull()
diff --git a/lawnchair/src/app/lawnchair/views/component/IconFrame.kt b/lawnchair/src/app/lawnchair/views/component/IconFrame.kt
new file mode 100644
index 0000000000..e95ffc2a57
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/views/component/IconFrame.kt
@@ -0,0 +1,73 @@
+package app.lawnchair.views.component
+
+import android.content.Context
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import com.android.launcher3.R
+import com.android.launcher3.util.Themes
+
+class IconFrame @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr) {
+
+ private val imageView: ImageView
+
+ init {
+ layoutParams = LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ )
+
+ imageView = ImageView(context).apply {
+ layoutParams = LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ )
+ setPadding(12.dpToPx(context), 12.dpToPx(context), 12.dpToPx(context), 12.dpToPx(context))
+ }
+ addView(imageView)
+
+ setBackgroundWithRadius(
+ bgColor = ContextCompat.getColor(context, R.color.accent_primary_device_default),
+ cornerRadius = Themes.getDialogCornerRadius(context),
+ )
+ }
+
+ /**
+ * Convert dp to pixels for consistent padding across devices.
+ */
+ private fun Int.dpToPx(context: Context): Int {
+ val density = context.resources.displayMetrics.density
+ return (this * density).toInt()
+ }
+
+ /**
+ * Set the vector drawable for the ImageView.
+ *
+ * @param drawableRes The resource ID of the vector drawable.
+ */
+ fun setIcon(@DrawableRes drawableRes: Int) {
+ imageView.setImageResource(drawableRes)
+ }
+
+ /**
+ * Set the background color and corner radius of the FrameLayout.
+ *
+ * @param bgColor The background color.
+ * @param cornerRadius The corner radius in pixels.
+ */
+ fun setBackgroundWithRadius(bgColor: Int, cornerRadius: Float) {
+ val backgroundDrawable = GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ setColor(bgColor)
+ this.cornerRadius = cornerRadius
+ }
+ background = backgroundDrawable
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
index 2aa6ac266e..2e3a03bc4b 100644
--- a/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
+++ b/lawnchair/src/app/lawnchair/wallpaper/WallpaperManagerCompat.kt
@@ -5,7 +5,11 @@ import android.content.Context
import app.lawnchair.util.MainThreadInitializedObject
import app.lawnchair.util.requireSystemService
import app.lawnchair.wallpaper.WallpaperColorsCompat.Companion.HINT_SUPPORTS_DARK_THEME
+import app.lawnchair.wallpaper.service.WallpaperService
import com.android.launcher3.Utilities
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
sealed class WallpaperManagerCompat(val context: Context) {
@@ -29,6 +33,10 @@ sealed class WallpaperManagerCompat(val context: Context) {
listeners.toTypedArray().forEach {
it.onColorsChanged()
}
+
+ CoroutineScope(Dispatchers.IO).launch {
+ WallpaperService(context).saveWallpaper(wallpaperManager)
+ }
}
interface OnColorsChangedListener {
diff --git a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt
new file mode 100644
index 0000000000..0e4d13bd91
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModel.kt
@@ -0,0 +1,28 @@
+package app.lawnchair.wallpaper.model
+
+import android.content.Context
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import app.lawnchair.wallpaper.service.Wallpaper
+import app.lawnchair.wallpaper.service.WallpaperDatabase
+import kotlinx.coroutines.launch
+
+class WallpaperViewModel(context: Context) : ViewModel() {
+ private val dao = WallpaperDatabase.INSTANCE.get(context).wallpaperDao()
+
+ private val _wallpapers = MutableLiveData>()
+ val wallpapers: LiveData> = _wallpapers
+
+ init {
+ loadTopWallpapers()
+ }
+
+ private fun loadTopWallpapers() {
+ viewModelScope.launch {
+ val topWallpapers = dao.getTopWallpapers()
+ _wallpapers.postValue(topWallpapers)
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt
new file mode 100644
index 0000000000..805eae1118
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/model/WallpaperViewModelFactory.kt
@@ -0,0 +1,15 @@
+package app.lawnchair.wallpaper.model
+
+import android.content.Context
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+class WallpaperViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ if (modelClass.isAssignableFrom(WallpaperViewModel::class.java)) {
+ @Suppress("UNCHECKED_CAST")
+ return WallpaperViewModel(context) as T
+ }
+ throw IllegalArgumentException("Unknown ViewModel class")
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt b/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt
new file mode 100644
index 0000000000..7116943004
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/service/Wallpaper.kt
@@ -0,0 +1,12 @@
+package app.lawnchair.wallpaper.service
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "wallpapers")
+data class Wallpaper(
+ @PrimaryKey(autoGenerate = true) val id: Long = 0,
+ val imagePath: String,
+ val rank: Int,
+ val timestamp: Long,
+)
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt
new file mode 100644
index 0000000000..0c901e68d1
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDao.kt
@@ -0,0 +1,25 @@
+package app.lawnchair.wallpaper.service
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.RawQuery
+import androidx.sqlite.db.SupportSQLiteQuery
+
+@Dao
+interface WallpaperDao {
+ @Insert
+ suspend fun insert(wallpaper: Wallpaper)
+
+ @Query("SELECT * FROM wallpapers ORDER BY timestamp DESC LIMIT 4")
+ suspend fun getTopWallpapers(): List
+
+ @Query("UPDATE wallpapers SET rank = rank + 1 WHERE rank >= :rank")
+ suspend fun updateRank(rank: Int)
+
+ @Query("DELETE FROM wallpapers WHERE id = :id")
+ suspend fun deleteWallpaper(id: Long)
+
+ @RawQuery
+ suspend fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery): Int
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt
new file mode 100644
index 0000000000..05953ac225
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperDatabase.kt
@@ -0,0 +1,34 @@
+package app.lawnchair.wallpaper.service
+
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.sqlite.db.SimpleSQLiteQuery
+import app.lawnchair.util.MainThreadInitializedObject
+import kotlinx.coroutines.runBlocking
+
+@Database(entities = [Wallpaper::class], version = 1, exportSchema = false)
+abstract class WallpaperDatabase : RoomDatabase() {
+
+ abstract fun wallpaperDao(): WallpaperDao
+
+ private suspend fun checkpoint() {
+ wallpaperDao().checkpoint(SimpleSQLiteQuery("pragma wal_checkpoint(full)"))
+ }
+
+ fun checkpointSync() {
+ runBlocking {
+ checkpoint()
+ }
+ }
+
+ companion object {
+ val INSTANCE = MainThreadInitializedObject { context ->
+ Room.databaseBuilder(
+ context,
+ WallpaperDatabase::class.java,
+ "wallpaper_database",
+ ).build()
+ }
+ }
+}
diff --git a/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt
new file mode 100644
index 0000000000..076102692e
--- /dev/null
+++ b/lawnchair/src/app/lawnchair/wallpaper/service/WallpaperService.kt
@@ -0,0 +1,98 @@
+package app.lawnchair.wallpaper.service
+
+import android.app.WallpaperManager
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.util.Log
+import androidx.core.graphics.drawable.toBitmap
+import app.lawnchair.util.bitmapToByteArray
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+import java.io.File
+import java.io.FileOutputStream
+import kotlinx.coroutines.runBlocking
+
+class WallpaperService(val context: Context) : SafeCloseable {
+
+ private val dao = WallpaperDatabase.INSTANCE.get(context).wallpaperDao()
+
+ suspend fun saveWallpaper(wallpaperManager: WallpaperManager) {
+ try {
+ val wallpaperDrawable = wallpaperManager.drawable
+ val currentBitmap = (wallpaperDrawable as BitmapDrawable).toBitmap()
+
+ val byteArray = bitmapToByteArray(currentBitmap)
+
+ saveWallpaper(byteArray)
+ } catch (e: Exception) {
+ Log.e("WallpaperChange", "Error detecting wallpaper change: ${e.message}")
+ }
+ }
+
+ private suspend fun saveWallpaper(imageData: ByteArray) {
+ val timestamp = System.currentTimeMillis()
+
+ val topWallpapers = dao.getTopWallpapers()
+
+ val imagePath = saveImageToAppStorage(imageData)
+
+ if (topWallpapers.size < 4) {
+ val wallpaper = Wallpaper(imagePath = imagePath, rank = topWallpapers.size, timestamp = timestamp)
+ dao.insert(wallpaper)
+ } else {
+ val lowestRankedWallpaper = topWallpapers.minByOrNull { it.timestamp }
+
+ if (lowestRankedWallpaper != null) {
+ dao.deleteWallpaper(lowestRankedWallpaper.id)
+ deleteWallpaperFile(lowestRankedWallpaper.imagePath)
+ }
+
+ for (wallpaper in topWallpapers) {
+ if (wallpaper.rank >= (lowestRankedWallpaper?.rank ?: 0)) {
+ dao.updateRank(wallpaper.rank)
+ }
+ }
+
+ val wallpaper = Wallpaper(imagePath = imagePath, rank = 0, timestamp = timestamp)
+ dao.insert(wallpaper)
+ }
+ }
+
+ fun getTopWallpapers(): List = runBlocking {
+ val wallpapers = dao.getTopWallpapers()
+ wallpapers.ifEmpty { emptyList() }
+ }
+
+ private fun deleteWallpaperFile(imagePath: String) {
+ val file = File(imagePath)
+ if (file.exists()) {
+ file.delete()
+ }
+ }
+
+ private fun saveImageToAppStorage(imageData: ByteArray): String {
+ val storageDir = File(context.filesDir, "wallpapers")
+ if (!storageDir.exists()) {
+ storageDir.mkdirs()
+ }
+
+ val imageHash = imageData.hashCode().toString()
+ val imageFile = File(storageDir, "wallpaper_$imageHash.jpg")
+
+ if (!imageFile.exists()) {
+ FileOutputStream(imageFile).use { fos ->
+ fos.write(imageData)
+ }
+ }
+
+ return imageFile.absolutePath
+ }
+
+ override fun close() {
+ TODO("Not yet implemented")
+ }
+ companion object {
+ @JvmField
+ val INSTANCE = MainThreadInitializedObject(::WallpaperService)
+ }
+}
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index b1b0512708..51caf99ffb 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -150,8 +150,6 @@ public abstract class ArrowPopup
// Initialize arrow view
final Resources resources = getResources();
- mArrowColor = getColorStateList(getContext(), R.color.popup_color_background)
- .getDefaultColor();
mChildContainerMargin = resources.getDimensionPixelSize(R.dimen.popup_margin);
mArrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
mArrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index e577b332c7..061a650d6e 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -56,6 +56,7 @@ import java.util.List;
import app.lawnchair.preferences2.PreferenceManager2;
import app.lawnchair.ui.popup.LauncherOptionsPopup;
+import app.lawnchair.wallpaper.service.WallpaperService;
/**
* Popup shown on long pressing an empty space in launcher
@@ -171,13 +172,19 @@ public class OptionsPopupView extends Arrow
if (activityContext == null) {
return null;
}
+
+ final Context context = activityContext.getDragLayer().mActivity;
+ final boolean isEmpty = WallpaperService.INSTANCE.get(context).getTopWallpapers().isEmpty();
+
+ var layout = isEmpty ? R.layout.longpress_options_menu : R.layout.wallpaper_options_popup;
OptionsPopupView popup = (OptionsPopupView) activityContext.getLayoutInflater()
- .inflate(R.layout.longpress_options_menu, activityContext.getDragLayer(), false);
+ .inflate(layout, activityContext.getDragLayer(), false);
popup.mTargetRect = targetRect;
popup.setShouldAddArrow(shouldAddArrow);
for (OptionItem item : items) {
- DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ var deepLayout = isEmpty ? R.layout.system_shortcut : R.layout.wallpaper_options_popup_item;
+ DeepShortcutView view = popup.inflateAndAdd(deepLayout, popup);
if (width > 0) {
view.getLayoutParams().width = width;
}