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()
|
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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user