From 48d2fb85ced961a9b4131de405bdb42066089dc9 Mon Sep 17 00:00:00 2001 From: oliexdev Date: Sat, 2 Aug 2025 17:49:52 +0200 Subject: [PATCH] Fixed some warnings e.g. clean up unused variables and simplify logic --- .../java/com/health/openscale/MainActivity.kt | 5 +- .../core/bluetooth/data/ScaleMeasurement.java | 4 +- .../scalesJava/LegacyScaleAdapter.kt | 26 ++++----- .../com/health/openscale/core/data/Enums.kt | 18 +++--- .../openscale/core/database/AppDatabase.kt | 2 +- .../core/database/DatabaseRepository.kt | 12 +--- .../core/database/UserSettingsRepository.kt | 4 +- .../openscale/ui/screen/SharedViewModel.kt | 4 +- .../bluetooth/BluetoothScannerManager.kt | 22 +------ .../ui/screen/bluetooth/BluetoothViewModel.kt | 6 +- .../components/MeasurementTypeFilterRow.kt | 3 +- .../ui/screen/dialog/DateInputDialog.kt | 2 - .../ui/screen/dialog/IconPickerDialog.kt | 1 - .../ui/screen/settings/AboutScreen.kt | 1 - .../ui/screen/settings/BluetoothScreen.kt | 1 - .../settings/DataManagementSettingsScreen.kt | 17 +----- .../screen/settings/GeneralSettingsScreen.kt | 5 +- .../settings/MeasurementTypeDetailScreen.kt | 5 +- .../ui/screen/settings/SettingsViewModel.kt | 58 +++++-------------- .../ui/screen/settings/UserDetailScreen.kt | 7 ++- .../ui/screen/statistics/StatisticsScreen.kt | 2 +- .../openscale/ui/screen/table/TableScreen.kt | 4 +- .../com/health/openscale/ui/theme/Theme.kt | 3 +- .../app/src/main/res/xml/file_paths.xml | 14 +---- 24 files changed, 65 insertions(+), 161 deletions(-) diff --git a/android_app/app/src/main/java/com/health/openscale/MainActivity.kt b/android_app/app/src/main/java/com/health/openscale/MainActivity.kt index 2b8624ad..bbef7e29 100644 --- a/android_app/app/src/main/java/com/health/openscale/MainActivity.kt +++ b/android_app/app/src/main/java/com/health/openscale/MainActivity.kt @@ -17,7 +17,6 @@ */ package com.health.openscale -import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -57,7 +56,7 @@ import kotlinx.coroutines.launch * @param context The context used to access string resources. * @return A list of [MeasurementType] objects. */ -fun getDefaultMeasurementTypes(context: Context): List { +fun getDefaultMeasurementTypes(): List { return listOf( MeasurementType(key = MeasurementTypeKey.WEIGHT, unit = UnitType.KG, color = 0xFFEF2929.toInt(), icon = "ic_weight", isPinned = true, isEnabled = true), MeasurementType(key = MeasurementTypeKey.BMI, color = 0xFFF57900.toInt(), icon = "ic_bmi", isDerived = true, isPinned = true, isEnabled = true), @@ -152,7 +151,7 @@ class MainActivity : ComponentActivity() { LogManager.d(TAG, "Checking for first app start. isFirstAppStart: $isActuallyFirstStart") if (isActuallyFirstStart) { LogManager.i(TAG, "First app start detected. Inserting default measurement types...") - val defaultTypesToInsert = getDefaultMeasurementTypes(this@MainActivity) + val defaultTypesToInsert = getDefaultMeasurementTypes() db.measurementTypeDao().insertAll(defaultTypesToInsert) userSettingsRepository.setFirstAppStartCompleted(false) LogManager.i(TAG, "Default measurement types inserted and first start marked as completed.") diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/data/ScaleMeasurement.java b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/data/ScaleMeasurement.java index 16f6a0b2..aff76e16 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/data/ScaleMeasurement.java +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/data/ScaleMeasurement.java @@ -19,10 +19,9 @@ package com.health.openscale.core.bluetooth.data; import java.util.Date; -public class ScaleMeasurement implements Cloneable { +public class ScaleMeasurement { private int id; private int userId; - private boolean enabled; private Date dateTime; private float weight; private float fat; @@ -34,7 +33,6 @@ public class ScaleMeasurement implements Cloneable { public ScaleMeasurement() { userId = -1; - enabled = true; dateTime = new Date(); weight = 0.0f; fat = 0.0f; diff --git a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt index e2253f4a..cb32522f 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/bluetooth/scalesJava/LegacyScaleAdapter.kt @@ -99,7 +99,7 @@ class LegacyScaleAdapter( override fun handleMessage(msg: Message) { val adapter = adapterRef.get() ?: return // Adapter instance might have been garbage collected - val status = BluetoothCommunication.BT_STATUS.values().getOrNull(msg.what) + val status = BluetoothCommunication.BT_STATUS.entries.getOrNull(msg.what) val eventData = msg.obj val arg1 = msg.arg1 val arg2 = msg.arg2 @@ -219,43 +219,43 @@ class LegacyScaleAdapter( } } - override fun connect(deviceAddress: String, uiScaleUser: ScaleUser?, appUserId: Int?) { + override fun connect(address: String, scaleUser: ScaleUser?, appUserId: Int?) { adapterScope.launch { val currentDeviceName = currentTargetAddress ?: bluetoothDriverInstance.driverName() if (_isConnected.value || _isConnecting.value) { - LogManager.w(TAG, "connect: Already connected/connecting to $currentDeviceName. Ignoring request for $deviceAddress.") - if (currentTargetAddress != deviceAddress && currentTargetAddress != null) { + LogManager.w(TAG, "connect: Already connected/connecting to $currentDeviceName. Ignoring request for $address.") + if (currentTargetAddress != address && currentTargetAddress != null) { val message = applicationContext.getString(R.string.legacy_adapter_connect_busy, currentTargetAddress) - _eventsFlow.tryEmit(BluetoothEvent.ConnectionFailed(deviceAddress, message)) + _eventsFlow.tryEmit(BluetoothEvent.ConnectionFailed(address, message)) } else if (currentTargetAddress == null) { // This case implies isConnecting is true but currentTargetAddress is null, // which might indicate a race condition or an incomplete previous cleanup. // Allow proceeding with the new connection attempt. - LogManager.d(TAG, "connect: Retrying connection for $deviceAddress to ${bluetoothDriverInstance.driverName()} while isConnecting=true but currentTargetAddress=null") + LogManager.d(TAG, "connect: Retrying connection for $address to ${bluetoothDriverInstance.driverName()} while isConnecting=true but currentTargetAddress=null") } else { // Already connecting to or connected to the same deviceAddress return@launch } } - LogManager.i(TAG, "connect: REQUEST for address $deviceAddress to driver ${bluetoothDriverInstance.driverName()}, UI ScaleUser ID: ${uiScaleUser?.id}, AppUserID: $appUserId") + LogManager.i(TAG, "connect: REQUEST for address $address to driver ${bluetoothDriverInstance.driverName()}, UI ScaleUser ID: ${scaleUser?.id}, AppUserID: $appUserId") _isConnecting.value = true _isConnected.value = false - currentTargetAddress = deviceAddress // Store the address being connected to - currentInternalUser = uiScaleUser + currentTargetAddress = address // Store the address being connected to + currentInternalUser = scaleUser LogManager.d(TAG, "connect: Internal user for connection: ${currentInternalUser?.id}, AppUserID: $appUserId") currentInternalUser?.let { bluetoothDriverInstance.setSelectedScaleUser(it) } appUserId?.let { bluetoothDriverInstance.setSelectedScaleUserId(it) } - LogManager.d(TAG, "connect: Calling connect() on Java driver instance (${bluetoothDriverInstance.driverName()}) for $deviceAddress.") + LogManager.d(TAG, "connect: Calling connect() on Java driver instance (${bluetoothDriverInstance.driverName()}) for $address.") try { - bluetoothDriverInstance.connect(deviceAddress) + bluetoothDriverInstance.connect(address) } catch (e: Exception) { - LogManager.e(TAG, "connect: Exception while calling bluetoothDriverInstance.connect() for $deviceAddress to ${bluetoothDriverInstance.driverName()}", e) + LogManager.e(TAG, "connect: Exception while calling bluetoothDriverInstance.connect() for $address to ${bluetoothDriverInstance.driverName()}", e) val message = applicationContext.getString(R.string.legacy_adapter_connect_exception, bluetoothDriverInstance.driverName(), e.message) - _eventsFlow.tryEmit(BluetoothEvent.ConnectionFailed(deviceAddress, message)) + _eventsFlow.tryEmit(BluetoothEvent.ConnectionFailed(address, message)) cleanupAfterDisconnect() // Ensure state is reset } } diff --git a/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt b/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt index bd509bf0..220f0822 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/data/Enums.kt @@ -83,28 +83,28 @@ enum class WeightUnit { override fun toString(): String { when (this) { - WeightUnit.LB -> return "lb" - WeightUnit.ST -> return "st" - WeightUnit.KG -> return "kg" + LB -> return "lb" + ST -> return "st" + KG -> return "kg" } } fun toInt(): Int { when (this) { - WeightUnit.LB -> return 1 - WeightUnit.ST -> return 2 - WeightUnit.KG -> return 0 + LB -> return 1 + ST -> return 2 + KG -> return 0 } } companion object { fun fromInt(unit: Int): WeightUnit { when (unit) { - 1 -> return WeightUnit.LB - 2 -> return WeightUnit.ST + 1 -> return LB + 2 -> return ST } - return WeightUnit.KG + return KG } } } diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/AppDatabase.kt b/android_app/app/src/main/java/com/health/openscale/core/database/AppDatabase.kt index 3b976283..bd31c44c 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/database/AppDatabase.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/database/AppDatabase.kt @@ -71,7 +71,7 @@ abstract class AppDatabase : RoomDatabase() { companion object { private const val TAG = "AppDatabase" - const val DATABASE_NAME = "openScaleDB.db" + const val DATABASE_NAME = "openScale.db" @Volatile private var INSTANCE: AppDatabase? = null diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/DatabaseRepository.kt b/android_app/app/src/main/java/com/health/openscale/core/database/DatabaseRepository.kt index 0b49a90c..c84b02c2 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/database/DatabaseRepository.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/database/DatabaseRepository.kt @@ -354,7 +354,7 @@ class DatabaseRepository( if (weightKg == null || weightKg <= 0f || heightCm == null || heightCm <= 0f || - birthDateTimestamp <= 0L || gender == null + birthDateTimestamp <= 0L ) { LogManager.d(CALC_PROCESS_TAG, "BMR calculation skipped: Missing or invalid weight, height, birthdate, or gender.") return null @@ -367,10 +367,6 @@ class DatabaseRepository( when (gender) { GenderType.MALE -> (10.0f * weightKg) + (6.25f * heightCm) - (5.0f * ageYears) + 5.0f GenderType.FEMALE -> (10.0f * weightKg) + (6.25f * heightCm) - (5.0f * ageYears) - 161.0f - else -> { - LogManager.w(CALC_PROCESS_TAG, "BMR calculation not supported for gender: '$gender'. User ID: ${user.id}") - null - } } } else { LogManager.w(CALC_PROCESS_TAG, "Invalid age for BMR calculation: $ageYears years. User ID: ${user.id}") @@ -414,7 +410,7 @@ class DatabaseRepository( val gender = user.gender val ageYears = CalculationUtil.dateToAge(user.birthDate) - if (gender == null || ageYears <= 0) { + if (ageYears <= 0) { LogManager.w(CALC_PROCESS_TAG, "Fat Caliper calculation skipped: Invalid gender ($gender) or age ($ageYears years). User ID: ${user.id}") return null } @@ -441,10 +437,6 @@ class DatabaseRepository( k2 = 0.0000023f ka = 0.0001392f } - else -> { - LogManager.w(CALC_PROCESS_TAG, "Fat Caliper calculation not supported for gender: '$gender'. User ID: ${user.id}") - return null - } } val bodyDensity = k0 - (k1 * sumSkinfoldsMm) + (k2 * sumSkinfoldsMm * sumSkinfoldsMm) - (ka * ageYears) diff --git a/android_app/app/src/main/java/com/health/openscale/core/database/UserSettingsRepository.kt b/android_app/app/src/main/java/com/health/openscale/core/database/UserSettingsRepository.kt index 752552b2..aeed1b67 100644 --- a/android_app/app/src/main/java/com/health/openscale/core/database/UserSettingsRepository.kt +++ b/android_app/app/src/main/java/com/health/openscale/core/database/UserSettingsRepository.kt @@ -107,7 +107,7 @@ interface UserSettingsRepository { /** * Implementation of [UserSettingsRepository] using Jetpack DataStore. */ -class UserSettingsRepositoryImpl(private val context: Context) : UserSettingsRepository { +class UserSettingsRepositoryImpl(context: Context) : UserSettingsRepository { private val dataStore: DataStore = context.userSettingsDataStore private val TAG = "UserSettingsRepository" // Tag for logging @@ -315,7 +315,7 @@ class UserSettingsRepositoryImpl(private val context: Context) : UserSettingsRep } } else -> { - val errorMsg = "Unsupported type for preference: $keyName (Type: ${value!!::class.java.name})" + val errorMsg = "Unsupported type for preference: $keyName (Type: ${value::class.java.name})" LogManager.e(TAG, errorMsg) throw IllegalArgumentException(errorMsg) // This will be caught by the outer try-catch } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/SharedViewModel.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/SharedViewModel.kt index 30b8e599..e782fee8 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/SharedViewModel.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/SharedViewModel.kt @@ -395,7 +395,7 @@ class SharedViewModel( return@combine emptyList() } - if (globalTypes.isEmpty() && measurements.isNotEmpty()) { + if (globalTypes.isEmpty()) { LogManager.w(TAG, "Global measurement types are empty during enrichment. Trend calculation will be limited or inaccurate. (Data Enrichment Warning)") return@combine measurements.map { currentMeasurement -> val trendValuesUnsorted = currentMeasurement.values.map { currentValueWithType -> @@ -472,7 +472,7 @@ class SharedViewModel( TimeRangeFilter.LAST_7_DAYS -> calendar.add(Calendar.DAY_OF_YEAR, -7) TimeRangeFilter.LAST_30_DAYS -> calendar.add(Calendar.DAY_OF_YEAR, -30) TimeRangeFilter.LAST_365_DAYS -> calendar.add(Calendar.DAY_OF_YEAR, -365) - TimeRangeFilter.ALL_DAYS -> { /* Handled */ } + else -> { /* Handled */ } } calendar.set(Calendar.HOUR_OF_DAY, 0) calendar.set(Calendar.MINUTE, 0) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothScannerManager.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothScannerManager.kt index fa79a704..40b9188e 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothScannerManager.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothScannerManager.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import java.util.UUID +import androidx.core.util.isNotEmpty /** * Data class to hold information about a scanned Bluetooth LE device. @@ -294,7 +295,7 @@ class BluetoothScannerManager( if (newDevice.isSupported || !newDevice.name.isNullOrEmpty() || newDevice.serviceUuids.isNotEmpty() || - (newDevice.manufacturerData != null && newDevice.manufacturerData.size() > 0) + (newDevice.manufacturerData != null && newDevice.manufacturerData.isNotEmpty()) ) { deviceMap[newDevice.address] = newDevice listShouldBeUpdated = true @@ -323,23 +324,4 @@ class BluetoothScannerManager( } } - /** - * Extension function for content-based comparison of two `SparseArray?` instances. - * The standard `equals` on `SparseArray` only checks for reference equality. - */ - private fun SparseArray?.contentEquals(other: SparseArray?): Boolean { - if (this === other) return true - if (this == null || other == null) return false - if (this.size() != other.size()) return false - - for (i in 0 until this.size()) { - val key = this.keyAt(i) - val valueThis = this.valueAt(i) - val valueOther = other.get(key) - if (valueOther == null || !valueThis.contentEquals(valueOther)) { - return false - } - } - return true - } } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothViewModel.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothViewModel.kt index 9bb32a32..4a06e3fd 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothViewModel.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/bluetooth/BluetoothViewModel.kt @@ -59,8 +59,6 @@ enum class ConnectionStatus { NONE, /** Bluetooth adapter is present and enabled, but not actively scanning or connected. */ IDLE, - /** Actively scanning for Bluetooth devices. */ - SCANNING, /** No active connection to a device. */ DISCONNECTED, /** Attempting to establish a connection to a device. */ @@ -142,8 +140,6 @@ class BluetoothViewModel( val scanError: StateFlow = bluetoothScannerManager.scanError // --- Connection State Flows (from BluetoothConnectionManager) --- - /** Emits the name of the currently connected device, or null if not connected. */ - val connectedDeviceName: StateFlow = bluetoothConnectionManager.connectedDeviceName /** Emits the MAC address of the currently connected device, or null if not connected. */ val connectedDeviceAddress: StateFlow = bluetoothConnectionManager.connectedDeviceAddress /** Emits the current [ConnectionStatus] of the Bluetooth device. */ @@ -253,7 +249,7 @@ class BluetoothViewModel( id = appUser.id userName = appUser.name birthday = Date(appUser.birthDate) // Ensure birthDate is in millis - bodyHeight = appUser.heightCm?.toFloat() ?: 0f // Default to 0f if height is null + bodyHeight = appUser.heightCm ?: 0f // Default to 0f if height is null gender = appUser.gender } } diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt index f68bab3d..ef206d6b 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/components/MeasurementTypeFilterRow.kt @@ -111,7 +111,7 @@ fun MeasurementTypeFilterRow( if (selectableTypes.isNotEmpty()) { if (savedTypeIdsSet.isNotEmpty()) { // Filter saved IDs to include only those present in the current selectableTypes - var validPersistedIds = savedTypeIdsSet + val validPersistedIds = savedTypeIdsSet .mapNotNull { it.toIntOrNull() } .filter { id -> selectableTypes.any { type -> type.id == id } } @@ -145,7 +145,6 @@ fun MeasurementTypeFilterRow( } } else { // No selectable types are available - initialIdsToDisplay = emptyList() if (displayedSelectedIds.isNotEmpty() || savedTypeIdsSet.isNotEmpty()) { // Clear any previous selection if types become unavailable displayedSelectedIds = emptyList() diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt index 0a60890d..32862118 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/DateInputDialog.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.Alignment 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.painterResource import androidx.compose.ui.unit.dp @@ -51,7 +50,6 @@ fun DateInputDialog( onDismiss: () -> Unit, onConfirm: (Long) -> Unit ) { - val context = LocalContext.current val datePickerState = rememberDatePickerState( initialSelectedDateMillis = initialTimestamp ) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt index c340f462..8b78adde 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/dialog/IconPickerDialog.kt @@ -72,7 +72,6 @@ fun getIconResIdByName(name: String): Int { @Composable fun IconPickerDialog( - currentIcon: String, onIconSelected: (String) -> Unit, onDismiss: () -> Unit ) { diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/AboutScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/AboutScreen.kt index c2d84149..c37ad7f1 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/AboutScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/AboutScreen.kt @@ -38,7 +38,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothScreen.kt index 295e6a50..cbd005aa 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/BluetoothScreen.kt @@ -439,7 +439,6 @@ fun DeviceCardItem( isCurrentlySaved: Boolean, onClick: () -> Unit ) { - val context = LocalContext.current val supportColor = if (deviceInfo.isSupported) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) val unknownDeviceName = stringResource(R.string.unknown_device_placeholder) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/DataManagementSettingsScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/DataManagementSettingsScreen.kt index 501f338a..28b4b825 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/DataManagementSettingsScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/DataManagementSettingsScreen.kt @@ -91,12 +91,6 @@ sealed class DataManagementSettingListItem { ) : DataManagementSettingListItem() } -/** - * Represents a header item in a list, used for section titles. - * @param title The text of the header. - */ -data class HeaderItem(val title: String) : DataManagementSettingListItem() // While not used in the provided snippet, it's good practice to document all parts of a sealed class if they exist. - /** * Composable screen for managing application data, including import/export of measurements, * database backup/restore, and deletion of user data or the entire database. @@ -133,7 +127,6 @@ fun DataManagementSettingsScreen( val context = LocalContext.current var activeSafActionUserId by remember { mutableStateOf(null) } // Stores user ID for SAF actions like CSV export/import - var activeSafActionId by remember { mutableStateOf(null) } // Stores action ID for distinguishing SAF operations // --- ActivityResultLauncher for CSV Export --- val exportCsvLauncher = rememberLauncherForActivityResult( @@ -143,7 +136,6 @@ fun DataManagementSettingsScreen( activeSafActionUserId?.let { userId -> settingsViewModel.performCsvExport(userId, fileUri, context.contentResolver) activeSafActionUserId = null // Reset after use - activeSafActionId = null } } } @@ -157,7 +149,6 @@ fun DataManagementSettingsScreen( activeSafActionUserId?.let { userId -> settingsViewModel.performCsvImport(userId, fileUri, context.contentResolver) activeSafActionUserId = null // Reset after use - activeSafActionId = null } } } @@ -170,7 +161,6 @@ fun DataManagementSettingsScreen( uri?.let { fileUri -> // activeSafActionUserId is not relevant here as it's a global backup. settingsViewModel.performDatabaseBackup(fileUri, context.applicationContext, context.contentResolver) - activeSafActionId = null // Reset } } ) @@ -182,7 +172,6 @@ fun DataManagementSettingsScreen( uri?.let { fileUri -> // activeSafActionUserId is not relevant here. settingsViewModel.performDatabaseRestore(fileUri, context.applicationContext, context.contentResolver) - activeSafActionId = null // Reset } } ) @@ -193,7 +182,6 @@ fun DataManagementSettingsScreen( when (event) { is SafEvent.RequestCreateFile -> { activeSafActionUserId = event.userId // Retain for CSV export if applicable - activeSafActionId = event.actionId if (event.actionId == SettingsViewModel.ACTION_ID_BACKUP_DB) { backupDbLauncher.launch(event.suggestedName) } else { // Assumption: other CreateFile is CSV export @@ -202,7 +190,6 @@ fun DataManagementSettingsScreen( } is SafEvent.RequestOpenFile -> { activeSafActionUserId = event.userId // Retain for CSV import if applicable - activeSafActionId = event.actionId if (event.actionId == SettingsViewModel.ACTION_ID_RESTORE_DB) { // For DB Restore, we might expect specific MIME types, // e.g., "application/octet-stream" or "application/x-sqlite3" for .db, @@ -305,7 +292,7 @@ fun DataManagementSettingsScreen( ) { // Regular Actions items(regularDataManagementItems.size) { index -> - val item = regularDataManagementItems[index] as DataManagementSettingListItem.ActionItem // Safe cast + val item = regularDataManagementItems[index] SettingsCardItem( label = item.label, icon = item.icon, @@ -334,7 +321,7 @@ fun DataManagementSettingsScreen( } items(destructiveDataManagementItems.size) { index -> - val item = destructiveDataManagementItems[index] as DataManagementSettingListItem.ActionItem // Safe cast + val item = destructiveDataManagementItems[index] SettingsCardItem( label = item.label, icon = item.icon, diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/GeneralSettingsScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/GeneralSettingsScreen.kt index 6160af4a..6ffcd9ec 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/GeneralSettingsScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/GeneralSettingsScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Switch @@ -64,7 +65,6 @@ import com.health.openscale.core.utils.LogManager import com.health.openscale.ui.screen.SharedViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -75,7 +75,6 @@ fun GeneralSettingsScreen( ) { val context = LocalContext.current val scope = rememberCoroutineScope() - val currentAppDisplayLocale = Locale.getDefault() // Get supported languages (enum instances) val supportedLanguagesEnumEntries = remember { @@ -190,7 +189,7 @@ fun GeneralSettingsScreen( ExposedDropdownMenuDefaults.TrailingIcon(expanded = expandedLanguageMenu) }, modifier = Modifier - .menuAnchor() + .menuAnchor(type = MenuAnchorType.PrimaryNotEditable) .fillMaxWidth() ) diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt index 9d5bab97..ff2225fb 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/MeasurementTypeDetailScreen.kt @@ -257,7 +257,7 @@ fun MeasurementTypeDetailScreen( expanded = expandedUnit, onDismissRequest = { expandedUnit = false } ) { - UnitType.values().forEach { unit -> + UnitType.entries.forEach { unit -> DropdownMenuItem( text = { Text(unit.name.lowercase().replaceFirstChar { it.uppercase() }) }, onClick = { @@ -293,7 +293,7 @@ fun MeasurementTypeDetailScreen( expanded = expandedInputType, onDismissRequest = { expandedInputType = false } ) { - InputFieldType.values().forEach { type -> + InputFieldType.entries.forEach { type -> DropdownMenuItem( text = { Text(type.name.lowercase().replaceFirstChar { it.uppercase() }) }, onClick = { @@ -328,7 +328,6 @@ fun MeasurementTypeDetailScreen( // Icon Picker Dialog if (showIconPicker) { IconPickerDialog( - currentIcon = selectedIcon, onIconSelected = { selectedIcon = it showIconPicker = false // Close picker after selection diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/SettingsViewModel.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/SettingsViewModel.kt index 6886f348..68cc3405 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/SettingsViewModel.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/SettingsViewModel.kt @@ -224,10 +224,10 @@ class SettingsViewModel( try { val allAppTypes: List = repository.getAllMeasurementTypes().first() val exportableValueTypes = allAppTypes.filter { - it.key != null && it.key != MeasurementTypeKey.DATE && it.key != MeasurementTypeKey.TIME + it.key != MeasurementTypeKey.DATE && it.key != MeasurementTypeKey.TIME } val valueColumnKeys = exportableValueTypes - .mapNotNull { it.key?.name } + .map { it.key.name } .distinct() val dateColumnKey = MeasurementTypeKey.DATE.name @@ -265,10 +265,7 @@ class SettingsViewModel( measurementData.values.forEach { mvWithType -> val typeEntity = mvWithType.type val valueEntity = mvWithType.value - if (typeEntity.key != null && - typeEntity.key != MeasurementTypeKey.DATE && - typeEntity.key != MeasurementTypeKey.TIME && - valueColumnKeys.contains(typeEntity.key.name) + if (typeEntity.key != MeasurementTypeKey.DATE && typeEntity.key != MeasurementTypeKey.TIME && valueColumnKeys.contains(typeEntity.key.name) ) { val valueAsString: String? = when (typeEntity.inputType) { InputFieldType.TEXT -> valueEntity.textValue @@ -339,7 +336,7 @@ class SettingsViewModel( try { // ... (Rest of the import logic including CSV parsing as before) ... val allAppTypes: List = repository.getAllMeasurementTypes().first() - val typeMapByKeyName = allAppTypes.filter { it.key != null }.associateBy { it.key!!.name } + val typeMapByKeyName = allAppTypes.associateBy { it.key.name } val dateColumnKey = MeasurementTypeKey.DATE.name val timeColumnKey = MeasurementTypeKey.TIME.name @@ -360,18 +357,17 @@ class SettingsViewModel( readAllAsSequence().forEachIndexed { rowIndex, row -> if (rowIndex == 0) { // Header row header = row - dateColumnIndex = header?.indexOf(dateColumnKey) - ?: throw IOException("CSV header is missing the mandatory column '$dateColumnKey'.") + dateColumnIndex = header.indexOf(dateColumnKey) // ... (rest of header processing) - timeColumnIndex = header?.indexOf(timeColumnKey) ?: -1 - header?.forEachIndexed { colIdx, columnName -> + timeColumnIndex = header.indexOf(timeColumnKey) + header.forEachIndexed { colIdx, columnName -> if (columnName != dateColumnKey && columnName != timeColumnKey) { typeMapByKeyName[columnName]?.let { type -> valueColumnMap[colIdx] = type } ?: LogManager.w(TAG, "CSV import for user $userId: Column '$columnName' in CSV not found in known measurement types. It will be ignored.") } } - if (valueColumnMap.isEmpty() && header?.any { it != dateColumnKey && it != timeColumnKey } == true) { + if (valueColumnMap.isEmpty() && header.any { it != dateColumnKey && it != timeColumnKey }) { LogManager.w(TAG, "CSV import for user $userId: No measurement value columns in CSV could be mapped to known types.") } return@forEachIndexed // Continue to next row @@ -437,11 +433,11 @@ class SettingsViewModel( if (isValidValue) { measurementValues.add(mv) } else { - LogManager.w(TAG, "CSV import for user $userId: Could not parse value '$valueString' for type '${type.key?.name}' in row ${rowIndex + 1}.") + LogManager.w(TAG, "CSV import for user $userId: Could not parse value '$valueString' for type '${type.key.name}' in row ${rowIndex + 1}.") valuesSkippedParseError++ } } catch (e: Exception) { - LogManager.w(TAG, "CSV import for user $userId: Error processing value '$valueString' for type '${type.key?.name}' in row ${rowIndex + 1}.", e) + LogManager.w(TAG, "CSV import for user $userId: Error processing value '$valueString' for type '${type.key.name}' in row ${rowIndex + 1}.", e) valuesSkippedParseError++ } } @@ -460,19 +456,8 @@ class SettingsViewModel( importedMeasurementsCount = newMeasurementsToSave.size LogManager.i(TAG, "CSV Import for User ID $userId successful. $importedMeasurementsCount measurements imported.") - // Constructing the detailed message for UI - // This part is tricky if you want one single formatted string from resources. - // Often, it's better to send a base success message and log details, - // or have multiple UiMessageEvents if details are crucial for UI. - // Here's an attempt to build arguments for a potentially complex string resource: - val messageArgs = mutableListOf(importedMeasurementsCount) var detailsForMessage = "" if (linesSkippedMissingDate > 0) { - // This assumes you have a string like: "%1$d records. %2$d skipped (date), %3$d skipped (parse), %4$d values skipped." - // Or you emit separate messages. - // For simplicity, let's assume a main message and details are appended if they exist. - // This would require a more complex string resource or multiple resources. - // R.string.import_successful_details might take multiple args detailsForMessage += " ($linesSkippedMissingDate rows skipped due to missing dates" } if (linesSkippedDateParseError > 0) { @@ -686,7 +671,7 @@ class SettingsViewModel( fun startDatabaseBackup() { viewModelScope.launch { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val dbName = repository.getDatabaseName() ?: "openscale_db" + val dbName = repository.getDatabaseName() val suggestedName = "${dbName}_backup_${timeStamp}.zip" _safEvent.emit(SafEvent.RequestCreateFile(suggestedName, ACTION_ID_BACKUP_DB, userId = 0)) LogManager.i(TAG, "Database backup process started. Suggested name: $suggestedName. SAF event emitted.") @@ -698,12 +683,7 @@ class SettingsViewModel( _isLoadingBackup.value = true LogManager.i(TAG, "Performing database backup to URI: $backupUri") try { - val dbName = repository.getDatabaseName() ?: run { - LogManager.e(TAG, "Database backup error: Database name could not be retrieved.") - _uiMessageEvents.emit(UiMessageEvent.Resource(R.string.backup_error_db_name_not_retrieved)) - _isLoadingBackup.value = false - return@launch - } + val dbName = repository.getDatabaseName() val dbFile = applicationContext.getDatabasePath(dbName) val dbDir = dbFile.parentFile ?: run { LogManager.e(TAG, "Database backup error: Database directory could not be determined for $dbName.") @@ -788,12 +768,7 @@ class SettingsViewModel( _isLoadingRestore.value = true LogManager.i(TAG, "Performing database restore from URI: $restoreUri") try { - val dbName = repository.getDatabaseName() ?: run { - LogManager.e(TAG, "Database restore error: Database name could not be retrieved.") - _uiMessageEvents.emit(UiMessageEvent.Resource(R.string.backup_error_db_name_not_retrieved)) // Re-use backup error string - _isLoadingRestore.value = false - return@launch - } + val dbName = repository.getDatabaseName() val dbFile = applicationContext.getDatabasePath(dbName) val dbDir = dbFile.parentFile ?: run { LogManager.e(TAG, "Database restore error: Database directory could not be determined for $dbName.") @@ -923,12 +898,7 @@ class SettingsViewModel( LogManager.i(TAG, "Database closed for deletion.") withContext(Dispatchers.IO) { - val dbName = repository.getDatabaseName() // Get it before it's potentially gone - if (dbName == null) { - LogManager.e(TAG, "Failed to get database name. Cannot ensure complete deletion.") - _uiMessageEvents.emit(UiMessageEvent.Resource(R.string.delete_db_error)) // Generic error - return@withContext - } + val dbName = repository.getDatabaseName() val databaseDeleted = applicationContext.deleteDatabase(dbName) // Also try to delete -shm and -wal files explicitly, as deleteDatabase might not always get them. diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/UserDetailScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/UserDetailScreen.kt index 9b3483c7..4e084ca7 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/UserDetailScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/settings/UserDetailScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Text @@ -198,7 +199,7 @@ fun UserDetailScreen( Text(stringResource(id = R.string.user_detail_label_gender)) // "Gender" Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - GenderType.values().forEach { option -> + GenderType.entries.forEach { option -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -230,7 +231,7 @@ fun UserDetailScreen( ExposedDropdownMenuDefaults.TrailingIcon(expanded = activityLevelExpanded) }, modifier = Modifier - .menuAnchor() // Anchors the dropdown menu to this text field + .menuAnchor(type = MenuAnchorType.PrimaryNotEditable) // Anchors the dropdown menu to this text field .fillMaxWidth() ) @@ -238,7 +239,7 @@ fun UserDetailScreen( expanded = activityLevelExpanded, onDismissRequest = { activityLevelExpanded = false } ) { - ActivityLevel.values().forEach { selectionOption -> + ActivityLevel.entries.forEach { selectionOption -> DropdownMenuItem( text = { Text(selectionOption.name.lowercase().replaceFirstChar { it.uppercaseChar().toString() }) }, onClick = { diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt index 2ad5bb61..22fb5549 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/statistics/StatisticsScreen.kt @@ -154,7 +154,7 @@ fun StatisticsScreen(sharedViewModel: SharedViewModel) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } - } else if (measurementsForStatistics.isEmpty() && !isLoadingData && relevantTypesForStatsDisplay.isEmpty()) { + } else if (measurementsForStatistics.isEmpty() && relevantTypesForStatsDisplay.isEmpty()) { // Show a message if no relevant measurement types are configured or no data is present. // This condition is refined to also check relevantTypesForStatsDisplay. Box( diff --git a/android_app/app/src/main/java/com/health/openscale/ui/screen/table/TableScreen.kt b/android_app/app/src/main/java/com/health/openscale/ui/screen/table/TableScreen.kt index 847aae73..e0c5ba4e 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/screen/table/TableScreen.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/screen/table/TableScreen.kt @@ -269,7 +269,7 @@ fun TableScreen( .fillMaxSize() .padding(16.dp), Alignment.Center ) { Text(noColumnsSelectedMessage) } - } else if (tableData.isEmpty() && enrichedMeasurements.isNotEmpty() && displayedTypes.isNotEmpty()) { + } else if (tableData.isEmpty()) { // This case implies data exists, but not for the currently selected combination of columns. Box( Modifier @@ -458,7 +458,7 @@ fun TableDataCellInternal( else -> null } - if (trendIconVector != null && trendContentDescription != null) { + if (trendIconVector != null) { Icon( imageVector = trendIconVector, contentDescription = trendContentDescription, diff --git a/android_app/app/src/main/java/com/health/openscale/ui/theme/Theme.kt b/android_app/app/src/main/java/com/health/openscale/ui/theme/Theme.kt index 8445f8ff..6802694a 100644 --- a/android_app/app/src/main/java/com/health/openscale/ui/theme/Theme.kt +++ b/android_app/app/src/main/java/com/health/openscale/ui/theme/Theme.kt @@ -17,7 +17,6 @@ */ package com.health.openscale.ui.theme -import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.darkColorScheme @@ -48,7 +47,7 @@ fun OpenScaleTheme( content: @Composable () -> Unit ) { val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + dynamicColor -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } diff --git a/android_app/app/src/main/res/xml/file_paths.xml b/android_app/app/src/main/res/xml/file_paths.xml index 49e5a6b9..c7f6c373 100644 --- a/android_app/app/src/main/res/xml/file_paths.xml +++ b/android_app/app/src/main/res/xml/file_paths.xml @@ -1,5 +1,5 @@ - + -