1
0
mirror of https://github.com/oliexdev/openScale.git synced 2025-09-02 12:54:10 +02:00

Refactor BluetoothFacade and BluetoothViewModel for Snackbar handling

This commit is contained in:
oliexdev
2025-08-23 16:59:39 +02:00
parent 7e416f11b6
commit b43b6f9828
9 changed files with 59 additions and 110 deletions

View File

@@ -42,6 +42,7 @@ import javax.inject.Singleton
import com.health.openscale.core.service.ScannedDeviceInfo import com.health.openscale.core.service.ScannedDeviceInfo
import com.health.openscale.core.service.BluetoothScannerManager import com.health.openscale.core.service.BluetoothScannerManager
import com.health.openscale.core.service.BleConnector import com.health.openscale.core.service.BleConnector
import com.health.openscale.ui.shared.SnackbarEvent
/** /**
* Facade responsible for orchestrating Bluetooth operations. * Facade responsible for orchestrating Bluetooth operations.
@@ -71,15 +72,11 @@ class BluetoothFacade @Inject constructor(
scope = scope, scope = scope,
scaleFactory = scaleFactory, scaleFactory = scaleFactory,
databaseRepository = databaseRepository, databaseRepository = databaseRepository,
getCurrentScaleUser = { currentBtScaleUser.value }, getCurrentScaleUser = { currentBtScaleUser.value }
onSavePreferredDevice = { address, name ->
scope.launch { _oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_scale_saved_as_preferred, listOf(name))) }
},
onSnackbarText = { message, duration ->
scope.launch { _oneShotText.emit(OneShotText(message, duration)) }
}
) )
val snackbarEventsFromConnector: SharedFlow<SnackbarEvent> = connection.snackbarEvents
// --- Publicly observable state --- // --- Publicly observable state ---
val scannedDevices: StateFlow<List<ScannedDeviceInfo>> = scanner.scannedDevices val scannedDevices: StateFlow<List<ScannedDeviceInfo>> = scanner.scannedDevices
val isScanning: StateFlow<Boolean> = scanner.isScanning val isScanning: StateFlow<Boolean> = scanner.isScanning
@@ -101,23 +98,6 @@ class BluetoothFacade @Inject constructor(
val savedScaleName: StateFlow<String?> = val savedScaleName: StateFlow<String?> =
settingsFacade.savedBluetoothScaleName.stateIn(scope, SharingStarted.WhileSubscribed(5000), null) settingsFacade.savedBluetoothScaleName.stateIn(scope, SharingStarted.WhileSubscribed(5000), null)
/**
* One-shot message with resource ID, args, and duration.
* Used for Snackbar feedback from non-UI layers.
*/
data class OneShotMessage(val resId: Int, val args: List<Any> = emptyList(), val duration: SnackbarDuration = SnackbarDuration.Short)
/**
* One-shot message with plain text.
*/
data class OneShotText(val text: String, val duration: SnackbarDuration = SnackbarDuration.Short)
private val _oneShotMessages = MutableSharedFlow<OneShotMessage>()
val oneShotMessages: SharedFlow<OneShotMessage> = _oneShotMessages.asSharedFlow()
private val _oneShotText = MutableSharedFlow<OneShotText>()
val oneShotText: SharedFlow<OneShotText> = _oneShotText.asSharedFlow()
// --- Current user context --- // --- Current user context ---
private val currentAppUser = MutableStateFlow<User?>(null) private val currentAppUser = MutableStateFlow<User?>(null)
private val currentBtScaleUser = MutableStateFlow<ScaleUser?>(null) private val currentBtScaleUser = MutableStateFlow<ScaleUser?>(null)
@@ -160,7 +140,7 @@ class BluetoothFacade @Inject constructor(
fun connectTo(device: ScannedDeviceInfo) { fun connectTo(device: ScannedDeviceInfo) {
val (supported, handlerName) = scaleFactory.getSupportingHandlerInfo(device) val (supported, handlerName) = scaleFactory.getSupportingHandlerInfo(device)
if (!supported) { if (!supported) {
scope.launch { _oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_saved_scale_no_longer_supported, listOf(device.name ?: "?"))) } LogManager.w(TAG, "Device ${device.name} is not supported by this app")
return return
} }
device.isSupported = true device.isSupported = true
@@ -173,7 +153,6 @@ class BluetoothFacade @Inject constructor(
val address = savedScaleAddress.value val address = savedScaleAddress.value
val name = savedScaleName.value val name = savedScaleName.value
if (address == null || name == null) { if (address == null || name == null) {
_oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_no_scale_saved))
return@launch return@launch
} }
val already = (connectionStatus.value == ConnectionStatus.CONNECTED || connectionStatus.value == ConnectionStatus.CONNECTING) && val already = (connectionStatus.value == ConnectionStatus.CONNECTED || connectionStatus.value == ConnectionStatus.CONNECTING) &&
@@ -191,20 +170,17 @@ class BluetoothFacade @Inject constructor(
fun saveAsPreferred(device: ScannedDeviceInfo) { fun saveAsPreferred(device: ScannedDeviceInfo) {
scope.launch { scope.launch {
val display = device.name ?: application.getString(R.string.unknown_scale_name) val display = device.name ?: application.getString(R.string.unknown_device)
settingsFacade.saveBluetoothScale(device.address, display) settingsFacade.saveBluetoothScale(device.address, display)
_oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_scale_saved_as_preferred, listOf(display)))
} }
} }
fun provideUserInteractionFeedback(type: BluetoothEvent.UserInteractionType, feedbackData: Any) { fun provideUserInteractionFeedback(type: BluetoothEvent.UserInteractionType, feedbackData: Any) {
val user = currentAppUser.value ?: run { val user = currentAppUser.value ?: run {
scope.launch { _oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_error_no_app_user_selected)) }
connection.clearUserInteractionEvent() connection.clearUserInteractionEvent()
return return
} }
connection.provideUserInteractionFeedback(type, user.id, feedbackData, Handler(Looper.getMainLooper())) connection.provideUserInteractionFeedback(type, user.id, feedbackData, Handler(Looper.getMainLooper()))
scope.launch { _oneShotMessages.emit(OneShotMessage(R.string.bt_snackbar_user_input_processed)) }
connection.clearUserInteractionEvent() connection.clearUserInteractionEvent()
} }

View File

@@ -19,7 +19,7 @@ package com.health.openscale.core.service
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Handler import android.os.Handler
import androidx.compose.material3.SnackbarDuration import com.health.openscale.R
import com.health.openscale.core.bluetooth.BluetoothEvent import com.health.openscale.core.bluetooth.BluetoothEvent
import com.health.openscale.core.bluetooth.ScaleCommunicator import com.health.openscale.core.bluetooth.ScaleCommunicator
import com.health.openscale.core.bluetooth.ScaleFactory import com.health.openscale.core.bluetooth.ScaleFactory
@@ -31,12 +31,16 @@ import com.health.openscale.core.data.MeasurementTypeKey
import com.health.openscale.core.data.MeasurementValue import com.health.openscale.core.data.MeasurementValue
import com.health.openscale.core.database.DatabaseRepository import com.health.openscale.core.database.DatabaseRepository
import com.health.openscale.core.utils.LogManager import com.health.openscale.core.utils.LogManager
import com.health.openscale.ui.shared.SnackbarEvent
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -64,8 +68,6 @@ class BleConnector(
private val scaleFactory: ScaleFactory, private val scaleFactory: ScaleFactory,
private val databaseRepository: DatabaseRepository, private val databaseRepository: DatabaseRepository,
private val getCurrentScaleUser: () -> ScaleUser?, private val getCurrentScaleUser: () -> ScaleUser?,
private val onSavePreferredDevice: suspend (address: String, name: String) -> Unit,
private val onSnackbarText: (message: String, duration: SnackbarDuration) -> Unit,
) : AutoCloseable { ) : AutoCloseable {
private companion object { private companion object {
@@ -73,6 +75,9 @@ class BleConnector(
const val DISCONNECT_TIMEOUT_MS = 3000L // Timeout for forceful disconnect if no event received. const val DISCONNECT_TIMEOUT_MS = 3000L // Timeout for forceful disconnect if no event received.
} }
private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 0, extraBufferCapacity = 1)
val snackbarEvents: SharedFlow<SnackbarEvent> = _snackbarEvents.asSharedFlow()
private val _connectedDeviceName = MutableStateFlow<String?>(null) private val _connectedDeviceName = MutableStateFlow<String?>(null)
/** Emits the name of the currently connected device, or null if not connected. */ /** Emits the name of the currently connected device, or null if not connected. */
val connectedDeviceName: StateFlow<String?> = _connectedDeviceName.asStateFlow() val connectedDeviceName: StateFlow<String?> = _connectedDeviceName.asStateFlow()
@@ -180,8 +185,7 @@ class BleConnector(
// but confirm here for safety. // but confirm here for safety.
_connectedDeviceAddress.value = connectedDeviceInfo.address _connectedDeviceAddress.value = connectedDeviceInfo.address
_connectedDeviceName.value = connectedDeviceInfo.name _connectedDeviceName.value = connectedDeviceInfo.name
onSavePreferredDevice(connectedDeviceInfo.address, connectedDeviceInfo.name ?: "Unknown Scale") _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_connected_to, messageFormatArgs = listOf(deviceDisplayName)))
onSnackbarText("Connected to $deviceDisplayName", SnackbarDuration.Short)
_connectionError.value = null // Clear any errors on successful connection. _connectionError.value = null // Clear any errors on successful connection.
LogManager.i(TAG, "Successfully connected to $deviceDisplayName via adapter's isConnected flow.") LogManager.i(TAG, "Successfully connected to $deviceDisplayName via adapter's isConnected flow.")
disconnectTimeoutJob?.cancel() // Successfully connected, timeout no longer needed. disconnectTimeoutJob?.cancel() // Successfully connected, timeout no longer needed.
@@ -232,8 +236,7 @@ class BleConnector(
_connectionStatus.value = ConnectionStatus.CONNECTED _connectionStatus.value = ConnectionStatus.CONNECTED
_connectedDeviceAddress.value = event.deviceAddress _connectedDeviceAddress.value = event.deviceAddress
_connectedDeviceName.value = event.deviceName ?: deviceInfo.name // Prefer event name. _connectedDeviceName.value = event.deviceName ?: deviceInfo.name // Prefer event name.
onSavePreferredDevice(event.deviceAddress, event.deviceName ?: deviceInfo.name ?: "Unknown Scale") _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_connected_to, messageFormatArgs = listOf(event.deviceName ?: deviceDisplayName)))
onSnackbarText("Connected to ${event.deviceName ?: deviceDisplayName}", SnackbarDuration.Short)
_connectionError.value = null _connectionError.value = null
} }
} }
@@ -271,7 +274,7 @@ class BleConnector(
} }
is BluetoothEvent.DeviceMessage -> { is BluetoothEvent.DeviceMessage -> {
LogManager.d(TAG, "Event: Message from $deviceDisplayName: ${event.message}") LogManager.d(TAG, "Event: Message from $deviceDisplayName: ${event.message}")
onSnackbarText("$deviceDisplayName: ${event.message}", SnackbarDuration.Long) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_device_message, messageFormatArgs = listOf(deviceDisplayName, event.message)))
} }
is BluetoothEvent.Error -> { is BluetoothEvent.Error -> {
LogManager.e(TAG, "Event: Error from $deviceDisplayName: ${event.error}") LogManager.e(TAG, "Event: Error from $deviceDisplayName: ${event.error}")
@@ -322,7 +325,7 @@ class BleConnector(
val currentAppUserId = getCurrentScaleUser()?.id val currentAppUserId = getCurrentScaleUser()?.id
if (currentAppUserId == 0) { if (currentAppUserId == 0) {
LogManager.e(TAG, "($deviceName): No App User ID to save measurement.") LogManager.e(TAG, "($deviceName): No App User ID to save measurement.")
onSnackbarText("Measurement from $deviceName cannot be assigned to a user.", SnackbarDuration.Long) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_measurement_user_missing, messageFormatArgs = listOf(deviceName)))
return return
} }
LogManager.i(TAG, "($deviceName): Saving measurement for App User ID $currentAppUserId.") LogManager.i(TAG, "($deviceName): Saving measurement for App User ID $currentAppUserId.")
@@ -340,7 +343,7 @@ class BleConnector(
databaseRepository.getAllMeasurementTypes().firstOrNull() databaseRepository.getAllMeasurementTypes().firstOrNull()
?.associate { it.key to it.id } ?: run { ?.associate { it.key to it.id } ?: run {
LogManager.e(TAG, "Could not load MeasurementTypes from DB for $deviceName.") LogManager.e(TAG, "Could not load MeasurementTypes from DB for $deviceName.")
onSnackbarText("Error: Measurement types not loaded.", SnackbarDuration.Long) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_measurement_types_not_loaded))
return@launch return@launch
} }
fun getTypeIdFromMap(key: MeasurementTypeKey): Int? = typeKeyToIdMap[key] fun getTypeIdFromMap(key: MeasurementTypeKey): Int? = typeKeyToIdMap[key]
@@ -416,7 +419,7 @@ class BleConnector(
if (values.isEmpty()) { if (values.isEmpty()) {
LogManager.w(TAG, "No valid values from measurement of $deviceName to save.") LogManager.w(TAG, "No valid values from measurement of $deviceName to save.")
onSnackbarText("No valid measurement values received from $deviceName.", SnackbarDuration.Long) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_measurement_no_values, messageFormatArgs = listOf(deviceName)))
return@launch return@launch
} }
@@ -426,10 +429,10 @@ class BleConnector(
finalValues.forEach { databaseRepository.insertMeasurementValue(it) } finalValues.forEach { databaseRepository.insertMeasurementValue(it) }
LogManager.i(TAG, "Measurement from $deviceName for User $currentAppUserId saved (ID: $measurementId). Values: ${finalValues.size}") LogManager.i(TAG, "Measurement from $deviceName for User $currentAppUserId saved (ID: $measurementId). Values: ${finalValues.size}")
onSnackbarText("Measurement (${measurementData.weight} kg) from $deviceName saved.", SnackbarDuration.Short) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_measurement_saved, messageFormatArgs = listOf(measurementData.weight, deviceName)))
} catch (e: Exception) { } catch (e: Exception) {
LogManager.e(TAG, "Error saving measurement from $deviceName.", e) LogManager.e(TAG, "Error saving measurement from $deviceName.", e)
onSnackbarText("Error saving measurement from $deviceName.", SnackbarDuration.Long) _snackbarEvents.tryEmit(SnackbarEvent(messageResId = R.string.bluetooth_connector_measurement_save_error, messageFormatArgs = listOf(deviceName)))
} }
} }
} }

View File

@@ -18,6 +18,7 @@
package com.health.openscale.ui.navigation package com.health.openscale.ui.navigation
import android.util.Pair import android.util.Pair
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -185,6 +186,10 @@ fun AppNavigation(sharedViewModel: SharedViewModel) {
else -> "" // Default to empty string if title data is null or unexpected type else -> "" // Default to empty string if title data is null or unexpected type
} }
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
// Reset top bar actions when the current route changes. // Reset top bar actions when the current route changes.
// This prevents actions from a previous screen from lingering on the new screen. // This prevents actions from a previous screen from lingering on the new screen.
LaunchedEffect(currentRoute) { LaunchedEffect(currentRoute) {

View File

@@ -341,7 +341,7 @@ fun OverviewScreen(
} }
} else { } else {
sharedViewModel.showSnackbar( sharedViewModel.showSnackbar(
message = context.getString(R.string.bt_snackbar_bluetooth_disabled_to_connect_default), message = context.getString(R.string.bluetooth_permissions_required_for_scan),
duration = SnackbarDuration.Long duration = SnackbarDuration.Long
) )
} }
@@ -360,7 +360,7 @@ fun OverviewScreen(
} }
} else { } else {
sharedViewModel.showSnackbar( sharedViewModel.showSnackbar(
messageResId = R.string.bt_snackbar_permissions_required_to_connect_default messageResId = R.string.bluetooth_permissions_required_for_scan
) )
} }
} }

View File

@@ -63,30 +63,13 @@ class BluetoothViewModel @Inject constructor(
val savedScaleName = bt.savedScaleName val savedScaleName = bt.savedScaleName
// --- Snackbar events for UI --- // --- Snackbar events for UI ---
private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 1, extraBufferCapacity = 1) private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 0, extraBufferCapacity = 1)
val snackbarEvents: SharedFlow<SnackbarEvent> = _snackbarEvents.asSharedFlow() val snackbarEvents: SharedFlow<SnackbarEvent> = _snackbarEvents.asSharedFlow()
init { init {
// Translate facade one-shot messages into SnackbarEvents
viewModelScope.launch { viewModelScope.launch {
bt.oneShotMessages.collect { msg -> bt.snackbarEventsFromConnector.collect { evt ->
_snackbarEvents.emit( _snackbarEvents.emit(evt)
SnackbarEvent(
messageResId = msg.resId,
messageFormatArgs = msg.args,
duration = msg.duration
)
)
}
}
viewModelScope.launch {
bt.oneShotText.collect { txt ->
_snackbarEvents.emit(
SnackbarEvent(
message = txt.text,
duration = txt.duration
)
)
} }
} }
} }
@@ -94,7 +77,7 @@ class BluetoothViewModel @Inject constructor(
// --- Delegated actions --- // --- Delegated actions ---
fun requestStartDeviceScan() { fun requestStartDeviceScan() {
if (!bt.isBluetoothEnabled()) { if (!bt.isBluetoothEnabled()) {
emitSnack(R.string.bt_snackbar_bluetooth_disabled_to_scan, SnackbarDuration.Long) emitSnack(R.string.bluetooth_must_be_enabled_for_scan, SnackbarDuration.Long)
return return
} }
bt.startScan(SCAN_DURATION_MS) bt.startScan(SCAN_DURATION_MS)

View File

@@ -67,7 +67,7 @@ class SettingsViewModel @Inject constructor(
} }
// --- Snackbar --- // --- Snackbar ---
private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 1, extraBufferCapacity = 1) private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 0, extraBufferCapacity = 1)
val snackbarEvents = _snackbarEvents.asSharedFlow() val snackbarEvents = _snackbarEvents.asSharedFlow()
private suspend fun showSnackbar( private suspend fun showSnackbar(

View File

@@ -93,7 +93,7 @@ class SharedViewModel @Inject constructor(
fun setTopBarActions(actions: List<TopBarAction>) { _topBarActions.value = actions } fun setTopBarActions(actions: List<TopBarAction>) { _topBarActions.value = actions }
// --- Snackbar events --- // --- Snackbar events ---
private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 1, extraBufferCapacity = 1) private val _snackbarEvents = MutableSharedFlow<SnackbarEvent>(replay = 0, extraBufferCapacity = 1)
val snackbarEvents: SharedFlow<SnackbarEvent> = _snackbarEvents.asSharedFlow() val snackbarEvents: SharedFlow<SnackbarEvent> = _snackbarEvents.asSharedFlow()
fun showSnackbar( fun showSnackbar(

View File

@@ -217,6 +217,18 @@
<string name="not_supported_label">Nicht unterstützt</string> <string name="not_supported_label">Nicht unterstützt</string>
<string name="rssi_format">%1$d dBm</string> <string name="rssi_format">%1$d dBm</string>
<!-- Bluetooth Connector -->
<string name="bluetooth_connector_connected_to">Verbunden mit %1$s</string>
<string name="bluetooth_connector_connection_failed">Verbindung zu %1$s fehlgeschlagen: %2$s</string>
<string name="bluetooth_connector_device_not_supported">%1$s wird nicht unterstützt.</string>
<string name="bluetooth_connector_driver_not_found">Treiber für %1$s nicht gefunden oder interner Fehler.</string>
<string name="bluetooth_connector_measurement_user_missing">Messung von %1$s kann keinem Benutzer zugeordnet werden.</string>
<string name="bluetooth_connector_measurement_types_not_loaded">Fehler: Messtypen konnten nicht geladen werden.</string>
<string name="bluetooth_connector_measurement_no_values">Keine gültigen Messwerte von %1$s empfangen.</string>
<string name="bluetooth_connector_measurement_saved">Messung (%1$.1f kg) von %2$s gespeichert.</string>
<string name="bluetooth_connector_measurement_save_error">Fehler beim Speichern der Messung von %1$s.</string>
<string name="bluetooth_connector_device_message">%1$s: %2$s</string>
<!-- Bluetooth Berechtigungen & Aktivierungs-Karten --> <!-- Bluetooth Berechtigungen & Aktivierungs-Karten -->
<string name="permissions_required_icon_desc">Symbol für erforderliche Berechtigungen</string> <string name="permissions_required_icon_desc">Symbol für erforderliche Berechtigungen</string>
<string name="permissions_required_title">Berechtigungen erforderlich</string> <string name="permissions_required_title">Berechtigungen erforderlich</string>
@@ -448,27 +460,6 @@
<string name="eval_no_age_band">Keine Bewertung möglich: Kein passender Altersbereich zum Messzeitpunkt.</string> <string name="eval_no_age_band">Keine Bewertung möglich: Kein passender Altersbereich zum Messzeitpunkt.</string>
<string name="eval_out_of_plausible_range_percent">Auffälliger Wert: außerhalb des plausiblen Bereichs (%1$.0f%2$.0f%%).</string> <string name="eval_out_of_plausible_range_percent">Auffälliger Wert: außerhalb des plausiblen Bereichs (%1$.0f%2$.0f%%).</string>
<!-- BluetoothViewModel Snackbar Messages -->
<string name="bt_snackbar_permissions_required_to_scan">Bluetooth-Berechtigungen werden zum Scannen von Geräten benötigt.</string>
<string name="bt_snackbar_bluetooth_disabled_to_scan">Bluetooth ist deaktiviert. Bitte aktiviere es, um nach Geräten zu scannen.</string>
<string name="bt_snackbar_permissions_required_to_connect">Bluetooth-Berechtigungen werden benötigt, um eine Verbindung zu %1$s herzustellen.</string>
<string name="bt_snackbar_permissions_required_to_connect_default">Bluetooth-Berechtigungen werden benötigt, um eine Verbindung zum Gerät herzustellen.</string>
<string name="bt_snackbar_bluetooth_disabled_to_connect">Bluetooth ist deaktiviert. Bitte aktiviere es, um eine Verbindung zu %1$s herzustellen.</string>
<string name="bt_snackbar_bluetooth_disabled_to_connect_default">Bluetooth ist deaktiviert. Bitte aktiviere es, um eine Verbindung zum Gerät herzustellen.</string>
<string name="bt_snackbar_scale_saved_as_preferred">%1$s als bevorzugte Waage gespeichert.</string>
<string name="bt_snackbar_saved_scale_no_longer_supported">Gespeicherte Waage \'%1$s\' wird nicht mehr unterstützt.</string>
<string name="bt_snackbar_no_scale_saved">Keine Bluetooth-Waage in den Einstellungen gespeichert.</string>
<string name="bt_snackbar_error_no_app_user_selected">Fehler: Kein App-Benutzer ausgewählt.</string>
<string name="bt_snackbar_user_input_processed">Benutzereingabe verarbeitet.</string>
<string name="bt_snackbar_operation_successful">Vorgang erfolgreich.</string>
<string name="bt_snackbar_operation_failed">Vorgang fehlgeschlagen. Bitte versuche es erneut.</string>
<string name="device_placeholder_name">das Gerät</string>
<string name="unknown_scale_name">Unbekannte Waage</string>
<!-- Bluetooth Waagen Nachrichten --> <!-- Bluetooth Waagen Nachrichten -->
<string name="bluetooth_scale_trisa_message_not_paired_instruction">Diese Waage wurde nicht gekoppelt!\n\nHalten Sie die Taste an der Unterseite der Waage gedrückt, um sie in den Kopplungsmodus zu versetzen, und verbinden Sie sich dann erneut, um das Gerätepasswort abzurufen.</string> <string name="bluetooth_scale_trisa_message_not_paired_instruction">Diese Waage wurde nicht gekoppelt!\n\nHalten Sie die Taste an der Unterseite der Waage gedrückt, um sie in den Kopplungsmodus zu versetzen, und verbinden Sie sich dann erneut, um das Gerätepasswort abzurufen.</string>
<string name="bluetooth_scale_trisa_success_pairing">Kopplung erfolgreich!\n\nVerbinden Sie sich erneut, um Messdaten abzurufen.</string> <string name="bluetooth_scale_trisa_success_pairing">Kopplung erfolgreich!\n\nVerbinden Sie sich erneut, um Messdaten abzurufen.</string>

View File

@@ -219,6 +219,18 @@
<string name="not_supported_label">Not Supported</string> <string name="not_supported_label">Not Supported</string>
<string name="rssi_format">%1$d dBm</string> <string name="rssi_format">%1$d dBm</string>
<!-- Bluetooth Connector -->
<string name="bluetooth_connector_connected_to">Connected to %1$s</string>
<string name="bluetooth_connector_connection_failed">Connection to %1$s failed: %2$s</string>
<string name="bluetooth_connector_device_not_supported">%1$s is not supported.</string>
<string name="bluetooth_connector_driver_not_found">Driver for %1$s not found or internal error.</string>
<string name="bluetooth_connector_measurement_user_missing">Measurement from %1$s cannot be assigned to a user.</string>
<string name="bluetooth_connector_measurement_types_not_loaded">Error: Measurement types not loaded.</string>
<string name="bluetooth_connector_measurement_no_values">No valid measurement values received from %1$s.</string>
<string name="bluetooth_connector_measurement_saved">Measurement (%1$.1f kg) from %2$s saved.</string>
<string name="bluetooth_connector_measurement_save_error">Error saving measurement from %1$s.</string>
<string name="bluetooth_connector_device_message">%1$s: %2$s</string>
<!-- Bluetooth Permissions & Enable Cards --> <!-- Bluetooth Permissions & Enable Cards -->
<string name="permissions_required_icon_desc">Permissions required icon</string> <string name="permissions_required_icon_desc">Permissions required icon</string>
<string name="permissions_required_title">Permissions Required</string> <string name="permissions_required_title">Permissions Required</string>
@@ -450,27 +462,6 @@
<string name="eval_no_age_band">No evaluation possible: No matching age band at the time of measurement.</string> <string name="eval_no_age_band">No evaluation possible: No matching age band at the time of measurement.</string>
<string name="eval_out_of_plausible_range_percent">Unusual value: outside the plausible range (%1$.0f%2$.0f%%).</string> <string name="eval_out_of_plausible_range_percent">Unusual value: outside the plausible range (%1$.0f%2$.0f%%).</string>
<!-- BluetoothViewModel Snackbar Messages -->
<string name="bt_snackbar_permissions_required_to_scan">Bluetooth permissions are required to scan for devices.</string>
<string name="bt_snackbar_bluetooth_disabled_to_scan">Bluetooth is disabled. Please enable it to scan for devices.</string>
<string name="bt_snackbar_permissions_required_to_connect">Bluetooth permissions are required to connect to %1$s.</string>
<string name="bt_snackbar_permissions_required_to_connect_default">Bluetooth permissions are required to connect to the device.</string>
<string name="bt_snackbar_bluetooth_disabled_to_connect">Bluetooth is disabled. Please enable it to connect to %1$s.</string>
<string name="bt_snackbar_bluetooth_disabled_to_connect_default">Bluetooth is disabled. Please enable it to connect to the device.</string>
<string name="bt_snackbar_scale_saved_as_preferred">%1$s saved as preferred scale.</string>
<string name="bt_snackbar_saved_scale_no_longer_supported">Saved scale \'%1$s\' is no longer supported.</string>
<string name="bt_snackbar_no_scale_saved">No Bluetooth scale saved in settings.</string>
<string name="bt_snackbar_error_no_app_user_selected">Error: No app user selected.</string>
<string name="bt_snackbar_user_input_processed">User input processed.</string>
<string name="bt_snackbar_operation_successful">Operation successful.</string>
<string name="bt_snackbar_operation_failed">Operation failed. Please try again.</string>
<string name="device_placeholder_name">the device</string>
<string name="unknown_scale_name">Unknown Scale</string>
<!-- Bluetooth Scales Messages --> <!-- Bluetooth Scales Messages -->
<string name="bluetooth_scale_trisa_message_not_paired_instruction">This scale has not been paired!\n\nHold the button on the bottom of the scale to switch it to pairing mode, and then reconnect to retrieve the device password.</string> <string name="bluetooth_scale_trisa_message_not_paired_instruction">This scale has not been paired!\n\nHold the button on the bottom of the scale to switch it to pairing mode, and then reconnect to retrieve the device password.</string>
<string name="bluetooth_scale_trisa_success_pairing">Pairing succeeded!\n\nReconnect to retrieve measurement data.</string> <string name="bluetooth_scale_trisa_success_pairing">Pairing succeeded!\n\nReconnect to retrieve measurement data.</string>