|
|
@@ -6,9 +6,12 @@ import android.text.TextWatcher
|
|
|
import android.view.LayoutInflater
|
|
|
import android.view.View
|
|
|
import android.view.ViewGroup
|
|
|
+import android.widget.ImageView
|
|
|
import android.widget.TextView
|
|
|
import androidx.lifecycle.LifecycleOwner
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
|
+import com.simplauncher.datastore.AppDrawerFolder
|
|
|
+import com.simplauncher.datastore.AppDrawerFolders
|
|
|
import com.simplauncher.datastore.UnlauncherApp
|
|
|
import com.simplauncher.R
|
|
|
import com.simplauncher.datasource.UnlauncherDataSource
|
|
|
@@ -25,6 +28,8 @@ class AppDrawerAdapter(
|
|
|
private val WORK_APP_PREFIX = "\uD83C\uDD46 " //Unicode for boxed w
|
|
|
private val regex = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/? ]")
|
|
|
private var apps: List<UnlauncherApp> = listOf()
|
|
|
+ private var folders: AppDrawerFolders = AppDrawerFolders.getDefaultInstance()
|
|
|
+ private val expandedFolderIds: MutableSet<String> = mutableSetOf()
|
|
|
private var filteredApps: List<AppDrawerRow> = listOf()
|
|
|
private var gravity = 3
|
|
|
|
|
|
@@ -37,6 +42,10 @@ class AppDrawerAdapter(
|
|
|
gravity = corePrefs.alignmentFormat.gravity()
|
|
|
updateFilteredApps()
|
|
|
}
|
|
|
+ unlauncherDataSource.appDrawerFoldersRepo.liveData().observe(lifecycleOwner) { appDrawerFolders ->
|
|
|
+ folders = appDrawerFolders
|
|
|
+ updateFilteredApps()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
override fun getItemCount(): Int = filteredApps.size
|
|
|
@@ -55,6 +64,18 @@ class AppDrawerAdapter(
|
|
|
}
|
|
|
|
|
|
is AppDrawerRow.Header -> (holder as HeaderViewHolder).bind(drawerRow.letter)
|
|
|
+
|
|
|
+ is AppDrawerRow.FolderHeader -> {
|
|
|
+ (holder as FolderViewHolder).bind(drawerRow.folder, drawerRow.isExpanded)
|
|
|
+ holder.itemView.setOnClickListener { toggleFolder(drawerRow.folder.id) }
|
|
|
+ }
|
|
|
+
|
|
|
+ is AppDrawerRow.FolderItem -> {
|
|
|
+ val app = drawerRow.app
|
|
|
+ (holder as FolderAppViewHolder).bind(app)
|
|
|
+ holder.itemView.setOnClickListener { listener.onAppClicked(app) }
|
|
|
+ holder.itemView.setOnLongClickListener { listener.onAppLongClicked(app, it) }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -70,13 +91,43 @@ class AppDrawerAdapter(
|
|
|
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)
|
|
|
)
|
|
|
+ RowType.FolderHeader -> FolderViewHolder(
|
|
|
+ inflater.inflate(R.layout.app_drawer_folder_item, parent, false)
|
|
|
+ )
|
|
|
+ RowType.FolderApp -> FolderAppViewHolder(
|
|
|
+ inflater.inflate(R.layout.add_app_fragment_list_item, parent, false)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun toggleFolder(folderId: String) {
|
|
|
+ if (expandedFolderIds.contains(folderId)) {
|
|
|
+ expandedFolderIds.remove(folderId)
|
|
|
+ } else {
|
|
|
+ expandedFolderIds.add(folderId)
|
|
|
}
|
|
|
+ updateFilteredApps()
|
|
|
}
|
|
|
|
|
|
+ private fun buildFolderRows(): List<AppDrawerRow> {
|
|
|
+ val rows = mutableListOf<AppDrawerRow>()
|
|
|
+ for (folder in folders.foldersList) {
|
|
|
+ val isExpanded = expandedFolderIds.contains(folder.id)
|
|
|
+ rows.add(AppDrawerRow.FolderHeader(folder, isExpanded))
|
|
|
+ if (isExpanded) {
|
|
|
+ for (folderApp in folder.appsList) {
|
|
|
+ val unlauncherApp = apps.firstOrNull {
|
|
|
+ it.packageName == folderApp.packageName && it.className == folderApp.className
|
|
|
+ } ?: continue
|
|
|
+ rows.add(AppDrawerRow.FolderItem(unlauncherApp))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return rows
|
|
|
+ }
|
|
|
|
|
|
private fun onlyFirstStringStartsWith(first: String, second: String, query: String) : Boolean {
|
|
|
return first.startsWith(query, true) and !second.startsWith(query, true);
|
|
|
@@ -99,21 +150,15 @@ class AppDrawerAdapter(
|
|
|
}
|
|
|
|
|
|
val includeHeadings = !showDrawerHeadings || filterQuery != ""
|
|
|
- val updatedApps = when (includeHeadings) {
|
|
|
+ val appRows = 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
|
|
|
else -> a.displayName.compareTo(b.displayName, true)
|
|
|
}
|
|
|
}.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 ->
|
|
|
if(app.displayName.startsWith(WORK_APP_PREFIX)) WORK_APP_PREFIX
|
|
|
@@ -125,6 +170,13 @@ class AppDrawerAdapter(
|
|
|
)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ val updatedApps = if (filterQuery.isEmpty() && folders.foldersCount > 0) {
|
|
|
+ buildFolderRows() + appRows
|
|
|
+ } else {
|
|
|
+ appRows
|
|
|
+ }
|
|
|
+
|
|
|
if (updatedApps != filteredApps) {
|
|
|
filteredApps = updatedApps
|
|
|
notifyDataSetChanged()
|
|
|
@@ -132,13 +184,9 @@ class AppDrawerAdapter(
|
|
|
}
|
|
|
|
|
|
val searchBoxListener: TextWatcher = object : TextWatcher {
|
|
|
- override fun afterTextChanged(s: Editable?) {
|
|
|
- // Do nothing
|
|
|
- }
|
|
|
+ override fun afterTextChanged(s: Editable?) {}
|
|
|
|
|
|
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
|
- // Do nothing
|
|
|
- }
|
|
|
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
|
|
|
|
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
|
|
setAppFilter(s.toString())
|
|
|
@@ -149,9 +197,7 @@ class AppDrawerAdapter(
|
|
|
|
|
|
val item: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
|
|
|
|
|
|
- override fun toString(): String {
|
|
|
- return "${super.toString()} '${item.text}'"
|
|
|
- }
|
|
|
+ override fun toString(): String = "${super.toString()} '${item.text}'"
|
|
|
|
|
|
fun bind(item: UnlauncherApp) {
|
|
|
this.item.text = item.displayName
|
|
|
@@ -162,22 +208,41 @@ class AppDrawerAdapter(
|
|
|
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}'"
|
|
|
- }
|
|
|
+ override fun toString(): String = "${super.toString()} '${header.text}'"
|
|
|
|
|
|
fun bind(letter: String) {
|
|
|
header.text = letter
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ inner class FolderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
|
+ private val name: TextView = itemView.findViewById(R.id.folder_item_name)
|
|
|
+ private val arrow: ImageView = itemView.findViewById(R.id.folder_item_arrow)
|
|
|
+
|
|
|
+ fun bind(folder: AppDrawerFolder, isExpanded: Boolean) {
|
|
|
+ name.text = folder.name
|
|
|
+ name.gravity = gravity
|
|
|
+ arrow.rotation = if (isExpanded) 180f else 0f
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ inner class FolderAppViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
|
+ private val item: TextView = itemView.findViewById(R.id.aa_list_item_app_name)
|
|
|
+
|
|
|
+ fun bind(app: UnlauncherApp) {
|
|
|
+ item.text = app.displayName
|
|
|
+ item.gravity = gravity
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
enum class RowType {
|
|
|
- Header, App
|
|
|
+ Header, App, FolderHeader, FolderApp
|
|
|
}
|
|
|
|
|
|
sealed class AppDrawerRow(val rowType: RowType) {
|
|
|
data class Item(val app: UnlauncherApp) : AppDrawerRow(RowType.App)
|
|
|
-
|
|
|
data class Header(val letter: String) : AppDrawerRow(RowType.Header)
|
|
|
-}
|
|
|
+ data class FolderHeader(val folder: AppDrawerFolder, val isExpanded: Boolean) : AppDrawerRow(RowType.FolderHeader)
|
|
|
+ data class FolderItem(val app: UnlauncherApp) : AppDrawerRow(RowType.FolderApp)
|
|
|
+}
|