Device screen was implemented

This commit is contained in:
2024-01-01 21:56:23 +07:00
parent d7f3bf386d
commit efa93ab912
28 changed files with 546 additions and 185 deletions

View File

@@ -2,6 +2,7 @@ plugins {
id("com.android.application") id("com.android.application")
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp").version("1.6.10-1.0.4") id("com.google.devtools.ksp").version("1.6.10-1.0.4")
id("org.jlleitschuh.gradle.ktlint").version("12.0.3")
} }
android { android {
@@ -64,6 +65,7 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
implementation("androidx.navigation:navigation-compose:2.6.0") implementation("androidx.navigation:navigation-compose:2.6.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0") implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -15,10 +15,11 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.helible.pilot.components.BluetoothScannerScreen import com.helible.pilot.components.scannerScreen.BluetoothScannerScreen
import com.helible.pilot.components.FlightControlScreen import com.helible.pilot.components.deviceScreen.DeviceControlScreen
import com.helible.pilot.components.AppPreferences import com.helible.pilot.viewmodels.AppPreferences
import com.helible.pilot.components.SavedPreferencesImpl import com.helible.pilot.viewmodels.SavedPreferencesImpl
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
import com.helible.pilot.permissions.PermissionsLauncher import com.helible.pilot.permissions.PermissionsLauncher
import com.helible.pilot.permissions.PermissionsRequest import com.helible.pilot.permissions.PermissionsRequest
import com.helible.pilot.permissions.RequestHardwareFeatures import com.helible.pilot.permissions.RequestHardwareFeatures
@@ -31,8 +32,10 @@ import com.helible.pilot.viewmodels.PreferencesViewModel
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
// TODO: device screen logic // TODO: device screen logic
// TODO: constrain text size
// TODO: add Bluetooth telemetry... // TODO: add Bluetooth telemetry...
// TODO: move text strings to resources // TODO: move text strings to resources
// TODO: review permissions logic
private val preferences by lazy { private val preferences by lazy {
SavedPreferencesImpl(getSharedPreferences(packageName, MODE_PRIVATE)) SavedPreferencesImpl(getSharedPreferences(packageName, MODE_PRIVATE))
@@ -127,32 +130,48 @@ class MainActivity : ComponentActivity() {
} }
composable("device") composable("device")
{ {
FlightControlScreen( DeviceControlScreen(
bluetoothUiState = bluetoothState, bluetoothUiState = bluetoothState,
getPreferences = { preferencesViewModel.preferences }, getPreferences = { preferencesViewModel.preferences },
navigateToScanner = { navController.navigate("scanner") }, navigateToPage = { page -> navController.navigate(page) },
connectToDevice = { device -> connectToDevice = { device ->
bluetoothViewModel.connectToDevice( bluetoothViewModel.connectToDevice(
device device
) )
}, },
sendRotorsState = { message ->
bluetoothViewModel.sendRotorsDutySpeed(
message
)
},
disconnectFromDevice = { bluetoothViewModel.disconnectFromDevice() }, disconnectFromDevice = { bluetoothViewModel.disconnectFromDevice() },
sendEmergStop = { bluetoothViewModel.sendEmergStop() }, deviceActionsList = defaultDeviceActionsList()
sendAlarm = { message -> bluetoothViewModel.sendAlarmState(message) },
sendR3Duty = { duty -> bluetoothViewModel.sendR3Duty(duty) }
) )
if (preferencesViewModel.preferences != null) BackHandler {} if (preferencesViewModel.preferences != null) BackHandler {}
} }
composable("console")
{
}
composable("codeblocks")
{
}
composable("imu_calibration")
{
}
composable("motor_test")
{
}
composable("pid_settings")
{
}
composable("reports")
{
}
} }
} }
} }
} }
} }
} }

View File

@@ -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)
) {
}
}
}

View File

@@ -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)
)
}
}
}
}
}

View File

@@ -14,6 +14,6 @@ fun Title(text: String, modifier: Modifier = Modifier) {
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = modifier, modifier = modifier,
fontSize = 23.sp, fontSize = 23.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.ExtraBold
) )
} }

View File

@@ -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")}
)
}

View File

@@ -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))
}
}

View File

@@ -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, Array<Pair<String, Pair<Pair<Int, Color>, 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()
)
}
}

View File

@@ -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, Array<Pair<String, Pair<Pair<Int, Color>, 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),
"Отчеты о полётах"
)
)
)
)
)
}

View File

@@ -1,4 +1,4 @@
package com.helible.pilot.components package com.helible.pilot.components.scannerScreen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Log import android.util.Log
@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Dimension
import com.helible.pilot.components.Title
import com.helible.pilot.dataclasses.BluetoothUiState import com.helible.pilot.dataclasses.BluetoothUiState
import com.helible.pilot.dataclasses.BluetoothDevice import com.helible.pilot.dataclasses.BluetoothDevice
@@ -103,7 +104,6 @@ fun BluetoothScannerScreen(
Text(text = "Далее") Text(text = "Далее")
} }
} }
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package com.helible.pilot.components package com.helible.pilot.components.scannerScreen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.foundation.clickable 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.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -19,6 +20,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.helible.pilot.dataclasses.BluetoothDevice import com.helible.pilot.dataclasses.BluetoothDevice
import com.helible.pilot.R import com.helible.pilot.R
@@ -42,7 +44,7 @@ fun DeviceItem(
) )
) { ) {
Row(modifier = Modifier.padding(8.dp)) { Row(modifier = Modifier.padding(8.dp)) {
Column(verticalArrangement = Arrangement.Center) { Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
Text( Text(
text = deviceInfo.name, text = deviceInfo.name,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
@@ -74,4 +76,15 @@ fun getSignalIconForRssiValue(rssi: Short): Int {
else if (rssi >= -90) return R.drawable.signal_icon3 else if (rssi >= -90) return R.drawable.signal_icon3
else if (rssi >= -100) return R.drawable.signal_icon2 else if (rssi >= -100) return R.drawable.signal_icon2
return R.drawable.signal_icon1 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)
)
} }

View File

@@ -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.Column
import androidx.compose.foundation.layout.fillMaxSize 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.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.helible.pilot.components.scannerScreen.DeviceItem
import com.helible.pilot.dataclasses.BluetoothUiState import com.helible.pilot.dataclasses.BluetoothUiState
import com.helible.pilot.dataclasses.BluetoothDevice import com.helible.pilot.dataclasses.BluetoothDevice

View File

@@ -57,24 +57,24 @@ class BluetoothViewModel(
it.copy(errorMessage = error) it.copy(errorMessage = error)
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
bluetoothController.isScanning.onEach { result -> bluetoothController.isScanning.onEach { isDiscovering ->
_state.update { _state.update {
it.copy( it.copy(
isDiscovering = result, isDiscovering = isDiscovering,
) )
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
bluetoothController.isEnabled.onEach { result -> bluetoothController.isEnabled.onEach { isEnabled ->
_state.update { _state.update {
it.copy( it.copy(
isEnabled = result, isEnabled = isEnabled,
) )
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
bluetoothController.isLocationEnabled.onEach { result -> bluetoothController.isLocationEnabled.onEach { isLocationEnabled ->
_state.update { _state.update {
it.copy( it.copy(
isLocationEnabled = result isLocationEnabled = isLocationEnabled
) )
} }
}.launchIn(viewModelScope) }.launchIn(viewModelScope)
@@ -123,6 +123,9 @@ class BluetoothViewModel(
private var deviceConnectionJob: Job? = null private var deviceConnectionJob: Job? = null
fun connectToDevice(device: String) { fun connectToDevice(device: String) {
if(_state.value.isConnected) {
return
}
_state.update { it.copy(isConnecting = true) } _state.update { it.copy(isConnecting = true) }
deviceConnectionJob = bluetoothController deviceConnectionJob = bluetoothController
.connectToDevice(device) .connectToDevice(device)

View File

@@ -2,8 +2,6 @@ package com.helible.pilot.viewmodels
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.helible.pilot.components.AppPreferences
import com.helible.pilot.components.SavedPreferences
class PermissionDialogViewModel : ViewModel() { class PermissionDialogViewModel : ViewModel() {
val visiblePermissionDialogQueue = mutableStateListOf<String>() val visiblePermissionDialogQueue = mutableStateListOf<String>()

View File

@@ -1,4 +1,4 @@
package com.helible.pilot.components package com.helible.pilot.viewmodels
import android.content.SharedPreferences import android.content.SharedPreferences
import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonAdapter

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M336,680L480,536L624,680L680,624L536,480L680,336L624,280L480,424L336,280L280,336L424,480L280,624L336,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M384,624L440,567L353,480L440,393L384,336L240,480L384,624ZM576,624L720,480L576,336L520,393L607,480L520,567L576,624ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M756,840L537,621L621,537L840,756L756,840ZM204,840L120,756L396,480L328,412L300,440L249,389L249,471L221,499L100,378L128,350L210,350L160,300L302,158Q322,138 345,129Q368,120 392,120Q416,120 439,129Q462,138 482,158L390,250L440,300L412,328L480,396L570,306Q566,295 563.5,283Q561,271 561,259Q561,200 601.5,159.5Q642,119 701,119Q716,119 729.5,122Q743,125 757,131L658,230L730,302L829,203Q836,217 838.5,230.5Q841,244 841,259Q841,318 800.5,358.5Q760,399 701,399Q689,399 677,397Q665,395 654,390L204,840Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M480,720Q580,720 650,650Q720,580 720,480Q720,380 650,310Q580,240 480,240Q380,240 310,310Q240,380 240,480Q240,580 310,650Q380,720 480,720ZM480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,546 593,593Q546,640 480,640ZM452,508Q463,519 480,519Q497,519 508,508L564,452Q575,441 575,424Q575,407 564,396Q553,385 536,385Q519,385 508,396L452,452Q441,463 441,480Q441,497 452,508ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM200,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L200,760Q200,760 200,760Q200,760 200,760L200,200Q200,200 200,200Q200,200 200,200Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M360,880L360,840Q360,808 384,786.5Q408,765 440,760L440,760L440,474Q418,466 401.5,451Q385,436 374,416L298,436Q262,444 229.5,430Q197,416 188,382L362,338Q370,297 400,270Q430,243 472,240L496,150Q506,115 534.5,94.5Q563,74 598,82L548,262Q573,278 586.5,304Q600,330 600,360Q600,373 597,385.5Q594,398 590,410L644,466Q669,492 673.5,527Q678,562 654,586L534,466Q531,469 527.5,470.5Q524,472 520,474L520,760L520,760Q552,765 576,786.5Q600,808 600,840L600,880L360,880ZM480,420Q505,420 522.5,402.5Q540,385 540,360Q540,335 522.5,317.5Q505,300 480,300Q455,300 437.5,317.5Q420,335 420,360Q420,385 437.5,402.5Q455,420 480,420Z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M360,520L360,280L360,280Q260,280 190,350Q120,420 120,520L120,520L360,520ZM520,680L520,280L440,280L440,600L120,600L120,680Q120,680 120,680Q120,680 120,680L520,680ZM600,552L840,528L840,480L600,480L600,552ZM520,880L120,880L120,800L520,800L520,880ZM600,760L120,760Q87,760 63.5,736.5Q40,713 40,680L40,520Q40,386 133,293Q226,200 360,200L600,200L600,400L800,400L840,320L920,320L920,600L600,632L600,760ZM760,160L120,160L120,80L760,80L760,160ZM600,552L600,480L600,480L600,552L600,552ZM520,680L520,680Q520,680 520,680Q520,680 520,680L520,680L520,680L520,680L520,680L520,680Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M200,800L200,520L120,520L120,440L360,440L360,520L280,520L280,800L200,800ZM200,360L200,160L280,160L280,360L200,360ZM360,360L360,280L440,280L440,160L520,160L520,280L600,280L600,360L360,360ZM440,800L440,440L520,440L520,800L440,800ZM680,800L680,680L600,680L600,600L840,600L840,680L760,680L760,800L680,800ZM680,520L680,160L760,160L760,520L680,520Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/holo_orange_light"
android:pathData="M272,520L480,640Q480,640 480,640Q480,640 480,640L688,520L520,423L520,560L440,560L440,423L272,520ZM440,331L440,314Q396,301 368,264.5Q340,228 340,180Q340,122 381,81Q422,40 480,40Q538,40 579,81Q620,122 620,180Q620,228 592,264.5Q564,301 520,314L520,331L800,492Q819,503 829.5,521.5Q840,540 840,562L840,638Q840,660 829.5,678.5Q819,697 800,708L520,869Q501,880 480,880Q459,880 440,869L160,708Q141,697 130.5,678.5Q120,660 120,638L120,562Q120,540 130.5,521.5Q141,503 160,492L440,331ZM440,709L200,571L200,638Q200,638 200,638Q200,638 200,638L480,800Q480,800 480,800Q480,800 480,800L760,638Q760,638 760,638Q760,638 760,638L760,571L520,709Q501,720 480,720Q459,720 440,709ZM480,240Q505,240 522.5,222.5Q540,205 540,180Q540,155 522.5,137.5Q505,120 480,120Q455,120 437.5,137.5Q420,155 420,180Q420,205 437.5,222.5Q455,240 480,240ZM480,800Q480,800 480,800Q480,800 480,800L480,800L480,800Q480,800 480,800Q480,800 480,800L480,800Q480,800 480,800Q480,800 480,800L480,800Q480,800 480,800Q480,800 480,800L480,800L480,800Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L480,120L480,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760L480,760L480,840L200,840ZM640,680L585,622L687,520L360,520L360,440L687,440L585,338L640,280L840,480L640,680Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M160,800L160,720L270,720L254,706Q202,660 181,601Q160,542 160,482Q160,371 226.5,284.5Q293,198 400,170L400,254Q328,280 284,342.5Q240,405 240,482Q240,527 257,569.5Q274,612 310,648L320,658L320,560L400,560L400,800L160,800ZM560,790L560,706Q632,680 676,617.5Q720,555 720,478Q720,433 703,390.5Q686,348 650,312L640,302L640,400L560,400L560,160L800,160L800,240L690,240L706,254Q755,303 777.5,360.5Q800,418 800,478Q800,589 733.5,675.5Q667,762 560,790Z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?android:colorPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M440,840L440,600L520,600L520,680L840,680L840,760L520,760L520,840L440,840ZM120,760L120,680L360,680L360,760L120,760ZM280,600L280,520L120,520L120,440L280,440L280,360L360,360L360,600L280,600ZM440,520L440,440L840,440L840,520L440,520ZM600,360L600,120L680,120L680,200L840,200L840,280L680,280L680,360L600,360ZM120,280L120,200L520,200L520,280L120,280Z"/>
</vector>