Streamline UI updates and cleanup maintenance debt

This commit is contained in:
ajp_anton
2026-07-27 23:21:59 +00:00
parent c28144b9ed
commit 2a330cc831
8 changed files with 33 additions and 50 deletions
@@ -62,6 +62,7 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
super.onDestroyView() super.onDestroyView()
} }
@Suppress("ClickableViewAccessibility") // RecyclerView has no bottom pull-to-refresh API.
private fun installBottomRefresh() { private fun installBottomRefresh() {
val list = binding!!.appList val list = binding!!.appList
var touchStartY = 0f var touchStartY = 0f
@@ -309,14 +310,11 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
fun updateEventOverride(packageName: String, hasEventOverride: Boolean) { fun updateEventOverride(packageName: String, hasEventOverride: Boolean) {
items = items.map { item -> val index = items.indexOfFirst { it is AppListItem.App && it.value.packageName == packageName }
if (item is AppListItem.App && item.value.packageName == packageName) { if (index < 0) return
AppListItem.App(item.value.copy(hasEventOverride = hasEventOverride)) val app = (items[index] as AppListItem.App).value
} else { items = items.toMutableList().also { it[index] = AppListItem.App(app.copy(hasEventOverride = hasEventOverride)) }
item notifyItemChanged(index)
}
}
notifyDataSetChanged()
} }
private class AppHolder( private class AppHolder(
@@ -4,7 +4,6 @@ import android.app.Dialog
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle import android.os.Bundle
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
@@ -51,13 +50,7 @@ class ImageViewerDialogFragment : DialogFragment() {
super.onStart() super.onStart()
dialog?.window?.apply { dialog?.window?.apply {
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
setBackgroundDrawable(ColorDrawable(Color.BLACK))
WindowCompat.setDecorFitsSystemWindows(this, false) WindowCompat.setDecorFitsSystemWindows(this, false)
statusBarColor = Color.TRANSPARENT
navigationBarColor = Color.TRANSPARENT
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
attributes = attributes.apply { attributes = attributes.apply {
layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
} }
@@ -17,6 +17,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.forEach
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
@@ -161,8 +162,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private fun syncNavigationSelection(navigationView: NavigationView, itemId: Int) { private fun syncNavigationSelection(navigationView: NavigationView, itemId: Int) {
val menu = navigationView.menu ?: return val menu = navigationView.menu ?: return
for (index in 0 until menu.size()) { menu.forEach { item ->
val item = menu.getItem(index)
item.isCheckable = true item.isCheckable = true
item.isChecked = item.itemId == itemId item.isChecked = item.itemId == itemId
} }
@@ -273,7 +273,11 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
} }
} }
private fun countVisibleMenuRows(menu: Menu): Int = (0 until menu.size()).count { menu.getItem(it).isVisible } private fun countVisibleMenuRows(menu: Menu): Int {
var visible = 0
menu.forEach { if (it.isVisible) visible++ }
return visible
}
private fun applyPageTitleVisibility() { private fun applyPageTitleVisibility() {
(supportFragmentManager.findFragmentById(R.id.content_frame) as? PageFragment) (supportFragmentManager.findFragmentById(R.id.content_frame) as? PageFragment)
@@ -3,6 +3,7 @@ package se.ajpanton.notificationlog
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.util.AttributeSet import android.util.AttributeSet
import androidx.core.graphics.withClip
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
/** /**
@@ -33,9 +34,8 @@ class SafeInsetDrawerLayout @JvmOverloads constructor(
super.dispatchDraw(canvas) super.dispatchDraw(canvas)
return return
} }
val saveCount = canvas.save() canvas.withClip(safeInsetLeft, 0, (width - safeInsetRight).coerceAtLeast(safeInsetLeft), height) {
canvas.clipRect(safeInsetLeft, 0, (width - safeInsetRight).coerceAtLeast(safeInsetLeft), height) super.dispatchDraw(this)
super.dispatchDraw(canvas) }
canvas.restoreToCount(saveCount)
} }
} }
@@ -456,7 +456,11 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt() private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt()
private fun sp(value: Float): Float = value * resources.displayMetrics.scaledDensity private fun sp(value: Float): Float = android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_SP,
value,
resources.displayMetrics,
)
private data class LogRow(val entry: NotificationLogEntry) private data class LogRow(val entry: NotificationLogEntry)
private data class MetadataValue( private data class MetadataValue(
@@ -9,6 +9,8 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Bundle import android.os.Bundle
import android.service.notification.StatusBarNotification import android.service.notification.StatusBarNotification
import androidx.core.graphics.createBitmap
import androidx.core.graphics.scale
data class NotificationSnapshot( data class NotificationSnapshot(
val key: String, val key: String,
@@ -94,7 +96,7 @@ object NotificationContents {
val scale = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(sourceWidth, sourceHeight)) val scale = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(sourceWidth, sourceHeight))
val width = (sourceWidth * scale).toInt() val width = (sourceWidth * scale).toInt()
val height = (sourceHeight * scale).toInt() val height = (sourceHeight * scale).toInt()
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { bitmap -> createBitmap(width, height, Bitmap.Config.ARGB_8888).also { bitmap ->
setBounds(0, 0, width, height) setBounds(0, 0, width, height)
draw(Canvas(bitmap)) draw(Canvas(bitmap))
} }
@@ -102,8 +104,8 @@ object NotificationContents {
} }
private fun Bitmap.toPng(): ByteArray? = runCatching { private fun Bitmap.toPng(): ByteArray? = runCatching {
val scale = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(width, height)) val scaleFactor = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(width, height))
val bitmap = if (scale < 1f) Bitmap.createScaledBitmap(this, (width * scale).toInt(), (height * scale).toInt(), true) else this val bitmap = if (scaleFactor < 1f) scale((width * scaleFactor).toInt(), (height * scaleFactor).toInt()) else this
java.io.ByteArrayOutputStream().use { output -> java.io.ByteArrayOutputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output) bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
output.toByteArray() output.toByteArray()
@@ -31,15 +31,6 @@ class EncryptedNotificationLogStore(context: Context) {
private val imageDirectory = File(appContext.filesDir, IMAGE_DIRECTORY_NAME) private val imageDirectory = File(appContext.filesDir, IMAGE_DIRECTORY_NAME)
private val cipher = AesGcmCipher(LogEncryptionKeyProvider().getOrCreate()) private val cipher = AesGcmCipher(LogEncryptionKeyProvider().getOrCreate())
init {
// This development build deliberately does not retain the former monolithic format.
File(appContext.filesDir, LEGACY_FILE_NAME).delete()
}
fun readAll(): List<NotificationLogEntry> = synchronized(lock) {
chunkFiles().flatMap(::readChunk)
}
fun readNewest(cursor: NewestLogCursor?, limit: Int): NotificationLogPage = synchronized(lock) { fun readNewest(cursor: NewestLogCursor?, limit: Int): NotificationLogPage = synchronized(lock) {
require(limit > 0) require(limit > 0)
val result = ArrayList<NotificationLogEntry>(limit) val result = ArrayList<NotificationLogEntry>(limit)
@@ -97,14 +88,6 @@ class EncryptedNotificationLogStore(context: Context) {
enforceLimits() enforceLimits()
} }
fun replaceAll(entries: List<NotificationLogEntry>) = synchronized(lock) {
clearChunksOnly()
entries.chunked(MAX_ENTRIES_PER_CHUNK).forEachIndexed { index, chunk ->
writeChunk(chunkFile(index.toLong()), chunk)
}
enforceLimits()
}
fun clear() = synchronized(lock) { fun clear() = synchronized(lock) {
clearChunksOnly() clearChunksOnly()
imageDirectory.listFiles()?.forEach(File::delete) imageDirectory.listFiles()?.forEach(File::delete)
@@ -258,7 +241,6 @@ class EncryptedNotificationLogStore(context: Context) {
private companion object { private companion object {
val lock = Any() val lock = Any()
const val DIRECTORY_NAME = "notification-log" const val DIRECTORY_NAME = "notification-log"
const val LEGACY_FILE_NAME = "notification-log.v1"
const val IMAGE_DIRECTORY_NAME = "notification-images" const val IMAGE_DIRECTORY_NAME = "notification-images"
const val CHUNK_PREFIX = "chunk-" const val CHUNK_PREFIX = "chunk-"
const val CHUNK_SUFFIX = ".bin" const val CHUNK_SUFFIX = ".bin"
@@ -267,7 +249,6 @@ class EncryptedNotificationLogStore(context: Context) {
const val MAX_IV_BYTES = 32 const val MAX_IV_BYTES = 32
const val MAX_CHUNK_CIPHER_TEXT_BYTES = 1024 * 1024 const val MAX_CHUNK_CIPHER_TEXT_BYTES = 1024 * 1024
const val MAX_CHUNK_PLAINTEXT_BYTES = 256 * 1024 const val MAX_CHUNK_PLAINTEXT_BYTES = 256 * 1024
const val MAX_ENTRIES_PER_CHUNK = 500
val IMAGE_ID_PATTERN = Regex("[0-9a-f-]{36}") val IMAGE_ID_PATTERN = Regex("[0-9a-f-]{36}")
} }
} }
+6 -5
View File
@@ -28,11 +28,10 @@
app:itemTextColor="@color/drawer_item_text_color" app:itemTextColor="@color/drawer_item_text_color"
app:menu="@menu/drawer_menu" /> app:menu="@menu/drawer_menu" />
<LinearLayout <RelativeLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1">
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
@@ -41,6 +40,7 @@
android:background="@color/primary" android:background="@color/primary"
android:elevation="0dp" android:elevation="0dp"
android:theme="@style/ThemeOverlay.NotificationLog.Toolbar" android:theme="@style/ThemeOverlay.NotificationLog.Toolbar"
android:layout_alignParentTop="true"
app:navigationIconTint="@color/on_primary" app:navigationIconTint="@color/on_primary"
app:titleTextColor="@color/on_primary" /> app:titleTextColor="@color/on_primary" />
@@ -48,8 +48,9 @@
android:id="@+id/content_frame" android:id="@+id/content_frame"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_below="@id/toolbar"
</LinearLayout> android:layout_alignParentBottom="true" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
<com.google.android.material.navigation.NavigationView <com.google.android.material.navigation.NavigationView