Add OneUI cards layout settings
This commit is contained in:
@@ -0,0 +1,130 @@
|
|||||||
|
package se.ajpanton.notificationsmaster
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.SeekBar
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
import se.ajpanton.notificationsmaster.databinding.FragmentCardsLayoutBinding
|
||||||
|
import se.ajpanton.notificationsmaster.visibility.CardsGridSettings
|
||||||
|
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicy
|
||||||
|
import se.ajpanton.notificationsmaster.visibility.VisibilityPolicyStore
|
||||||
|
|
||||||
|
class CardsLayoutFragment : Fragment(R.layout.fragment_cards_layout) {
|
||||||
|
private var binding: FragmentCardsLayoutBinding? = null
|
||||||
|
private lateinit var store: VisibilityPolicyStore
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
binding = FragmentCardsLayoutBinding.bind(view)
|
||||||
|
store = VisibilityPolicyStore(requireContext())
|
||||||
|
val policy = store.load()
|
||||||
|
bind(
|
||||||
|
controls(lockscreen = true),
|
||||||
|
policy.lockscreenCards,
|
||||||
|
VisibilityPolicy::lockscreenCards,
|
||||||
|
) { current, settings -> current.copy(lockscreenCards = settings) }
|
||||||
|
bind(
|
||||||
|
controls(lockscreen = false),
|
||||||
|
policy.aodCards,
|
||||||
|
VisibilityPolicy::aodCards,
|
||||||
|
) { current, settings -> current.copy(aodCards = settings) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
activity?.title = getString(R.string.page_cards_layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bind(
|
||||||
|
controls: Controls,
|
||||||
|
initial: CardsGridSettings,
|
||||||
|
currentSettings: (VisibilityPolicy) -> CardsGridSettings,
|
||||||
|
update: (VisibilityPolicy, CardsGridSettings) -> VisibilityPolicy,
|
||||||
|
) {
|
||||||
|
controls.rows.bindNumbers(0..5, initial.maxRows) { value ->
|
||||||
|
save(currentSettings, update) { it.copy(maxRows = value) }
|
||||||
|
}
|
||||||
|
controls.icons.bindNumbers(1..15, initial.maxIconsPerRow) { value ->
|
||||||
|
save(currentSettings, update) { it.copy(maxIconsPerRow = value) }
|
||||||
|
}
|
||||||
|
controls.even.isChecked = initial.evenDistribution
|
||||||
|
controls.even.setOnCheckedChangeListener { _, checked ->
|
||||||
|
save(currentSettings, update) { it.copy(evenDistribution = checked) }
|
||||||
|
}
|
||||||
|
controls.limit.isChecked = initial.limitToScreenWidth
|
||||||
|
controls.limit.setOnCheckedChangeListener { _, checked ->
|
||||||
|
save(currentSettings, update) { it.copy(limitToScreenWidth = checked) }
|
||||||
|
}
|
||||||
|
controls.height.max = 90
|
||||||
|
controls.height.progress = initial.iconHeightPercent - 10
|
||||||
|
controls.heightValue.text = "${initial.iconHeightPercent}%"
|
||||||
|
controls.height.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
controls.heightValue.text = "${progress + 10}%"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
|
save(currentSettings, update) { it.copy(iconHeightPercent = seekBar.progress + 10) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun save(
|
||||||
|
currentSettings: (VisibilityPolicy) -> CardsGridSettings,
|
||||||
|
update: (VisibilityPolicy, CardsGridSettings) -> VisibilityPolicy,
|
||||||
|
change: (CardsGridSettings) -> CardsGridSettings,
|
||||||
|
) {
|
||||||
|
store.update { current ->
|
||||||
|
update(current, change(currentSettings(current)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Spinner.bindNumbers(range: IntRange, selected: Int, changed: (Int) -> Unit) {
|
||||||
|
var current = selected
|
||||||
|
adapter = ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_spinner_dropdown_item,
|
||||||
|
range.map(Int::toString),
|
||||||
|
)
|
||||||
|
setSelection(selected - range.first, false)
|
||||||
|
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
val value = range.first + position
|
||||||
|
if (value != current) {
|
||||||
|
current = value
|
||||||
|
changed(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun controls(lockscreen: Boolean): Controls {
|
||||||
|
val b = binding!!
|
||||||
|
return if (lockscreen) b.lockscreenSettings.run {
|
||||||
|
Controls(maxRows, maxIcons, evenDistribution, limitWidth, iconHeight, iconHeightValue)
|
||||||
|
} else b.aodSettings.run {
|
||||||
|
Controls(maxRows, maxIcons, evenDistribution, limitWidth, iconHeight, iconHeightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Controls(
|
||||||
|
val rows: Spinner,
|
||||||
|
val icons: Spinner,
|
||||||
|
val even: SwitchMaterial,
|
||||||
|
val limit: SwitchMaterial,
|
||||||
|
val height: SeekBar,
|
||||||
|
val heightValue: TextView,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -168,6 +168,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||||||
page == Page.RULES -> RulesFragment()
|
page == Page.RULES -> RulesFragment()
|
||||||
page == Page.APPS -> AppsFragment()
|
page == Page.APPS -> AppsFragment()
|
||||||
page == Page.NOTIFICATION_VISIBILITY -> NotificationVisibilityFragment()
|
page == Page.NOTIFICATION_VISIBILITY -> NotificationVisibilityFragment()
|
||||||
|
page == Page.CARDS_LAYOUT -> CardsLayoutFragment()
|
||||||
page == Page.LOG_DISPLAY -> LogDisplayFragment()
|
page == Page.LOG_DISPLAY -> LogDisplayFragment()
|
||||||
page == Page.FILTER_LOGGING -> FilterLoggingFragment()
|
page == Page.FILTER_LOGGING -> FilterLoggingFragment()
|
||||||
page == Page.FILTER_APPS -> FilterAppsFragment.newInstance()
|
page == Page.FILTER_APPS -> FilterAppsFragment.newInstance()
|
||||||
@@ -373,6 +374,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||||||
RULES(R.id.nav_rules, R.string.page_rules),
|
RULES(R.id.nav_rules, R.string.page_rules),
|
||||||
APPS(R.id.nav_apps, R.string.page_apps),
|
APPS(R.id.nav_apps, R.string.page_apps),
|
||||||
NOTIFICATION_VISIBILITY(R.id.nav_notification_visibility, R.string.page_notification_visibility),
|
NOTIFICATION_VISIBILITY(R.id.nav_notification_visibility, R.string.page_notification_visibility),
|
||||||
|
CARDS_LAYOUT(R.id.nav_cards_layout, R.string.page_cards_layout),
|
||||||
LOG_DISPLAY(R.id.nav_log_display, R.string.page_log_display),
|
LOG_DISPLAY(R.id.nav_log_display, R.string.page_log_display),
|
||||||
FILTER_LOGGING(R.id.nav_filter_logging, R.string.page_filter_logging),
|
FILTER_LOGGING(R.id.nav_filter_logging, R.string.page_filter_logging),
|
||||||
FILTER_APPS(R.id.nav_filter_apps, R.string.page_filter_apps),
|
FILTER_APPS(R.id.nav_filter_apps, R.string.page_filter_apps),
|
||||||
|
|||||||
+2
@@ -33,6 +33,8 @@ internal object ProcessVisibilityPolicyCache {
|
|||||||
fun hasExceptions(packageName: String) = policy.hasExceptions(packageName)
|
fun hasExceptions(packageName: String) = policy.hasExceptions(packageName)
|
||||||
|
|
||||||
val unlockedIconLimit get() = policy.unlockedIconLimit
|
val unlockedIconLimit get() = policy.unlockedIconLimit
|
||||||
|
val lockscreenCards get() = policy.lockscreenCards
|
||||||
|
val aodCards get() = policy.aodCards
|
||||||
|
|
||||||
private fun tryInstall() {
|
private fun tryInstall() {
|
||||||
if (installed) return
|
if (installed) return
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package se.ajpanton.notificationsmaster.visibility
|
||||||
|
|
||||||
|
data class CardsGridRows(
|
||||||
|
val rowSizes: List<Int>,
|
||||||
|
val showsOverflow: Boolean,
|
||||||
|
) {
|
||||||
|
val visibleSlots get() = rowSizes.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
object CardsGridLayout {
|
||||||
|
fun arrange(itemCount: Int, settings: CardsGridSettings, widthCapacity: Int? = null): CardsGridRows {
|
||||||
|
require(itemCount >= 0)
|
||||||
|
val perRow = if (settings.limitToScreenWidth && widthCapacity != null) {
|
||||||
|
minOf(settings.maxIconsPerRow, widthCapacity.coerceAtLeast(0))
|
||||||
|
} else {
|
||||||
|
settings.maxIconsPerRow
|
||||||
|
}
|
||||||
|
val capacity = perRow * settings.maxRows
|
||||||
|
if (itemCount == 0 || capacity == 0) return CardsGridRows(emptyList(), itemCount > 0)
|
||||||
|
|
||||||
|
val overflow = itemCount > capacity
|
||||||
|
val slots = minOf(itemCount, capacity)
|
||||||
|
val rows = minOf(settings.maxRows, (slots + perRow - 1) / perRow)
|
||||||
|
if (!settings.evenDistribution || rows == 1) {
|
||||||
|
var remaining = slots
|
||||||
|
return CardsGridRows(List(rows) {
|
||||||
|
minOf(perRow, remaining).also { remaining -= it }
|
||||||
|
}, overflow)
|
||||||
|
}
|
||||||
|
val base = slots / rows
|
||||||
|
val remainder = slots % rows
|
||||||
|
return CardsGridRows(List(rows) { base + if (it < remainder) 1 else 0 }, overflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,25 @@ import com.google.re2j.Pattern
|
|||||||
|
|
||||||
enum class NotificationSurface { AOD, LOCKSCREEN_COLLAPSED, UNLOCKED_STATUSBAR }
|
enum class NotificationSurface { AOD, LOCKSCREEN_COLLAPSED, UNLOCKED_STATUSBAR }
|
||||||
|
|
||||||
|
data class CardsGridSettings(
|
||||||
|
val maxRows: Int = 1,
|
||||||
|
val maxIconsPerRow: Int = 5,
|
||||||
|
val evenDistribution: Boolean = true,
|
||||||
|
val limitToScreenWidth: Boolean = true,
|
||||||
|
val iconHeightPercent: Int,
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
require(maxRows in 0..5)
|
||||||
|
require(maxIconsPerRow in 1..15)
|
||||||
|
require(iconHeightPercent in 10..100)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val LOCKSCREEN_DEFAULT = CardsGridSettings(iconHeightPercent = 59)
|
||||||
|
val AOD_DEFAULT = CardsGridSettings(iconHeightPercent = 65)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data class VisibilityExceptionRule(
|
data class VisibilityExceptionRule(
|
||||||
val pattern: String,
|
val pattern: String,
|
||||||
val blockedSurfaces: Set<NotificationSurface>,
|
val blockedSurfaces: Set<NotificationSurface>,
|
||||||
@@ -26,6 +45,8 @@ data class VisibilityPolicy(
|
|||||||
val enabled: Boolean = true,
|
val enabled: Boolean = true,
|
||||||
val apps: List<AppVisibilityPolicy> = emptyList(),
|
val apps: List<AppVisibilityPolicy> = emptyList(),
|
||||||
val unlockedIconLimit: Int? = null,
|
val unlockedIconLimit: Int? = null,
|
||||||
|
val lockscreenCards: CardsGridSettings = CardsGridSettings.LOCKSCREEN_DEFAULT,
|
||||||
|
val aodCards: CardsGridSettings = CardsGridSettings.AOD_DEFAULT,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
require(generation >= 0)
|
require(generation >= 0)
|
||||||
@@ -51,6 +72,8 @@ data class VisibilityNotification(
|
|||||||
class CompiledVisibilityPolicy(policy: VisibilityPolicy) {
|
class CompiledVisibilityPolicy(policy: VisibilityPolicy) {
|
||||||
val generation = policy.generation
|
val generation = policy.generation
|
||||||
val unlockedIconLimit = policy.unlockedIconLimit
|
val unlockedIconLimit = policy.unlockedIconLimit
|
||||||
|
val lockscreenCards = policy.lockscreenCards
|
||||||
|
val aodCards = policy.aodCards
|
||||||
private val enabled = policy.enabled
|
private val enabled = policy.enabled
|
||||||
private val apps = policy.apps.associate { app ->
|
private val apps = policy.apps.associate { app ->
|
||||||
app.packageName to CompiledAppPolicy(
|
app.packageName to CompiledAppPolicy(
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ internal object VisibilityPolicyJson {
|
|||||||
.put("generation", policy.generation)
|
.put("generation", policy.generation)
|
||||||
.put("enabled", policy.enabled)
|
.put("enabled", policy.enabled)
|
||||||
.put("unlockedIconLimit", policy.unlockedIconLimit ?: JSONObject.NULL)
|
.put("unlockedIconLimit", policy.unlockedIconLimit ?: JSONObject.NULL)
|
||||||
|
.put("lockscreenCards", policy.lockscreenCards.toJson())
|
||||||
|
.put("aodCards", policy.aodCards.toJson())
|
||||||
.put("apps", JSONArray().apply { policy.apps.forEach { put(it.toJson()) } })
|
.put("apps", JSONArray().apply { policy.apps.forEach { put(it.toJson()) } })
|
||||||
.toString().encodeToByteArray()
|
.toString().encodeToByteArray()
|
||||||
|
|
||||||
@@ -22,9 +24,28 @@ internal object VisibilityPolicyJson {
|
|||||||
enabled = root.getBoolean("enabled"),
|
enabled = root.getBoolean("enabled"),
|
||||||
apps = root.getJSONArray("apps").map { (it as JSONObject).toAppPolicy() },
|
apps = root.getJSONArray("apps").map { (it as JSONObject).toAppPolicy() },
|
||||||
unlockedIconLimit = if (root.isNull("unlockedIconLimit")) null else root.getInt("unlockedIconLimit"),
|
unlockedIconLimit = if (root.isNull("unlockedIconLimit")) null else root.getInt("unlockedIconLimit"),
|
||||||
|
lockscreenCards = root.optJSONObject("lockscreenCards")?.toCardsGridSettings()
|
||||||
|
?: CardsGridSettings.LOCKSCREEN_DEFAULT,
|
||||||
|
aodCards = root.optJSONObject("aodCards")?.toCardsGridSettings()
|
||||||
|
?: CardsGridSettings.AOD_DEFAULT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun CardsGridSettings.toJson() = JSONObject()
|
||||||
|
.put("maxRows", maxRows)
|
||||||
|
.put("maxIconsPerRow", maxIconsPerRow)
|
||||||
|
.put("evenDistribution", evenDistribution)
|
||||||
|
.put("limitToScreenWidth", limitToScreenWidth)
|
||||||
|
.put("iconHeightPercent", iconHeightPercent)
|
||||||
|
|
||||||
|
private fun JSONObject.toCardsGridSettings() = CardsGridSettings(
|
||||||
|
maxRows = getInt("maxRows"),
|
||||||
|
maxIconsPerRow = getInt("maxIconsPerRow"),
|
||||||
|
evenDistribution = getBoolean("evenDistribution"),
|
||||||
|
limitToScreenWidth = getBoolean("limitToScreenWidth"),
|
||||||
|
iconHeightPercent = getInt("iconHeightPercent"),
|
||||||
|
)
|
||||||
|
|
||||||
private fun AppVisibilityPolicy.toJson() = JSONObject()
|
private fun AppVisibilityPolicy.toJson() = JSONObject()
|
||||||
.put("packageName", packageName)
|
.put("packageName", packageName)
|
||||||
.put("blockedSurfaces", JSONArray(blockedSurfaces.map { it.name }))
|
.put("blockedSurfaces", JSONArray(blockedSurfaces.map { it.name }))
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/page_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="These settings apply only to OneUI Cards mode. Icons mode remains rendered by Android or StatusBarTweak."
|
||||||
|
android:textAppearance="?attr/textAppearanceBody2" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="Lockscreen"
|
||||||
|
android:textAppearance="?attr/textAppearanceHeadline6" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/lockscreen_settings"
|
||||||
|
layout="@layout/include_cards_grid_settings_lockscreen" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
android:text="Always-on display"
|
||||||
|
android:textAppearance="?attr/textAppearanceHeadline6" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/aod_settings"
|
||||||
|
layout="@layout/include_cards_grid_settings_aod" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="Max rows" />
|
||||||
|
<Spinner android:id="@+id/max_rows" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Max icons per row" />
|
||||||
|
<Spinner android:id="@+id/max_icons" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/even_distribution" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Even distribution" />
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/limit_width" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Limit to screen width" />
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="center_vertical" android:orientation="horizontal">
|
||||||
|
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Icon height (% of status bar)" />
|
||||||
|
<TextView android:id="@+id/icon_height_value" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
<SeekBar android:id="@+id/icon_height" android:layout_width="match_parent" android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="12dp" android:text="Max rows" />
|
||||||
|
<Spinner android:id="@+id/max_rows" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Max icons per row" />
|
||||||
|
<Spinner android:id="@+id/max_icons" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/even_distribution" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="Even distribution" />
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/limit_width" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Limit to screen width" />
|
||||||
|
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:gravity="center_vertical" android:orientation="horizontal">
|
||||||
|
<TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Icon height (% of status bar)" />
|
||||||
|
<TextView android:id="@+id/icon_height_value" android:layout_width="wrap_content" android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
<SeekBar android:id="@+id/icon_height" android:layout_width="match_parent" android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/nav_notification_visibility"
|
android:id="@+id/nav_notification_visibility"
|
||||||
android:title="@string/navigation_indented_notification_visibility" />
|
android:title="@string/navigation_indented_notification_visibility" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/nav_cards_layout"
|
||||||
|
android:title="@string/navigation_indented_cards_layout" />
|
||||||
</group>
|
</group>
|
||||||
<group android:checkableBehavior="single">
|
<group android:checkableBehavior="single">
|
||||||
<item
|
<item
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<string name="page_rules">Rules</string>
|
<string name="page_rules">Rules</string>
|
||||||
<string name="page_apps">Apps</string>
|
<string name="page_apps">Apps</string>
|
||||||
<string name="page_notification_visibility">Notification visibility</string>
|
<string name="page_notification_visibility">Notification visibility</string>
|
||||||
|
<string name="page_cards_layout">Cards layout</string>
|
||||||
<string name="page_filter_apps">Filter apps</string>
|
<string name="page_filter_apps">Filter apps</string>
|
||||||
<string name="page_log_display">Log display</string>
|
<string name="page_log_display">Log display</string>
|
||||||
<string name="page_filter_logging">Filter logging</string>
|
<string name="page_filter_logging">Filter logging</string>
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
<string name="navigation_indented_rules">    Rules</string>
|
<string name="navigation_indented_rules">    Rules</string>
|
||||||
<string name="navigation_indented_apps">    Apps</string>
|
<string name="navigation_indented_apps">    Apps</string>
|
||||||
<string name="navigation_indented_notification_visibility">    Visibility</string>
|
<string name="navigation_indented_notification_visibility">    Visibility</string>
|
||||||
|
<string name="navigation_indented_cards_layout">    Cards layout</string>
|
||||||
<string name="navigation_indented_filter_logging">    Filter logging</string>
|
<string name="navigation_indented_filter_logging">    Filter logging</string>
|
||||||
<string name="navigation_indented_filter_apps">    Filter apps</string>
|
<string name="navigation_indented_filter_apps">    Filter apps</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package se.ajpanton.notificationsmaster.visibility
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CardsGridLayoutTest {
|
||||||
|
@Test fun `ordinary rows fill in order`() {
|
||||||
|
val settings = CardsGridSettings(3, 4, false, false, 50)
|
||||||
|
assertEquals(listOf(4, 4, 2), CardsGridLayout.arrange(10, settings).rowSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `even distribution balances occupied rows`() {
|
||||||
|
val settings = CardsGridSettings(3, 4, true, false, 50)
|
||||||
|
assertEquals(listOf(3, 2), CardsGridLayout.arrange(5, settings).rowSizes)
|
||||||
|
assertEquals(listOf(4, 3, 3), CardsGridLayout.arrange(10, settings).rowSizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `overflow uses the configured capacity`() {
|
||||||
|
val result = CardsGridLayout.arrange(13, CardsGridSettings(3, 4, true, false, 50))
|
||||||
|
assertEquals(listOf(4, 4, 4), result.rowSizes)
|
||||||
|
assertEquals(12, result.visibleSlots)
|
||||||
|
assertTrue(result.showsOverflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `screen width can reduce the row capacity`() {
|
||||||
|
val settings = CardsGridSettings(2, 8, false, true, 50)
|
||||||
|
val result = CardsGridLayout.arrange(7, settings, widthCapacity = 3)
|
||||||
|
assertEquals(listOf(3, 3), result.rowSizes)
|
||||||
|
assertTrue(result.showsOverflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun `disabled rows have no slots`() {
|
||||||
|
val settings = CardsGridSettings(0, 5, true, true, 50)
|
||||||
|
val result = CardsGridLayout.arrange(2, settings)
|
||||||
|
assertEquals(emptyList<Int>(), result.rowSizes)
|
||||||
|
assertTrue(result.showsOverflow)
|
||||||
|
assertFalse(CardsGridLayout.arrange(0, settings).showsOverflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -9,6 +9,8 @@ class VisibilityPolicyJsonTest {
|
|||||||
generation = 17,
|
generation = 17,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
unlockedIconLimit = 7,
|
unlockedIconLimit = 7,
|
||||||
|
lockscreenCards = CardsGridSettings(2, 7, false, false, 72),
|
||||||
|
aodCards = CardsGridSettings(3, 6, true, true, 48),
|
||||||
apps = listOf(AppVisibilityPolicy(
|
apps = listOf(AppVisibilityPolicy(
|
||||||
packageName = "example.app",
|
packageName = "example.app",
|
||||||
blockedSurfaces = setOf(NotificationSurface.AOD),
|
blockedSurfaces = setOf(NotificationSurface.AOD),
|
||||||
@@ -27,6 +29,16 @@ class VisibilityPolicyJsonTest {
|
|||||||
assertEquals(policy, VisibilityPolicyJson.decode(VisibilityPolicyJson.encode(policy)))
|
assertEquals(policy, VisibilityPolicyJson.decode(VisibilityPolicyJson.encode(policy)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun `missing cards settings use defaults`() {
|
||||||
|
val decoded = VisibilityPolicyJson.decode(
|
||||||
|
"""{"version":1,"generation":0,"enabled":true,"unlockedIconLimit":null,"apps":[]}"""
|
||||||
|
.encodeToByteArray(),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(CardsGridSettings.LOCKSCREEN_DEFAULT, decoded.lockscreenCards)
|
||||||
|
assertEquals(CardsGridSettings.AOD_DEFAULT, decoded.aodCards)
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException::class)
|
@Test(expected = IllegalArgumentException::class)
|
||||||
fun `unknown schemas are rejected`() {
|
fun `unknown schemas are rejected`() {
|
||||||
VisibilityPolicyJson.decode("{\"version\":99}".encodeToByteArray())
|
VisibilityPolicyJson.decode("{\"version\":99}".encodeToByteArray())
|
||||||
|
|||||||
Reference in New Issue
Block a user