Joystick & Pid Configuration
Flexible PID configuration and joysticks were added
This commit is contained in:
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
10
.idea/deploymentTargetDropDown.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<value>
|
||||||
|
<entry key="app">
|
||||||
|
<State />
|
||||||
|
</entry>
|
||||||
|
</value>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.idea/gradle.xml
generated
5
.idea/gradle.xml
generated
@@ -4,16 +4,15 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="jbr-17" />
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="1.6.10" />
|
<option name="version" value="1.8.10" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
10
.idea/migrations.xml
generated
Normal file
10
.idea/migrations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectMigrations">
|
||||||
|
<option name="MigrateToGradleLocalJavaHome">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
plugins {
|
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("org.jlleitschuh.gradle.ktlint").version("12.0.3")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -65,6 +63,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")
|
||||||
|
implementation("com.github.manalkaff:JetStick:1.2")
|
||||||
|
|
||||||
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")
|
||||||
|
|||||||
@@ -40,12 +40,12 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Testblue"
|
android:theme="@style/Theme.Main"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.helible.pilot.MainActivity"
|
android:name="com.helible.pilot.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Testblue">
|
android:theme="@style/Theme.Main">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import android.widget.Toast
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -18,8 +16,11 @@ 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.CalibrationPage
|
import com.helible.pilot.components.CalibrationPage
|
||||||
|
import com.helible.pilot.components.NotImplementedPage
|
||||||
|
import com.helible.pilot.components.console.ConsolePage
|
||||||
import com.helible.pilot.components.deviceScreen.DeviceControlScreen
|
import com.helible.pilot.components.deviceScreen.DeviceControlScreen
|
||||||
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
|
import com.helible.pilot.components.deviceScreen.defaultDeviceActionsList
|
||||||
|
import com.helible.pilot.components.pidSettings.PidSettingsPage
|
||||||
import com.helible.pilot.components.scannerScreen.ScannerScreen
|
import com.helible.pilot.components.scannerScreen.ScannerScreen
|
||||||
import com.helible.pilot.permissions.PermissionsLauncher
|
import com.helible.pilot.permissions.PermissionsLauncher
|
||||||
import com.helible.pilot.permissions.PermissionsRequest
|
import com.helible.pilot.permissions.PermissionsRequest
|
||||||
@@ -141,13 +142,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
composable("console/{title}")
|
composable("console/{title}")
|
||||||
{ backStackEntry ->
|
{ backStackEntry ->
|
||||||
NotImplementedPage(
|
ConsolePage(
|
||||||
title = backStackEntry.arguments?.getString("title") ?: "null",
|
title = backStackEntry.arguments?.getString("title") ?: "null",
|
||||||
navigateBack = { navController.popBackStack() }
|
navigateBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
Button(onClick = { bluetoothViewModel.sendHelloWorld() }) {
|
|
||||||
Text("Click me!")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
composable("codeblocks/{title}")
|
composable("codeblocks/{title}")
|
||||||
{ backStackEntry ->
|
{ backStackEntry ->
|
||||||
@@ -174,10 +172,20 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
composable("pid_settings/{title}")
|
composable("pid_settings/{title}")
|
||||||
{ backStackEntry ->
|
{ backStackEntry ->
|
||||||
NotImplementedPage(
|
PidSettingsPage(
|
||||||
title = backStackEntry.arguments?.getString("title") ?: "null",
|
title = backStackEntry.arguments?.getString("title") ?: "null",
|
||||||
navigateBack = { navController.popBackStack() }
|
navigateBack = {
|
||||||
|
navController.popBackStack()
|
||||||
|
bluetoothViewModel.clearPidSettings()
|
||||||
|
},
|
||||||
|
requestPidSettings = { bluetoothViewModel.requestPidSettings() },
|
||||||
|
setPidSettings = {settings -> bluetoothViewModel.applyPidSettings(settings)},
|
||||||
|
deviceState = bluetoothState.deviceState
|
||||||
)
|
)
|
||||||
|
BackHandler {
|
||||||
|
navController.popBackStack()
|
||||||
|
bluetoothViewModel.clearPidSettings()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable("reports/{title}")
|
composable("reports/{title}")
|
||||||
{ backStackEntry ->
|
{ backStackEntry ->
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.helible.pilot
|
package com.helible.pilot.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.helible.pilot.components.console
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.helible.pilot.components.BlankPage
|
||||||
|
import com.helible.pilot.dataclasses.DeviceStatus
|
||||||
|
import com.manalkaff.jetstick.JoyStick
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ConsolePage(
|
||||||
|
title: String,
|
||||||
|
navigateBack: () -> Unit
|
||||||
|
) {
|
||||||
|
BlankPage(title = title, navigateBack = navigateBack) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
JoyStick(
|
||||||
|
Modifier.padding(30.dp),
|
||||||
|
size = 150.dp,
|
||||||
|
dotSize = 30.dp
|
||||||
|
){ x: Float, y: Float ->
|
||||||
|
Log.d("JoyStick", "$x, $y")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
package com.helible.pilot.components.pidSettings
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
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.geometry.Size
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.toSize
|
||||||
|
import com.helible.pilot.components.BlankPage
|
||||||
|
import com.helible.pilot.dataclasses.DeviceState
|
||||||
|
import com.helible.pilot.dataclasses.DeviceStatus
|
||||||
|
import com.helible.pilot.dataclasses.PidParams
|
||||||
|
import com.helible.pilot.dataclasses.PidSettings
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun PidSettingsPage(
|
||||||
|
title: String,
|
||||||
|
navigateBack: () -> Unit,
|
||||||
|
requestPidSettings: () -> Unit,
|
||||||
|
setPidSettings: (PidSettings) -> Unit,
|
||||||
|
deviceState: DeviceState?,
|
||||||
|
) {
|
||||||
|
BlankPage(title = title, navigateBack = navigateBack) {
|
||||||
|
var pValue by remember { mutableStateOf("") }
|
||||||
|
var iValue by remember { mutableStateOf("") }
|
||||||
|
var dValue by remember { mutableStateOf("") }
|
||||||
|
var selectedRegulator by remember { mutableStateOf(1) }
|
||||||
|
val dropdownMenuItems = listOf("PID 1", "PID 2", "PID 3")
|
||||||
|
|
||||||
|
LaunchedEffect(null) {
|
||||||
|
requestPidSettings()
|
||||||
|
}
|
||||||
|
LaunchedEffect(deviceState?.pidSettings) {
|
||||||
|
if (deviceState?.pidSettings != null) {
|
||||||
|
val pidSettings = deviceState.pidSettings
|
||||||
|
when(selectedRegulator){
|
||||||
|
1 -> {
|
||||||
|
pidSettings.p1.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p1.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p1.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
pidSettings.p2.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p2.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p2.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
pidSettings.p3.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p3.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p3.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(10.dp)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (deviceState?.status != DeviceStatus.Idle) {
|
||||||
|
Text(
|
||||||
|
text = "Этот раздел доступен только с подключенным заряженным бездействующим устройством.",
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
} else if (deviceState.pidSettings == null) {
|
||||||
|
Column {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.padding(10.dp))
|
||||||
|
Text(text = "Синхронизация...")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val pidSettings = deviceState.pidSettings
|
||||||
|
|
||||||
|
OutlinedDropdownMenu(
|
||||||
|
label = "ПИД регулятор",
|
||||||
|
suggestions = dropdownMenuItems,
|
||||||
|
onChange = { selected ->
|
||||||
|
Log.i("BluetoothVM", selected)
|
||||||
|
when (dropdownMenuItems.indexOf(selected)) {
|
||||||
|
0 -> {
|
||||||
|
selectedRegulator = 1
|
||||||
|
pidSettings.p1.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p1.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p1.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
selectedRegulator = 2
|
||||||
|
pidSettings.p2.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p2.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p2.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
selectedRegulator = 3
|
||||||
|
pidSettings.p3.p.toString().also { pValue = it }
|
||||||
|
pidSettings.p3.i.toString().also { iValue = it }
|
||||||
|
pidSettings.p3.d.toString().also { dValue = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(10.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
modifier = Modifier.padding(10.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = pValue,
|
||||||
|
onValueChange = { pValue = it },
|
||||||
|
label = { Text(text = "P") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = iValue,
|
||||||
|
onValueChange = { iValue = it },
|
||||||
|
label = { Text(text = "I") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = dValue,
|
||||||
|
onValueChange = { dValue = it },
|
||||||
|
label = { Text(text = "D") },
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
val p = pValue
|
||||||
|
val i = iValue
|
||||||
|
val d = dValue
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
when (selectedRegulator) {
|
||||||
|
1 -> {
|
||||||
|
val newPidSettings = pidSettings.copy(
|
||||||
|
p1 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
|
||||||
|
)
|
||||||
|
setPidSettings(newPidSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
2 -> {
|
||||||
|
val newPidSettings = pidSettings.copy(
|
||||||
|
p2 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
|
||||||
|
)
|
||||||
|
setPidSettings(newPidSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
3 -> {
|
||||||
|
val newPidSettings = pidSettings.copy(
|
||||||
|
p3 = PidParams(p.toFloat(), i.toFloat(), d.toFloat())
|
||||||
|
)
|
||||||
|
setPidSettings(newPidSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = isValidValue(p) && isValidValue(i) && isValidValue(d)
|
||||||
|
) {
|
||||||
|
Text(text = "Применить")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isValidValue(k: String): Boolean {
|
||||||
|
return k.toFloatOrNull() != null && k.toFloat() >= 0f && k.toFloat() <= 2f
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun PidSettingsPreview() {
|
||||||
|
PidSettingsPage(
|
||||||
|
title = "Настройки ПИД регуляторов",
|
||||||
|
navigateBack = { },
|
||||||
|
requestPidSettings = { },
|
||||||
|
setPidSettings = {},
|
||||||
|
deviceState = DeviceState(
|
||||||
|
status = DeviceStatus.Idle,
|
||||||
|
pidSettings = PidSettings(
|
||||||
|
PidParams(1f, 1f, 1f),
|
||||||
|
PidParams(1f, 1f, 1f),
|
||||||
|
PidParams(1f, 1f, 1f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
fun DropdownDemo() {
|
||||||
|
OutlinedDropdownMenu(label = "", suggestions = listOf("A", "B"), onChange = {})
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun OutlinedDropdownMenu(
|
||||||
|
label: String,
|
||||||
|
suggestions: List<String>,
|
||||||
|
onChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
var selectedText by remember { mutableStateOf(suggestions.first()) }
|
||||||
|
var textfieldSize by remember { mutableStateOf(Size.Zero) }
|
||||||
|
|
||||||
|
val icon = if (expanded)
|
||||||
|
Icons.Filled.KeyboardArrowUp
|
||||||
|
else
|
||||||
|
Icons.Filled.KeyboardArrowDown
|
||||||
|
|
||||||
|
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = selectedText,
|
||||||
|
onValueChange = {
|
||||||
|
selectedText = it
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onGloballyPositioned { coordinates ->
|
||||||
|
// This value is used to assign to the DropDown the same width
|
||||||
|
textfieldSize = coordinates.size.toSize()
|
||||||
|
},
|
||||||
|
label = { Text(label) },
|
||||||
|
trailingIcon = {
|
||||||
|
Icon(icon, "contentDescription",
|
||||||
|
Modifier.clickable { expanded = !expanded })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
modifier = Modifier
|
||||||
|
.width(with(LocalDensity.current) { textfieldSize.width.toDp() })
|
||||||
|
) {
|
||||||
|
suggestions.forEach { label ->
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
selectedText = label
|
||||||
|
expanded = false
|
||||||
|
onChange(selectedText)
|
||||||
|
},
|
||||||
|
text = { Text(label) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import android.widget.Toast
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import com.helible.pilot.viewmodels.BluetoothDataTransferService
|
import com.helible.pilot.viewmodels.BluetoothDataTransferService
|
||||||
import com.helible.pilot.dataclasses.BluetoothDeviceDomain
|
import com.helible.pilot.dataclasses.BluetoothDeviceDomain
|
||||||
import com.helible.pilot.dataclasses.DeviceState
|
import com.helible.pilot.dataclasses.GeneralMessage
|
||||||
import com.helible.pilot.receivers.BluetoothAdapterStateReceiver
|
import com.helible.pilot.receivers.BluetoothAdapterStateReceiver
|
||||||
import com.helible.pilot.receivers.BluetoothStateReceiver
|
import com.helible.pilot.receivers.BluetoothStateReceiver
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -40,7 +40,7 @@ import java.util.UUID
|
|||||||
|
|
||||||
sealed interface ConnectionResult {
|
sealed interface ConnectionResult {
|
||||||
object ConnectionEstablished : ConnectionResult
|
object ConnectionEstablished : ConnectionResult
|
||||||
data class TransferSucceded(val message: DeviceState) : ConnectionResult
|
data class TransferSucceded(val message: GeneralMessage) : ConnectionResult
|
||||||
data class Error(val message: String) : ConnectionResult
|
data class Error(val message: String) : ConnectionResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ class AndroidBluetoothController(private val context: Context) : BluetoothContro
|
|||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
socket.close()
|
socket.close()
|
||||||
currentClientSocket = null
|
currentClientSocket = null
|
||||||
Log.e("BluetoothController", "I/O exception: e")
|
Log.e("BluetoothController", "I/O exception: ${e.message}")
|
||||||
emit(ConnectionResult.Error("Connection was interrupted"))
|
emit(ConnectionResult.Error("Connection was interrupted"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.helible.pilot.controllers
|
|
||||||
|
|
||||||
data class DeviceState(
|
|
||||||
val isHandshakeWaiting: Boolean = true,
|
|
||||||
val isIMUCalibrating: Boolean = false,
|
|
||||||
val flightMode: Boolean = false,
|
|
||||||
val batteryCharge: Int?,
|
|
||||||
val flightHeight: Float?
|
|
||||||
)
|
|
||||||
@@ -12,4 +12,5 @@ data class DeviceState(
|
|||||||
@Json(name = "p") val pitch: Float = 0f,
|
@Json(name = "p") val pitch: Float = 0f,
|
||||||
@Json(name = "r") val roll: Float = 0f,
|
@Json(name = "r") val roll: Float = 0f,
|
||||||
@Json(name = "zIn") val zInertial: Float = 0f,
|
@Json(name = "zIn") val zInertial: Float = 0f,
|
||||||
|
val pidSettings: PidSettings? = null
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
// This dataclass provide message content with its type without any markers
|
||||||
|
data class GeneralMessage(
|
||||||
|
val type: MessageType,
|
||||||
|
val data: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
enum class MessageType {
|
||||||
|
UpdateMessage,
|
||||||
|
PidSettings
|
||||||
|
}
|
||||||
10
app/src/main/java/com/helible/pilot/dataclasses/PidParams.kt
Normal file
10
app/src/main/java/com/helible/pilot/dataclasses/PidParams.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PidParams (
|
||||||
|
val p: Float,
|
||||||
|
val i: Float,
|
||||||
|
val d: Float
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PidSettingRequiredMessage (
|
||||||
|
val pidSettingOpened: Boolean = true
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PidSettings (
|
||||||
|
val p1: PidParams,
|
||||||
|
val p2: PidParams,
|
||||||
|
val p3: PidParams
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.helible.pilot.dataclasses
|
||||||
|
|
||||||
|
data class StickPosition (
|
||||||
|
val x: Float,
|
||||||
|
val y: Float
|
||||||
|
)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.helible.pilot.exceptions
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class TransferFailedException : IOException("Reading incoming data failed")
|
||||||
@@ -4,6 +4,11 @@ import android.bluetooth.BluetoothSocket
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.helible.pilot.dataclasses.DeviceState
|
import com.helible.pilot.dataclasses.DeviceState
|
||||||
import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
|
import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
|
||||||
|
import com.helible.pilot.dataclasses.GeneralMessage
|
||||||
|
import com.helible.pilot.dataclasses.MessageType
|
||||||
|
import com.helible.pilot.dataclasses.PidSettings
|
||||||
|
import com.helible.pilot.exceptions.TransferFailedException
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
import com.squareup.moshi.JsonEncodingException
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.adapter
|
import com.squareup.moshi.adapter
|
||||||
@@ -16,19 +21,18 @@ import kotlinx.coroutines.withContext
|
|||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class TransferFailedException : IOException("Reading incoming data failed")
|
const val maxPackageSize = 512; // bytes
|
||||||
|
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
class BluetoothDataTransferService(
|
class BluetoothDataTransferService(
|
||||||
private val socket: BluetoothSocket,
|
private val socket: BluetoothSocket,
|
||||||
) {
|
) {
|
||||||
fun listenForIncomingMessages(): Flow<DeviceState> {
|
fun listenForIncomingMessages(): Flow<GeneralMessage> {
|
||||||
val moshi = Moshi.Builder().add(DeviceStatusJsonAdapter()).add(KotlinJsonAdapterFactory()).build()
|
|
||||||
val deviceStateMessageAdapter = moshi.adapter<DeviceState>()
|
|
||||||
return flow {
|
return flow {
|
||||||
if (!socket.isConnected)
|
if (!socket.isConnected)
|
||||||
return@flow
|
return@flow
|
||||||
val buffer = ByteArray(512)
|
|
||||||
|
val buffer = ByteArray(maxPackageSize)
|
||||||
while (true) {
|
while (true) {
|
||||||
val byteCount: Int = try {
|
val byteCount: Int = try {
|
||||||
socket.inputStream.read(buffer)
|
socket.inputStream.read(buffer)
|
||||||
@@ -36,23 +40,17 @@ class BluetoothDataTransferService(
|
|||||||
Log.e("BluetoothController", "Failed to receive incoming data")
|
Log.e("BluetoothController", "Failed to receive incoming data")
|
||||||
throw TransferFailedException()
|
throw TransferFailedException()
|
||||||
}
|
}
|
||||||
|
var messageData: String = buffer.decodeToString(endIndex = byteCount)
|
||||||
|
val messageType: MessageType? = MessageType.values()
|
||||||
|
.elementAtOrNull(messageData.split(";")[0].toInt())
|
||||||
|
|
||||||
val messageData: String = buffer.decodeToString(endIndex = byteCount)
|
if (messageData.endsWith("\n\r") && messageType != null) {
|
||||||
if (!messageData.endsWith("\n\r")) {
|
messageData = messageData.dropLast(2).split(";")[1]
|
||||||
|
emit(GeneralMessage(messageType, messageData))
|
||||||
|
Log.d("BluetoothController", "Received: $messageData")
|
||||||
|
} else {
|
||||||
Log.i("BluetoothController", "Package end isn't valid.")
|
Log.i("BluetoothController", "Package end isn't valid.")
|
||||||
Log.i("BluetoothController", messageData)
|
Log.i("BluetoothController", messageData)
|
||||||
|
|
||||||
} else {
|
|
||||||
val messageJson = messageData.dropLast(2)
|
|
||||||
try {
|
|
||||||
val deviceState = deviceStateMessageAdapter.fromJson(messageJson)!!
|
|
||||||
emit(deviceState)
|
|
||||||
Log.i("BluetoothController", "Received: $deviceState")
|
|
||||||
} catch (e: NullPointerException) {
|
|
||||||
Log.e("BluetoothController", "Nullable message received: $messageJson")
|
|
||||||
} catch (e: JsonEncodingException) {
|
|
||||||
Log.e("BluetoothController", "Invalid message received: $messageJson")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.IO)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|||||||
@@ -8,8 +8,14 @@ import com.helible.pilot.controllers.ConnectionResult
|
|||||||
import com.helible.pilot.dataclasses.BluetoothDevice
|
import com.helible.pilot.dataclasses.BluetoothDevice
|
||||||
import com.helible.pilot.dataclasses.BluetoothUiState
|
import com.helible.pilot.dataclasses.BluetoothUiState
|
||||||
import com.helible.pilot.dataclasses.ChangedDeviceStatus
|
import com.helible.pilot.dataclasses.ChangedDeviceStatus
|
||||||
|
import com.helible.pilot.dataclasses.DeviceState
|
||||||
import com.helible.pilot.dataclasses.DeviceStatus
|
import com.helible.pilot.dataclasses.DeviceStatus
|
||||||
import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
|
import com.helible.pilot.dataclasses.DeviceStatusJsonAdapter
|
||||||
|
import com.helible.pilot.dataclasses.MessageType
|
||||||
|
import com.helible.pilot.dataclasses.PidSettingRequiredMessage
|
||||||
|
import com.helible.pilot.dataclasses.PidSettings
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import com.squareup.moshi.JsonEncodingException
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -25,7 +31,6 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
class BluetoothViewModel(
|
class BluetoothViewModel(
|
||||||
private val bluetoothController: BluetoothController,
|
private val bluetoothController: BluetoothController,
|
||||||
@@ -46,7 +51,10 @@ class BluetoothViewModel(
|
|||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value)
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), _state.value)
|
||||||
private var deviceConnectionJob: Job? = null
|
private var deviceConnectionJob: Job? = null
|
||||||
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build()
|
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).add(DeviceStatusJsonAdapter()).build()
|
||||||
private val newStatusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java)
|
private val statusMessageAdapter = moshi.adapter(ChangedDeviceStatus::class.java)
|
||||||
|
private val deviceStateMessageAdapter = moshi.adapter(DeviceState::class.java)
|
||||||
|
private val pidSittingsMessageAdapter = moshi.adapter(PidSettings::class.java)
|
||||||
|
private val pidSittingsRequiredMessageAdapter = moshi.adapter(PidSettingRequiredMessage::class.java)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
bluetoothController.isConnected.onEach { isConnected ->
|
bluetoothController.isConnected.onEach { isConnected ->
|
||||||
@@ -94,13 +102,38 @@ class BluetoothViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ConnectionResult.TransferSucceded -> {
|
is ConnectionResult.TransferSucceded -> {
|
||||||
|
try {
|
||||||
|
when (result.message.type) {
|
||||||
|
MessageType.PidSettings -> {
|
||||||
|
val newPidSettings =
|
||||||
|
pidSittingsMessageAdapter.fromJson(result.message.data)
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
deviceState = result.message
|
deviceState = it.deviceState?.copy(pidSettings = newPidSettings)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageType.UpdateMessage -> {
|
||||||
|
val newDeviceState =
|
||||||
|
deviceStateMessageAdapter.fromJson(result.message.data)
|
||||||
|
if (newDeviceState != null) {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
deviceState = newDeviceState.copy(pidSettings = it.deviceState?.pidSettings)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: JsonDataException) {
|
||||||
|
Log.e("BluetoothVM", "Failed to parse message: ${result.message.data}")
|
||||||
|
} catch (e: JsonEncodingException) {
|
||||||
|
Log.e("BluetoothVM", "Failed to decode message: ${result.message.data}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
is ConnectionResult.Error -> {
|
is ConnectionResult.Error -> {
|
||||||
_state.update {
|
_state.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -113,13 +146,18 @@ class BluetoothViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.catch { throwable ->
|
.catch { throwable ->
|
||||||
Log.e("BluetoothController", "Error occured while data transfer: ${throwable.message}")
|
Log.e(
|
||||||
|
"BluetoothController",
|
||||||
|
"Error occured while data transfer: ${throwable.message}"
|
||||||
|
)
|
||||||
bluetoothController.closeConnection()
|
bluetoothController.closeConnection()
|
||||||
_state.update { it.copy(
|
_state.update {
|
||||||
|
it.copy(
|
||||||
isConnected = false,
|
isConnected = false,
|
||||||
isConnecting = false,
|
isConnecting = false,
|
||||||
deviceState = null
|
deviceState = null
|
||||||
) }
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
@@ -176,15 +214,59 @@ class BluetoothViewModel(
|
|||||||
|
|
||||||
fun startImuCalibration() {
|
fun startImuCalibration() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val message = newStatusMessageAdapter.toJson(
|
val message = statusMessageAdapter.toJson(
|
||||||
ChangedDeviceStatus(DeviceStatus.IsImuCalibration)
|
ChangedDeviceStatus(DeviceStatus.IsImuCalibration)
|
||||||
) + "\n\r"
|
) + "\n\r"
|
||||||
val success = bluetoothController.trySendMessage(
|
val isSuccess = bluetoothController.trySendMessage(
|
||||||
message.toByteArray()
|
message.toByteArray()
|
||||||
)
|
)
|
||||||
if(!success) {
|
if(!isSuccess) {
|
||||||
Log.e("BluetoothVM", "Failed to start IMU calibration: $message")
|
Log.e("BluetoothVM", "Failed to start IMU calibration: $message")
|
||||||
|
} else {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
deviceState = it.deviceState?.copy(status = DeviceStatus.IsImuCalibration)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun requestPidSettings() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val message = pidSittingsRequiredMessageAdapter.toJson(PidSettingRequiredMessage(true)) + "\n\r"
|
||||||
|
Log.i("BluetoothVM", "Requested PID settings: $message")
|
||||||
|
val isSuccess = bluetoothController.trySendMessage(
|
||||||
|
message.toByteArray()
|
||||||
|
)
|
||||||
|
if(!isSuccess) {
|
||||||
|
Log.e("BluetoothVM", "Failed to request PID settings: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyPidSettings(pidSettings: PidSettings) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val message = pidSittingsMessageAdapter.toJson(pidSettings) + "\n\r"
|
||||||
|
val isSuccess = bluetoothController.trySendMessage(message.toByteArray())
|
||||||
|
if(!isSuccess) {
|
||||||
|
Log.e("BluetoothVM", "Failed to request PID settings: $message")
|
||||||
|
_state.update {
|
||||||
|
it.copy(errorMessage = "Не удалось обновить значения PID")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_state.update {
|
||||||
|
it.copy(deviceState = it.deviceState?.copy(pidSettings = pidSettings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearPidSettings() {
|
||||||
|
Log.i("BluetoothVM", "PidSettings cleared")
|
||||||
|
_state.update {
|
||||||
|
it.copy(deviceState = it.deviceState?.copy(pidSettings = null))
|
||||||
|
}
|
||||||
|
Log.i("BluetoothVM", "PidSettings: ${_state.value.deviceState?.pidSettings}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Testblue" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.Main" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "8.1.0" apply false
|
id("com.android.application") version "8.2.2" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
||||||
id("org.jetbrains.kotlin.jvm") version "1.8.10" apply false
|
id("org.jetbrains.kotlin.jvm") version "1.8.10" apply false
|
||||||
}
|
}
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Aug 13 15:00:54 KRAT 2023
|
#Sun Aug 13 15:00:54 KRAT 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pluginManagement {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
maven(url = "https://jitpack.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
@@ -10,6 +11,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven(url = "https://jitpack.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user