Clarify seen and checked app filtering
This commit is contained in:
@@ -70,7 +70,6 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
binding!!.appRuleToggle.isChecked = rule.mode == AppRuleMode.WHITELIST
|
||||
binding!!.onlySeenToggle.isChecked = rule.onlySeenApps
|
||||
binding!!.seenFirstToggle.isChecked = rule.seenAppsFirst
|
||||
binding!!.seenFirstToggle.isEnabled = !rule.onlySeenApps
|
||||
updateRuleLabels(rule)
|
||||
binding!!.appRuleToggle.setOnCheckedChangeListener { _, checked ->
|
||||
save { copy(mode = if (checked) AppRuleMode.WHITELIST else AppRuleMode.BLACKLIST) }
|
||||
@@ -87,7 +86,7 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
|
||||
private fun updateRuleLabels(rule: AppFilterSettings) {
|
||||
binding!!.appRuleToggle.text = modeLabel(rule.mode)
|
||||
binding!!.onlySeenToggle.text = "Show only seen apps"
|
||||
binding!!.onlySeenToggle.text = "Show only seen and checked apps"
|
||||
binding!!.seenFirstToggle.text = "Show seen apps first"
|
||||
}
|
||||
|
||||
@@ -121,7 +120,7 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
selected = info.packageName in rule.selectedPackages,
|
||||
)
|
||||
}
|
||||
val items = AppListOrdering.items(apps, rule.onlySeenApps, rule.seenAppsFirst)
|
||||
val items = AppListOrdering.items(apps, rule.onlySeenApps, rule.seenAppsFirst, rule.mode)
|
||||
activity?.runOnUiThread {
|
||||
if (binding === currentBinding && generation == appLoadGeneration) {
|
||||
appAdapter.submit(items)
|
||||
@@ -151,7 +150,11 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = if (items[position] is AppListItem.App) APP else SEPARATOR
|
||||
override fun getItemViewType(position: Int): Int = when (items[position]) {
|
||||
is AppListItem.App -> APP
|
||||
is AppListItem.SectionTitle -> SECTION_TITLE
|
||||
AppListItem.Separator -> SEPARATOR
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == APP) {
|
||||
val row = LinearLayout(parent.context).apply {
|
||||
@@ -170,6 +173,15 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
row.addView(checkbox)
|
||||
row.addView(textColumn)
|
||||
AppHolder(row, checkbox, label, packageName)
|
||||
} else if (viewType == SECTION_TITLE) {
|
||||
SectionTitleHolder(TextView(parent.context).apply {
|
||||
layoutParams = RecyclerView.LayoutParams(
|
||||
RecyclerView.LayoutParams.MATCH_PARENT,
|
||||
RecyclerView.LayoutParams.WRAP_CONTENT,
|
||||
).apply { setMargins(0, dp(parent, 8), 0, dp(parent, 4)) }
|
||||
setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12f)
|
||||
setTypeface(typeface, android.graphics.Typeface.BOLD)
|
||||
})
|
||||
} else {
|
||||
val divider = View(parent.context).apply {
|
||||
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, dp(parent, 1)).apply {
|
||||
@@ -197,6 +209,9 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (holder is SectionTitleHolder && item is AppListItem.SectionTitle) {
|
||||
holder.text.text = item.value
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
@@ -210,9 +225,12 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
|
||||
|
||||
private class SeparatorHolder(view: View) : RecyclerView.ViewHolder(view)
|
||||
|
||||
private class SectionTitleHolder(val text: TextView) : RecyclerView.ViewHolder(text)
|
||||
|
||||
private companion object {
|
||||
const val APP = 0
|
||||
const val SEPARATOR = 1
|
||||
const val SECTION_TITLE = 2
|
||||
fun dp(parent: ViewGroup, value: Int): Int = (value * parent.resources.displayMetrics.density).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,35 @@ package se.ajpanton.notificationlog.settings
|
||||
data class ListedApp(val label: String, val packageName: String, val seen: Boolean, val selected: Boolean)
|
||||
sealed interface AppListItem {
|
||||
data class App(val value: ListedApp) : AppListItem
|
||||
data class SectionTitle(val value: String) : AppListItem
|
||||
data object Separator : AppListItem
|
||||
}
|
||||
|
||||
object AppListOrdering {
|
||||
fun items(allApps: List<ListedApp>, onlySeen: Boolean, seenAppsFirst: Boolean): List<AppListItem> {
|
||||
fun items(
|
||||
allApps: List<ListedApp>,
|
||||
onlySeen: Boolean,
|
||||
seenAppsFirst: Boolean,
|
||||
mode: AppRuleMode,
|
||||
): List<AppListItem> {
|
||||
val apps = allApps.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.label })
|
||||
val shown = if (onlySeen) apps.filter { it.seen || it.selected } else apps
|
||||
val seen = shown.filter { it.seen }
|
||||
val selectedUnseen = shown.filter { !it.seen }
|
||||
if (!seenAppsFirst || seen.isEmpty()) return shown.map(AppListItem::App)
|
||||
return buildList {
|
||||
if (!onlySeen) add(AppListItem.SectionTitle("Seen apps"))
|
||||
if (onlySeen && selectedUnseen.isNotEmpty()) add(AppListItem.SectionTitle("Seen apps"))
|
||||
seen.forEach { add(AppListItem.App(it)) }
|
||||
if (onlySeen) {
|
||||
if (selectedUnseen.isNotEmpty()) {
|
||||
add(AppListItem.Separator)
|
||||
add(AppListItem.SectionTitle(if (mode == AppRuleMode.BLACKLIST) "Blacklisted apps" else "Whitelisted apps"))
|
||||
selectedUnseen.forEach { add(AppListItem.App(it)) }
|
||||
}
|
||||
} else {
|
||||
add(AppListItem.Separator)
|
||||
add(AppListItem.SectionTitle("All apps"))
|
||||
// The full alphabetical list intentionally repeats seen apps after its quick-access group.
|
||||
shown.forEach { add(AppListItem.App(it)) }
|
||||
}
|
||||
|
||||
@@ -6,13 +6,61 @@ import org.junit.Test
|
||||
class AppListOrderingTest {
|
||||
private fun app(label: String, seen: Boolean = false, selected: Boolean = false) = ListedApp(label, "pkg.$label", seen, selected)
|
||||
|
||||
@Test fun `seen apps are repeated before the full list when requested`() {
|
||||
val items = AppListOrdering.items(listOf(app("Zulu"), app("Beta", seen = true), app("Alpha", seen = true)), false, true)
|
||||
assertEquals(listOf("Alpha", "Beta", "|", "Alpha", "Beta", "Zulu"), items.map { if (it is AppListItem.App) it.value.label else "|" })
|
||||
@Test fun `seen apps are titled and repeated before the full list when requested`() {
|
||||
val items = AppListOrdering.items(
|
||||
listOf(app("Zulu"), app("Beta", seen = true), app("Alpha", seen = true)),
|
||||
onlySeen = false,
|
||||
seenAppsFirst = true,
|
||||
mode = AppRuleMode.BLACKLIST,
|
||||
)
|
||||
assertEquals(listOf("[Seen apps]", "Alpha", "Beta", "|", "[All apps]", "Alpha", "Beta", "Zulu"), items.labels())
|
||||
}
|
||||
|
||||
@Test fun `selected unseen app remains after seen apps in only-seen mode`() {
|
||||
val items = AppListOrdering.items(listOf(app("Seen", seen = true), app("Chosen", selected = true), app("Hidden")), true, true)
|
||||
assertEquals(listOf("Seen", "|", "Chosen"), items.map { if (it is AppListItem.App) it.value.label else "|" })
|
||||
@Test fun `selected unseen apps are titled after seen apps in only-seen mode`() {
|
||||
val items = AppListOrdering.items(
|
||||
listOf(app("Seen", seen = true), app("Chosen", selected = true), app("Hidden")),
|
||||
onlySeen = true,
|
||||
seenAppsFirst = true,
|
||||
mode = AppRuleMode.WHITELIST,
|
||||
)
|
||||
assertEquals(listOf("[Seen apps]", "Seen", "|", "[Whitelisted apps]", "Chosen"), items.labels())
|
||||
}
|
||||
|
||||
@Test fun `only seen and checked without grouping is one alphabetical list`() {
|
||||
val items = AppListOrdering.items(
|
||||
listOf(app("Zulu", seen = true), app("Beta", selected = true), app("Hidden")),
|
||||
onlySeen = true,
|
||||
seenAppsFirst = false,
|
||||
mode = AppRuleMode.BLACKLIST,
|
||||
)
|
||||
assertEquals(listOf("Beta", "Zulu"), items.labels())
|
||||
}
|
||||
|
||||
@Test fun `only seen apps omit section titles without unseen checked apps`() {
|
||||
val items = AppListOrdering.items(
|
||||
listOf(app("Zulu", seen = true), app("Alpha", seen = true), app("Hidden")),
|
||||
onlySeen = true,
|
||||
seenAppsFirst = true,
|
||||
mode = AppRuleMode.BLACKLIST,
|
||||
)
|
||||
assertEquals(listOf("Alpha", "Zulu"), items.labels())
|
||||
}
|
||||
|
||||
@Test fun `all apps without grouping is one alphabetical list`() {
|
||||
val items = AppListOrdering.items(
|
||||
listOf(app("Zulu", seen = true), app("Alpha"), app("Beta", selected = true)),
|
||||
onlySeen = false,
|
||||
seenAppsFirst = false,
|
||||
mode = AppRuleMode.BLACKLIST,
|
||||
)
|
||||
assertEquals(listOf("Alpha", "Beta", "Zulu"), items.labels())
|
||||
}
|
||||
|
||||
private fun List<AppListItem>.labels() = map {
|
||||
when (it) {
|
||||
is AppListItem.App -> it.value.label
|
||||
is AppListItem.SectionTitle -> "[${it.value}]"
|
||||
AppListItem.Separator -> "|"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user