|
|
@@ -12,77 +12,119 @@ 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.datasource.coreprefs.CorePreferencesRepository
|
|
|
import com.sduduzog.slimlauncher.ui.main.HomeFragment
|
|
|
+import com.sduduzog.slimlauncher.utils.firstUppercase
|
|
|
|
|
|
class AppDrawerAdapter(
|
|
|
private val listener: HomeFragment.AppDrawerListener,
|
|
|
lifecycleOwner: LifecycleOwner,
|
|
|
- appsRepo: UnlauncherAppsRepository
|
|
|
-) : RecyclerView.Adapter<AppDrawerAdapter.ViewHolder>() {
|
|
|
+ appsRepo: UnlauncherAppsRepository,
|
|
|
+ private val corePreferencesRepo: CorePreferencesRepository
|
|
|
+) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
|
+
|
|
|
private val regex = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/? ]")
|
|
|
private var apps: List<UnlauncherApp> = listOf()
|
|
|
- private var filteredApps: List<UnlauncherApp> = listOf()
|
|
|
- private var filterQuery = ""
|
|
|
+ private var filteredApps: List<AppDrawerRow> = listOf()
|
|
|
|
|
|
init {
|
|
|
- appsRepo.liveData().observe(lifecycleOwner, { unlauncherApps ->
|
|
|
- apps = unlauncherApps.appsList.filter { app -> app.displayInDrawer }.toList()
|
|
|
- updateDisplayedApps()
|
|
|
- })
|
|
|
+ appsRepo.liveData().observe(lifecycleOwner) { unlauncherApps ->
|
|
|
+ apps = unlauncherApps.appsList
|
|
|
+ updateFilteredApps()
|
|
|
+ }
|
|
|
+ corePreferencesRepo.liveData().observe(lifecycleOwner) { _ ->
|
|
|
+ updateFilteredApps()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override fun getItemCount(): Int = filteredApps.size
|
|
|
|
|
|
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
|
- val item = filteredApps[position]
|
|
|
- holder.appName.text = item.displayName
|
|
|
- holder.itemView.setOnClickListener {
|
|
|
- listener.onAppClicked(item)
|
|
|
- }
|
|
|
- holder.itemView.setOnLongClickListener {
|
|
|
- listener.onAppLongClicked(item, it)
|
|
|
+ override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
|
+ when (val drawerRow = filteredApps[position]) {
|
|
|
+ is AppDrawerRow.Item -> {
|
|
|
+ val unlauncherApp = drawerRow.app
|
|
|
+ (holder as ItemViewHolder).bind(unlauncherApp)
|
|
|
+ holder.itemView.setOnClickListener {
|
|
|
+ listener.onAppClicked(unlauncherApp)
|
|
|
+ }
|
|
|
+ holder.itemView.setOnLongClickListener {
|
|
|
+ listener.onAppLongClicked(unlauncherApp, it)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ is AppDrawerRow.Header -> (holder as HeaderViewHolder).bind(drawerRow.letter)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
fun getFirstApp(): UnlauncherApp {
|
|
|
- return filteredApps.first()
|
|
|
+ return filteredApps.filterIsInstance<AppDrawerRow.Item>().first().app
|
|
|
}
|
|
|
|
|
|
- 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)
|
|
|
- }
|
|
|
+ override fun getItemViewType(position: Int): Int = filteredApps[position].rowType.ordinal
|
|
|
|
|
|
- fun setAppFilter(query: String = "") {
|
|
|
- filterQuery = regex.replace(query, "")
|
|
|
- this.updateDisplayedApps()
|
|
|
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
|
+ val inflater = LayoutInflater.from(parent.context)
|
|
|
+ return when (RowType.values()[viewType]) {
|
|
|
+ RowType.App -> ItemViewHolder(
|
|
|
+ inflater.inflate(R.layout.add_app_fragment_list_item, parent, false)
|
|
|
+ )
|
|
|
+
|
|
|
+ RowType.Header -> HeaderViewHolder(
|
|
|
+ inflater.inflate(R.layout.app_drawer_fragment_header_item, parent, false)
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+
|
|
|
private fun onlyFirstStringStartsWith(first: String, second: String, query: String) : Boolean {
|
|
|
return first.startsWith(query, true) and !second.startsWith(query, true);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
@SuppressLint("NotifyDataSetChanged")
|
|
|
- private fun updateDisplayedApps() {
|
|
|
- filteredApps = apps.filter { app ->
|
|
|
- regex.replace(app.displayName, "").contains(filterQuery, ignoreCase = true)
|
|
|
- }.toList().sortedWith(
|
|
|
- Comparator<UnlauncherApp>{
|
|
|
- a, b -> when {
|
|
|
- // if an app's name starts with the query prefer it
|
|
|
- onlyFirstStringStartsWith(a.displayName, b.displayName, filterQuery) -> -1
|
|
|
- onlyFirstStringStartsWith(b.displayName, a.displayName, filterQuery) -> 1
|
|
|
- // if both or none start with the query sort in normal oder
|
|
|
- a.displayName > b.displayName -> 1
|
|
|
- a.displayName < b.displayName -> -1
|
|
|
- else -> 0
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
+ fun setAppFilter(query: String = "") {
|
|
|
+ val filterQuery = regex.replace(query, "")
|
|
|
+ updateFilteredApps(filterQuery)
|
|
|
notifyDataSetChanged()
|
|
|
}
|
|
|
|
|
|
+ private fun updateFilteredApps(filterQuery: String = "") {
|
|
|
+ val showDrawerHeadings = corePreferencesRepo.get().showDrawerHeadings
|
|
|
+ val displayableApps = apps
|
|
|
+ .filter { app ->
|
|
|
+ app.displayInDrawer && regex.replace(app.displayName, "")
|
|
|
+ .contains(filterQuery, ignoreCase = true)
|
|
|
+ }
|
|
|
+
|
|
|
+ val includeHeadings = !showDrawerHeadings || filterQuery != ""
|
|
|
+ filteredApps = when (includeHeadings) {
|
|
|
+ true -> displayableApps
|
|
|
+ .sortedWith { a, b ->
|
|
|
+ when {
|
|
|
+ // if an app's name starts with the query prefer it
|
|
|
+ onlyFirstStringStartsWith(a.displayName, b.displayName, filterQuery) -> -1
|
|
|
+ onlyFirstStringStartsWith(b.displayName, a.displayName, filterQuery) -> 1
|
|
|
+ // if both or none start with the query sort in normal oder
|
|
|
+ a.displayName > b.displayName -> 1
|
|
|
+ a.displayName < b.displayName -> -1
|
|
|
+ else -> 0
|
|
|
+ }
|
|
|
+ }.map { AppDrawerRow.Item(it) }
|
|
|
+ // building a list with each letter and filtered app resulting in a list of
|
|
|
+ // [
|
|
|
+ // Header<"G">, App<"Gmail">, App<"Google Drive">, Header<"Y">, App<"YouTube">, ...
|
|
|
+ // ]
|
|
|
+ false -> displayableApps
|
|
|
+ .groupBy {
|
|
|
+ app -> app.displayName.firstUppercase()
|
|
|
+ }.flatMap { entry ->
|
|
|
+ listOf(
|
|
|
+ AppDrawerRow.Header(entry.key),
|
|
|
+ *(entry.value.map { AppDrawerRow.Item(it) }).toTypedArray()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
val searchBoxListener: TextWatcher = object : TextWatcher {
|
|
|
override fun afterTextChanged(s: Editable?) {
|
|
|
// Do nothing
|
|
|
@@ -97,12 +139,38 @@ class AppDrawerAdapter(
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
|
+ inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
|
|
|
|
- val appName: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
|
|
|
+ val item: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
|
|
|
|
|
|
override fun toString(): String {
|
|
|
- return super.toString() + " '${appName.text}'"
|
|
|
+ return "${super.toString()} '${item.text}'"
|
|
|
+ }
|
|
|
+
|
|
|
+ fun bind(item: UnlauncherApp) {
|
|
|
+ this.item.text = item.displayName
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ inner class HeaderViewHolder(headerView: View) : RecyclerView.ViewHolder(headerView) {
|
|
|
+ private val header: TextView = itemView.findViewById(R.id.aa_list_header_letter)
|
|
|
+
|
|
|
+ override fun toString(): String {
|
|
|
+ return "${super.toString()} '${header.text}'"
|
|
|
+ }
|
|
|
+
|
|
|
+ fun bind(letter: String) {
|
|
|
+ header.text = letter
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+enum class RowType {
|
|
|
+ Header, App
|
|
|
+}
|
|
|
+
|
|
|
+sealed class AppDrawerRow(val rowType: RowType) {
|
|
|
+ data class Item(val app: UnlauncherApp) : AppDrawerRow(RowType.App)
|
|
|
+
|
|
|
+ data class Header(val letter: String) : AppDrawerRow(RowType.Header)
|
|
|
}
|