소스 검색

Update Unlauncher app data flow (#82)

Updates the data flow for the Unlauncher apps (that populate the data in the drawer) so that the installed apps are loaded into the Unlauncher data straight from the system data (and not piggybacking on the `AddAppViewModel`).
Joshua Kuestersteffen 4 년 전
부모
커밋
343d88f671

+ 37 - 2
app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt

@@ -1,5 +1,8 @@
 package com.sduduzog.slimlauncher.adapters
 
+import android.annotation.SuppressLint
+import android.text.Editable
+import android.text.TextWatcher
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -16,18 +19,23 @@ class AppDrawerAdapter(
     lifecycleOwner: LifecycleOwner,
     appsRepo: UnlauncherAppsRepository
 ) : RecyclerView.Adapter<AppDrawerAdapter.ViewHolder>() {
+    private val regex = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/? ]")
+
     private var apps: List<UnlauncherApp> = listOf()
+    private var filteredApps: List<UnlauncherApp> = listOf()
+    private var filterQuery = ""
 
     init {
         appsRepo.liveData().observe(lifecycleOwner, { unlauncherApps ->
             apps = unlauncherApps.appsList.filter { app -> app.displayInDrawer }.toList()
+            updateDisplayedApps()
         })
     }
 
-    override fun getItemCount(): Int = apps.size
+    override fun getItemCount(): Int = filteredApps.size
 
     override fun onBindViewHolder(holder: ViewHolder, position: Int) {
-        val item = apps[position]
+        val item = filteredApps[position]
         holder.appName.text = item.displayName
         holder.itemView.setOnClickListener {
             listener.onAppClicked(item)
@@ -40,6 +48,33 @@ class AppDrawerAdapter(
         return ViewHolder(view)
     }
 
+    fun setAppFilter(query: String = "") {
+        filterQuery = regex.replace(query, "")
+        this.updateDisplayedApps()
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    private fun updateDisplayedApps() {
+        filteredApps = apps.filter { app ->
+            regex.replace(app.displayName, "").contains(filterQuery, ignoreCase = true)
+        }.toList()
+        notifyDataSetChanged()
+    }
+
+    val searchBoxListener: TextWatcher = object : TextWatcher {
+        override fun afterTextChanged(s: Editable?) {
+            // Do nothing
+        }
+
+        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+            // Do nothing
+        }
+
+        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+            setAppFilter(s.toString())
+        }
+    }
+
     inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
 
         val appName: TextView = itemView.findViewById(R.id.aa_list_item_app_name)

+ 84 - 39
app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt

@@ -8,11 +8,10 @@ import androidx.lifecycle.asLiveData
 import com.jkuester.unlauncher.datastore.UnlauncherApp
 import com.jkuester.unlauncher.datastore.UnlauncherApps
 import com.sduduzog.slimlauncher.data.model.App
+import com.sduduzog.slimlauncher.models.HomeApp
 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(
@@ -38,62 +37,108 @@ class UnlauncherAppsRepository(
         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 unlauncherAppsBuilder.appsList.size,
-                        UnlauncherApp.newBuilder().setPackageName(app.packageName)
-                            .setClassName(app.activityName).setUserSerial(app.userSerial)
-                            .setDisplayName(app.appName).setDisplayInDrawer(true)
+    suspend fun setApps(apps: List<App>) {
+        unlauncherAppsStore.updateData { unlauncherApps ->
+            val unlauncherAppsBuilder = unlauncherApps.toBuilder()
+            // Add any new apps
+            apps.filter { app ->
+                findApp(
+                    unlauncherAppsBuilder.appsList,
+                    app.packageName,
+                    app.activityName
+                ) == null
+            }.forEach { app ->
+                val index =
+                    unlauncherAppsBuilder.appsList.indexOfFirst { unlauncherApp -> unlauncherApp.displayName > app.appName }
+                unlauncherAppsBuilder.addApps(
+                    if (index >= 0) index else unlauncherAppsBuilder.appsList.size,
+                    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()
+        }
+    }
+
+    suspend fun setHomeApps(apps: List<HomeApp>) {
+        unlauncherAppsStore.updateData { unlauncherApps ->
+            val unlauncherAppsBuilder = unlauncherApps.toBuilder()
+            val unlauncherHomeApps = mutableListOf<UnlauncherApp>()
+
+            // Set home apps
+            apps.forEach { homeApp ->
+                findApp(
+                    unlauncherAppsBuilder.appsList,
+                    homeApp.packageName,
+                    homeApp.activityName
+                )?.let { unlauncherApp ->
+                    if (!unlauncherApp.homeApp) {
+                        val index = unlauncherAppsBuilder.appsList.indexOf(unlauncherApp)
+                        if (index >= 0) {
+                            unlauncherAppsBuilder.setApps(
+                                index,
+                                unlauncherApp.toBuilder().setHomeApp(true)
+                                    .setDisplayInDrawer(false)
+                                    .build()
+                            )
+                        }
+                    }
+                    unlauncherHomeApps.add(unlauncherApp)
                 }
-                // 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
+            }
+
+            // Clear out old home apps
+            unlauncherAppsBuilder.appsList
+                .filter { findApp(unlauncherHomeApps, it.packageName, it.className) == null }
+                .filter { it.homeApp }
+                .forEach { unlauncherApp ->
+                    val index = unlauncherAppsBuilder.appsList.indexOf(unlauncherApp)
+                    if (index >= 0) {
+                        unlauncherAppsBuilder.setApps(
+                            index,
+                            unlauncherApp.toBuilder().setHomeApp(false).setDisplayInDrawer(true)
+                                .build()
                         )
-                    )
+                    }
                 }
 
-                unlauncherAppsBuilder.build()
-            }
+            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()
+                val builder = currentApps.toBuilder()
+                val index = builder.appsList.indexOf(appToUpdate)
+                if (index >= 0) {
+                    builder.setApps(index, appToUpdate.toBuilder().setDisplayInDrawer(displayInDrawer))
+                }
+                builder.build()
             }
         }
     }
 
     private fun findApp(
-        unlauncherApps: UnlauncherApps,
+        unlauncherApps: List<UnlauncherApp>,
         packageName: String,
         className: String
     ): UnlauncherApp? {
-        return unlauncherApps.appsList.firstOrNull { app ->
+        return unlauncherApps.firstOrNull { app ->
             packageName == app.packageName && className == app.className
         }
     }

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

@@ -8,8 +8,6 @@ import android.os.UserManager
 import android.provider.AlarmClock
 import android.provider.CalendarContract
 import android.provider.MediaStore
-import android.text.Editable
-import android.text.TextWatcher
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -17,6 +15,7 @@ import android.view.inputmethod.InputMethodManager
 import androidx.constraintlayout.motion.widget.MotionLayout
 import androidx.constraintlayout.motion.widget.MotionLayout.TransitionListener
 import androidx.lifecycle.Observer
+import androidx.lifecycle.lifecycleScope
 import androidx.navigation.Navigation
 import com.jkuester.unlauncher.datastore.UnlauncherApp
 import com.sduduzog.slimlauncher.R
@@ -28,6 +27,7 @@ import com.sduduzog.slimlauncher.utils.BaseFragment
 import com.sduduzog.slimlauncher.utils.OnLaunchAppListener
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.android.synthetic.main.home_fragment.*
+import kotlinx.coroutines.launch
 import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.*
@@ -36,6 +36,7 @@ import java.util.*
 class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLaunchAppListener {
 
     private lateinit var receiver: BroadcastReceiver
+    private lateinit var appDrawerAdapter: AppDrawerAdapter
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                               savedInstanceState: Bundle?): View {
@@ -49,6 +50,8 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         home_fragment_list.adapter = adapter1
         home_fragment_list_exp.adapter = adapter2
 
+        val unlauncherAppsRepo = getUnlauncherDataSource().unlauncherAppsRepo
+
         viewModel.apps.observe(viewLifecycleOwner, Observer { list ->
             list?.let { apps ->
                 adapter1.setItems(apps.filter {
@@ -57,40 +60,20 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
                 adapter2.setItems(apps.filter {
                     it.sortingIndex >= 3
                 })
-            }
-        })
-
-        setEventListeners()
 
-        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 -> unlauncherAppRepo.setApps(apps) }
-        })
-        home_fragment.setTransitionListener(object : TransitionListener {
-            override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
-                // hide the keyboard and remove focus from the EditText when swiping back up
-                if (currentId == motionLayout?.startState) {
-                    resetAppDrawerEditText()
-                    val inputMethodManager = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
-                    inputMethodManager.hideSoftInputFromWindow(requireView().windowToken, 0)
+                // Set the home apps in the Unlauncher data
+                lifecycleScope.launch {
+                    unlauncherAppsRepo.setHomeApps(apps)
                 }
             }
+        })
 
-            override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
-                // do nothing
-            }
+        appDrawerAdapter =
+            AppDrawerAdapter(AppDrawerListener(), viewLifecycleOwner, unlauncherAppsRepo)
 
-            override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
-                // do nothing
-            }
+        setEventListeners()
 
-            override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
-                // do nothing
-            }
-        })
+        app_drawer_fragment_list.adapter = appDrawerAdapter
     }
 
     override fun onStart() {
@@ -105,8 +88,12 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         super.onResume()
         updateClock()
 
-        viewModel.addAppViewModel.setInstalledApps(getInstalledApps())
-        viewModel.addAppViewModel.filterApps("")
+        lifecycleScope.launch {
+            getUnlauncherDataSource().unlauncherAppsRepo.setApps(getInstalledApps())
+        }
+        if (!::appDrawerAdapter.isInitialized) {
+            appDrawerAdapter.setAppFilter()
+        }
     }
 
     override fun onStop() {
@@ -183,7 +170,30 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
                 }
             })
 
-        app_drawer_edit_text.addTextChangedListener(onTextChangeListener)
+        app_drawer_edit_text.addTextChangedListener(appDrawerAdapter.searchBoxListener)
+
+        home_fragment.setTransitionListener(object : TransitionListener {
+            override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
+                // hide the keyboard and remove focus from the EditText when swiping back up
+                if (currentId == motionLayout?.startState) {
+                    resetAppDrawerEditText()
+                    val inputMethodManager = requireContext().getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+                    inputMethodManager.hideSoftInputFromWindow(requireView().windowToken, 0)
+                }
+            }
+
+            override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
+                // do nothing
+            }
+
+            override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
+                // do nothing
+            }
+
+            override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
+                // do nothing
+            }
+        })
     }
 
     fun updateClock() {
@@ -243,21 +253,6 @@ class HomeFragment(private val viewModel: MainViewModel) : BaseFragment(), OnLau
         app_drawer_edit_text.clearFocus()
     }
 
-    private val onTextChangeListener: TextWatcher = object : TextWatcher {
-
-        override fun afterTextChanged(s: Editable?) {
-            // Do nothing
-        }
-
-        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
-            // Do nothing
-        }
-
-        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
-            viewModel.addAppViewModel.filterApps(s.toString())
-        }
-    }
-
     inner class AppDrawerListener {
         fun onAppClicked(app: UnlauncherApp) {
             launchApp(app.packageName, app.className, app.userSerial)

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

@@ -9,6 +9,7 @@ message UnlauncherApp {
   int64 user_serial = 3;
   string display_name = 4;
   bool display_in_drawer = 5;
+  bool home_app = 6;
 }
 
 message UnlauncherApps {