AppDrawerAdapter.kt 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package com.sduduzog.slimlauncher.adapters
  2. import android.annotation.SuppressLint
  3. import android.text.Editable
  4. import android.text.TextWatcher
  5. import android.view.LayoutInflater
  6. import android.view.View
  7. import android.view.ViewGroup
  8. import android.widget.TextView
  9. import androidx.lifecycle.LifecycleOwner
  10. import androidx.recyclerview.widget.RecyclerView
  11. import com.jkuester.unlauncher.datastore.UnlauncherApp
  12. import com.sduduzog.slimlauncher.R
  13. import com.sduduzog.slimlauncher.datasource.apps.UnlauncherAppsRepository
  14. import com.sduduzog.slimlauncher.datasource.coreprefs.CorePreferencesRepository
  15. import com.sduduzog.slimlauncher.ui.main.HomeFragment
  16. import com.sduduzog.slimlauncher.utils.firstUppercase
  17. class AppDrawerAdapter(
  18. private val listener: HomeFragment.AppDrawerListener,
  19. lifecycleOwner: LifecycleOwner,
  20. appsRepo: UnlauncherAppsRepository,
  21. private val corePreferencesRepo: CorePreferencesRepository
  22. ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
  23. private val regex = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/? ]")
  24. private var apps: List<UnlauncherApp> = listOf()
  25. private var filteredApps: List<AppDrawerRow> = listOf()
  26. init {
  27. appsRepo.liveData().observe(lifecycleOwner) { unlauncherApps ->
  28. apps = unlauncherApps.appsList
  29. updateFilteredApps()
  30. }
  31. corePreferencesRepo.liveData().observe(lifecycleOwner) { _ ->
  32. updateFilteredApps()
  33. }
  34. }
  35. override fun getItemCount(): Int = filteredApps.size
  36. override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
  37. when (val drawerRow = filteredApps[position]) {
  38. is AppDrawerRow.Item -> {
  39. val unlauncherApp = drawerRow.app
  40. (holder as ItemViewHolder).bind(unlauncherApp)
  41. holder.itemView.setOnClickListener {
  42. listener.onAppClicked(unlauncherApp)
  43. }
  44. holder.itemView.setOnLongClickListener {
  45. listener.onAppLongClicked(unlauncherApp, it)
  46. }
  47. }
  48. is AppDrawerRow.Header -> (holder as HeaderViewHolder).bind(drawerRow.letter)
  49. }
  50. }
  51. fun getFirstApp(): UnlauncherApp {
  52. return filteredApps.filterIsInstance<AppDrawerRow.Item>().first().app
  53. }
  54. override fun getItemViewType(position: Int): Int = filteredApps[position].rowType.ordinal
  55. override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
  56. val inflater = LayoutInflater.from(parent.context)
  57. return when (RowType.values()[viewType]) {
  58. RowType.App -> ItemViewHolder(
  59. inflater.inflate(R.layout.add_app_fragment_list_item, parent, false)
  60. )
  61. RowType.Header -> HeaderViewHolder(
  62. inflater.inflate(R.layout.app_drawer_fragment_header_item, parent, false)
  63. )
  64. }
  65. }
  66. private fun onlyFirstStringStartsWith(first: String, second: String, query: String) : Boolean {
  67. return first.startsWith(query, true) and !second.startsWith(query, true);
  68. }
  69. @SuppressLint("NotifyDataSetChanged")
  70. fun setAppFilter(query: String = "") {
  71. val filterQuery = regex.replace(query, "")
  72. updateFilteredApps(filterQuery)
  73. notifyDataSetChanged()
  74. }
  75. private fun updateFilteredApps(filterQuery: String = "") {
  76. val showDrawerHeadings = corePreferencesRepo.get().showDrawerHeadings
  77. val searchAllApps = corePreferencesRepo.get().searchAllAppsInDrawer && filterQuery != ""
  78. val displayableApps = apps
  79. .filter { app ->
  80. (app.displayInDrawer || searchAllApps) && regex.replace(app.displayName, "")
  81. .contains(filterQuery, ignoreCase = true)
  82. }
  83. val includeHeadings = !showDrawerHeadings || filterQuery != ""
  84. filteredApps = when (includeHeadings) {
  85. true -> displayableApps
  86. .sortedWith { a, b ->
  87. when {
  88. // if an app's name starts with the query prefer it
  89. onlyFirstStringStartsWith(a.displayName, b.displayName, filterQuery) -> -1
  90. onlyFirstStringStartsWith(b.displayName, a.displayName, filterQuery) -> 1
  91. // if both or none start with the query sort in normal oder
  92. a.displayName > b.displayName -> 1
  93. a.displayName < b.displayName -> -1
  94. else -> 0
  95. }
  96. }.map { AppDrawerRow.Item(it) }
  97. // building a list with each letter and filtered app resulting in a list of
  98. // [
  99. // Header<"G">, App<"Gmail">, App<"Google Drive">, Header<"Y">, App<"YouTube">, ...
  100. // ]
  101. false -> displayableApps
  102. .groupBy {
  103. app -> app.displayName.firstUppercase()
  104. }.flatMap { entry ->
  105. listOf(
  106. AppDrawerRow.Header(entry.key),
  107. *(entry.value.map { AppDrawerRow.Item(it) }).toTypedArray()
  108. )
  109. }
  110. }
  111. }
  112. val searchBoxListener: TextWatcher = object : TextWatcher {
  113. override fun afterTextChanged(s: Editable?) {
  114. // Do nothing
  115. }
  116. override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
  117. // Do nothing
  118. }
  119. override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
  120. setAppFilter(s.toString())
  121. }
  122. }
  123. inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
  124. val item: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
  125. override fun toString(): String {
  126. return "${super.toString()} '${item.text}'"
  127. }
  128. fun bind(item: UnlauncherApp) {
  129. this.item.text = item.displayName
  130. }
  131. }
  132. inner class HeaderViewHolder(headerView: View) : RecyclerView.ViewHolder(headerView) {
  133. private val header: TextView = itemView.findViewById(R.id.aa_list_header_letter)
  134. override fun toString(): String {
  135. return "${super.toString()} '${header.text}'"
  136. }
  137. fun bind(letter: String) {
  138. header.text = letter
  139. }
  140. }
  141. }
  142. enum class RowType {
  143. Header, App
  144. }
  145. sealed class AppDrawerRow(val rowType: RowType) {
  146. data class Item(val app: UnlauncherApp) : AppDrawerRow(RowType.App)
  147. data class Header(val letter: String) : AppDrawerRow(RowType.Header)
  148. }