Add configurable chunked storage limits

This commit is contained in:
ajp_anton
2026-07-27 14:06:14 +00:00
parent dca86ae393
commit 8f21874492
5 changed files with 306 additions and 95 deletions
@@ -25,23 +25,32 @@ import androidx.fragment.app.Fragment
class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
private var binding: FragmentViewLogsBinding? = null
private val expandedIds = mutableSetOf<String>()
private val rows = mutableListOf<LogRow>()
private var loadedEntries = 0
private var loading = false
private var noMoreRows = false
private var loadGeneration = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentViewLogsBinding.bind(view)
binding!!.logsRefresh.setOnRefreshListener(::loadLogs)
binding!!.logsRefresh.setOnRefreshListener { loadLogs(reset = true) }
binding!!.logScroll.setOnScrollChangeListener { _, _, scrollY, _, _ ->
binding?.scrollToTop?.visibility = if (scrollY > dp(SCROLL_TO_TOP_THRESHOLD_DP)) View.VISIBLE else View.GONE
val scroll = binding?.logScroll ?: return@setOnScrollChangeListener
if (!noMoreRows && !loading && scrollY >= scroll.getChildAt(0).height - scroll.height - dp(LOAD_MORE_THRESHOLD_DP)) {
loadLogs(reset = false)
}
}
binding!!.scrollToTop.setOnClickListener {
binding?.logScroll?.smoothScrollTo(0, 0)
}
loadLogs()
loadLogs(reset = true)
}
override fun onResume() {
super.onResume()
loadLogs()
loadLogs(reset = true)
}
override fun onDestroyView() {
@@ -49,26 +58,40 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
super.onDestroyView()
}
private fun loadLogs() {
private fun loadLogs(reset: Boolean) {
if (loading) return
if (!reset && noMoreRows) return
val context = requireContext().applicationContext
if (reset) {
rows.clear()
loadedEntries = 0
noMoreRows = false
loadGeneration++
}
val generation = loadGeneration
loading = true
binding?.logsRefresh?.isRefreshing = true
Thread {
val imageStore = EncryptedImageStore(context)
val rows = EncryptedNotificationLogStore(context).readAll()
.sortedByDescending { it.recordedAtEpochMillis }
.map { entry -> LogRow(entry, entry.imageId?.let(imageStore::read)) }
val entries = EncryptedNotificationLogStore(context).readNewest(loadedEntries, PAGE_SIZE)
activity?.runOnUiThread {
binding?.let { render(it, rows) }
if (generation == loadGeneration) {
loadedEntries += entries.size
noMoreRows = entries.size < PAGE_SIZE
val addedRows = entries.map(::LogRow)
rows += addedRows
binding?.let { render(it, addedRows, reset) }
}
loading = false
binding?.logsRefresh?.isRefreshing = false
}
}.start()
}
private fun render(view: FragmentViewLogsBinding, rows: List<LogRow>) {
view.logRows.removeAllViews()
private fun render(view: FragmentViewLogsBinding, addedRows: List<LogRow>, reset: Boolean) {
if (reset) view.logRows.removeAllViews()
view.emptyView.visibility = if (rows.isEmpty()) View.VISIBLE else View.GONE
val settings = LogViewSettingsStore(requireContext()).load()
rows.filter { eventFor(it.entry.action) in settings.visibleEvents }
addedRows.filter { eventFor(it.entry.action) in settings.visibleEvents }
.forEach { row -> view.logRows.addView(logRowView(row, settings)) }
}
@@ -159,18 +182,23 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
setPadding(0, dp(4), 0, 0)
})
}
if (expanded) {
row.imageBytes?.let { bytes ->
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)?.let { bitmap ->
addView(ImageView(context).apply {
setImageBitmap(bitmap)
adjustViewBounds = true
maxHeight = dp(240)
contentDescription = "Notification image"
setPadding(0, dp(4), 0, 0)
})
}
if (expanded && row.entry.imageId != null) {
val image = ImageView(context).apply {
adjustViewBounds = true
maxHeight = dp(240)
contentDescription = "Notification image"
setPadding(0, dp(4), 0, 0)
}
addView(image)
val imageId = row.entry.imageId
val appContext = requireContext().applicationContext
Thread {
EncryptedImageStore(appContext).read(imageId)?.let { bytes ->
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}?.let { bitmap ->
activity?.runOnUiThread { if (image.isAttachedToWindow) image.setImageBitmap(bitmap) }
}
}.start()
}
setOnClickListener { onToggleExpanded() }
setOnLongClickListener { showDeleteDialog(row.entry.id) }
@@ -225,7 +253,7 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
.setPositiveButton("Delete") { _, _ ->
Thread {
EncryptedNotificationLogStore(requireContext().applicationContext).delete(entryId)
activity?.runOnUiThread(::loadLogs)
activity?.runOnUiThread { loadLogs(reset = true) }
}.start()
}
.show()
@@ -244,12 +272,14 @@ class ViewLogsFragment : Fragment(R.layout.fragment_view_logs) {
private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt()
private data class LogRow(val entry: NotificationLogEntry, val imageBytes: ByteArray?)
private data class LogRow(val entry: NotificationLogEntry)
private data class MetadataValue(val text: String, val textSize: Float, val indented: Boolean = false, val bold: Boolean = false)
private companion object {
const val LEFT_PILL_WEIGHT = 0.42f
const val RIGHT_PILL_WEIGHT = 0.58f
const val SCROLL_TO_TOP_THRESHOLD_DP = 120
const val LOAD_MORE_THRESHOLD_DP = 480
const val PAGE_SIZE = 80
}
}