Explorar o código

Add support for hiding apps in the app drawer (#78)

Refactors the data flow for the app data so that the data about the apps is stored in a Proto data store.
Joshua Kuestersteffen %!s(int64=4) %!d(string=hai) anos
pai
achega
efdcdaab8f
Modificáronse 19 ficheiros con 386 adicións e 21 borrados
  1. 51 0
      app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt
  2. 50 0
      app/src/main/java/com/sduduzog/slimlauncher/adapters/CustomizeAppDrawerAppsAdapter.kt
  3. 12 1
      app/src/main/java/com/sduduzog/slimlauncher/datasource/UnlauncherDataSource.kt
  4. 100 0
      app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt
  5. 28 0
      app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsSerializer.kt
  6. 1 1
      app/src/main/java/com/sduduzog/slimlauncher/datasource/quickbuttonprefs/QuickButtonPreferencesRepository.kt
  7. 2 2
      app/src/main/java/com/sduduzog/slimlauncher/datasource/quickbuttonprefs/QuickButtonPreferencesSerializer.kt
  8. 1 1
      app/src/main/java/com/sduduzog/slimlauncher/ui/dialogs/ChooseQuickButtonDialog.kt
  9. 15 15
      app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt
  10. 39 0
      app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeAppDrawerFragment.kt
  11. 1 1
      app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeQuickButtonsFragment.kt
  12. 1 0
      app/src/main/java/com/sduduzog/slimlauncher/ui/options/OptionsFragment.kt
  13. 0 0
      app/src/main/proto/quick_button_preferences.proto
  14. 16 0
      app/src/main/proto/unlauncher_apps.proto
  15. 40 0
      app/src/main/res/layout/customize_app_drawer_fragment.xml
  16. 8 0
      app/src/main/res/layout/customize_app_drawer_fragment_app_list_item.xml
  17. 12 0
      app/src/main/res/layout/options_fragment.xml
  18. 8 0
      app/src/main/res/navigation/nav_graph.xml
  19. 1 0
      app/src/main/res/values/strings.xml

+ 51 - 0
app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt

@@ -0,0 +1,51 @@
+package com.sduduzog.slimlauncher.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.lifecycle.LifecycleOwner
+import androidx.recyclerview.widget.RecyclerView
+import com.jkuester.unlauncher.datastore.UnlauncherApp
+import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
+import com.sduduzog.slimlauncher.ui.main.HomeFragment
+
+class AppDrawerAdapter(
+    private val listener: HomeFragment.AppDrawerListener,
+    lifecycleOwner: LifecycleOwner,
+    appsRepo: UnlauncherAppsRepository
+) : RecyclerView.Adapter<AppDrawerAdapter.ViewHolder>() {
+    private var apps: List<UnlauncherApp> = listOf()
+
+    init {
+        appsRepo.liveData().observe(lifecycleOwner, { unlauncherApps ->
+            apps = unlauncherApps.appsList.filter { app -> app.displayInDrawer }.toList()
+        })
+    }
+
+    override fun getItemCount(): Int = apps.size
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = apps[position]
+        holder.appName.text = item.displayName
+        holder.itemView.setOnClickListener {
+            listener.onAppClicked(item)
+        }
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.add_app_fragment_list_item, parent, false)
+        return ViewHolder(view)
+    }
+
+    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+        val appName: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
+
+        override fun toString(): String {
+            return super.toString() + " '${appName.text}'"
+        }
+    }
+}

+ 50 - 0
app/src/main/java/com/sduduzog/slimlauncher/adapters/CustomizeAppDrawerAppsAdapter.kt

@@ -0,0 +1,50 @@
+package com.sduduzog.slimlauncher.adapters
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import androidx.lifecycle.LifecycleOwner
+import androidx.recyclerview.widget.RecyclerView
+import com.jkuester.unlauncher.datastore.UnlauncherApps
+import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
+
+class CustomizeAppDrawerAppsAdapter(
+    lifecycleOwner: LifecycleOwner, private val appsRepo: UnlauncherAppsRepository
+) : RecyclerView.Adapter<CustomizeAppDrawerAppsAdapter.ViewHolder>() {
+    private var apps: UnlauncherApps = UnlauncherApps.getDefaultInstance()
+
+    init {
+        appsRepo.liveData().observe(lifecycleOwner, { unlauncherApps ->
+            apps = unlauncherApps
+        })
+    }
+
+    override fun getItemCount(): Int = apps.appsCount
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+        val item = apps.getApps(position)
+        holder.appName.text = item.displayName
+        holder.appName.isChecked = item.displayInDrawer
+        holder.itemView.setOnClickListener {
+            appsRepo.updateDisplayInDrawer(item, holder.appName.isChecked)
+        }
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val view = LayoutInflater.from(parent.context)
+            .inflate(R.layout.customize_app_drawer_fragment_app_list_item, parent, false)
+        return ViewHolder(view)
+    }
+
+    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+
+        val appName: CheckBox =
+            itemView.findViewById(R.id.customize_app_drawer_fragment_app_list_item)
+
+        override fun toString(): String {
+            return super.toString() + " '${appName.text}'"
+        }
+    }
+}

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

@@ -7,9 +7,14 @@ import androidx.datastore.migrations.SharedPreferencesMigration
 import androidx.datastore.migrations.SharedPreferencesView
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.jkuester.unlauncher.datastore.QuickButtonPreferences
+import com.jkuester.unlauncher.datastore.UnlauncherApps
+import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
+import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsSerializer
+import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesRepository
+import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesSerializer
 
 private val Context.quickButtonPreferencesStore: DataStore<QuickButtonPreferences> by dataStore(
-    fileName = "unlauncher_data.pb",
+    fileName = "quick_button_preferences.proto",
     serializer = QuickButtonPreferencesSerializer,
     produceMigrations = { context ->
         listOf(
@@ -53,7 +58,13 @@ private val Context.quickButtonPreferencesStore: DataStore<QuickButtonPreference
     }
 )
 
+private val Context.unlauncherAppsStore: DataStore<UnlauncherApps> by dataStore(
+    fileName = "unlauncher_apps.proto",
+    serializer = UnlauncherAppsSerializer
+)
+
 class UnlauncherDataSource(context: Context, lifecycleScope: LifecycleCoroutineScope) {
     val quickButtonPreferencesRepo =
         QuickButtonPreferencesRepository(context.quickButtonPreferencesStore, lifecycleScope)
+    val unlauncherAppsRepo = UnlauncherAppsRepository(context.unlauncherAppsStore, lifecycleScope)
 }

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

@@ -0,0 +1,100 @@
+package com.sduduzog.slimlauncher.datasource.apps
+
+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.UnlauncherApp
+import com.jkuester.unlauncher.datastore.UnlauncherApps
+import com.sduduzog.slimlauncher.data.model.App
+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 UnlauncherAppsRepository(
+    private val unlauncherAppsStore: DataStore<UnlauncherApps>,
+    private val lifecycleScope: LifecycleCoroutineScope
+) {
+    private val unlauncherAppsFlow: Flow<UnlauncherApps> =
+        unlauncherAppsStore.data
+            .catch { exception ->
+                if (exception is IOException) {
+                    Log.e(
+                        "UnlauncherAppsRepo",
+                        "Error reading Unlauncher apps.",
+                        exception
+                    )
+                    emit(UnlauncherApps.getDefaultInstance())
+                } else {
+                    throw exception
+                }
+            }
+
+    fun liveData(): LiveData<UnlauncherApps> {
+        return unlauncherAppsFlow.asLiveData()
+    }
+
+    fun setApps(apps: List<App>) {
+        lifecycleScope.launch {
+            unlauncherAppsStore.updateData { unlauncherApps ->
+                val unlauncherAppsBuilder = unlauncherApps.toBuilder()
+                // Add any new apps
+                apps.filter { app ->
+                    findApp(
+                        unlauncherApps,
+                        app.packageName,
+                        app.activityName
+                    ) == null
+                }.forEach { app ->
+                    val index =
+                        unlauncherAppsBuilder.appsList.indexOfFirst { unlauncherApp -> unlauncherApp.displayName > app.appName }
+                    unlauncherAppsBuilder.addApps(
+                        if (index >= 0) index else 0,
+                        UnlauncherApp.newBuilder().setPackageName(app.packageName)
+                            .setClassName(app.activityName).setUserSerial(app.userSerial)
+                            .setDisplayName(app.appName).setDisplayInDrawer(true)
+                    )
+                }
+                // Remove any apps that no longer exist
+                unlauncherApps.appsList.filter { unlauncherApp ->
+                    apps.find { app ->
+                        unlauncherApp.packageName == app.packageName && unlauncherApp.className == app.activityName
+                    } == null
+                }.forEach { unlauncherApp ->
+                    unlauncherAppsBuilder.removeApps(
+                        unlauncherAppsBuilder.appsList.indexOf(
+                            unlauncherApp
+                        )
+                    )
+                }
+
+                unlauncherAppsBuilder.build()
+            }
+        }
+    }
+
+    fun updateDisplayInDrawer(appToUpdate: UnlauncherApp, displayInDrawer: Boolean) {
+        lifecycleScope.launch {
+            unlauncherAppsStore.updateData { currentApps ->
+                currentApps.toBuilder().setApps(
+                    currentApps.appsList.indexOf(appToUpdate),
+                    appToUpdate.toBuilder().setDisplayInDrawer(displayInDrawer)
+                ).build()
+            }
+        }
+    }
+
+    private fun findApp(
+        unlauncherApps: UnlauncherApps,
+        packageName: String,
+        className: String
+    ): UnlauncherApp? {
+        return unlauncherApps.appsList.firstOrNull { app ->
+            packageName == app.packageName && className == app.className
+        }
+    }
+}

+ 28 - 0
app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsSerializer.kt

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

+ 1 - 1
app/src/main/java/com/sduduzog/slimlauncher/datasource/QuickButtonPreferencesRepository.kt → app/src/main/java/com/sduduzog/slimlauncher/datasource/quickbuttonprefs/QuickButtonPreferencesRepository.kt

@@ -1,4 +1,4 @@
-package com.sduduzog.slimlauncher.datasource
+package com.sduduzog.slimlauncher.datasource.quickbuttonprefs
 
 import android.util.Log
 import androidx.datastore.core.DataStore

+ 2 - 2
app/src/main/java/com/sduduzog/slimlauncher/datasource/QuickButtonPreferencesSerializer.kt → app/src/main/java/com/sduduzog/slimlauncher/datasource/quickbuttonprefs/QuickButtonPreferencesSerializer.kt

@@ -1,4 +1,4 @@
-package com.sduduzog.slimlauncher.datasource
+package com.sduduzog.slimlauncher.datasource.quickbuttonprefs
 
 import androidx.datastore.core.CorruptionException
 import androidx.datastore.core.Serializer
@@ -8,7 +8,7 @@ import java.io.InputStream
 import java.io.OutputStream
 
 /**
- * Serializer for the [QuickButtonPreferences] object defined in unlauncher_data.proto.
+ * Serializer for the [QuickButtonPreferences] object defined in quick_button_preferences.proto.
  */
 object QuickButtonPreferencesSerializer : Serializer<QuickButtonPreferences> {
     override val defaultValue: QuickButtonPreferences = QuickButtonPreferences.getDefaultInstance()

+ 1 - 1
app/src/main/java/com/sduduzog/slimlauncher/ui/dialogs/ChooseQuickButtonDialog.kt

@@ -6,7 +6,7 @@ import android.content.DialogInterface
 import android.os.Bundle
 import androidx.fragment.app.DialogFragment
 import com.sduduzog.slimlauncher.R
-import com.sduduzog.slimlauncher.datasource.QuickButtonPreferencesRepository
+import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesRepository
 
 class ChooseQuickButtonDialog(
     private val repo: QuickButtonPreferencesRepository,

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

@@ -18,14 +18,13 @@ import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener
 import androidx.lifecycle.Observer
 import androidx.navigation.Navigation
+import com.jkuester.unlauncher.datastore.UnlauncherApp
 import com.sduduzog.slimlauncher.R
-import com.sduduzog.slimlauncher.adapters.AddAppAdapter
+import com.sduduzog.slimlauncher.adapters.AppDrawerAdapter
 import com.sduduzog.slimlauncher.adapters.HomeAdapter
-import com.sduduzog.slimlauncher.data.model.App
 import com.sduduzog.slimlauncher.models.HomeApp
 import com.sduduzog.slimlauncher.models.MainViewModel
 import com.sduduzog.slimlauncher.utils.BaseFragment
-import com.sduduzog.slimlauncher.utils.OnAppClickedListener
 import com.sduduzog.slimlauncher.utils.OnLaunchAppListener
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.home_fragment.*
@@ -34,7 +33,7 @@ import java.text.SimpleDateFormat
 import java.util.*
 
 @AndroidEntryPoint
-class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLaunchAppListener, OnAppClickedListener {
+class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLaunchAppListener {
 
     private lateinit var receiver: BroadcastReceiver
 
@@ -63,13 +62,12 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
 
         setEventListeners()
 
-        // Populate the app drawer
-        val openAppAdapter = AddAppAdapter(this)
-        app_drawer_fragment_list.adapter = openAppAdapter
+        val unlauncherAppRepo = getUnlauncherDataSource().unlauncherAppsRepo;
+        app_drawer_fragment_list.adapter =
+            AppDrawerAdapter(AppDrawerListener(), viewLifecycleOwner, unlauncherAppRepo)
+        // Send the apps to Unlauncher data source
         viewModel.addAppViewModel.apps.observe(viewLifecycleOwner, Observer {
-            it?.let { apps ->
-                openAppAdapter.setItems(apps)
-            }
+            it?.let { apps -> unlauncherAppRepo.setApps(apps) }
         })
         home_fragment.setTransitionListener(object : TransitionListener {
             override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
@@ -225,11 +223,6 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         }
     }
 
-    override fun onAppClicked(app: App) {
-        launchApp(app.packageName, app.activityName, app.userSerial)
-        home_fragment.transitionToStart()
-    }
-
     private fun launchApp(packageName: String, activityName: String, userSerial: Long) {
         try {
             val manager = requireContext().getSystemService(Context.USER_SERVICE) as UserManager
@@ -264,4 +257,11 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
             viewModel.addAppViewModel.filterApps(s.toString())
         }
     }
+
+    inner class AppDrawerListener {
+        fun onAppClicked(app: UnlauncherApp) {
+            launchApp(app.packageName, app.className, app.userSerial)
+            home_fragment.transitionToStart()
+        }
+    }
 }

+ 39 - 0
app/src/main/java/com/sduduzog/slimlauncher/ui/options/CustomizeAppDrawerFragment.kt

@@ -0,0 +1,39 @@
+package com.sduduzog.slimlauncher.ui.options
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.sduduzog.slimlauncher.R
+import com.sduduzog.slimlauncher.adapters.CustomizeAppDrawerAppsAdapter
+import com.sduduzog.slimlauncher.utils.BaseFragment
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.android.synthetic.main.customize_app_drawer_fragment.*
+
+@AndroidEntryPoint
+class CustomizeAppDrawerFragment : BaseFragment() {
+
+    override fun getFragmentView(): ViewGroup = customize_app_drawer_fragment
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.customize_app_drawer_fragment, container, false)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        val unlauncherAppsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+        customize_app_drawer_fragment_app_list.adapter =
+            CustomizeAppDrawerAppsAdapter(viewLifecycleOwner, unlauncherAppsRepo)
+        unlauncherAppsRepo.liveData().observe(viewLifecycleOwner, {
+            it?.let {
+                customize_app_drawer_fragment_app_progress_bar.visibility = View.GONE
+            } ?: run {
+                customize_app_drawer_fragment_app_progress_bar.visibility = View.VISIBLE
+            }
+        })
+    }
+}

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

@@ -5,7 +5,7 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import com.sduduzog.slimlauncher.R
-import com.sduduzog.slimlauncher.datasource.QuickButtonPreferencesRepository
+import com.sduduzog.slimlauncher.datasource.quickbuttonprefs.QuickButtonPreferencesRepository
 import com.sduduzog.slimlauncher.ui.dialogs.ChooseQuickButtonDialog
 import com.sduduzog.slimlauncher.utils.BaseFragment
 import kotlinx.android.synthetic.main.customize_quick_buttons_fragment.*

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

@@ -55,5 +55,6 @@ class OptionsFragment : BaseFragment() {
         }
         options_fragment_customise_apps.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_optionsFragment_to_customiseAppsFragment))
         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))
     }
 }

+ 0 - 0
app/src/main/proto/unlauncher_data.proto → app/src/main/proto/quick_button_preferences.proto


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

@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+option java_package = "com.jkuester.unlauncher.datastore";
+option java_multiple_files = true;
+
+message UnlauncherApp {
+  string package_name = 1;
+  string class_name = 2;
+  int64 user_serial = 3;
+  string display_name = 4;
+  bool display_in_drawer = 5;
+}
+
+message UnlauncherApps {
+  repeated UnlauncherApp apps = 1;
+}

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

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/customize_app_drawer_fragment"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".ui.options.CustomizeAppDrawerFragment">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/customize_app_drawer_fragment_app_list"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginLeft="8dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:listitem="@layout/customize_app_drawer_fragment_app_list_item" />
+
+    <ProgressBar
+        android:id="@+id/customize_app_drawer_fragment_app_progress_bar"
+        style="?android:attr/progressBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginLeft="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_bias="0.25" />
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 8 - 0
app/src/main/res/layout/customize_app_drawer_fragment_app_list_item.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/customize_app_drawer_fragment_app_list_item"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="6dp"
+    android:textAppearance="@style/TextAppearance.AppCompat"
+    android:textSize="@dimen/_18ssp" />

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

@@ -108,4 +108,16 @@
         android:textSize="@dimen/_20ssp"
         app:layout_constraintStart_toStartOf="@+id/options_fragment_customise_apps"
         app:layout_constraintTop_toBottomOf="@+id/options_fragment_customise_apps" />
+
+    <TextView
+        android:id="@+id/options_fragment_customize_app_drawer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="32dp"
+        android:layout_marginBottom="32dp"
+        android:text="@string/options_fragment_customize_app_drawer"
+        android:textAppearance="@style/TextAppearance.AppCompat"
+        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.constraintlayout.widget.ConstraintLayout>

+ 8 - 0
app/src/main/res/navigation/nav_graph.xml

@@ -25,6 +25,9 @@
         <action
             android:id="@+id/action_optionsFragment_to_customiseQuickButtonsFragment"
             app:destination="@id/customiseQuickButtonsFragment" />
+        <action
+            android:id="@+id/action_optionsFragment_to_customiseAppDrawerFragment"
+            app:destination="@id/customiseAppDrawerFragment" />
     </fragment>
     <fragment
         android:id="@+id/customiseAppsFragment"
@@ -45,5 +48,10 @@
         android:name="com.sduduzog.slimlauncher.ui.options.CustomizeQuickButtonsFragment"
         android:label="customise_quick_buttons_fragment"
         tools:layout="@layout/customize_quick_buttons_fragment" />
+    <fragment
+        android:id="@+id/customiseAppDrawerFragment"
+        android:name="com.sduduzog.slimlauncher.ui.options.CustomizeAppDrawerFragment"
+        android:label="customise_app_drawer_fragment"
+        tools:layout="@layout/customize_app_drawer_fragment" />
 
 </navigation>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -39,6 +39,7 @@
     <string name="options_fragment_choose_time_format">Choose Time Format</string>
     <string name="options_fragment_customise_apps">Customise Apps</string>
     <string name="options_fragment_customize_quick_buttons">Customise Quick Buttons</string>
+    <string name="options_fragment_customize_app_drawer">Customise Drawer</string>
     <string name="options_fragment_toggle_status_bar">Toggle Status Bar</string>
     <string name="options_fragment_hide_status_bar">Hide Status Bar</string>
     <string name="options_fragment_show_status_bar">Show Status Bar</string>