view model and object

This commit is contained in:
lash404 committed 2026-10-30 15:00:46 +01:00
1 parent b1d89fbd08
commit 025f754504
16 files changed
+2609 -89

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
File diff suppressed because it is too large. Load diff
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
</GradleProjectSettings>
</option>
</component>
</project>
+10
View 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>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+1
View File
@@ -54,5 +54,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation("androidx.activity:activity-ktx:1.13.0")
implementation("androidx.navigation:navigation-compose:2.10.2")
}
+1 -2
View File
@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
@@ -19,7 +19,6 @@
android:theme="@style/Theme.SmartWatchAndroid">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:exported="true"
android:theme="@style/Theme.SmartWatchAndroid"
android:windowSoftInputMode="adjustResize">
@@ -9,16 +9,22 @@ import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresPermission
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ActivityCompat
import com.example.smart_watch_android.MainViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.LocalTime
@@ -26,18 +32,15 @@ import java.time.format.DateTimeFormatter
import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds
class BleManager(private val context: Context) {
//var deviceAddress = "10:BD:A3:8F:01:0E"
object BLEManager{
var devices = mutableListOf<Devices>()
var isConnected = mutableStateOf(false)
private val _isConnected = MutableStateFlow(false)
val isConnected: StateFlow<Boolean> = _isConnected.asStateFlow()
val currentDate: LocalDate? = LocalDate.now()
private val deviceUUIDs = DeviceUUIDs
private var bluetoothGatt: BluetoothGatt? = null
private var bluetoothManager: BluetoothManager? = null
private var bluetoothAdapter: BluetoothAdapter? = null
var deviceAddress: String = ""
private lateinit var bluetoothManager: BluetoothManager
private lateinit var bluetoothAdapter: BluetoothAdapter
val gattCallback = object : BluetoothGattCallback() {
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
@@ -46,7 +49,7 @@ class BleManager(private val context: Context) {
Log.d("ESP32_BLE", "Connected! Start discoverServices()...")
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
isConnected.value = false
updateConnectionStatus(false)
gatt.close()
bluetoothGatt = null
@@ -54,14 +57,14 @@ class BleManager(private val context: Context) {
}
} else {
Log.d("ESP32_BLE", "error in onConnectionStateChange! Status-Code: $status")
isConnected.value = false
updateConnectionStatus(false)
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d("ESP32_BLE", "connected successfully.")
isConnected.value = true
updateConnectionStatus(true)
}
}
}
@@ -69,28 +72,31 @@ class BleManager(private val context: Context) {
val deviceName: String,
val deviceMac: String
)
@RequiresApi(Build.VERSION_CODES.S)
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun init() {
checkAndRequestBluetoothPermissions()
fun init(context: Context, activity: Activity) {
checkAndRequestBluetoothPermissions(
activity = activity
)
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager?.adapter
bluetoothAdapter = bluetoothManager.adapter
devices = getDeviceList() as MutableList<Devices>
}
fun updateConnectionStatus(status: Boolean) {
_isConnected.value = status
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun getDeviceList(): List<Devices>{
bluetoothAdapter?.bondedDevices?.forEach { device ->
devices.clear()
bluetoothAdapter.bondedDevices?.forEach { device ->
devices.add(Devices(deviceName = device.name, deviceMac = device.address))
}
Log.d("devices","$devices")
return devices
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@RequiresApi(Build.VERSION_CODES.S)
fun checkAndRequestBluetoothPermissions() {
fun checkAndRequestBluetoothPermissions(activity: Activity) {
val permissions = arrayOf(
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_SCAN,
@@ -98,27 +104,20 @@ class BleManager(private val context: Context) {
)
val missingPermissions = permissions.filter {
ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED
ActivityCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED
}
if (missingPermissions.isNotEmpty()) {
val activity = context as? Activity
if (activity != null) {
ActivityCompat.requestPermissions(activity, missingPermissions.toTypedArray(), 101)
} else {
Log.d("ESP32_BLE", "Cannot request permissions: Context is not an Activity!")
}
// Berechtigungen anfordern
ActivityCompat.requestPermissions(activity, missingPermissions.toTypedArray(), 101)
} else {
Log.d("ESP32_BLE", "Alle Bluetooth-Berechtigungen sind bereits erteilt!")
}
}
fun connectToEsp32() {
fun connectToEsp32(context: Context, deviceAddress: String) {
Log.d("ESP32_BLE", "connectToEsp32 started with address: $deviceAddress")
if (bluetoothAdapter == null) {
Log.d("ESP32_BLE", "error: Bluetooth is not supported!")
return
}
try {
val device = bluetoothAdapter!!.getRemoteDevice(deviceAddress)
val device = bluetoothAdapter.getRemoteDevice(deviceAddress)
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
bluetoothGatt = device.connectGatt(context, false, gattCallback)
@@ -131,7 +130,7 @@ class BleManager(private val context: Context) {
}
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun disconnectEsp32() {
fun disconnectEsp32(context: Context) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
bluetoothGatt?.disconnect()
Log.d("ESP32_BLE","disconnectEsp32")
@@ -140,8 +139,7 @@ class BleManager(private val context: Context) {
Log.d("BLE_DEBUG", "Permission BLUETOOTH_CONNECT error!")
}
}
fun writeToEsp32(dataString: String, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) {
fun writeToEsp32(context: Context, dataString: String, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) {
val gatt = bluetoothGatt
if (gatt == null) {
Log.d("ESP32_BLE", "error: no GATT-Objekt existing (not connected).")
@@ -170,7 +168,7 @@ class BleManager(private val context: Context) {
Log.d("ESP32_BLE", "started writing: $success")
}
}
fun syncTime() {
fun syncTime(context: Context) {
val currentTime = LocalTime.now()
val formatterTime = DateTimeFormatter.ofPattern("HH mm ss")
val formattedTime = currentTime.format(formatterTime)
@@ -181,11 +179,19 @@ class BleManager(private val context: Context) {
Log.d("Time/Date","$formattedDate $formattedTime")
CoroutineScope(Dispatchers.IO).launch {
writeToEsp32(formattedTime, deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_TIME_UUID)
writeToEsp32(
context = context,
dataString = formattedTime,
SERVICE_UUID = deviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = deviceUUIDs.CHAR_TIME_UUID)
delay(150.milliseconds)
writeToEsp32("$formattedDate", deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_DATE_UUID)
writeToEsp32(
context = context,
dataString ="$formattedDate",
SERVICE_UUID = deviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = deviceUUIDs.CHAR_DATE_UUID)
}
}
}
@@ -1,7 +1,6 @@
package com.example.smart_watch_android
import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
@@ -13,20 +12,17 @@ import androidx.annotation.RequiresPermission
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.smart_watch_android.BLE.BleManager
import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.Screens.AppNavigation
class MainActivity : ComponentActivity() {
lateinit var bleManager: BleManager
private val viewModel: MainViewModel by viewModels()
@RequiresApi(Build.VERSION_CODES.S)
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onCreate(savedInstanceState: Bundle?,) {
super.onCreate(savedInstanceState)
bleManager = BleManager(this)
bleManager.init()
BLEManager.init(context = applicationContext, activity = this)
enableEdgeToEdge()
setContent {
MaterialTheme {
@@ -34,16 +30,9 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AppNavigation(bleManager = bleManager)
AppNavigation(viewModel)
}
}
}
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onDestroy() {
super.onDestroy()
bleManager.disconnectEsp32()
}
}
@@ -0,0 +1,17 @@
package com.example.smart_watch_android
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
var selectedDevice by mutableStateOf("device")
fun updateSelectedDevice(newName: String) {
selectedDevice = newName
}
var selectedDeviceAddress = ""
fun updateSelectedDeviceAddress(address: String) {
selectedDeviceAddress = address
}
}
@@ -4,17 +4,23 @@ import android.Manifest
import androidx.annotation.RequiresPermission
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.smart_watch_android.BLE.BleManager
import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.MainViewModel
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@Composable
fun AppNavigation(bleManager: BleManager) {
fun AppNavigation(viewModel: MainViewModel) {
val context = LocalContext.current
val isConnected by BLEManager.isConnected.collectAsState()
val navController = rememberNavController()
LaunchedEffect(bleManager.isConnected) {
if (bleManager.isConnected.value) {
LaunchedEffect(BLEManager.isConnected) {
if (isConnected) {
navController.navigate("control_panel") {
popUpTo("connect") { inclusive = true }
}
@@ -24,7 +30,7 @@ fun AppNavigation(bleManager: BleManager) {
}
}
}
val startDestination = if (bleManager.isConnected.value) {
val startDestination = if (isConnected) {
"control_panel"
} else {
"connect"
@@ -36,17 +42,15 @@ fun AppNavigation(bleManager: BleManager) {
) {
// Connect Screen
composable("connect") {
ConnectScreen(
bleManager = bleManager
)
ConnectScreen(viewModel)
}
composable("control_panel") {
ControlPanelScreen(
bleManager = bleManager,
viewModel,
onDisconnectClicked = {
bleManager.disconnectEsp32()
}
BLEManager.disconnectEsp32(context = context)
},
)
}
}
@@ -1,13 +1,17 @@
package com.example.smart_watch_android.Screens
import android.Manifest
import android.content.res.Configuration
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -27,15 +31,29 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.smart_watch_android.BLE.BleManager
import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.MainViewModel
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@Composable
fun ConnectScreen(bleManager: BleManager) {
fun ConnectScreen(viewModel: MainViewModel) {
val context = LocalContext.current
var expanded by remember { mutableStateOf(false) }
var selectedDevice by remember { mutableStateOf("device") }
val configuration = LocalConfiguration.current
val isLandscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
val fraction = 0.7f
val dynamicModifier = if (isLandscape) {
Modifier
.fillMaxHeight(fraction)
.aspectRatio(1f)
} else {
Modifier
.fillMaxWidth(fraction)
.aspectRatio(1f)
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -44,29 +62,34 @@ fun ConnectScreen(bleManager: BleManager) {
horizontalAlignment = Alignment.CenterHorizontally
)
{
Box(
){
Text("if the list is empty ensure you have bluetooth enabled!")
}
Box(
modifier = Modifier
.padding(16.dp)
) {
Button(
onClick = { expanded = !expanded },
onClick = { expanded = !expanded},
shape = RoundedCornerShape(4.dp),
modifier = Modifier.width(400.dp)
modifier = Modifier.fillMaxWidth(fraction),
colors = ButtonDefaults.buttonColors(Color(0xFF5E18BE))
) {
Text(selectedDevice)
Text(viewModel.selectedDevice)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(400.dp)
.fillMaxWidth(0.6f)
) {
bleManager.devices.forEach { item ->
BLEManager.devices.forEach { item ->
DropdownMenuItem(
text = { Text(item.deviceName) },
onClick = {
selectedDevice = item.deviceName
bleManager.deviceAddress = item.deviceMac
viewModel.updateSelectedDevice(item.deviceName)
viewModel.updateSelectedDeviceAddress(item.deviceMac)
Log.d("ConnectScreen", item.deviceMac)
expanded = false
}
@@ -75,13 +98,11 @@ fun ConnectScreen(bleManager: BleManager) {
}
}
Spacer(modifier = Modifier.height(64.dp))
Button(
modifier = Modifier.size(400.dp),
onClick = {bleManager.connectToEsp32() },
modifier = dynamicModifier,
onClick = {BLEManager.connectToEsp32(context, viewModel.selectedDeviceAddress) },
shape = CircleShape,
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5E18BE))
) {
@@ -12,6 +12,7 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -19,15 +20,18 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.smart_watch_android.BLE.BleManager
import com.example.smart_watch_android.BLE.BLEManager
import com.example.smart_watch_android.BLE.DeviceUUIDs
import com.example.smart_watch_android.MainViewModel
@Composable
fun ControlPanelScreen(
bleManager: BleManager,
viewModel: MainViewModel,
onDisconnectClicked: () -> Unit
) {
val context = LocalContext.current
var textInput by remember { mutableStateOf("") }
Column(
@@ -50,9 +54,12 @@ fun ControlPanelScreen(
Button(
onClick = {
if (textInput.isNotEmpty()) {
bleManager.writeToEsp32(
textInput, DeviceUUIDs.SERVICE_UUID, DeviceUUIDs.CHAR_MESSAGE_UUID
)
BLEManager.writeToEsp32(
context = context,
dataString = textInput,
SERVICE_UUID = DeviceUUIDs.SERVICE_UUID,
CHARACTERISTIC_UUID = DeviceUUIDs.CHAR_MESSAGE_UUID,
)
textInput = ""
}
},
@@ -67,7 +74,9 @@ fun ControlPanelScreen(
Button(
onClick = {
bleManager.syncTime()
BLEManager.syncTime(
context
)
},
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF50128F)),
modifier = Modifier.fillMaxWidth()