From efa93ab912b380e21a178f29d7401bcb5110db3f Mon Sep 17 00:00:00 2001 From: gogacoder Date: Mon, 1 Jan 2024 21:56:23 +0700 Subject: [PATCH] Device screen was implemented --- app/build.gradle.kts | 2 + app/src/main/ic_helicopter-playstore.png | Bin 0 -> 48310 bytes .../java/com/helible/pilot/MainActivity.kt | 49 ++++-- .../helible/pilot/components/DeviceScreen.kt | 25 --- .../pilot/components/FlightControlScreen.kt | 130 ---------------- .../com/helible/pilot/components/Title.kt | 2 +- .../components/deviceScreen/DeviceBadge.kt | 96 ++++++++++++ .../deviceScreen/DeviceConnectionStatus.kt | 66 ++++++++ .../deviceScreen/DeviceControlScreen.kt | 143 ++++++++++++++++++ .../deviceScreen/defaultDeviceActionsLinks.kt | 64 ++++++++ .../BluetoothScannerScreen.kt | 4 +- .../{ => scannerScreen}/DeviceItem.kt | 17 ++- .../DiscoveredDevicesList.kt | 3 +- .../pilot/viewmodels/BluetoothViewModel.kt | 15 +- .../pilot/viewmodels/PermissionsViewModel.kt | 2 - .../SavedPreferences.kt | 2 +- app/src/main/res/drawable/cancel.xml | 10 ++ app/src/main/res/drawable/code_blocks.xml | 10 ++ app/src/main/res/drawable/construction.xml | 10 ++ app/src/main/res/drawable/controller_gen.xml | 10 ++ app/src/main/res/drawable/energy.xml | 10 ++ app/src/main/res/drawable/helicopter.png | Bin 0 -> 15220 bytes app/src/main/res/drawable/helicopter_icon.xml | 10 ++ app/src/main/res/drawable/instant_mix.xml | 10 ++ app/src/main/res/drawable/joystick.xml | 10 ++ app/src/main/res/drawable/logout.xml | 11 ++ app/src/main/res/drawable/sync.xml | 10 ++ app/src/main/res/drawable/tune.xml | 10 ++ 28 files changed, 546 insertions(+), 185 deletions(-) create mode 100644 app/src/main/ic_helicopter-playstore.png delete mode 100644 app/src/main/java/com/helible/pilot/components/DeviceScreen.kt delete mode 100644 app/src/main/java/com/helible/pilot/components/FlightControlScreen.kt create mode 100644 app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt create mode 100644 app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt create mode 100644 app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceControlScreen.kt create mode 100644 app/src/main/java/com/helible/pilot/components/deviceScreen/defaultDeviceActionsLinks.kt rename app/src/main/java/com/helible/pilot/components/{ => scannerScreen}/BluetoothScannerScreen.kt (97%) rename app/src/main/java/com/helible/pilot/components/{ => scannerScreen}/DeviceItem.kt (85%) rename app/src/main/java/com/helible/pilot/components/{ => scannerScreen}/DiscoveredDevicesList.kt (97%) rename app/src/main/java/com/helible/pilot/{components => viewmodels}/SavedPreferences.kt (97%) create mode 100644 app/src/main/res/drawable/cancel.xml create mode 100644 app/src/main/res/drawable/code_blocks.xml create mode 100644 app/src/main/res/drawable/construction.xml create mode 100644 app/src/main/res/drawable/controller_gen.xml create mode 100644 app/src/main/res/drawable/energy.xml create mode 100644 app/src/main/res/drawable/helicopter.png create mode 100644 app/src/main/res/drawable/helicopter_icon.xml create mode 100644 app/src/main/res/drawable/instant_mix.xml create mode 100644 app/src/main/res/drawable/joystick.xml create mode 100644 app/src/main/res/drawable/logout.xml create mode 100644 app/src/main/res/drawable/sync.xml create mode 100644 app/src/main/res/drawable/tune.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 120ff06..02c26aa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp").version("1.6.10-1.0.4") + id("org.jlleitschuh.gradle.ktlint").version("12.0.3") } android { @@ -64,6 +65,7 @@ dependencies { implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") implementation("androidx.navigation:navigation-compose:2.6.0") implementation("com.squareup.moshi:moshi-kotlin:1.14.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") diff --git a/app/src/main/ic_helicopter-playstore.png b/app/src/main/ic_helicopter-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d4e631fe8f644e74da729b2cec2496751157b6 GIT binary patch literal 48310 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�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 Mw9>m$DWk9d1227A&j0`b literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/helible/pilot/MainActivity.kt b/app/src/main/java/com/helible/pilot/MainActivity.kt index c9c9aa2..57e37c5 100644 --- a/app/src/main/java/com/helible/pilot/MainActivity.kt +++ b/app/src/main/java/com/helible/pilot/MainActivity.kt @@ -15,10 +15,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.helible.pilot.components.BluetoothScannerScreen -import com.helible.pilot.components.FlightControlScreen -import com.helible.pilot.components.AppPreferences -import com.helible.pilot.components.SavedPreferencesImpl +import com.helible.pilot.components.scannerScreen.BluetoothScannerScreen +import com.helible.pilot.components.deviceScreen.DeviceControlScreen +import com.helible.pilot.viewmodels.AppPreferences +import com.helible.pilot.viewmodels.SavedPreferencesImpl +import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList import com.helible.pilot.permissions.PermissionsLauncher import com.helible.pilot.permissions.PermissionsRequest import com.helible.pilot.permissions.RequestHardwareFeatures @@ -31,8 +32,10 @@ import com.helible.pilot.viewmodels.PreferencesViewModel class MainActivity : ComponentActivity() { // TODO: device screen logic + // TODO: constrain text size // TODO: add Bluetooth telemetry... // TODO: move text strings to resources + // TODO: review permissions logic private val preferences by lazy { SavedPreferencesImpl(getSharedPreferences(packageName, MODE_PRIVATE)) @@ -127,32 +130,48 @@ class MainActivity : ComponentActivity() { } composable("device") { - FlightControlScreen( + DeviceControlScreen( bluetoothUiState = bluetoothState, getPreferences = { preferencesViewModel.preferences }, - navigateToScanner = { navController.navigate("scanner") }, + navigateToPage = { page -> navController.navigate(page) }, connectToDevice = { device -> bluetoothViewModel.connectToDevice( device ) }, - sendRotorsState = { message -> - bluetoothViewModel.sendRotorsDutySpeed( - message - ) - }, disconnectFromDevice = { bluetoothViewModel.disconnectFromDevice() }, - sendEmergStop = { bluetoothViewModel.sendEmergStop() }, - sendAlarm = { message -> bluetoothViewModel.sendAlarmState(message) }, - sendR3Duty = { duty -> bluetoothViewModel.sendR3Duty(duty) } + deviceActionsList = defaultDeviceActionsList() ) if (preferencesViewModel.preferences != null) BackHandler {} } + composable("console") + { + + } + composable("codeblocks") + { + + } + composable("imu_calibration") + { + + } + composable("motor_test") + { + + } + composable("pid_settings") + { + + } + composable("reports") + { + + } } } } } - } } diff --git a/app/src/main/java/com/helible/pilot/components/DeviceScreen.kt b/app/src/main/java/com/helible/pilot/components/DeviceScreen.kt deleted file mode 100644 index 89cd545..0000000 --- a/app/src/main/java/com/helible/pilot/components/DeviceScreen.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.helible.pilot.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun DeviceScreen() { - Scaffold( - topBar = { - Text(text = "") - } - ) { innerPadding -> - Column( - modifier = Modifier.padding(innerPadding) - ) { - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/FlightControlScreen.kt b/app/src/main/java/com/helible/pilot/components/FlightControlScreen.kt deleted file mode 100644 index 77c786e..0000000 --- a/app/src/main/java/com/helible/pilot/components/FlightControlScreen.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.helible.pilot.components - -import android.util.Log -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Warning -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.FilledIconButton -import androidx.compose.material3.Icon -import androidx.compose.material3.Slider -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import com.helible.pilot.dataclasses.AlarmStateMessage -import com.helible.pilot.dataclasses.BluetoothUiState -import com.helible.pilot.dataclasses.RotorsSpeedMessage -import kotlin.math.roundToInt - - -@Composable -fun FlightControlScreen( - bluetoothUiState: BluetoothUiState, - getPreferences: () -> AppPreferences?, - navigateToScanner: () -> Unit, - connectToDevice: (String) -> Unit, - disconnectFromDevice: () -> Unit, - sendRotorsState: (RotorsSpeedMessage) -> Unit, - sendAlarm: (AlarmStateMessage) -> Unit, - sendEmergStop: () -> Unit, - sendR3Duty: (Int) -> Unit, -) { - LaunchedEffect(Unit) { - val preferences: AppPreferences? = getPreferences() - if (preferences == null) { - navigateToScanner() - } else { - connectToDevice(preferences.deviceAddress) - } - } - - var rotor1Duty by remember { mutableStateOf(0f) } - var rotor2Duty by remember { mutableStateOf(0f) } - var rotor3Duty by remember { mutableStateOf(0f) } - - BackHandler { - disconnectFromDevice() - Log.i("FlightScreen", "Disconnected from the device") - navigateToScanner() - } - when { - bluetoothUiState.isConnecting -> { - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - CircularProgressIndicator() - Text(text = "Подключение...", textAlign = TextAlign.Center) - } - } - - else -> { - - Column(modifier = Modifier.fillMaxSize()) { - Text( - text = "Device name: ${getPreferences()?.deviceName ?: "(устройство отключено)"}", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center - ) - Text(text = "Rotor 1 value: $rotor1Duty", textAlign = TextAlign.Center) - Slider( - value = rotor1Duty, - onValueChange = { rotor1Duty = it.roundToInt().toFloat() }, - valueRange = 0f..1000f, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp) - ) - Text(text = "Rotor 2 value: $rotor2Duty", textAlign = TextAlign.Center) - Slider( - value = rotor2Duty, - onValueChange = { rotor2Duty = it.roundToInt().toFloat() }, - valueRange = 0f..1000f, - modifier = Modifier - .fillMaxWidth() - .padding(10.dp) - ) - Text(text = "Rotor 3 value: $rotor1Duty", textAlign = TextAlign.Center) - Slider( - value = rotor3Duty, - onValueChange = { rotor3Duty = it.roundToInt().toFloat() }, - valueRange = 0f..1000f, - - modifier = Modifier - .fillMaxWidth() - .padding(10.dp) - ) - FilledIconButton( - onClick = { sendEmergStop() }, - modifier = Modifier.padding(10.dp) - ) { - Icon( - Icons.Default.Warning, - contentDescription = null, - modifier = Modifier.padding(3.dp) - ) - Text( - text = "СТОП", - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(3.dp) - ) - } - } - } - } -} diff --git a/app/src/main/java/com/helible/pilot/components/Title.kt b/app/src/main/java/com/helible/pilot/components/Title.kt index 714fa00..c4d397b 100644 --- a/app/src/main/java/com/helible/pilot/components/Title.kt +++ b/app/src/main/java/com/helible/pilot/components/Title.kt @@ -14,6 +14,6 @@ fun Title(text: String, modifier: Modifier = Modifier) { textAlign = TextAlign.Center, modifier = modifier, fontSize = 23.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.ExtraBold ) } \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt new file mode 100644 index 0000000..d7490e4 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceBadge.kt @@ -0,0 +1,96 @@ +package com.helible.pilot.components.deviceScreen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +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.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.helible.pilot.R +import com.helible.pilot.dataclasses.BluetoothUiState +import com.helible.pilot.viewmodels.AppPreferences + +@Composable +fun DeviceBadge( + bluetoothUiState: BluetoothUiState, + tryToReconnect: () -> Unit, + getPreferences: () -> AppPreferences? +) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 15.dp), + shape = RoundedCornerShape(15) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 15.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier + .size(60.dp) + .graphicsLayer { + clip = true + shape = RoundedCornerShape(15) + } + .fillMaxSize()) { + Image( + painter = painterResource(id = R.drawable.helicopter), + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) + } + Column(modifier = Modifier.padding(horizontal = 8.dp)) { + Text( + text = getPreferences()?.deviceName ?: "null", + fontWeight = FontWeight.Bold + ) + DeviceConnectionStatus(bluetoothUiState) + Text(text = "Заряд батареи: 79%") + } + Box( + contentAlignment = Alignment.CenterEnd, + modifier = Modifier + .padding(2.dp) + .fillMaxWidth() + ) { + Icon( + Icons.Default.Refresh, + contentDescription = null, + modifier = Modifier + .requiredSize(Icons.Default.Refresh.defaultWidth) + .clickable { tryToReconnect() } + ) + } + } + } +} + +@Preview +@Composable +fun DeviceBadgePreview() { + DeviceBadge( + bluetoothUiState = BluetoothUiState(isConnected = true), + tryToReconnect = {}, + getPreferences = {AppPreferences("Helicopter", "AA:BB:CC:FF:DD")} + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt new file mode 100644 index 0000000..037b87a --- /dev/null +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceConnectionStatus.kt @@ -0,0 +1,66 @@ +package com.helible.pilot.components.deviceScreen + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +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.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.helible.pilot.R +import com.helible.pilot.dataclasses.BluetoothUiState + +@Composable +fun DeviceConnectionStatus(bluetoothState: BluetoothUiState) { + Row(verticalAlignment = Alignment.CenterVertically) { + if (bluetoothState.isConnected) { + Icon( + Icons.Default.CheckCircle, + contentDescription = null, + tint = Color(56, 200, 35), + modifier = Modifier + .requiredSize(Icons.Default.CheckCircle.defaultWidth) + .padding(2.dp) + ) + Text ("На связи") + } + else if (bluetoothState.errorMessage != null) { + Icon( + painter = painterResource(id = R.drawable.cancel), + contentDescription = null, + tint = Color(255, 24, 35), + modifier = Modifier + .requiredSize(R.drawable.cancel.dp) + .padding(2.dp) + ) + Text ("Ошибка: ${bluetoothState.errorMessage}") + } + else if (bluetoothState.isConnecting) { + Icon( + painter = painterResource(id = R.drawable.sync), + contentDescription = null, + tint = Color(40, 123, 207), + modifier = Modifier + .requiredSize(R.drawable.sync.dp) + .padding(2.dp) + ) + Text ("Подключение...") + } + } +} + +@Preview +@Composable +fun DeviceConnectionStatusPreview() { + Surface { + DeviceConnectionStatus(bluetoothState = BluetoothUiState(isConnecting = true)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceControlScreen.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceControlScreen.kt new file mode 100644 index 0000000..9a62fcd --- /dev/null +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/DeviceControlScreen.kt @@ -0,0 +1,143 @@ +package com.helible.pilot.components.deviceScreen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.helible.pilot.R +import com.helible.pilot.components.Title +import com.helible.pilot.dataclasses.BluetoothUiState +import com.helible.pilot.viewmodels.AppPreferences + +@Composable +fun DeviceControlScreen( + bluetoothUiState: BluetoothUiState, + getPreferences: () -> AppPreferences?, + navigateToPage: (String) -> Unit, + connectToDevice: (String) -> Unit, + disconnectFromDevice: () -> Unit, + deviceActionsList: Map, String>>>>, + scannerPageName: String = "scanner", +) { + LaunchedEffect(Unit) { + val preferences: AppPreferences? = getPreferences() + if (preferences == null) { + navigateToPage(scannerPageName) + } else { + connectToDevice(preferences.deviceAddress) + } + } + + LaunchedEffect(key1 = bluetoothUiState.isEnabled) { + /* Trying to reconnect, when bluetooth is turned on */ + val preferences = getPreferences() + if(preferences != null && bluetoothUiState.isEnabled) + connectToDevice(preferences.deviceAddress) + } + + LaunchedEffect(key1 = bluetoothUiState.isLocationEnabled) { + /* Trying to reconnect, when location is turned on */ + val preferences = getPreferences() + if(preferences != null && bluetoothUiState.isLocationEnabled) + connectToDevice(preferences.deviceAddress) + } + + Column( + Modifier + .fillMaxSize() + .padding(5.dp) + ) { + Title( + text = "Ваше устройство", + modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp) + ) + DeviceBadge( + bluetoothUiState = bluetoothUiState, + tryToReconnect = { + /* Trying to reconnect, when error occurred */ + val preferences = getPreferences() + if(preferences != null) + connectToDevice(preferences.deviceAddress) + }, + getPreferences = getPreferences + ) + + Column(modifier = Modifier.padding(horizontal = 3.dp)) { + for (section in deviceActionsList) { + Text(section.key, + color = Color.Gray, + fontWeight = FontWeight.Light, + modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp) + ) + for (action in section.value) { + TextButton(onClick = { /* TODO */}) { + Icon( + painter = painterResource(id = action.second.first.first), + tint = action.second.first.second, + contentDescription = null, + modifier = Modifier.size(25.dp) + ) + Text( + text = action.second.second, + color = MaterialTheme.colorScheme.inverseSurface, + modifier = Modifier.padding(horizontal = 5.dp) + ) + } + } + } + + TextButton(onClick = { + disconnectFromDevice() + navigateToPage(scannerPageName) + }, modifier = Modifier.padding(vertical = 10.dp)) { + Icon(painterResource(id = R.drawable.logout), contentDescription = null) + Text( + text = "Отвязать устройство", + color = MaterialTheme.colorScheme.inverseSurface, + modifier = Modifier.padding(horizontal = 4.dp) + ) + } + } + } +} + +@Preview +@Composable +fun DeviceControlScreenPreview() { + Surface { + DeviceControlScreen( + bluetoothUiState = BluetoothUiState(isConnected = true), + getPreferences = { AppPreferences("Helicopter", "AA:BB:CC:DD:FF") }, + navigateToPage = { /*TODO*/ }, + connectToDevice = {}, + disconnectFromDevice = { /*TODO*/ }, + deviceActionsList = defaultDeviceActionsList() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/deviceScreen/defaultDeviceActionsLinks.kt b/app/src/main/java/com/helible/pilot/components/deviceScreen/defaultDeviceActionsLinks.kt new file mode 100644 index 0000000..510ef43 --- /dev/null +++ b/app/src/main/java/com/helible/pilot/components/deviceScreen/defaultDeviceActionsLinks.kt @@ -0,0 +1,64 @@ +package com.helible.pilot.components.deviceScreen + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.helible.pilot.R + +@Composable +fun defaultDeviceActionsList(): Map, String>>>> { + return mapOf( + Pair( + "Управление", + arrayOf( + Pair( + "console", + Pair( + Pair(R.drawable.joystick, MaterialTheme.colorScheme.primary), + "Пульт управления" + ) + ), + Pair( + "codeblocks", + Pair( + Pair(R.drawable.code_blocks, MaterialTheme.colorScheme.primary), + "Палитра команд" + ) + ) + ) + ), + Pair( + "Настройки", + arrayOf( + Pair( + "imu_calibration", + Pair( + Pair(R.drawable.tune, MaterialTheme.colorScheme.primary), + "Калибровка гироскопа и акселерометра" + ) + ), + Pair( + "motor_test", + Pair( + Pair(R.drawable.helicopter_icon, MaterialTheme.colorScheme.primary), + "Тестирование двигателей" + ) + ), + Pair( + "pid_settings", + Pair( + Pair(R.drawable.controller_gen, MaterialTheme.colorScheme.primary), + "Настройки ПИД регуляторов" + ) + ), + Pair( + "reports", + Pair( + Pair(R.drawable.construction, MaterialTheme.colorScheme.primary), + "Отчеты о полётах" + ) + ) + ) + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/BluetoothScannerScreen.kt b/app/src/main/java/com/helible/pilot/components/scannerScreen/BluetoothScannerScreen.kt similarity index 97% rename from app/src/main/java/com/helible/pilot/components/BluetoothScannerScreen.kt rename to app/src/main/java/com/helible/pilot/components/scannerScreen/BluetoothScannerScreen.kt index 2de9511..8defc25 100644 --- a/app/src/main/java/com/helible/pilot/components/BluetoothScannerScreen.kt +++ b/app/src/main/java/com/helible/pilot/components/scannerScreen/BluetoothScannerScreen.kt @@ -1,4 +1,4 @@ -package com.helible.pilot.components +package com.helible.pilot.components.scannerScreen import android.annotation.SuppressLint import android.util.Log @@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import com.helible.pilot.components.Title import com.helible.pilot.dataclasses.BluetoothUiState import com.helible.pilot.dataclasses.BluetoothDevice @@ -103,7 +104,6 @@ fun BluetoothScannerScreen( Text(text = "Далее") } } - } } } \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/DeviceItem.kt b/app/src/main/java/com/helible/pilot/components/scannerScreen/DeviceItem.kt similarity index 85% rename from app/src/main/java/com/helible/pilot/components/DeviceItem.kt rename to app/src/main/java/com/helible/pilot/components/scannerScreen/DeviceItem.kt index 531d630..3fa7381 100644 --- a/app/src/main/java/com/helible/pilot/components/DeviceItem.kt +++ b/app/src/main/java/com/helible/pilot/components/scannerScreen/DeviceItem.kt @@ -1,4 +1,4 @@ -package com.helible.pilot.components +package com.helible.pilot.components.scannerScreen import android.annotation.SuppressLint import androidx.compose.foundation.clickable @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material3.CardDefaults import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon @@ -19,6 +20,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.helible.pilot.dataclasses.BluetoothDevice import com.helible.pilot.R @@ -42,7 +44,7 @@ fun DeviceItem( ) ) { Row(modifier = Modifier.padding(8.dp)) { - Column(verticalArrangement = Arrangement.Center) { + Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { Text( text = deviceInfo.name, fontWeight = FontWeight.Bold, @@ -74,4 +76,15 @@ fun getSignalIconForRssiValue(rssi: Short): Int { else if (rssi >= -90) return R.drawable.signal_icon3 else if (rssi >= -100) return R.drawable.signal_icon2 return R.drawable.signal_icon1 +} + +@Preview +@Composable +fun DeviceItemPreview() { + DeviceItem( + BluetoothDevice("Helicopter", "AA:BB:CC:DD:FF", -90, true), + null, + {_ -> }, + modifier = Modifier.size(500.dp, 60.dp) + ) } \ No newline at end of file diff --git a/app/src/main/java/com/helible/pilot/components/DiscoveredDevicesList.kt b/app/src/main/java/com/helible/pilot/components/scannerScreen/DiscoveredDevicesList.kt similarity index 97% rename from app/src/main/java/com/helible/pilot/components/DiscoveredDevicesList.kt rename to app/src/main/java/com/helible/pilot/components/scannerScreen/DiscoveredDevicesList.kt index 4ec4592..7a4acb4 100644 --- a/app/src/main/java/com/helible/pilot/components/DiscoveredDevicesList.kt +++ b/app/src/main/java/com/helible/pilot/components/scannerScreen/DiscoveredDevicesList.kt @@ -1,4 +1,4 @@ -package com.helible.pilot.components +package com.helible.pilot.components.scannerScreen import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.helible.pilot.components.scannerScreen.DeviceItem import com.helible.pilot.dataclasses.BluetoothUiState import com.helible.pilot.dataclasses.BluetoothDevice diff --git a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt index 12c3355..96cbb37 100644 --- a/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/BluetoothViewModel.kt @@ -57,24 +57,24 @@ class BluetoothViewModel( it.copy(errorMessage = error) } }.launchIn(viewModelScope) - bluetoothController.isScanning.onEach { result -> + bluetoothController.isScanning.onEach { isDiscovering -> _state.update { it.copy( - isDiscovering = result, + isDiscovering = isDiscovering, ) } }.launchIn(viewModelScope) - bluetoothController.isEnabled.onEach { result -> + bluetoothController.isEnabled.onEach { isEnabled -> _state.update { it.copy( - isEnabled = result, + isEnabled = isEnabled, ) } }.launchIn(viewModelScope) - bluetoothController.isLocationEnabled.onEach { result -> + bluetoothController.isLocationEnabled.onEach { isLocationEnabled -> _state.update { it.copy( - isLocationEnabled = result + isLocationEnabled = isLocationEnabled ) } }.launchIn(viewModelScope) @@ -123,6 +123,9 @@ class BluetoothViewModel( private var deviceConnectionJob: Job? = null fun connectToDevice(device: String) { + if(_state.value.isConnected) { + return + } _state.update { it.copy(isConnecting = true) } deviceConnectionJob = bluetoothController .connectToDevice(device) diff --git a/app/src/main/java/com/helible/pilot/viewmodels/PermissionsViewModel.kt b/app/src/main/java/com/helible/pilot/viewmodels/PermissionsViewModel.kt index 9f36fda..441d212 100644 --- a/app/src/main/java/com/helible/pilot/viewmodels/PermissionsViewModel.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/PermissionsViewModel.kt @@ -2,8 +2,6 @@ package com.helible.pilot.viewmodels import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel -import com.helible.pilot.components.AppPreferences -import com.helible.pilot.components.SavedPreferences class PermissionDialogViewModel : ViewModel() { val visiblePermissionDialogQueue = mutableStateListOf() diff --git a/app/src/main/java/com/helible/pilot/components/SavedPreferences.kt b/app/src/main/java/com/helible/pilot/viewmodels/SavedPreferences.kt similarity index 97% rename from app/src/main/java/com/helible/pilot/components/SavedPreferences.kt rename to app/src/main/java/com/helible/pilot/viewmodels/SavedPreferences.kt index c9c6f4c..668abe0 100644 --- a/app/src/main/java/com/helible/pilot/components/SavedPreferences.kt +++ b/app/src/main/java/com/helible/pilot/viewmodels/SavedPreferences.kt @@ -1,4 +1,4 @@ -package com.helible.pilot.components +package com.helible.pilot.viewmodels import android.content.SharedPreferences import com.squareup.moshi.JsonAdapter diff --git a/app/src/main/res/drawable/cancel.xml b/app/src/main/res/drawable/cancel.xml new file mode 100644 index 0000000..dae6d0f --- /dev/null +++ b/app/src/main/res/drawable/cancel.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/code_blocks.xml b/app/src/main/res/drawable/code_blocks.xml new file mode 100644 index 0000000..746f928 --- /dev/null +++ b/app/src/main/res/drawable/code_blocks.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/construction.xml b/app/src/main/res/drawable/construction.xml new file mode 100644 index 0000000..e779597 --- /dev/null +++ b/app/src/main/res/drawable/construction.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/controller_gen.xml b/app/src/main/res/drawable/controller_gen.xml new file mode 100644 index 0000000..0fb3858 --- /dev/null +++ b/app/src/main/res/drawable/controller_gen.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/energy.xml b/app/src/main/res/drawable/energy.xml new file mode 100644 index 0000000..73f35b3 --- /dev/null +++ b/app/src/main/res/drawable/energy.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/helicopter.png b/app/src/main/res/drawable/helicopter.png new file mode 100644 index 0000000000000000000000000000000000000000..6363155c247b8e3d2d34875ae8e141c94ab5bf55 GIT binary patch literal 15220 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 + + diff --git a/app/src/main/res/drawable/instant_mix.xml b/app/src/main/res/drawable/instant_mix.xml new file mode 100644 index 0000000..9ef2e8c --- /dev/null +++ b/app/src/main/res/drawable/instant_mix.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/joystick.xml b/app/src/main/res/drawable/joystick.xml new file mode 100644 index 0000000..f8954f8 --- /dev/null +++ b/app/src/main/res/drawable/joystick.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/logout.xml b/app/src/main/res/drawable/logout.xml new file mode 100644 index 0000000..99cdf28 --- /dev/null +++ b/app/src/main/res/drawable/logout.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/sync.xml b/app/src/main/res/drawable/sync.xml new file mode 100644 index 0000000..7d55533 --- /dev/null +++ b/app/src/main/res/drawable/sync.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/tune.xml b/app/src/main/res/drawable/tune.xml new file mode 100644 index 0000000..45ca5af --- /dev/null +++ b/app/src/main/res/drawable/tune.xml @@ -0,0 +1,10 @@ + + +