Переглянути джерело

Refactor preferences into a new core_preferences proto file (#122)

Joshua Kuestersteffen 3 роки тому
батько
коміт
8afcc8d9cc

+ 6 - 78
app/src/main/java/com/sduduzog/slimlauncher/MainActivity.kt

@@ -1,35 +1,25 @@
 package com.sduduzog.slimlauncher
 
 import android.annotation.SuppressLint
-import android.app.WallpaperManager
 import android.content.SharedPreferences
 import android.content.res.Resources
-import android.graphics.Bitmap
-import android.graphics.Canvas
 import android.os.Bundle
 import android.util.Log
 import android.view.GestureDetector
 import android.view.GestureDetector.SimpleOnGestureListener
 import android.view.MotionEvent
 import android.view.View
-import androidx.annotation.ColorInt
 import androidx.annotation.StyleRes
-import androidx.annotation.WorkerThread
 import androidx.appcompat.app.AppCompatActivity
 import androidx.constraintlayout.motion.widget.MotionLayout
-import androidx.lifecycle.lifecycleScope
 import androidx.navigation.NavController
 import androidx.navigation.Navigation.findNavController
-import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.di.MainFragmentFactoryEntryPoint
 import com.sduduzog.slimlauncher.utils.*
 import dagger.hilt.android.AndroidEntryPoint
 import dagger.hilt.android.EntryPointAccessors
-import kotlin.math.absoluteValue
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import java.io.IOException
 import java.lang.reflect.Method
+import kotlin.math.absoluteValue
 
 
 @AndroidEntryPoint
@@ -37,10 +27,11 @@ class MainActivity : AppCompatActivity(),
     SharedPreferences.OnSharedPreferenceChangeListener,
     HomeWatcher.OnHomePressedListener, IPublisher {
 
+    private val wallpaperManager = WallpaperManager(this)
+
     private lateinit var settings: SharedPreferences
     private lateinit var navigator: NavController
     private lateinit var homeWatcher: HomeWatcher
-    private lateinit var unlauncherDataSource: UnlauncherDataSource
 
     private val subscribers: MutableSet<BaseFragment> = mutableSetOf()
 
@@ -115,70 +106,15 @@ class MainActivity : AppCompatActivity(),
 
     override fun onApplyThemeResource(theme: Resources.Theme?, @StyleRes resid: Int, first: Boolean) {
         super.onApplyThemeResource(theme, resid, first)
-        getUnlaucherDataSource().unlauncherAppsRepo.liveData().observe(this, {
-            if (!it.setThemeWallpaper && getUserSelectedThemeRes() == resid) {
-                // only change the wallpaper when user has allowed it and
-                // preventing to change the wallpaper multiple times once it is rechecked in the settings
-                return@observe
-            }
-            @ColorInt val backgroundColor = getThemeBackgroundColor(theme, resid)
-            if (backgroundColor == Int.MIN_VALUE) {
-                return@observe
-            }
-            lifecycleScope.launch(Dispatchers.IO) {
-                setWallpaperBackgroundColor(backgroundColor)
-            }
-        })
-    }
-
-    /**
-     * @return `Int.MIN_VALUE` if `android.R.attr.colorBackground` of `theme` could not be obtained.
-     */
-    @ColorInt
-    private fun getThemeBackgroundColor(theme: Resources.Theme?, @StyleRes themeRes: Int): Int {
-        val array =  theme?.obtainStyledAttributes(themeRes, intArrayOf(android.R.attr.colorBackground))
-        try {
-            return array?.getColor(0, Int.MIN_VALUE) ?: Int.MIN_VALUE
-        } finally {
-            array?.recycle()
-        }
-    }
-
-    @Throws(IOException::class)
-    @WorkerThread
-    private fun setWallpaperBackgroundColor(@ColorInt color: Int) {
-        val wallpaperManager = WallpaperManager.getInstance(applicationContext)
-        var width = wallpaperManager.desiredMinimumWidth
-        if (width <= 0) {
-            width = getScreenWidth(this)
-        }
-        var height = wallpaperManager.desiredMinimumHeight
-        if (height <= 0) {
-            height = getScreenHeight(this)
-        }
-        val wallpaperBitmap = createColoredWallpaperBitmap(color, width, height)
-        wallpaperManager.setBitmap(wallpaperBitmap)
-    }
-
-    private fun createColoredWallpaperBitmap(@ColorInt color: Int, width: Int, height: Int): Bitmap {
-        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
-        val canvas = Canvas(bitmap)
-        canvas.drawColor(color)
-        return bitmap
+        wallpaperManager.onApplyThemeResource(theme, resid)
     }
 
     override fun setTheme(resId: Int) {
-        val userThemeId = getUserSelectedThemeRes()
-        val id = if (resId != userThemeId) {
-            userThemeId
-        } else {
-            resId
-        }
-        super.setTheme(id)
+        super.setTheme(getUserSelectedThemeRes())
     }
 
     @StyleRes
-    private fun getUserSelectedThemeRes(): Int {
+    fun getUserSelectedThemeRes(): Int {
         settings = getSharedPreferences(getString(R.string.prefs_settings), MODE_PRIVATE)
         val active = settings.getInt(getString(R.string.prefs_settings_key_theme), 0)
         return resolveTheme(active)
@@ -211,15 +147,7 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
-    private fun getUnlaucherDataSource(): UnlauncherDataSource {
-        if (!::unlauncherDataSource.isInitialized) {
-            unlauncherDataSource = UnlauncherDataSource(this, lifecycleScope)
-        }
-        return unlauncherDataSource
-    }
-
     companion object {
-
         @StyleRes
         fun resolveTheme(i: Int): Int {
             return when (i) {

+ 9 - 1
app/src/main/java/com/sduduzog/slimlauncher/datasource/UnlauncherDataSource.kt

@@ -1,17 +1,19 @@
 package com.sduduzog.slimlauncher.datasource
 
 import android.content.Context
-import androidx.datastore.core.DataMigration
 import androidx.datastore.core.DataStore
 import androidx.datastore.dataStore
 import androidx.datastore.migrations.SharedPreferencesMigration
 import androidx.datastore.migrations.SharedPreferencesView
 import androidx.lifecycle.LifecycleCoroutineScope
+import com.jkuester.unlauncher.datastore.CorePreferences
 import com.jkuester.unlauncher.datastore.QuickButtonPreferences
 import com.jkuester.unlauncher.datastore.UnlauncherApps
 import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsMigrations
 import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
 import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsSerializer
+import com.sduduzog.slimlauncher.datasource.coreprefs.CorePreferencesRepository
+import com.sduduzog.slimlauncher.datasource.coreprefs.CorePreferencesSerializer
 import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesRepository
 import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesSerializer
 
@@ -66,8 +68,14 @@ private val Context.unlauncherAppsStore: DataStore<UnlauncherApps> by dataStore(
     produceMigrations = { context -> UnlauncherAppsMigrations().get(context) }
 )
 
+private val Context.corePreferencesStore: DataStore<CorePreferences> by dataStore(
+    fileName = "core_preferences.proto",
+    serializer = CorePreferencesSerializer
+)
+
 class UnlauncherDataSource(context: Context, lifecycleScope: LifecycleCoroutineScope) {
     val quickButtonPreferencesRepo =
         QuickButtonPreferencesRepository(context.quickButtonPreferencesStore, lifecycleScope)
     val unlauncherAppsRepo = UnlauncherAppsRepository(context.unlauncherAppsStore, lifecycleScope)
+    val corePreferencesRepo = CorePreferencesRepository(context.corePreferencesStore, lifecycleScope)
 }

+ 0 - 16
app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt

@@ -139,14 +139,6 @@ class UnlauncherAppsRepository(
         }
     }
 
-    fun updateActivateKeyboardInDrawer(activateKeyboardInDrawer: Boolean) {
-        lifecycleScope.launch {
-            unlauncherAppsStore.updateData {
-                it.toBuilder().setActivateKeyboardInDrawer(activateKeyboardInDrawer).build()
-            }
-        }
-    }
-
     private fun findApp(
         unlauncherApps: List<UnlauncherApp>,
         packageName: String,
@@ -156,14 +148,6 @@ class UnlauncherAppsRepository(
             packageName == app.packageName && className == app.className
         }
     }
-
-    fun updateSetAutomaticDeviceWallpaper(setDeviceWallpaper: Boolean) {
-        lifecycleScope.launch {
-            unlauncherAppsStore.updateData {
-                it.toBuilder().setSetThemeWallpaper(setDeviceWallpaper).build()
-            }
-        }
-    }
 }
 
 fun sortAppsAlphabetically(unlauncherAppsBuilder: UnlauncherApps.Builder) {

+ 60 - 0
app/src/main/java/com/sduduzog/slimlauncher/datasource/coreprefs/CorePreferencesRepository.kt

@@ -0,0 +1,60 @@
+package com.sduduzog.slimlauncher.datasource.coreprefs
+
+import android.util.Log
+import androidx.datastore.core.DataStore
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asLiveData
+import com.jkuester.unlauncher.datastore.CorePreferences
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import java.io.IOException
+
+class CorePreferencesRepository(
+    private val corePreferencesStore: DataStore<CorePreferences>,
+    private val lifecycleScope: LifecycleCoroutineScope
+) {
+    private val corereferencesFlow: Flow<CorePreferences> =
+        corePreferencesStore.data
+            .catch { exception ->
+                if (exception is IOException) {
+                    Log.e(
+                        "CorePrefRepo",
+                        "Error reading core preferences.",
+                        exception
+                    )
+                    emit(CorePreferences.getDefaultInstance())
+                } else {
+                    throw exception
+                }
+            }
+
+    fun liveData(): LiveData<CorePreferences> {
+        return corereferencesFlow.asLiveData()
+    }
+
+    fun get(): CorePreferences {
+        return runBlocking {
+            corereferencesFlow.first()
+        }
+    }
+
+    fun updateActivateKeyboardInDrawer(activateKeyboardInDrawer: Boolean) {
+        lifecycleScope.launch {
+            corePreferencesStore.updateData {
+                it.toBuilder().setActivateKeyboardInDrawer(activateKeyboardInDrawer).build()
+            }
+        }
+    }
+
+    fun updateSetAutomaticDeviceWallpaper(setDeviceWallpaper: Boolean) {
+        lifecycleScope.launch {
+            corePreferencesStore.updateData {
+                it.toBuilder().setSetThemeWallpaper(setDeviceWallpaper).build()
+            }
+        }
+    }
+}

+ 28 - 0
app/src/main/java/com/sduduzog/slimlauncher/datasource/coreprefs/CorePreferencesSerializer.kt

@@ -0,0 +1,28 @@
+package com.sduduzog.slimlauncher.datasource.coreprefs
+
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.Serializer
+import com.google.protobuf.InvalidProtocolBufferException
+import com.jkuester.unlauncher.datastore.CorePreferences
+import java.io.InputStream
+import java.io.OutputStream
+
+/**
+ * Serializer for the [CorePreferences] object defined in core_preferences.proto.
+ */
+object CorePreferencesSerializer : Serializer<CorePreferences> {
+    override val defaultValue: CorePreferences = CorePreferences.getDefaultInstance()
+
+    @Suppress("BlockingMethodInNonBlockingContext")
+    override suspend fun readFrom(input: InputStream): CorePreferences {
+        try {
+            return CorePreferences.parseFrom(input)
+        } catch (exception: InvalidProtocolBufferException) {
+            throw CorruptionException("Cannot read proto.", exception)
+        }
+    }
+
+    @Suppress("BlockingMethodInNonBlockingContext")
+    override suspend fun writeTo(t: CorePreferences, output: OutputStream) =
+        t.writeTo(output)
+}

+ 27 - 0
app/src/main/java/com/sduduzog/slimlauncher/di/ActivityModule.kt

@@ -0,0 +1,27 @@
+package com.sduduzog.slimlauncher.di
+
+import android.app.Activity
+import androidx.core.app.ComponentActivity
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.lifecycleScope
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ActivityComponent
+import dagger.hilt.android.scopes.ActivityScoped
+
+@Module
+@InstallIn(ActivityComponent::class)
+class ActivityModule {
+    @Provides
+    fun provideLifecycleCoroutineScope(activity: Activity): LifecycleCoroutineScope =
+        (activity as ComponentActivity).lifecycleScope
+
+    @Provides
+    @ActivityScoped
+    fun providesUnlauncherDataSource(
+        activity: Activity,
+        lifecycleCoroutineScope: LifecycleCoroutineScope
+    ) = UnlauncherDataSource(activity, lifecycleCoroutineScope)
+}

+ 12 - 8
app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt

@@ -21,6 +21,7 @@ import com.jkuester.unlauncher.datastore.UnlauncherApp
 import com.sduduzog.slimlauncher.R
 import com.sduduzog.slimlauncher.adapters.AppDrawerAdapter
 import com.sduduzog.slimlauncher.adapters.HomeAdapter
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.models.HomeApp
 import com.sduduzog.slimlauncher.models.MainViewModel
 import com.sduduzog.slimlauncher.utils.BaseFragment
@@ -32,9 +33,12 @@ import kotlinx.coroutines.launch
 import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.*
+import javax.inject.Inject
 
 @AndroidEntryPoint
 class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLaunchAppListener {
+    @Inject
+    lateinit var unlauncherDataSource: UnlauncherDataSource
 
     private lateinit var receiver: BroadcastReceiver
     private lateinit var appDrawerAdapter: AppDrawerAdapter
@@ -48,9 +52,9 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         home_fragment_list.adapter = adapter1
         home_fragment_list_exp.adapter = adapter2
 
-        val unlauncherAppsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        val unlauncherAppsRepo = unlauncherDataSource.unlauncherAppsRepo
 
-        viewModel.apps.observe(viewLifecycleOwner, { list ->
+        viewModel.apps.observe(viewLifecycleOwner) { list ->
             list?.let { apps ->
                 adapter1.setItems(apps.filter {
                     it.sortingIndex < 3
@@ -64,7 +68,7 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
                     unlauncherAppsRepo.setHomeApps(apps)
                 }
             }
-        })
+        }
 
         appDrawerAdapter =
             AppDrawerAdapter(AppDrawerListener(), viewLifecycleOwner, unlauncherAppsRepo)
@@ -87,7 +91,7 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         updateClock()
 
         lifecycleScope.launch(Dispatchers.IO) {
-            getUnlauncherDataSource().unlauncherAppsRepo.setApps(getInstalledApps())
+            unlauncherDataSource.unlauncherAppsRepo.setApps(getInstalledApps())
         }
         if (!::appDrawerAdapter.isInitialized) {
             appDrawerAdapter.setAppFilter()
@@ -130,8 +134,8 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
             }
         }
 
-        getUnlauncherDataSource().quickButtonPreferencesRepo.liveData()
-            .observe(viewLifecycleOwner, { prefs ->
+        unlauncherDataSource.quickButtonPreferencesRepo.liveData()
+            .observe(viewLifecycleOwner) { prefs ->
                 val leftButtonIcon = prefs.leftButton.iconId
                 home_fragment_call.setImageResource(leftButtonIcon)
                 if (leftButtonIcon != R.drawable.ic_empty) {
@@ -172,7 +176,7 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
                         }
                     }
                 }
-            })
+            }
 
         app_drawer_edit_text.addTextChangedListener(appDrawerAdapter.searchBoxListener)
 
@@ -189,7 +193,7 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
 
                     motionLayout?.endState -> {
                         // Check for preferences to open the keyboard
-                        getUnlauncherDataSource().unlauncherAppsRepo.liveData().observe(viewLifecycleOwner) {
+                        unlauncherDataSource.corePreferencesRepo.liveData().observe(viewLifecycleOwner) {
                             if (it.activateKeyboardInDrawer) {
                                 // show the keyboard and set focus to the EditText when swiping down
                                 inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0)

+ 5 - 1
app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeAppDrawerAppListFragment.kt

@@ -6,12 +6,16 @@ import android.view.View
 import android.view.ViewGroup
 import com.sduduzog.slimlauncher.R
 import com.sduduzog.slimlauncher.adapters.CustomizeAppDrawerAppsAdapter
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.utils.BaseFragment
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.customize_app_drawer_app_list_fragment.*
+import javax.inject.Inject
 
 @AndroidEntryPoint
 class CustomizeAppDrawerAppListFragment : BaseFragment() {
+    @Inject
+    lateinit var unlauncherDataSource: UnlauncherDataSource
 
     override fun getFragmentView(): ViewGroup = customize_app_drawer_fragment
 
@@ -25,7 +29,7 @@ class CustomizeAppDrawerAppListFragment : BaseFragment() {
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
-        val unlauncherAppsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        val unlauncherAppsRepo = unlauncherDataSource.unlauncherAppsRepo
         customize_app_drawer_fragment_app_list.adapter =
             CustomizeAppDrawerAppsAdapter(viewLifecycleOwner, unlauncherAppsRepo)
         unlauncherAppsRepo.liveData().observe(viewLifecycleOwner) {

+ 7 - 59
app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeAppDrawerFragment.kt

@@ -1,22 +1,21 @@
 package com.sduduzog.slimlauncher.ui.options
 
 import android.os.Bundle
-import android.text.SpannableStringBuilder
-import android.text.Spanned
-import android.text.style.TextAppearanceSpan
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.navigation.Navigation
 import com.sduduzog.slimlauncher.R
-import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.utils.BaseFragment
-import com.sduduzog.slimlauncher.utils.isActivityDefaultLauncher
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.customize_app_drawer_fragment.*
+import javax.inject.Inject
 
 @AndroidEntryPoint
 class CustomizeAppDrawerFragment : BaseFragment() {
+    @Inject
+    lateinit var unlauncherDataSource: UnlauncherDataSource
 
     override fun getFragmentView(): ViewGroup = customize_app_drawer_fragment
 
@@ -35,64 +34,13 @@ class CustomizeAppDrawerFragment : BaseFragment() {
         setupKeyboardSwitch()
     }
 
-    override fun onStart() {
-        super.onStart()
-        // setting up the switch text, since changing the default launcher re-starts the activity
-        // this should able to adapt to it.
-        setupAutomaticDeviceWallpaperSwitch()
-    }
-
     private fun setupKeyboardSwitch() {
-        val appsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        val prefsRepo = unlauncherDataSource.corePreferencesRepo
         customize_app_drawer_open_keyboard_switch.setOnCheckedChangeListener { _, checked ->
-            appsRepo.updateActivateKeyboardInDrawer(checked)
+            prefsRepo.updateActivateKeyboardInDrawer(checked)
         }
-        appsRepo.liveData().observe(viewLifecycleOwner) {
+        prefsRepo.liveData().observe(viewLifecycleOwner) {
             customize_app_drawer_open_keyboard_switch.isChecked = it.activateKeyboardInDrawer
         }
     }
-
-    private fun setupAutomaticDeviceWallpaperSwitch() {
-        val appsRepo = getUnlauncherDataSource().unlauncherAppsRepo
-        val appIsDefaultLauncher = isActivityDefaultLauncher(activity)
-        setupDeviceWallpaperSwitchText(appIsDefaultLauncher)
-        customize_app_drawer_auto_device_theme_wallpaper.isEnabled = appIsDefaultLauncher
-
-        appsRepo.liveData().observe(viewLifecycleOwner) {
-            // always uncheck once app isn't default launcher
-            customize_app_drawer_auto_device_theme_wallpaper.isChecked = appIsDefaultLauncher && it.setThemeWallpaper
-        }
-        customize_app_drawer_auto_device_theme_wallpaper.setOnCheckedChangeListener { _, checked ->
-            appsRepo.updateSetAutomaticDeviceWallpaper(checked)
-        }
-    }
-
-    /**
-     * Adds a hint text underneath the default text when app is not the default launcher.
-     */
-    private fun setupDeviceWallpaperSwitchText(appIsDefaultLauncher: Boolean) {
-        val text = if (appIsDefaultLauncher) {
-            getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
-        } else {
-            buildSwitchTextWithHint()
-        }
-        customize_app_drawer_auto_device_theme_wallpaper.text = text
-    }
-
-    private fun buildSwitchTextWithHint(): CharSequence {
-        val titleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
-        // have a title text and a subtitle text to indicate that adapting the
-        // wallpaper can only be done when app it the default launcher
-        val subTitleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_subtext_no_default_launcher)
-
-        val spanBuilder = SpannableStringBuilder("$titleText\n$subTitleText")
-        spanBuilder.setSpan(TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Large), 0, titleText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
-        spanBuilder.setSpan(
-            TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Small),
-            titleText.length + 1,
-            titleText.length + 1 + subTitleText.length,
-            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
-        )
-        return spanBuilder
-    }
 }

+ 8 - 1
app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeQuickButtonsFragment.kt

@@ -5,12 +5,19 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesRepository
 import com.sduduzog.slimlauncher.ui.dialogs.ChooseQuickButtonDialog
 import com.sduduzog.slimlauncher.utils.BaseFragment
+import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.customize_quick_buttons_fragment.*
+import javax.inject.Inject
 
+@AndroidEntryPoint
 class CustomizeQuickButtonsFragment : BaseFragment() {
+    @Inject
+    lateinit var unlauncherDataSource: UnlauncherDataSource
+
     override fun getFragmentView(): ViewGroup = customize_quick_buttons_fragment
 
     override fun onCreateView(
@@ -21,7 +28,7 @@ class CustomizeQuickButtonsFragment : BaseFragment() {
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        val prefsRepo = getUnlauncherDataSource().quickButtonPreferencesRepo
+        val prefsRepo = unlauncherDataSource.quickButtonPreferencesRepo
 
         prefsRepo.liveData().observe(viewLifecycleOwner, { prefs ->
             customize_quick_buttons_fragment_left.setImageResource(prefs.leftButton.iconId)

+ 62 - 0
app/src/main/java/com/sduduzog/slimlauncher/ui/options/OptionsFragment.kt

@@ -5,18 +5,29 @@ import android.content.Intent
 import android.net.Uri
 import android.os.Bundle
 import android.provider.Settings
+import android.text.SpannableStringBuilder
+import android.text.Spanned
+import android.text.style.TextAppearanceSpan
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.core.content.edit
 import androidx.navigation.Navigation
 import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 import com.sduduzog.slimlauncher.ui.dialogs.ChangeThemeDialog
 import com.sduduzog.slimlauncher.ui.dialogs.ChooseTimeFormatDialog
 import com.sduduzog.slimlauncher.utils.BaseFragment
+import com.sduduzog.slimlauncher.utils.isActivityDefaultLauncher
+import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.options_fragment.*
+import javax.inject.Inject
 
+@AndroidEntryPoint
 class OptionsFragment : BaseFragment() {
+    @Inject
+    lateinit var unlauncherDataSource: UnlauncherDataSource
+
     override fun getFragmentView(): ViewGroup = options_fragment
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@@ -57,4 +68,55 @@ class OptionsFragment : BaseFragment() {
         options_fragment_customize_quick_buttons.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_optionsFragment_to_customiseQuickButtonsFragment))
         options_fragment_customize_app_drawer.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_optionsFragment_to_customiseAppDrawerFragment))
     }
+
+    override fun onStart() {
+        super.onStart()
+        // setting up the switch text, since changing the default launcher re-starts the activity
+        // this should able to adapt to it.
+        setupAutomaticDeviceWallpaperSwitch()
+    }
+
+    private fun setupAutomaticDeviceWallpaperSwitch() {
+        val prefsRepo = unlauncherDataSource.corePreferencesRepo
+        val appIsDefaultLauncher = isActivityDefaultLauncher(activity)
+        setupDeviceWallpaperSwitchText(appIsDefaultLauncher)
+        options_fragment_auto_device_theme_wallpaper.isEnabled = appIsDefaultLauncher
+
+        prefsRepo.liveData().observe(viewLifecycleOwner) {
+            // always uncheck once app isn't default launcher
+            options_fragment_auto_device_theme_wallpaper.isChecked = appIsDefaultLauncher && it.setThemeWallpaper
+        }
+        options_fragment_auto_device_theme_wallpaper.setOnCheckedChangeListener { _, checked ->
+            prefsRepo.updateSetAutomaticDeviceWallpaper(checked)
+        }
+    }
+
+    /**
+     * Adds a hint text underneath the default text when app is not the default launcher.
+     */
+    private fun setupDeviceWallpaperSwitchText(appIsDefaultLauncher: Boolean) {
+        val text = if (appIsDefaultLauncher) {
+            getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
+        } else {
+            buildSwitchTextWithHint()
+        }
+        options_fragment_auto_device_theme_wallpaper.text = text
+    }
+
+    private fun buildSwitchTextWithHint(): CharSequence {
+        val titleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_text)
+        // have a title text and a subtitle text to indicate that adapting the
+        // wallpaper can only be done when app it the default launcher
+        val subTitleText = getText(R.string.customize_app_drawer_fragment_auto_theme_wallpaper_subtext_no_default_launcher)
+
+        val spanBuilder = SpannableStringBuilder("$titleText\n$subTitleText")
+        spanBuilder.setSpan(TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Large), 0, titleText.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+        spanBuilder.setSpan(
+            TextAppearanceSpan(context, R.style.TextAppearance_AppCompat_Small),
+            titleText.length + 1,
+            titleText.length + 1 + subTitleText.length,
+            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+        )
+        return spanBuilder
+    }
 }

+ 0 - 12
app/src/main/java/com/sduduzog/slimlauncher/utils/BaseFragment.kt

@@ -12,15 +12,11 @@ import android.view.ViewGroup
 import androidx.appcompat.app.AppCompatActivity
 import androidx.core.app.ActivityOptionsCompat
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
 import com.sduduzog.slimlauncher.BuildConfig
 import com.sduduzog.slimlauncher.R
 import com.sduduzog.slimlauncher.data.model.App
-import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
 
 abstract class BaseFragment : Fragment(), ISubscriber {
-    private lateinit var unlauncherDataSource: UnlauncherDataSource
-
     abstract fun getFragmentView(): ViewGroup
 
 
@@ -98,12 +94,4 @@ abstract class BaseFragment : Fragment(), ISubscriber {
         filter.add(BuildConfig.APPLICATION_ID)
         return list.filterNot { filter.contains(it.packageName) }
     }
-
-    protected fun getUnlauncherDataSource(): UnlauncherDataSource {
-        if (!::unlauncherDataSource.isInitialized) {
-            unlauncherDataSource =
-                UnlauncherDataSource(requireContext(), viewLifecycleOwner.lifecycleScope)
-        }
-        return unlauncherDataSource
-    }
 }

+ 72 - 0
app/src/main/java/com/sduduzog/slimlauncher/utils/WallpaperManager.kt

@@ -0,0 +1,72 @@
+package com.sduduzog.slimlauncher.utils
+
+import android.app.WallpaperManager
+import android.content.res.Resources
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import androidx.annotation.ColorInt
+import androidx.annotation.StyleRes
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.lifecycleScope
+import com.sduduzog.slimlauncher.MainActivity
+import com.sduduzog.slimlauncher.datasource.UnlauncherDataSource
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.io.IOException
+
+class WallpaperManager(private val mainActivity: MainActivity) {
+    fun onApplyThemeResource(theme: Resources.Theme?, @StyleRes resid: Int) {
+        // Cannot inject here because this is called too early in the lifecycle
+        val unlauncherDataSource = UnlauncherDataSource(mainActivity, mainActivity.lifecycleScope)
+        unlauncherDataSource.corePreferencesRepo.liveData().observe(mainActivity) {
+            if (!it.setThemeWallpaper && mainActivity.getUserSelectedThemeRes() == resid) {
+                // only change the wallpaper when user has allowed it and
+                // preventing to change the wallpaper multiple times once it is rechecked in the settings
+                return@observe
+            }
+            @ColorInt val backgroundColor = getThemeBackgroundColor(theme, resid)
+            if (backgroundColor == Int.MIN_VALUE) {
+                return@observe
+            }
+            mainActivity.lifecycleScope.launch(Dispatchers.IO) {
+                setWallpaperBackgroundColor(backgroundColor)
+            }
+        }
+    }
+
+    /**
+     * @return `Int.MIN_VALUE` if `android.R.attr.colorBackground` of `theme` could not be obtained.
+     */
+    @ColorInt
+    private fun getThemeBackgroundColor(theme: Resources.Theme?, @StyleRes themeRes: Int): Int {
+        val array =  theme?.obtainStyledAttributes(themeRes, intArrayOf(android.R.attr.colorBackground))
+        try {
+            return array?.getColor(0, Int.MIN_VALUE) ?: Int.MIN_VALUE
+        } finally {
+            array?.recycle()
+        }
+    }
+
+    @Throws(IOException::class)
+    @WorkerThread
+    private fun setWallpaperBackgroundColor(@ColorInt color: Int) {
+        val wallpaperManager = WallpaperManager.getInstance(mainActivity)
+        var width = wallpaperManager.desiredMinimumWidth
+        if (width <= 0) {
+            width = getScreenWidth(mainActivity)
+        }
+        var height = wallpaperManager.desiredMinimumHeight
+        if (height <= 0) {
+            height = getScreenHeight(mainActivity)
+        }
+        val wallpaperBitmap = createColoredWallpaperBitmap(color, width, height)
+        wallpaperManager.setBitmap(wallpaperBitmap)
+    }
+
+    private fun createColoredWallpaperBitmap(@ColorInt color: Int, width: Int, height: Int): Bitmap {
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bitmap)
+        canvas.drawColor(color)
+        return bitmap
+    }
+}

+ 9 - 0
app/src/main/proto/core_preferences.proto

@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+option java_package = "com.jkuester.unlauncher.datastore";
+option java_multiple_files = true;
+
+message CorePreferences {
+  bool activate_keyboard_in_drawer = 1;
+  bool set_theme_wallpaper = 2;
+}

+ 0 - 2
app/src/main/proto/unlauncher_apps.proto

@@ -15,6 +15,4 @@ message UnlauncherApp {
 message UnlauncherApps {
   repeated UnlauncherApp apps = 1;
   int32 version = 2;
-  bool activate_keyboard_in_drawer = 3;
-  bool set_theme_wallpaper = 4;
 }

+ 0 - 16
app/src/main/res/layout/customize_app_drawer_fragment.xml

@@ -37,20 +37,4 @@
         android:textSize="@dimen/_20ssp"
         app:layout_constraintTop_toBottomOf="@id/customize_app_drawer_fragment_visible_apps"
         app:layout_constraintStart_toStartOf="parent"/>
-
-    <androidx.appcompat.widget.SwitchCompat
-        android:id="@+id/customize_app_drawer_auto_device_theme_wallpaper"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/_16sdp"
-        android:layout_marginLeft="@dimen/_16sdp"
-        android:layout_marginTop="32dp"
-        android:layout_marginEnd="@dimen/_16sdp"
-        android:layout_marginRight="@dimen/_16sdp"
-        android:layout_marginBottom="32dp"
-        android:text="@string/customize_app_drawer_fragment_auto_theme_wallpaper_text"
-        android:textAppearance="@style/TextAppearance.AppCompat"
-        android:textSize="@dimen/_18ssp"
-        app:layout_constraintTop_toBottomOf="@id/customize_app_drawer_open_keyboard_switch"
-        app:layout_constraintStart_toStartOf="parent"/>
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 16 - 0
app/src/main/res/layout/options_fragment.xml

@@ -135,6 +135,22 @@
             android:textSize="@dimen/_20ssp"
             app:layout_constraintStart_toStartOf="@+id/options_fragment_customize_quick_buttons"
             app:layout_constraintTop_toBottomOf="@+id/options_fragment_customize_quick_buttons" />
+
+        <androidx.appcompat.widget.SwitchCompat
+            android:id="@+id/options_fragment_auto_device_theme_wallpaper"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/_16sdp"
+            android:layout_marginLeft="@dimen/_16sdp"
+            android:layout_marginTop="32dp"
+            android:layout_marginEnd="@dimen/_16sdp"
+            android:layout_marginRight="@dimen/_16sdp"
+            android:layout_marginBottom="32dp"
+            android:text="@string/customize_app_drawer_fragment_auto_theme_wallpaper_text"
+            android:textAppearance="@style/TextAppearance.AppCompat"
+            android:textSize="@dimen/_18ssp"
+            app:layout_constraintTop_toBottomOf="@id/options_fragment_customize_app_drawer"
+            app:layout_constraintStart_toStartOf="parent"/>
     </androidx.constraintlayout.widget.ConstraintLayout>
 
 </ScrollView>