feat: always-on service cost card with a link to notification settings

The uptime row told users the service ran, not what it cost — and the
setting to turn it off lives three screens away. The resource screen now
shows a dedicated card (only while the service has run this week) that
frames the cost in decision terms: running time and restarts, the relay
connections it held while the app was closed, and the measured background
battery drain — with a button straight to notification settings where the
delivery method can be changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016RJ8EAsdkHx5WHHU2eQJ1P
This commit is contained in:
Claude
2026-07-13 16:30:07 +00:00
parent b2914fbc4b
commit 53e8f8df09
2 changed files with 63 additions and 5 deletions
@@ -39,6 +39,7 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -51,6 +52,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.Amethyst
@@ -65,6 +67,7 @@ import com.vitorpamplona.amethyst.service.resourceusage.ResourceUsageReportAssem
import com.vitorpamplona.amethyst.service.resourceusage.ResourceUsageReportAssembler.Companion.formatDurationMs
import com.vitorpamplona.amethyst.service.resourceusage.UsageSummary
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.routes.routeToMessage
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -140,6 +143,7 @@ fun ResourceUsageScreen(
}
SubsystemSection(weekSummary)
ActivitySection(weekSummary)
AlwaysOnServiceSection(weekSummary, nav)
MemorySection(memory)
SendReportSection(accountViewModel, nav, loaded, today, memory)
}
@@ -310,10 +314,6 @@ private fun ActivitySection(s: UsageSummary) {
SettingsDivider()
MetricRow(R.string.resource_usage_tor, formatDurationMs(s.torMs))
}
if (s.alwaysOnMs > 0) {
SettingsDivider()
MetricRow(R.string.resource_usage_always_on, formatDurationMs(s.alwaysOnMs))
}
if (s.callMs > 0) {
SettingsDivider()
MetricRow(R.string.resource_usage_calls, formatDurationMs(s.callMs))
@@ -400,6 +400,55 @@ private fun MemorySection(memory: MemorySnapshot?) {
}
}
/**
* Decision-support card for the always-on notification service — the one
* background consumer users can directly turn off. Shows what the service
* actually costs in the last 7 days (uptime, the relay connections it holds
* while the app is closed, and the measured background battery drain) and
* links straight to the setting that controls it.
*/
@Composable
private fun AlwaysOnServiceSection(
s: UsageSummary,
nav: INav,
) {
if (s.alwaysOnMs <= 0) return
val starts = s.alwaysOnStarts.toInt()
SettingsSection(R.string.resource_usage_alwayson_section) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
Text(
text = stringRes(R.string.resource_usage_alwayson_explanation),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
MetricRow(
R.string.resource_usage_alwayson_uptime,
"${formatDurationMs(s.alwaysOnMs)} · ${pluralStringResource(R.plurals.resource_usage_alwayson_starts, starts, starts)}",
)
SettingsDivider()
MetricRow(
R.string.resource_usage_alwayson_bg_relay,
formatConnHours(s.relayConnMsMobileBg + s.relayConnMsWifiBg),
)
if (s.batteryDrainBg > 0) {
SettingsDivider()
MetricRow(R.string.resource_usage_alwayson_battery, "${s.batteryDrainBg}%")
}
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
TextButton(
onClick = { nav.nav(Route.NotificationSettings) },
modifier = Modifier.align(Alignment.End),
) {
Text(stringRes(R.string.resource_usage_alwayson_settings_button))
}
}
}
}
@Composable
private fun SendReportSection(
accountViewModel: AccountViewModel,
+10 -1
View File
@@ -3213,11 +3213,20 @@
<string name="resource_usage_remote_signs">Remote signatures</string>
<string name="resource_usage_pow">Proof-of-work mining</string>
<string name="resource_usage_tor">Tor uptime</string>
<string name="resource_usage_always_on">Always-on notification service</string>
<string name="resource_usage_calls">Time in calls</string>
<string name="resource_usage_nests">Time in audio rooms</string>
<string name="resource_usage_location">Location listening</string>
<string name="resource_usage_battery_drain">Battery used (in app / background)</string>
<string name="resource_usage_alwayson_section">Background notification service</string>
<string name="resource_usage_alwayson_explanation">Keeps connections to your relays open while the app is closed, so notifications arrive without Googles push servers. While enabled, this is usually the largest background battery cost. If battery matters more to you than instant notifications, you can change the delivery method or turn it off.</string>
<string name="resource_usage_alwayson_uptime">Running time</string>
<plurals name="resource_usage_alwayson_starts">
<item quantity="one">%1$d start</item>
<item quantity="other">%1$d starts</item>
</plurals>
<string name="resource_usage_alwayson_bg_relay">Relay connections held while closed</string>
<string name="resource_usage_alwayson_battery">Battery drained meanwhile (whole device)</string>
<string name="resource_usage_alwayson_settings_button">Change notification settings</string>
<string name="resource_usage_by_subsystem">Data by feature (7 days)</string>
<string name="resource_usage_subsystem_relay">Relay sync</string>
<string name="resource_usage_subsystem_image">Images</string>