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()
}
@Suppress("ClickableViewAccessibility") // RecyclerView has no bottom pull-to-refresh API.
private fun installBottomRefresh() {
val list = binding!!.appList
var touchStartY = 0f
@@ -309,14 +310,11 @@ class FilterAppsFragment : Fragment(R.layout.fragment_event_settings) {
override fun getItemCount(): Int = items.size
fun updateEventOverride(packageName: String, hasEventOverride: Boolean) {
items = items.map { item ->
if (item is AppListItem.App && item.value.packageName == packageName) {
AppListItem.App(item.value.copy(hasEventOverride = hasEventOverride))
} else {
item
}
}
notifyDataSetChanged()
val index = items.indexOfFirst { it is AppListItem.App && it.value.packageName == packageName }
if (index < 0) return
val app = (items[index] as AppListItem.App).value
items = items.toMutableList().also { it[index] = AppListItem.App(app.copy(hasEventOverride = hasEventOverride)) }
notifyItemChanged(index)
}
private class AppHolder(
@@ -4,7 +4,6 @@ import android.app.Dialog
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
import android.view.View
@@ -51,13 +50,7 @@ class ImageViewerDialogFragment : DialogFragment() {
super.onStart()
dialog?.window?.apply {
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
setBackgroundDrawable(ColorDrawable(Color.BLACK))
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 {
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.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.forEach
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.commit
import com.google.android.material.navigation.NavigationView
@@ -161,8 +162,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
private fun syncNavigationSelection(navigationView: NavigationView, itemId: Int) {
val menu = navigationView.menu ?: return
for (index in 0 until menu.size()) {
val item = menu.getItem(index)
menu.forEach { item ->
item.isCheckable = true
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() {
(supportFragmentManager.findFragmentById(R.id.content_frame) as? PageFragment)
@@ -3,6 +3,7 @@ package se.ajpanton.notificationlog
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import androidx.core.graphics.withClip
import androidx.drawerlayout.widget.DrawerLayout
/**
@@ -33,9 +34,8 @@ class SafeInsetDrawerLayout @JvmOverloads constructor(
super.dispatchDraw(canvas)
return
}
val saveCount = canvas.save()
canvas.clipRect(safeInsetLeft, 0, (width - safeInsetRight).coerceAtLeast(safeInsetLeft), height)
super.dispatchDraw(canvas)
canvas.restoreToCount(saveCount)
canvas.withClip(safeInsetLeft, 0, (width - safeInsetRight).coerceAtLeast(safeInsetLeft), height) {
super.dispatchDraw(this)
}
}
}
@@ -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 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 MetadataValue(
@@ -9,6 +9,8 @@ import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
import android.os.Bundle
import android.service.notification.StatusBarNotification
import androidx.core.graphics.createBitmap
import androidx.core.graphics.scale
data class NotificationSnapshot(
val key: String,
@@ -94,7 +96,7 @@ object NotificationContents {
val scale = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(sourceWidth, sourceHeight))
val width = (sourceWidth * 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)
draw(Canvas(bitmap))
}
@@ -102,8 +104,8 @@ object NotificationContents {
}
private fun Bitmap.toPng(): ByteArray? = runCatching {
val scale = 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 scaleFactor = minOf(1f, MAX_IMAGE_DIMENSION.toFloat() / maxOf(width, height))
val bitmap = if (scaleFactor < 1f) scale((width * scaleFactor).toInt(), (height * scaleFactor).toInt()) else this
java.io.ByteArrayOutputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)
output.toByteArray()
@@ -31,15 +31,6 @@ class EncryptedNotificationLogStore(context: Context) {
private val imageDirectory = File(appContext.filesDir, IMAGE_DIRECTORY_NAME)
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) {
require(limit > 0)
val result = ArrayList<NotificationLogEntry>(limit)
@@ -97,14 +88,6 @@ class EncryptedNotificationLogStore(context: Context) {
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) {
clearChunksOnly()
imageDirectory.listFiles()?.forEach(File::delete)
@@ -258,7 +241,6 @@ class EncryptedNotificationLogStore(context: Context) {
private companion object {
val lock = Any()
const val DIRECTORY_NAME = "notification-log"
const val LEGACY_FILE_NAME = "notification-log.v1"
const val IMAGE_DIRECTORY_NAME = "notification-images"
const val CHUNK_PREFIX = "chunk-"
const val CHUNK_SUFFIX = ".bin"
@@ -267,7 +249,6 @@ class EncryptedNotificationLogStore(context: Context) {
const val MAX_IV_BYTES = 32
const val MAX_CHUNK_CIPHER_TEXT_BYTES = 1024 * 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}")
}
}