diff --git a/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt index 6057300..d3902a8 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/EventSettingsFragment.kt @@ -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( diff --git a/app/src/main/java/se/ajpanton/notificationlog/ImageViewerDialogFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/ImageViewerDialogFragment.kt index d21458e..5b31b9a 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/ImageViewerDialogFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/ImageViewerDialogFragment.kt @@ -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 } diff --git a/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt b/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt index 90ea5fe..75717f4 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt @@ -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) diff --git a/app/src/main/java/se/ajpanton/notificationlog/SafeInsetDrawerLayout.kt b/app/src/main/java/se/ajpanton/notificationlog/SafeInsetDrawerLayout.kt index 140910d..08fdc35 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/SafeInsetDrawerLayout.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/SafeInsetDrawerLayout.kt @@ -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) + } } } diff --git a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt index b401a31..28e0f9a 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/ViewLogsFragment.kt @@ -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( diff --git a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt index 51a17c0..7b5e0f0 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/capture/NotificationSnapshot.kt @@ -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() diff --git a/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedNotificationLogStore.kt b/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedNotificationLogStore.kt index ac5f7ff..3a8e1a4 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedNotificationLogStore.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/data/EncryptedNotificationLogStore.kt @@ -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 = synchronized(lock) { - chunkFiles().flatMap(::readChunk) - } - fun readNewest(cursor: NewestLogCursor?, limit: Int): NotificationLogPage = synchronized(lock) { require(limit > 0) val result = ArrayList(limit) @@ -97,14 +88,6 @@ class EncryptedNotificationLogStore(context: Context) { enforceLimits() } - fun replaceAll(entries: List) = 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}") } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0432a0f..c83a32e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -28,11 +28,10 @@ app:itemTextColor="@color/drawer_item_text_color" app:menu="@menu/drawer_menu" /> - + android:layout_weight="1"> @@ -48,8 +48,9 @@ android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_weight="1" /> - + android:layout_below="@id/toolbar" + android:layout_alignParentBottom="true" /> +