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.APPS -> AppsFragment()
|
||||
page == Page.NOTIFICATION_VISIBILITY -> NotificationVisibilityFragment()
|
||||
page == Page.CARDS_LAYOUT -> CardsLayoutFragment()
|
||||
page == Page.LOG_DISPLAY -> LogDisplayFragment()
|
||||
page == Page.FILTER_LOGGING -> FilterLoggingFragment()
|
||||
page == Page.FILTER_APPS -> FilterAppsFragment.newInstance()
|
||||
@@ -373,6 +374,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
RULES(R.id.nav_rules, R.string.page_rules),
|
||||
APPS(R.id.nav_apps, R.string.page_apps),
|
||||
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),
|
||||
FILTER_LOGGING(R.id.nav_filter_logging, R.string.page_filter_logging),
|
||||
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)
|
||||
|
||||
val unlockedIconLimit get() = policy.unlockedIconLimit
|
||||
val lockscreenCards get() = policy.lockscreenCards
|
||||
val aodCards get() = policy.aodCards
|
||||
|
||||
private fun tryInstall() {
|
||||
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 }
|
||||
|
||||
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(
|
||||
val pattern: String,
|
||||
val blockedSurfaces: Set<NotificationSurface>,
|
||||
@@ -26,6 +45,8 @@ data class VisibilityPolicy(
|
||||
val enabled: Boolean = true,
|
||||
val apps: List<AppVisibilityPolicy> = emptyList(),
|
||||
val unlockedIconLimit: Int? = null,
|
||||
val lockscreenCards: CardsGridSettings = CardsGridSettings.LOCKSCREEN_DEFAULT,
|
||||
val aodCards: CardsGridSettings = CardsGridSettings.AOD_DEFAULT,
|
||||
) {
|
||||
init {
|
||||
require(generation >= 0)
|
||||
@@ -51,6 +72,8 @@ data class VisibilityNotification(
|
||||
class CompiledVisibilityPolicy(policy: VisibilityPolicy) {
|
||||
val generation = policy.generation
|
||||
val unlockedIconLimit = policy.unlockedIconLimit
|
||||
val lockscreenCards = policy.lockscreenCards
|
||||
val aodCards = policy.aodCards
|
||||
private val enabled = policy.enabled
|
||||
private val apps = policy.apps.associate { app ->
|
||||
app.packageName to CompiledAppPolicy(
|
||||
|
||||
@@ -11,6 +11,8 @@ internal object VisibilityPolicyJson {
|
||||
.put("generation", policy.generation)
|
||||
.put("enabled", policy.enabled)
|
||||
.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()) } })
|
||||
.toString().encodeToByteArray()
|
||||
|
||||
@@ -22,9 +24,28 @@ internal object VisibilityPolicyJson {
|
||||
enabled = root.getBoolean("enabled"),
|
||||
apps = root.getJSONArray("apps").map { (it as JSONObject).toAppPolicy() },
|
||||
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()
|
||||
.put("packageName", packageName)
|
||||
.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
|
||||
android:id="@+id/nav_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 android:checkableBehavior="single">
|
||||
<item
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<string name="page_rules">Rules</string>
|
||||
<string name="page_apps">Apps</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_log_display">Log display</string>
|
||||
<string name="page_filter_logging">Filter logging</string>
|
||||
@@ -23,6 +24,7 @@
|
||||
<string name="navigation_indented_rules">    Rules</string>
|
||||
<string name="navigation_indented_apps">    Apps</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_apps">    Filter apps</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user