Skip to content

Commit

Permalink
Update Unlauncher app data flow (#82)
Browse files Browse the repository at this point in the history
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`).
  • Loading branch information
jkuester authored Sep 25, 2021
1 parent f3eb7b2 commit 343d88f
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
}
}
Expand Down
91 changes: 43 additions & 48 deletions app/src/main/java/com/sduduzog/slimlauncher/ui/main/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ 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
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
Expand All @@ -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.*
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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() {
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions app/src/main/proto/unlauncher_apps.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 343d88f

Please sign in to comment.