vc=60: storage usage readouts in cache settings
Each store + Coil image cache shows its actual on-disk byte count
next to its cap chip-row. Closes the loop on vc=59's cache controls
— users can see what each cap is doing instead of guessing.
- StorageUsage.sharedPrefBytes — reads dataDir/shared_prefs/X.xml
length directly. Cheap, advisory; not authoritative on
Android's internal SP layout but close enough to be useful.
- StorageUsage.coilDiskCacheBytes — pulls
SingletonImageLoader.get().diskCache?.size, returns 0 if Coil
hasn't lazily initialized yet.
- StorageUsage.format — KB/MB/GB renderer with 0 -> '—'.
Usage snapshot is captured once per Settings entry via remember{}
so File.length() doesn't refire on every recomposition.
This commit is contained in:
parent
aead95f1bc
commit
26c9483b94
3 changed files with 119 additions and 6 deletions
|
|
@ -55,6 +55,6 @@ const val NEWPIPE_APPLICATION_ID_NEW = "net.newpipe.app"
|
|||
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
|
||||
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
|
||||
// NewPipeExtractor in the runtime path.
|
||||
const val STRAW_VERSION_CODE = 59
|
||||
const val STRAW_VERSION_NAME = "0.1.0-BS"
|
||||
const val STRAW_VERSION_CODE = 60
|
||||
const val STRAW_VERSION_NAME = "0.1.0-BT"
|
||||
const val STRAW_APPLICATION_ID = "com.sulkta.straw"
|
||||
|
|
|
|||
|
|
@ -601,10 +601,23 @@ fun SettingsScreen() {
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// Sample on-disk usage once per Settings entry — File.length() is
|
||||
// cheap but we don't need it to recompose on every state change.
|
||||
// remember keeps the same snapshot for the entire session.
|
||||
val usage = remember {
|
||||
object {
|
||||
val history = com.sulkta.straw.util.StorageUsage.sharedPrefBytes(context, "straw_history")
|
||||
val resume = com.sulkta.straw.util.StorageUsage.sharedPrefBytes(context, "straw_resume_positions")
|
||||
val search = com.sulkta.straw.util.StorageUsage.sharedPrefBytes(context, "straw_search_cache")
|
||||
val feed = com.sulkta.straw.util.StorageUsage.sharedPrefBytes(context, "straw_feed_cache")
|
||||
val coil = com.sulkta.straw.util.StorageUsage.coilDiskCacheBytes(context)
|
||||
}
|
||||
}
|
||||
CacheCapRow(
|
||||
label = "Watch history",
|
||||
label = "Watch + search history",
|
||||
selected = store.historyWatchesCap.collectAsState().value,
|
||||
onPick = { store.setHistoryWatchesCap(it) },
|
||||
usageBytes = usage.history,
|
||||
)
|
||||
CacheCapRow(
|
||||
label = "Search history",
|
||||
|
|
@ -615,12 +628,46 @@ fun SettingsScreen() {
|
|||
label = "Resume positions",
|
||||
selected = store.resumePositionsCap.collectAsState().value,
|
||||
onPick = { store.setResumePositionsCap(it) },
|
||||
usageBytes = usage.resume,
|
||||
)
|
||||
CacheCapRow(
|
||||
label = "Search results cache",
|
||||
selected = store.searchCacheCap.collectAsState().value,
|
||||
onPick = { store.setSearchCacheCap(it) },
|
||||
usageBytes = usage.search,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"Subs feed cache",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = "Used: ${com.sulkta.straw.util.StorageUsage.format(usage.feed)}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
"Image cache (thumbnails)",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Text(
|
||||
text = "Used: ${com.sulkta.straw.util.StorageUsage.format(usage.coil)}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
"Cache TTL",
|
||||
|
|
@ -799,17 +846,32 @@ private fun CategoryRow(
|
|||
|
||||
/**
|
||||
* Compact chip-group row for picking a CacheCap. Label on the left,
|
||||
* 5 chips on the right. Used four times in the Cache section so the
|
||||
* shape is consolidated here.
|
||||
* 5 chips on the right, optional "Used: X KB" suffix to the right
|
||||
* of the label so the user can see what each cap is doing.
|
||||
*/
|
||||
@Composable
|
||||
private fun CacheCapRow(
|
||||
label: String,
|
||||
selected: CacheCap,
|
||||
onPick: (CacheCap) -> Unit,
|
||||
usageBytes: Long = 0L,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||
Text(label, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (usageBytes > 0L) {
|
||||
Text(
|
||||
text = "Used: ${com.sulkta.straw.util.StorageUsage.format(usageBytes)}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 2.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Sulkta-Coop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* On-disk usage helper for the Settings → Storage section. Reads the
|
||||
* actual .xml file size for each SharedPreferences-backed store + the
|
||||
* Coil disk-cache size, so the user can see what's eating space rather
|
||||
* than guessing from cap settings.
|
||||
*
|
||||
* All values are best-effort: a missing file (store never written)
|
||||
* returns 0; permission/IO errors return 0 and log silently. The
|
||||
* displayed numbers are advisory, not authoritative.
|
||||
*/
|
||||
|
||||
package com.sulkta.straw.util
|
||||
|
||||
import android.content.Context
|
||||
import coil3.SingletonImageLoader
|
||||
import java.io.File
|
||||
|
||||
object StorageUsage {
|
||||
/**
|
||||
* Bytes-on-disk for a SharedPreferences file. The Android framework
|
||||
* writes `<dataDir>/shared_prefs/<prefsName>.xml`. dataDir is
|
||||
* `context.applicationInfo.dataDir` (the parent of filesDir,
|
||||
* approximately).
|
||||
*/
|
||||
fun sharedPrefBytes(context: Context, prefsName: String): Long {
|
||||
val dataDir = context.applicationInfo.dataDir ?: return 0L
|
||||
val f = File(dataDir, "shared_prefs/$prefsName.xml")
|
||||
return if (f.exists()) f.length() else 0L
|
||||
}
|
||||
|
||||
/**
|
||||
* Coil's disk cache total. Returns 0 if Coil hasn't lazily
|
||||
* initialized a disk cache yet (no images loaded this session).
|
||||
*/
|
||||
fun coilDiskCacheBytes(context: Context): Long = runCatching {
|
||||
SingletonImageLoader.get(context).diskCache?.size ?: 0L
|
||||
}.getOrDefault(0L)
|
||||
|
||||
/** Human-friendly rendering: "4.2 KB" / "13 MB" / "—" for 0. */
|
||||
fun format(bytes: Long): String {
|
||||
if (bytes <= 0L) return "—"
|
||||
val kb = bytes / 1024.0
|
||||
if (kb < 1024.0) return "%.1f KB".format(kb)
|
||||
val mb = kb / 1024.0
|
||||
if (mb < 1024.0) return "%.1f MB".format(mb)
|
||||
return "%.2f GB".format(mb / 1024.0)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue