Skip to content

Commit 286cd11

Browse files
committed
Unify developer mode indicator with tint and device swap warning
- Use icon tint to signal developer mode instead of swapping the icon, keeping the connection state visible in the top bar - Append a developer-mode note to the icon's content description so screen readers still convey the active state - Warn in the developer-mode dialog when selecting a different scale will replace the currently saved device - Replace the generic warning icon in Bluetooth settings with the bug report icon to match the top bar indicator
1 parent 8a839ed commit 286cd11

6 files changed

Lines changed: 52 additions & 16 deletions

File tree

android_app/app/src/main/java/com/health/openscale/ui/navigation/AppNavigation.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,11 @@ fun AppNavigation(sharedViewModel: SharedViewModel) {
367367
val contentDesc = action.contentDescriptionResId?.let { stringResource(id = it) }
368368
?: action.contentDescription
369369
IconButton(onClick = action.onClick) {
370-
Icon(imageVector = action.icon, contentDescription = contentDesc)
370+
Icon(
371+
imageVector = action.icon,
372+
contentDescription = contentDesc,
373+
tint = action.tint ?: LocalContentColor.current
374+
)
371375
}
372376
// If the action has associated dropdown content, invoke it here.
373377
// This allows TopAppBar actions to also host DropdownMenus.

android_app/app/src/main/java/com/health/openscale/ui/screen/components/BluetoothActionButton.kt

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import androidx.compose.material.icons.automirrored.filled.BluetoothSearching
2929
import androidx.compose.material.icons.filled.Bluetooth
3030
import androidx.compose.material.icons.filled.BluetoothConnected
3131
import androidx.compose.material.icons.filled.BluetoothDisabled
32-
import androidx.compose.material.icons.filled.BugReport
32+
import androidx.compose.material3.MaterialTheme
3333
import androidx.compose.material3.SnackbarDuration
3434
import androidx.compose.runtime.Composable
3535
import androidx.compose.runtime.collectAsState
@@ -84,6 +84,15 @@ fun rememberBluetoothActionButton(
8484
val currentUser by sharedViewModel.selectedUser.collectAsState()
8585
val developerMode by bluetoothViewModel.developerModeEnabled.collectAsStateWithLifecycle(false)
8686

87+
// Developer mode never stores a measurement, so mark the icon where the user actually weighs –
88+
// as a tint, so the connection state stays readable from the icon itself.
89+
val developerModeTint = MaterialTheme.colorScheme.error.takeIf { developerMode }
90+
91+
/** Keeps the action's own description and only appends the developer-mode note for screen readers. */
92+
fun withDeveloperNote(description: String): String =
93+
if (developerMode) "$description${context.getString(R.string.developer_mode_active_icon_desc)}"
94+
else description
95+
8796
return remember(connStatus, savedDevice, connectedDevice, currentUser, developerMode) {
8897
val savedAddr = savedDevice?.address
8998
val deviceName = savedDevice?.name ?: context.getString(R.string.fallback_device_name_saved_scale)
@@ -127,12 +136,11 @@ fun rememberBluetoothActionButton(
127136

128137
// 3. Already connected -> Disconnect on click
129138
connStatus == ConnectionStatus.CONNECTED -> TopBarAction(
130-
// Developer mode never stores a measurement; show it where the user weighs.
131-
icon = if (developerMode) Icons.Default.BugReport else Icons.Filled.BluetoothConnected,
132-
contentDescription = if (developerMode)
133-
context.getString(R.string.developer_mode_active_icon_desc)
134-
else
135-
context.getString(R.string.bluetooth_action_disconnect_desc, deviceName),
139+
icon = Icons.Filled.BluetoothConnected,
140+
tint = developerModeTint,
141+
contentDescription = withDeveloperNote(
142+
context.getString(R.string.bluetooth_action_disconnect_desc, deviceName)
143+
),
136144
onClick = {
137145
sharedViewModel.setPendingReferenceUserForBle(null)
138146
bluetoothViewModel.disconnectDevice()
@@ -145,11 +153,11 @@ fun rememberBluetoothActionButton(
145153

146154
// 4. Disconnected -> Request permissions/enable BT/connect
147155
else -> TopBarAction(
148-
icon = if (developerMode) Icons.Default.BugReport else Icons.Filled.BluetoothDisabled,
149-
contentDescription = if (developerMode)
150-
context.getString(R.string.developer_mode_active_icon_desc)
151-
else
152-
context.getString(R.string.bluetooth_action_disconnect_desc, deviceName),
156+
icon = Icons.Filled.BluetoothDisabled,
157+
tint = developerModeTint,
158+
contentDescription = withDeveloperNote(
159+
context.getString(R.string.bluetooth_action_disconnect_desc, deviceName)
160+
),
153161
onClick = {
154162
// Check for BOTH permissions (Scan and Connect)
155163
val hasScanPerm = ContextCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED

android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothDetailScreen.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import androidx.compose.material.icons.filled.BluetoothConnected
3737
import androidx.compose.material.icons.filled.BugReport
3838
import androidx.compose.material.icons.filled.DeleteForever
3939
import androidx.compose.material.icons.filled.People
40-
import androidx.compose.material.icons.filled.Warning
4140
import androidx.compose.material3.Card
4241
import androidx.compose.material3.DropdownMenuItem
4342
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -271,7 +270,9 @@ fun BluetoothDetailScreen(
271270
verticalAlignment = Alignment.CenterVertically
272271
) {
273272
Icon(
274-
imageVector = Icons.Default.Warning,
273+
// Same marker as the Bluetooth screen banner and the top-bar icon,
274+
// so the active mode is recognisable everywhere it shows up.
275+
imageVector = Icons.Default.BugReport,
275276
contentDescription = null,
276277
tint = MaterialTheme.colorScheme.error,
277278
modifier = Modifier.padding(end = 16.dp)

android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothScreen.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ fun BluetoothScreen(
388388
deviceToDebug?.let { device ->
389389
DeveloperModeAlertDialog(
390390
deviceName = device.name.ifEmpty { stringResource(R.string.unknown_device) },
391+
replacedDeviceName = savedDevice
392+
?.takeIf { it.address != device.address }
393+
?.let { it.name.ifEmpty { stringResource(R.string.unknown_device) } },
391394
onConfirm = {
392395
// The device is stored with its real identity; only the setting marks the
393396
// developer session, so turning it off later restores normal operation.
@@ -548,14 +551,27 @@ private fun CompatibilityAlertDialog(
548551
@Composable
549552
private fun DeveloperModeAlertDialog(
550553
deviceName: String,
554+
replacedDeviceName: String?,
551555
onConfirm: () -> Unit,
552556
onDismiss: () -> Unit
553557
) {
554558
AlertDialog(
555559
onDismissRequest = onDismiss,
556560
icon = { Icon(Icons.Default.BugReport, contentDescription = null) },
557561
title = { Text(stringResource(R.string.developer_mode_dialog_title)) },
558-
text = { Text(stringResource(R.string.developer_mode_dialog_message, deviceName)) },
562+
text = {
563+
Column {
564+
Text(stringResource(R.string.developer_mode_dialog_message, deviceName))
565+
// Picking a different scale here silently swapped the saved one before.
566+
replacedDeviceName?.let { replaced ->
567+
Spacer(Modifier.height(12.dp))
568+
Text(
569+
text = stringResource(R.string.developer_mode_dialog_replaces, deviceName, replaced),
570+
color = MaterialTheme.colorScheme.error
571+
)
572+
}
573+
}
574+
},
559575
confirmButton = {
560576
TextButton(
561577
onClick = {

android_app/app/src/main/java/com/health/openscale/ui/shared/TopBarAction.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ package com.health.openscale.ui.shared
1919

2020
import androidx.annotation.StringRes
2121
import androidx.compose.runtime.Composable
22+
import androidx.compose.ui.graphics.Color
2223
import androidx.compose.ui.graphics.vector.ImageVector
2324

25+
/**
26+
* @property tint Overrides the default icon color; use it to flag a state that must stay visible
27+
* without giving up the icon itself (e.g. Bluetooth developer mode). `null` keeps the theme color.
28+
*/
2429
data class TopBarAction(
2530
val icon: ImageVector,
2631
val onClick: () -> Unit,
2732
@param:StringRes val contentDescriptionResId: Int? = null,
2833
val contentDescription: String? = null,
34+
val tint: Color? = null,
2935
val dropdownContent: (@Composable () -> Unit)? = null
3036
)

android_app/app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@
357357
<string name="developer_banner_enable_logs_hint">Please enable log file: Settings → General → Enable log file.</string>
358358
<string name="developer_mode_dialog_title">Enable developer mode?</string>
359359
<string name="developer_mode_dialog_message">openScale will only log the Bluetooth traffic of %1$s for diagnostics. As long as developer mode is active, no measurement is saved. You can switch it off again on the Bluetooth screen or in the device settings.</string>
360+
<string name="developer_mode_dialog_replaces">%1$s becomes your saved scale and replaces %2$s.</string>
360361
<string name="developer_mode_dialog_confirm_button">Enable</string>
361362
<string name="developer_mode_disable_action">Disable</string>
362363
<string name="developer_mode_active_icon_desc">Developer mode is active</string>

0 commit comments

Comments
 (0)