diff --git a/app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt b/app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt index 95f56e8e..ca2709c4 100644 --- a/app/src/main/java/com/sduduzog/slimlauncher/adapters/AppDrawerAdapter.kt +++ b/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() { + private val regex = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/? ]") + private var apps: List = listOf() + private var filteredApps: List = 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) diff --git a/app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt b/app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt index 0dbd1072..c64c8e22 100644 --- a/app/src/main/java/com/sduduzog/slimlauncher/datasource/apps/UnlauncherAppsRepository.kt +++ b/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) { - 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) { + 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) { + unlauncherAppsStore.updateData { unlauncherApps -> + val unlauncherAppsBuilder = unlauncherApps.toBuilder() + val unlauncherHomeApps = mutableListOf() + + // 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, packageName: String, className: String ): UnlauncherApp? { - return unlauncherApps.appsList.firstOrNull { app -> + return unlauncherApps.firstOrNull { app -> packageName == app.packageName && className == app.className } } diff --git a/app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt b/app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt index a9a7f238..025a2cef 100644 --- a/app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt +++ b/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) diff --git a/app/src/main/proto/unlauncher_apps.proto b/app/src/main/proto/unlauncher_apps.proto index 01d2ed5c..1002ef53 100644 --- a/app/src/main/proto/unlauncher_apps.proto +++ b/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 {