From 9389ef6e6a6ede61b0737f26481fbb722c999169 Mon Sep 17 00:00:00 2001 From: Nedifinita Date: Sat, 19 Jul 2025 17:34:02 +0800 Subject: [PATCH] refactor: improve layout and formatting of HistoryScreen UI components --- .../java/org/noxylva/lbjconsole/BLEClient.kt | 199 +++++++++++++----- .../org/noxylva/lbjconsole/MainActivity.kt | 117 +++++++--- .../lbjconsole/ui/screens/HistoryScreen.kt | 86 ++++---- .../lbjconsole/ui/screens/MapScreen.kt | 2 +- .../lbjconsole/ui/screens/MonitorScreen.kt | 5 +- 5 files changed, 285 insertions(+), 124 deletions(-) diff --git a/app/src/main/java/org/noxylva/lbjconsole/BLEClient.kt b/app/src/main/java/org/noxylva/lbjconsole/BLEClient.kt index 67e624c..18d30e0 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/BLEClient.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/BLEClient.kt @@ -18,9 +18,7 @@ import java.util.* class BLEClient(private val context: Context) : BluetoothGattCallback() { companion object { - const val TAG = "LBJ_BT" - const val SCAN_PERIOD = 10000L - + const val TAG = "LBJ_BT" val SERVICE_UUID = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb") val CHAR_UUID = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb") @@ -42,20 +40,69 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { private var targetDeviceName: String? = null private var bluetoothLeScanner: BluetoothLeScanner? = null + private var continuousScanning = false + private var autoReconnect = true + private var lastKnownDeviceAddress: String? = null + private var connectionAttempts = 0 + private var isReconnecting = false + private val leScanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { val device = result.device val deviceName = device.name - if (targetDeviceName != null) { - if (deviceName == null || !deviceName.equals(targetDeviceName, ignoreCase = true)) { - return + + val shouldShowDevice = when { + targetDeviceName != null -> { + deviceName != null && deviceName.equals(targetDeviceName, ignoreCase = true) + } + else -> { + deviceName != null && ( + deviceName.contains("LBJ", ignoreCase = true) || + deviceName.contains("Receiver", ignoreCase = true) || + deviceName.contains("Train", ignoreCase = true) || + deviceName.contains("Console", ignoreCase = true) || + deviceName.contains("ESP", ignoreCase = true) || + deviceName.contains("Arduino", ignoreCase = true) || + deviceName.contains("BLE", ignoreCase = true) || + deviceName.contains("UART", ignoreCase = true) || + deviceName.contains("Serial", ignoreCase = true) + ) && !( + deviceName.contains("Midea", ignoreCase = true) || + deviceName.contains("TV", ignoreCase = true) || + deviceName.contains("Phone", ignoreCase = true) || + deviceName.contains("Watch", ignoreCase = true) || + deviceName.contains("Headset", ignoreCase = true) || + deviceName.contains("Speaker", ignoreCase = true) + ) } } - scanCallback?.invoke(device) + + if (shouldShowDevice) { + Log.d(TAG, "Showing filtered device: $deviceName") + scanCallback?.invoke(device) + } + + if (targetDeviceName != null && !isConnected && !isReconnecting) { + if (deviceName != null && deviceName.equals(targetDeviceName, ignoreCase = true)) { + Log.i(TAG, "Found target device: $deviceName, auto-connecting") + lastKnownDeviceAddress = device.address + connectImmediately(device.address) + } + } + + if (lastKnownDeviceAddress == device.address && !isConnected && !isReconnecting) { + Log.i(TAG, "Found known device, reconnecting immediately") + connectImmediately(device.address) + } } override fun onScanFailed(errorCode: Int) { Log.e(TAG, "BLE scan failed code=$errorCode") + if (continuousScanning) { + handler.post { + restartScan() + } + } } } @@ -107,12 +154,9 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { return } - handler.postDelayed({ - stopScan() - }, SCAN_PERIOD) - isScanning = true - Log.d(TAG, "Starting BLE scan target=${targetDeviceName ?: "Any"}") + continuousScanning = true + Log.d(TAG, "Starting continuous BLE scan target=${targetDeviceName ?: "Any"}") bluetoothLeScanner?.startScan(leScanCallback) } catch (e: SecurityException) { Log.e(TAG, "Scan security error: ${e.message}") @@ -127,6 +171,40 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { if (isScanning) { bluetoothLeScanner?.stopScan(leScanCallback) isScanning = false + continuousScanning = false + Log.d(TAG, "Stopped BLE scan") + } + } + + @SuppressLint("MissingPermission") + private fun restartScan() { + if (!continuousScanning) return + + try { + bluetoothLeScanner?.stopScan(leScanCallback) + bluetoothLeScanner?.startScan(leScanCallback) + isScanning = true + Log.d(TAG, "Restarted BLE scan") + } catch (e: Exception) { + Log.e(TAG, "Failed to restart scan: ${e.message}") + } + } + + private fun connectImmediately(address: String) { + if (isReconnecting) return + isReconnecting = true + + handler.post { + connect(address) { connected -> + isReconnecting = false + if (connected) { + connectionAttempts = 0 + Log.i(TAG, "Successfully connected to $address") + } else { + connectionAttempts++ + Log.w(TAG, "Connection attempt $connectionAttempts failed for $address") + } + } } } @@ -183,17 +261,7 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { bluetoothGatt = device.connectGatt(context, false, this, BluetoothDevice.TRANSPORT_LE) Log.d(TAG, "Connecting to address=$address") - - - handler.postDelayed({ - if (!isConnected && deviceAddress == address) { - Log.e(TAG, "Connection timeout reconnecting") - - bluetoothGatt?.close() - bluetoothGatt = - device.connectGatt(context, false, this, BluetoothDevice.TRANSPORT_LE) - } - }, 10000) + return true } catch (e: Exception) { @@ -286,30 +354,30 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { if (status != BluetoothGatt.GATT_SUCCESS) { Log.e(TAG, "Connection error status=$status") isConnected = false - + isReconnecting = false if (status == 133 || status == 8) { - Log.e(TAG, "GATT error closing connection") + Log.e(TAG, "GATT error, attempting immediate reconnection") try { gatt.close() bluetoothGatt = null - deviceAddress?.let { address -> - handler.postDelayed({ - Log.d(TAG, "Reconnecting to device") - val device = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address) - bluetoothGatt = device.connectGatt( - context, - false, - this, - BluetoothDevice.TRANSPORT_LE - ) - }, 2000) + if (autoReconnect) { + Log.d(TAG, "Immediate reconnection to device") + handler.post { + val device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address) + bluetoothGatt = device.connectGatt( + context, + false, + this, + BluetoothDevice.TRANSPORT_LE + ) + } + } } } catch (e: Exception) { - Log.e(TAG, "Reconnect error: ${e.message}") + Log.e(TAG, "Immediate reconnect error: ${e.message}") } } @@ -320,32 +388,34 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { when (newState) { BluetoothProfile.STATE_CONNECTED -> { isConnected = true + isReconnecting = false + connectionAttempts = 0 Log.i(TAG, "Connected to GATT server") handler.post { connectionStateCallback?.invoke(true) } - - handler.postDelayed({ + handler.post { try { gatt.discoverServices() } catch (e: Exception) { Log.e(TAG, "Service discovery failed: ${e.message}") } - }, 500) + } } BluetoothProfile.STATE_DISCONNECTED -> { isConnected = false + isReconnecting = false Log.i(TAG, "Disconnected from GATT server") handler.post { connectionStateCallback?.invoke(false) } - if (!deviceAddress.isNullOrBlank()) { - handler.postDelayed({ - Log.d(TAG, "Reconnecting after disconnect") + if (!deviceAddress.isNullOrBlank() && autoReconnect) { + handler.post { + Log.d(TAG, "Immediate reconnection after disconnect") connect(deviceAddress!!, connectionStateCallback) - }, 3000) + } } } } @@ -357,12 +427,14 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { private var lastDataTime = 0L + @Suppress("DEPRECATION") override fun onCharacteristicChanged( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { super.onCharacteristicChanged(gatt, characteristic) + @Suppress("DEPRECATION") val newData = characteristic.value?.let { String(it, StandardCharsets.UTF_8) } ?: return @@ -381,18 +453,17 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { val bufferContent = dataBuffer.toString() val currentTime = System.currentTimeMillis() - - if (lastDataTime > 0 && currentTime - lastDataTime > 5000) { - Log.w(TAG, "Data timeout ${(currentTime - lastDataTime) / 1000}s") - + if (lastDataTime > 0) { + val timeDiff = currentTime - lastDataTime + if (timeDiff > 10000) { + Log.w(TAG, "Long data gap: ${timeDiff / 1000}s") + } } Log.d(TAG, "Buffer size=${dataBuffer.length} bytes") - tryExtractJson(bufferContent) - lastDataTime = currentTime } @@ -511,9 +582,16 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") ) if (descriptor != null) { - descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE - val writeResult = gatt.writeDescriptor(descriptor) - Log.d(TAG, "Descriptor write result=$writeResult") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val writeResult = gatt.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) + Log.d(TAG, "Descriptor write result=$writeResult") + } else { + @Suppress("DEPRECATION") + descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + @Suppress("DEPRECATION") + val writeResult = gatt.writeDescriptor(descriptor) + Log.d(TAG, "Descriptor write result=$writeResult") + } } else { Log.e(TAG, "Descriptor not found") @@ -540,10 +618,19 @@ class BLEClient(private val context: Context) : BluetoothGattCallback() { private fun requestDataAfterDelay() { - handler.postDelayed({ + handler.post { statusCallback?.let { callback -> getStatus(callback) } - }, 1000) + } } + + fun setAutoReconnect(enabled: Boolean) { + autoReconnect = enabled + Log.d(TAG, "Auto reconnect set to: $enabled") + } + + fun getConnectionAttempts(): Int = connectionAttempts + + fun getLastKnownDeviceAddress(): String? = lastKnownDeviceAddress } \ No newline at end of file diff --git a/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt b/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt index 2080092..0946a47 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/MainActivity.kt @@ -6,6 +6,7 @@ import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothManager import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import java.io.File import android.os.Build import android.os.Bundle @@ -30,6 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -109,9 +111,8 @@ class MainActivity : ComponentActivity() { val locationPermissionsGranted = permissions.filter { it.key.contains("LOCATION") }.all { it.value } if (bluetoothPermissionsGranted && locationPermissionsGranted) { - Log.d(TAG, "Permissions granted") - - startScan() + Log.d(TAG, "Permissions granted, starting auto scan and connect") + startAutoScanAndConnect() } else { Log.e(TAG, "Missing permissions: $permissions") deviceStatus = "需要蓝牙和位置权限" @@ -374,16 +375,16 @@ class MainActivity : ComponentActivity() { runOnUiThread { if (connected) { deviceStatus = "已连接" + temporaryStatusMessage = null Log.d(TAG, "Connected to device name=${device.name ?: "Unknown"}") } else { - deviceStatus = "连接失败或已断开连接" - Log.e(TAG, "Connection failed name=${device.name ?: "Unknown"}") + deviceStatus = "连接失败,正在重试..." + Log.e(TAG, "Connection failed, auto-retry enabled for name=${device.name ?: "Unknown"}") } } } deviceAddress = device.address - stopScan() } @@ -393,7 +394,8 @@ class MainActivity : ComponentActivity() { runOnUiThread { try { val isTestData = jsonData.optBoolean("test_flag", false) - lastUpdateTime = Date() + lastUpdateTime = Date() + temporaryStatusMessage = null if (isTestData) { Log.i(TAG, "Received keep-alive signal") @@ -437,15 +439,56 @@ class MainActivity : ComponentActivity() { private fun updateTemporaryStatusMessage(message: String) { temporaryStatusMessage = message - - Handler(Looper.getMainLooper()).postDelayed({ - if (temporaryStatusMessage == message) { - temporaryStatusMessage = null - } - }, 3000) + } + private fun startAutoScanAndConnect() { + Log.d(TAG, "Starting auto scan and connect") + + if (!hasBluetoothPermissions()) { + Log.e(TAG, "Missing bluetooth permissions for auto scan") + deviceStatus = "需要蓝牙和位置权限" + return + } + + val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + val bluetoothAdapter = bluetoothManager.adapter + if (bluetoothAdapter == null) { + Log.e(TAG, "Bluetooth adapter unavailable") + deviceStatus = "设备不支持蓝牙" + return + } + + if (!bluetoothAdapter.isEnabled) { + Log.e(TAG, "Bluetooth adapter disabled") + deviceStatus = "请启用蓝牙" + return + } + + bleClient.setAutoReconnect(true) + + val targetDeviceName = if (settingsDeviceName.isNotBlank() && settingsDeviceName != "LBJReceiver") { + settingsDeviceName + } else { + "LBJReceiver" + } + + Log.d(TAG, "Auto scanning for target device: $targetDeviceName") + deviceStatus = "正在自动扫描连接..." + + bleClient.scanDevices(targetDeviceName) { device -> + val deviceName = device.name ?: "Unknown" + Log.d(TAG, "Auto scan found device: $deviceName") + + if (deviceName.equals(targetDeviceName, ignoreCase = true)) { + Log.d(TAG, "Found target device, auto connecting to: $deviceName") + bleClient.stopScan() + connectToDevice(device) + } + } + } + private fun startScan() { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter @@ -461,25 +504,21 @@ class MainActivity : ComponentActivity() { return } + bleClient.setAutoReconnect(true) + isScanning = true foundDevices = emptyList() - val targetDeviceName = settingsDeviceName.ifBlank { null } - Log.d(TAG, "Starting BLE scan target=${targetDeviceName ?: "Any"}") + val targetDeviceName = if (settingsDeviceName.isNotBlank() && settingsDeviceName != "LBJReceiver") { + settingsDeviceName + } else { + null + } + Log.d(TAG, "Starting continuous BLE scan target=${targetDeviceName ?: "Any"} (settings=${settingsDeviceName})") bleClient.scanDevices(targetDeviceName) { device -> if (!foundDevices.any { it.address == device.address }) { Log.d(TAG, "Found device name=${device.name ?: "Unknown"} address=${device.address}") foundDevices = foundDevices + device - - if (targetDeviceName != null && device.name == targetDeviceName) { - Log.d(TAG, "Found target=$targetDeviceName, connecting") - stopScan() - connectToDevice(device) - } else { - if (targetDeviceName == null) { - showConnectionDialog = true - } - } } } } @@ -490,6 +529,21 @@ class MainActivity : ComponentActivity() { bleClient.stopScan() Log.d(TAG, "Stopped BLE scan") } + + private fun hasBluetoothPermissions(): Boolean { + val bluetoothPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + ContextCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED + } else { + ContextCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED + } + + val locationPermissions = ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED + + return bluetoothPermissions && locationPermissions + } private fun updateDeviceList() { @@ -559,6 +613,16 @@ class MainActivity : ComponentActivity() { Log.d(TAG, "Saved settings deviceName=${settingsDeviceName} tab=${currentTab} mapCenter=${mapCenterPosition} zoom=${mapZoomLevel}") } + override fun onResume() { + super.onResume() + Log.d(TAG, "App resumed") + + if (hasBluetoothPermissions() && !bleClient.isConnected()) { + Log.d(TAG, "App resumed and not connected, starting auto scan") + startAutoScanAndConnect() + } + } + override fun onPause() { super.onPause() saveSettings() @@ -633,7 +697,8 @@ fun MainContent( diffInSec < 3600 -> "${diffInSec / 60}分钟前" else -> "${diffInSec / 3600}小时前" } - delay(1000) + val updateInterval = if (diffInSec < 60) 500L else if (diffInSec < 3600) 30000L else 300000L + delay(updateInterval) } } else { timeSinceLastUpdate.value = null diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt index 899776e..320eb56 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/HistoryScreen.kt @@ -86,7 +86,8 @@ fun HistoryScreen( diffInSec < 3600 -> "${diffInSec / 60}分钟前" else -> "${diffInSec / 3600}小时前" } - delay(1000) + val updateInterval = if (diffInSec < 60) 500L else if (diffInSec < 3600) 30000L else 300000L + delay(updateInterval) } } } @@ -114,7 +115,6 @@ fun HistoryScreen( LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) { if (!isInEditMode) { - delay(300) val selectedIds = selectedRecordsList.map { it.timestamp.time.toString() }.toSet() onStateChange(isInEditMode, selectedIds, expandedStatesMap.toMap(), listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) } @@ -217,11 +217,37 @@ fun HistoryScreen( Column( modifier = Modifier .fillMaxWidth() - .padding(16.dp) + .padding(horizontal = 16.dp, vertical = 8.dp) ) { val recordId = record.timestamp.time.toString() val isExpanded = expandedStatesMap[recordId] == true - val recordMap = record.toMap(showDetailedTime = isExpanded) + val recordMap = record.toMap(showDetailedTime = true) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + if (recordMap.containsKey("time")) { + Column { + recordMap["time"]?.split("\n")?.forEach { timeLine -> + Text( + text = timeLine, + fontSize = 10.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + + Text( + text = "${record.rssi} dBm", + fontSize = 10.sp, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + Spacer(modifier = Modifier.height(2.dp)) Row( modifier = Modifier.fillMaxWidth(), @@ -231,21 +257,6 @@ fun HistoryScreen( val trainDisplay = recordMap["train"]?.toString() ?: "未知列车" - val formattedInfo = when { - record.locoType.isNotEmpty() && record.loco.isNotEmpty() -> { - val shortLoco = if (record.loco.length > 5) { - record.loco.takeLast(5) - } else { - record.loco - } - "${record.locoType}-${shortLoco}" - } - - record.locoType.isNotEmpty() -> record.locoType - record.loco.isNotEmpty() -> record.loco - else -> "" - } - Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp) @@ -284,30 +295,27 @@ fun HistoryScreen( } } } - - if (formattedInfo.isNotEmpty() && formattedInfo != "") { - Text( - text = formattedInfo, - fontSize = 14.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } } - Text( - text = "${record.rssi} dBm", - fontSize = 10.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } + val formattedInfo = when { + record.locoType.isNotEmpty() && record.loco.isNotEmpty() -> { + val shortLoco = if (record.loco.length > 5) { + record.loco.takeLast(5) + } else { + record.loco + } + "${record.locoType}-${shortLoco}" + } - Spacer(modifier = Modifier.height(4.dp)) - - if (recordMap.containsKey("time")) { - recordMap["time"]?.split("\n")?.forEach { timeLine -> + record.locoType.isNotEmpty() -> record.locoType + record.loco.isNotEmpty() -> record.loco + else -> "" + } + + if (formattedInfo.isNotEmpty() && formattedInfo != "") { Text( - text = timeLine, - fontSize = 10.sp, + text = formattedInfo, + fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant ) } diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MapScreen.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MapScreen.kt index 16c9856..8e69801 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MapScreen.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MapScreen.kt @@ -306,7 +306,7 @@ fun MapScreen( val locationProvider = GpsMyLocationProvider(ctx).apply { locationUpdateMinDistance = 10f - locationUpdateMinTime = 1000 + locationUpdateMinTime = 5000 } diff --git a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt index de2322b..427c8f2 100644 --- a/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt +++ b/app/src/main/java/org/noxylva/lbjconsole/ui/screens/MonitorScreen.kt @@ -41,7 +41,8 @@ fun MonitorScreen( diffInSec < 3600 -> "${diffInSec / 60}分钟前" else -> "${diffInSec / 3600}小时前" } - delay(1000) + val updateInterval = if (diffInSec < 60) 500L else if (diffInSec < 3600) 30000L else 300000L + delay(updateInterval) } } } @@ -247,4 +248,4 @@ private fun InfoItem( color = MaterialTheme.colorScheme.onSurface ) } -} \ No newline at end of file +} \ No newline at end of file