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:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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(
|
||||||
|
@@ -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(
|
||||||
|
@@ -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>
|
||||||
|
@@ -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>
|
||||||
|
Reference in New Issue
Block a user