feat: auto connect

This commit is contained in:
玖叁 2024-12-24 15:23:13 +08:00
parent 9b8b922ea7
commit 61f37f71de
22 changed files with 268 additions and 105 deletions

View File

@ -61,6 +61,10 @@ dependencies {
implementation(libs.androidx.material3)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.datastore.core.android)
implementation(libs.androidx.datastore.preferences.core.jvm)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.lifecycle.viewmodel.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@ -23,6 +23,7 @@ import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@ -35,6 +36,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import icu.fur93.esp32_car.data.PreferencesDataStore
import icu.fur93.esp32_car.page.ControlGamepadModePage
import icu.fur93.esp32_car.page.ControlPage
import icu.fur93.esp32_car.page.ControlPathfinderModePage
@ -46,10 +48,16 @@ import icu.fur93.esp32_car.ui.theme.Esp32carTheme
import icu.fur93.esp32_car.viewmodel.CarControlUseCase
import icu.fur93.esp32_car.viewmodel.CarViewModel
import java.util.UUID
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collect
class MainActivity : ComponentActivity() {
private val preferencesDataStore by lazy {
PreferencesDataStore(this)
}
private val bluetoothRepository by lazy {
BluetoothRepositoryImpl(this, lifecycleScope)
BluetoothRepositoryImpl(this, lifecycleScope, preferencesDataStore)
}
private val carControlUseCase by lazy {
@ -64,6 +72,7 @@ class MainActivity : ComponentActivity() {
}
}
@RequiresApi(Build.VERSION_CODES.S)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -93,6 +102,16 @@ class MainActivity : ComponentActivity() {
)
}
// 检查权限后尝试自动连接
lifecycleScope.launch {
preferencesDataStore.getLastConnectedDevice().collect { address ->
if (address != null) {
viewModel.connectToDeviceByAddress(address)
}
}
}
setContent {
Esp32carTheme {
App(viewModel)
@ -136,7 +155,11 @@ fun App(viewModel: CarViewModel) {
composable(Route.Control.route) { ControlPage(navController, viewModel) }
composable(Route.Settings.route) { SettingsPage(navController, viewModel) }
composable(Route.ControlPathfinderMode.route) { ControlPathfinderModePage(navController) }
composable(Route.ControlSingleJoystickMode.route) { ControlSingleJoystickModePage(navController) }
composable(Route.ControlSingleJoystickMode.route) {
ControlSingleJoystickModePage(
navController
)
}
composable(Route.ControlGamepadMode.route) { ControlGamepadModePage(navController) }
}
}

View File

@ -0,0 +1,21 @@
package icu.fur93.esp32_car.const
object CarCommands {
const val PACKET_T_HEAD = 0x00u
const val PACKET_T_TAIL = 0xFFu
const val PACKET_R_HEAD = 0x01u
const val PACKET_R_TAIL = 0xFEu
const val PACKET_MAX_LENGTH = 32u
const val CMD_GET_BT_STATUS = 0x10u
const val CMD_GET_SPIFFS_STATUS = 0x11u
const val CMD_GET_DISTANCE = 0x12u
const val CMD_MOTOR_MOVE_CONTROL = 0x20u
const val CMD_MOTOR_STEER_CONTROL = 0x21u
const val CMD_MOTOR_SINGLE_CONTROL = 0x22u
const val CMD_MOTOR_ROTATE_CONTROL = 0x23u
const val CMD_MOTOR_XYR_CONTROL = 0x24u
const val CMD_DEMO_PID = 0xf0u
const val CMD_DEMO_PATH = 0xf1u
const val CMD_STATUS_MOTOR = 0xE0u
}

View File

@ -0,0 +1,28 @@
package icu.fur93.esp32_car.data
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
class PreferencesDataStore(private val context: Context) {
private val lastConnectedDevice = stringPreferencesKey("last_connected_device")
suspend fun saveLastConnectedDevice(address: String) {
context.dataStore.edit { preferences ->
preferences[lastConnectedDevice] = address
}
}
fun getLastConnectedDevice(): Flow<String?> {
return context.dataStore.data.map { preferences ->
preferences[lastConnectedDevice]
}
}
}

View File

@ -0,0 +1,12 @@
package icu.fur93.esp32_car.entity
data class LogEntry(
val timestamp: Long = System.currentTimeMillis(),
val direction: LogDirection,
val data: String
)
enum class LogDirection {
SEND,
RECEIVE
}

View File

@ -0,0 +1,6 @@
package icu.fur93.esp32_car.entity.car
data class CarControlState(
val speed: UInt = 0u,
val direction: UInt = 0u
)

View File

@ -0,0 +1,11 @@
package icu.fur93.esp32_car.entity.car
data class CarState(
val controlState: CarControlState = CarControlState(),
val motorAState: MotorState = MotorState(),
val motorBState: MotorState = MotorState(),
val motorCState: MotorState = MotorState(),
val motorDState: MotorState = MotorState(),
val infraredState: InfraredState = InfraredState(),
val ultrasoundState: UltrasoundState = UltrasoundState()
)

View File

@ -1,6 +1,6 @@
package icu.fur93.esp32_car.entity
package icu.fur93.esp32_car.entity.car
data class ConnectedDeviceInfo(
data class ConnectionInfoState(
val name: String = "",
val address: String = "",
val version: String = ""

View File

@ -0,0 +1,6 @@
package icu.fur93.esp32_car.entity.car
data class InfraredState(
val enable: Boolean = false,
val statusList: List<UInt> = listOf()
)

View File

@ -0,0 +1,7 @@
package icu.fur93.esp32_car.entity.car
data class MotorState(
val pwm: UInt = 0u,
val in1: UInt = 0u,
val in2: UInt = 0u
)

View File

@ -0,0 +1,7 @@
package icu.fur93.esp32_car.entity.car
data class UltrasoundState(
val enable: Boolean = false,
val servoAngle: UInt = 0u,
val distance: UInt = 0u
)

View File

@ -45,7 +45,7 @@ fun HomePage(navController: NavHostController, viewModel: CarViewModel) {
@Composable
fun HomePageStatusCard(viewModel: CarViewModel) {
val deviceInfo by viewModel.deviceInfo.collectAsState()
val deviceInfo by viewModel.connectionInfoState.collectAsState()
val carState by viewModel.carState.collectAsState()
StatusCard(

View File

@ -103,7 +103,7 @@ fun SettingConnectDeviceItem(viewModel: CarViewModel) {
var showDisconnectDialog by remember { mutableStateOf(false) }
val connectionState by viewModel.connectionState.collectAsState()
val deviceInfo by viewModel.deviceInfo.collectAsState()
val connectionInfoState by viewModel.connectionInfoState.collectAsState()
ListItem(
@ -136,7 +136,7 @@ fun SettingConnectDeviceItem(viewModel: CarViewModel) {
AlertDialog(
onDismissRequest = { showDisconnectDialog = false },
title = { Text("断开连接") },
text = { Text("确定要断开与 ${deviceInfo.name} 的连接吗?") },
text = { Text("确定要断开与 ${connectionInfoState.name} 的连接吗?") },
confirmButton = {
TextButton(
onClick = {

View File

@ -14,12 +14,8 @@ import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.widget.Toast
import icu.fur93.esp32_car.const.CarCommands
import icu.fur93.esp32_car.entity.BleDevice
import icu.fur93.esp32_car.viewmodel.CarCommands
import icu.fur93.esp32_car.viewmodel.CarState
import icu.fur93.esp32_car.viewmodel.LogDirection
import icu.fur93.esp32_car.viewmodel.LogEntry
import icu.fur93.esp32_car.viewmodel.MotorState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -29,6 +25,11 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.util.UUID
import icu.fur93.esp32_car.data.PreferencesDataStore
import icu.fur93.esp32_car.entity.LogDirection
import icu.fur93.esp32_car.entity.LogEntry
import icu.fur93.esp32_car.entity.car.CarState
import icu.fur93.esp32_car.entity.car.MotorState
// 蓝牙通信接口
interface BluetoothRepository {
@ -36,6 +37,7 @@ interface BluetoothRepository {
fun observeCarState(): Flow<CarState>
fun observeLogs(): Flow<List<LogEntry>>
fun clearLogs()
val connectionState: StateFlow<ConnectionState>
}
// 添加连接状态枚举
@ -53,7 +55,8 @@ val txCharUUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
// 蓝牙通信实现
class BluetoothRepositoryImpl(
private val context: Context,
private val scope: CoroutineScope
private val scope: CoroutineScope,
private val preferencesDataStore: PreferencesDataStore
) : BluetoothRepository {
private var bluetoothGatt: BluetoothGatt? = null
private var rxCharacteristic: BluetoothGattCharacteristic? = null
@ -72,7 +75,7 @@ class BluetoothRepositoryImpl(
// 连接状态流
private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
override val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
@ -126,7 +129,7 @@ class BluetoothRepositoryImpl(
}
@SuppressLint("MissingPermission")
fun connectToDevice(device: BluetoothDevice) {
fun connectToDevice(device: BluetoothDevice, callback: ((Boolean) -> Unit)? = null) {
_connectionState.value = ConnectionState.CONNECTING
device.connectGatt(
context,
@ -139,7 +142,8 @@ class BluetoothRepositoryImpl(
) {
if (status != BluetoothGatt.GATT_SUCCESS) {
scope.launch(Dispatchers.Main) {
Toast.makeText(context, "连接失败,错误码: $status", Toast.LENGTH_SHORT).show()
Toast.makeText(context, "连接失败,错误码: $status", Toast.LENGTH_SHORT)
.show()
}
return
}
@ -149,18 +153,38 @@ class BluetoothRepositoryImpl(
_connectionState.value = ConnectionState.CONNECTED
scope.launch(Dispatchers.Main) {
gatt.discoverServices()
_connectionState.collect { state ->
if (state == ConnectionState.CONNECTED) {
preferencesDataStore.saveLastConnectedDevice(device.address)
}
}
}
if (callback != null) {
callback(true)
}
}
BluetoothProfile.STATE_CONNECTING -> {
_connectionState.value = ConnectionState.CONNECTING
if (callback != null) {
callback(false)
}
}
BluetoothProfile.STATE_DISCONNECTING -> {
_connectionState.value = ConnectionState.DISCONNECTING
if (callback != null) {
callback(false)
}
}
BluetoothProfile.STATE_DISCONNECTED -> {
bluetoothGatt = null
rxCharacteristic = null
_connectionState.value = ConnectionState.DISCONNECTED
if (callback != null) {
callback(false)
}
}
}
}
@ -193,6 +217,24 @@ class BluetoothRepositoryImpl(
)
}
@SuppressLint("MissingPermission")
fun connectToDeviceByAddress(
address: String,
callback: ((Boolean, BluetoothDevice) -> Unit)? = null
) {
val bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
val device = bluetoothManager.adapter.getRemoteDevice(address)
if (callback != null) {
connectToDevice(device) { isConnected ->
callback(isConnected, device)
}
} else {
connectToDevice(device)
}
}
@SuppressLint("MissingPermission")
override fun sendCommand(command: ByteArray) {
rxCharacteristic?.let { characteristic ->
@ -233,7 +275,8 @@ class BluetoothRepositoryImpl(
private fun isValidPacket(packet: ByteArray): Boolean =
packet[0].toUByte() == CarCommands.PACKET_R_HEAD.toUByte() &&
packet.size == packet[1].toUByte().toInt() &&
packet[packet[1].toUByte().toInt() - 1].toUByte() == CarCommands.PACKET_R_TAIL.toUByte()
packet[packet[1].toUByte()
.toInt() - 1].toUByte() == CarCommands.PACKET_R_TAIL.toUByte()
private fun updateMotorStatus(packet: ByteArray) {
val data = packet.sliceArray(3 until packet[1].toUByte().toInt() - 1)

View File

@ -2,13 +2,13 @@ package icu.fur93.esp32_car.ui.carditem
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import icu.fur93.esp32_car.entity.ConnectedDeviceInfo
import icu.fur93.esp32_car.entity.car.ConnectionInfoState
import icu.fur93.esp32_car.ui.component.CardItem
import icu.fur93.esp32_car.ui.component.StatusCardItemText
@SuppressLint("ComposableNaming")
@Composable
fun StatusCardInfo(deviceInfo: ConnectedDeviceInfo) : CardItem {
fun StatusCardInfo(deviceInfo: ConnectionInfoState) : CardItem {
return CardItem(
title = "当前连接",
content = {

View File

@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import icu.fur93.esp32_car.ui.component.CardItem
import icu.fur93.esp32_car.ui.component.StatusCardItemText
import icu.fur93.esp32_car.viewmodel.InfraredState
import icu.fur93.esp32_car.entity.car.InfraredState
@SuppressLint("ComposableNaming")
@Composable

View File

@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import icu.fur93.esp32_car.ui.component.CardItem
import icu.fur93.esp32_car.ui.component.StatusCardItemText
import icu.fur93.esp32_car.viewmodel.UltrasoundState
import icu.fur93.esp32_car.entity.car.UltrasoundState
@SuppressLint("ComposableNaming")
@Composable

View File

@ -6,7 +6,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import icu.fur93.esp32_car.ui.component.CardItem
import icu.fur93.esp32_car.ui.component.StatusCardItemText
import icu.fur93.esp32_car.viewmodel.CarState
import icu.fur93.esp32_car.entity.car.CarState
@SuppressLint("ComposableNaming")
@Composable

View File

@ -2,8 +2,11 @@ package icu.fur93.esp32_car.ui.component
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.automirrored.outlined.Send
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
@ -29,19 +32,19 @@ fun BottomNavigationBar(
val navItems = listOf(
BottomNavigationItem(
title = "主页",
icon = Icons.Default.Home,
icon = Icons.Outlined.Home,
selectedIcon = Icons.Default.Home,
route = Route.Home
),
BottomNavigationItem(
title = "控制",
icon = Icons.AutoMirrored.Filled.Send,
icon = Icons.AutoMirrored.Outlined.Send,
selectedIcon = Icons.AutoMirrored.Filled.Send,
route = Route.Control
),
BottomNavigationItem(
title = "设置",
icon = Icons.Default.Settings,
icon = Icons.Outlined.Settings,
selectedIcon = Icons.Default.Settings,
route = Route.Settings
)

View File

@ -4,22 +4,22 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Card
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import icu.fur93.esp32_car.ui.theme.CardButtonTypography
data class CardButtonItem(
val text: String,
@ -37,7 +37,7 @@ fun CardButtonGroup(
) {
Text(
text = title,
style = CardButtonTypography.CardButtonGroupTitleTextStyle
style = MaterialTheme.typography.titleLarge
)
Spacer(Modifier.height(CardButtonGroupGap))
LazyVerticalGrid(
@ -45,14 +45,47 @@ fun CardButtonGroup(
horizontalArrangement = Arrangement.spacedBy(CardButtonGroupGap),
verticalArrangement = Arrangement.spacedBy(CardButtonGroupGap)
) {
buttons.forEach { buttonItem ->
item {
CardButton(buttonItem)
items(buttons.size) { index ->
M3CardButton(buttons[index])
}
}
}
@Composable
fun M3CardButton(item: CardButtonItem) {
ElevatedCard(
modifier = item.modifier ?: Modifier
) {
FilledTonalButton(
onClick = { item.onClick?.invoke() },
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = item.text,
style = MaterialTheme.typography.titleMedium
)
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterEnd
) {
Icon(
imageVector = item.icon,
contentDescription = item.text,
modifier = Modifier.size(24.dp)
)
}
}
}
}
}
/* 原代码注释
@Composable
fun CardButton(
item: CardButtonItem
@ -80,3 +113,4 @@ fun CardButton(
}
}
}
*/

View File

@ -4,8 +4,11 @@ import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import icu.fur93.esp32_car.const.CarCommands
import icu.fur93.esp32_car.entity.BleDevice
import icu.fur93.esp32_car.entity.ConnectedDeviceInfo
import icu.fur93.esp32_car.entity.LogEntry
import icu.fur93.esp32_car.entity.car.CarState
import icu.fur93.esp32_car.entity.car.ConnectionInfoState
import icu.fur93.esp32_car.repository.BluetoothRepository
import icu.fur93.esp32_car.repository.BluetoothRepositoryImpl
import icu.fur93.esp32_car.repository.ConnectionState
@ -16,71 +19,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
// 数据模型
data class MotorState(
val pwm: UInt = 0u,
val in1: UInt = 0u,
val in2: UInt = 0u
)
data class InfraredState(
val enable: Boolean = false,
val statusList: List<UInt> = listOf()
)
data class UltrasoundState(
val enable: Boolean = false,
val servoAngle: UInt = 0u,
val distance: UInt = 0u
)
data class CarState(
val controlState: CarControlState = CarControlState(),
val motorAState: MotorState = MotorState(),
val motorBState: MotorState = MotorState(),
val motorCState: MotorState = MotorState(),
val motorDState: MotorState = MotorState(),
val infraredState: InfraredState = InfraredState(),
val ultrasoundState: UltrasoundState = UltrasoundState()
)
data class CarControlState(
val speed: UInt = 0u,
val direction: UInt = 0u
)
data class LogEntry(
val timestamp: Long = System.currentTimeMillis(),
val direction: LogDirection,
val data: String
)
enum class LogDirection {
SEND,
RECEIVE
}
// 命令常量
object CarCommands {
const val PACKET_T_HEAD = 0x00u
const val PACKET_T_TAIL = 0xFFu
const val PACKET_R_HEAD = 0x01u
const val PACKET_R_TAIL = 0xFEu
const val PACKET_MAX_LENGTH = 32u
const val CMD_GET_BT_STATUS = 0x10u
const val CMD_GET_SPIFFS_STATUS = 0x11u
const val CMD_GET_DISTANCE = 0x12u
const val CMD_MOTOR_MOVE_CONTROL = 0x20u
const val CMD_MOTOR_STEER_CONTROL = 0x21u
const val CMD_MOTOR_SINGLE_CONTROL = 0x22u
const val CMD_MOTOR_ROTATE_CONTROL = 0x23u
const val CMD_MOTOR_XYR_CONTROL = 0x24u
const val CMD_DEMO_PID = 0xf0u
const val CMD_DEMO_PATH = 0xf1u
const val CMD_STATUS_MOTOR = 0xE0u
}
// 用例
class CarControlUseCase(
@ -140,9 +78,21 @@ class CarViewModel(
fun startScan() = (repository as BluetoothRepositoryImpl).startScan()
fun stopScan() = (repository as BluetoothRepositoryImpl).stopScan()
fun connectToDevice(device: BluetoothDevice) {
updateDeviceInfo(device)
(repository as BluetoothRepositoryImpl).connectToDevice(device)
(repository as BluetoothRepositoryImpl).connectToDevice(device) { isConnected ->
if (isConnected) {
updateDeviceInfo(device)
}
}
}
fun connectToDeviceByAddress(address: String) {
(repository as BluetoothRepositoryImpl).connectToDeviceByAddress(address) { isConnected, device ->
if (isConnected) {
updateDeviceInfo(device)
}
}
}
val carState: StateFlow<CarState> = repository.observeCarState()
@ -152,8 +102,8 @@ class CarViewModel(
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
// 设备信息状态
private val _deviceInfo = MutableStateFlow(ConnectedDeviceInfo())
val deviceInfo: StateFlow<ConnectedDeviceInfo> = _deviceInfo.asStateFlow()
private val _connectionInfoState = MutableStateFlow(ConnectionInfoState())
val connectionInfoState: StateFlow<ConnectionInfoState> = _connectionInfoState.asStateFlow()
val connectionState: StateFlow<ConnectionState> =
(repository as BluetoothRepositoryImpl).connectionState
@ -169,7 +119,7 @@ class CarViewModel(
connectionState.collect { state ->
if (state == ConnectionState.DISCONNECTED) {
// 断开连接时清空设备信息
_deviceInfo.value = ConnectedDeviceInfo()
_connectionInfoState.value = ConnectionInfoState()
}
}
}
@ -178,7 +128,7 @@ class CarViewModel(
// 更新设备信息的方法
@SuppressLint("MissingPermission")
fun updateDeviceInfo(device: BluetoothDevice) {
_deviceInfo.value = ConnectedDeviceInfo(
_connectionInfoState.value = ConnectionInfoState(
name = device.name ?: "未知设备",
address = device.address,
version = "001"

View File

@ -10,6 +10,10 @@ activityCompose = "1.9.3"
composeBom = "2024.04.01"
roomKtx = "2.6.1"
navigationCompose = "2.8.5"
datastoreCoreAndroid = "1.1.1"
datastorePreferencesCoreJvm = "1.1.1"
datastorePreferences = "1.1.1"
lifecycleViewmodelCompose = "2.9.0-alpha08"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -28,6 +32,10 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "roomKtx" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-datastore-core-android = { group = "androidx.datastore", name = "datastore-core-android", version.ref = "datastoreCoreAndroid" }
androidx-datastore-preferences-core-jvm = { group = "androidx.datastore", name = "datastore-preferences-core-jvm", version.ref = "datastorePreferencesCoreJvm" }
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version = "2.7.0" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }