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
|
// vc=19 / 0.1.0-AE — rust pipeline cutover. Extraction via
|
||||||
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
|
// strawcore-core (Sulkta-Coop/strawcore) via the UniFFI wrapper; no
|
||||||
// NewPipeExtractor in the runtime path.
|
// NewPipeExtractor in the runtime path.
|
||||||
const val STRAW_VERSION_CODE = 59
|
const val STRAW_VERSION_CODE = 60
|
||||||
const val STRAW_VERSION_NAME = "0.1.0-BS"
|
const val STRAW_VERSION_NAME = "0.1.0-BT"
|
||||||
const val STRAW_APPLICATION_ID = "com.sulkta.straw"
|
const val STRAW_APPLICATION_ID = "com.sulkta.straw"
|
||||||
|
|
|
||||||
|
|
@ -601,10 +601,23 @@ fun SettingsScreen() {
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
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(
|
CacheCapRow(
|
||||||
label = "Watch history",
|
label = "Watch + search history",
|
||||||
selected = store.historyWatchesCap.collectAsState().value,
|
selected = store.historyWatchesCap.collectAsState().value,
|
||||||
onPick = { store.setHistoryWatchesCap(it) },
|
onPick = { store.setHistoryWatchesCap(it) },
|
||||||
|
usageBytes = usage.history,
|
||||||
)
|
)
|
||||||
CacheCapRow(
|
CacheCapRow(
|
||||||
label = "Search history",
|
label = "Search history",
|
||||||
|
|
@ -615,12 +628,46 @@ fun SettingsScreen() {
|
||||||
label = "Resume positions",
|
label = "Resume positions",
|
||||||
selected = store.resumePositionsCap.collectAsState().value,
|
selected = store.resumePositionsCap.collectAsState().value,
|
||||||
onPick = { store.setResumePositionsCap(it) },
|
onPick = { store.setResumePositionsCap(it) },
|
||||||
|
usageBytes = usage.resume,
|
||||||
)
|
)
|
||||||
CacheCapRow(
|
CacheCapRow(
|
||||||
label = "Search results cache",
|
label = "Search results cache",
|
||||||
selected = store.searchCacheCap.collectAsState().value,
|
selected = store.searchCacheCap.collectAsState().value,
|
||||||
onPick = { store.setSearchCacheCap(it) },
|
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))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
"Cache TTL",
|
"Cache TTL",
|
||||||
|
|
@ -799,17 +846,32 @@ private fun CategoryRow(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compact chip-group row for picking a CacheCap. Label on the left,
|
* 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
|
* 5 chips on the right, optional "Used: X KB" suffix to the right
|
||||||
* shape is consolidated here.
|
* of the label so the user can see what each cap is doing.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun CacheCapRow(
|
private fun CacheCapRow(
|
||||||
label: String,
|
label: String,
|
||||||
selected: CacheCap,
|
selected: CacheCap,
|
||||||
onPick: (CacheCap) -> Unit,
|
onPick: (CacheCap) -> Unit,
|
||||||
|
usageBytes: Long = 0L,
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
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(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 2.dp),
|
modifier = Modifier.fillMaxWidth().padding(top = 2.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.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