Streamline UI updates and cleanup maintenance debt
This commit is contained in:
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user