time sync implemantation. app is open in the background

This commit is contained in:
Gitea committed 2026-10-17 22:28:13 +02:00
1 parent f8337eef46
commit c53eefaf79
11 files changed
+218 -122

No files matched your search

+4
View File
@@ -39,11 +39,13 @@ dependencies {
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.firebase.crashlytics.buildtools)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4) androidTestImplementation(libs.androidx.compose.ui.test.junit4)
@@ -51,4 +53,6 @@ dependencies {
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
implementation("androidx.navigation:navigation-compose:2.10.2")
} }
+6 -1
View File
@@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <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.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -16,10 +18,13 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.SmartWatchAndroid"> android:theme="@style/Theme.SmartWatchAndroid">
<service
android:name=".BluetoothForegroundService"
android:foregroundServiceType="connectedDevice"
android:exported="false" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SmartWatchAndroid" android:theme="@style/Theme.SmartWatchAndroid"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
@@ -29,8 +29,7 @@ import java.util.UUID
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
class BleManager(private val context: Context) { class BleManager(private val context: Context) {
val currentTime = LocalTime.now() val currentDate: LocalDate? = LocalDate.now()
val currentDate = LocalDate.now()
val deviceUUIDs = DeviceUUIDs val deviceUUIDs = DeviceUUIDs
private var bluetoothGatt: BluetoothGatt? = null private var bluetoothGatt: BluetoothGatt? = null
@@ -40,9 +39,31 @@ class BleManager(private val context: Context) {
var isConnected by mutableStateOf(false) var isConnected by mutableStateOf(false)
private set private set
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun init() { fun init() {
bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager?.adapter bluetoothAdapter = bluetoothManager?.adapter
checkCurrentConnectionState("10:BD:A3:8F:01:0E")
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun checkCurrentConnectionState(deviceAddress: String) {
if (bluetoothManager == null) return
try {
val device = bluetoothAdapter?.getRemoteDevice(deviceAddress)
if (device != null) {
val state = bluetoothManager!!.getConnectionState(device, BluetoothProfile.GATT)
if (state == BluetoothProfile.STATE_CONNECTED) {
isConnected = true
Log.d("ESP32_BLE", "App restarted: device still connected!")
} else {
isConnected = false
Log.d("ESP32_BLE", "App restarted: device not connected.")
}
}
} catch (e: Exception) {
Log.d("ESP32_BLE", "error while checking the status: ${e.message}")
}
} }
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
fun checkAndRequestBluetoothPermissions() { fun checkAndRequestBluetoothPermissions() {
@@ -52,132 +73,122 @@ class BleManager(private val context: Context) {
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
) )
// Prüfen, ob die Berechtigungen bereits erteilt wurden
val missingPermissions = permissions.filter { val missingPermissions = permissions.filter {
ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED
} }
if (missingPermissions.isNotEmpty()) { if (missingPermissions.isNotEmpty()) {
// Wenn Berechtigungen fehlen, frage den Nutzer danach val activity = context as? Activity
ActivityCompat.requestPermissions(context as Activity, missingPermissions.toTypedArray(), 101) if (activity != null) {
ActivityCompat.requestPermissions(activity, missingPermissions.toTypedArray(), 101)
} else {
Log.d("ESP32_BLE", "Cannot request permissions: Context is not an Activity!")
}
} }
} }
fun connectToEsp32(deviceAddress: String) { fun connectToEsp32(deviceAddress: String) {
Log.d("ESP32_BLE", "1. connectToEsp32 gestartet mit Adresse: $deviceAddress") Log.d("ESP32_BLE", "connectToEsp32 started with address: $deviceAddress")
if (bluetoothAdapter == null) { if (bluetoothAdapter == null) {
Log.d("ESP32_BLE", "Fehler: Bluetooth wird von diesem Gerät nicht unterstützt!") Log.d("ESP32_BLE", "error: Bluetooth is not supported!")
return return
} }
try { try {
val device = bluetoothAdapter!!.getRemoteDevice(deviceAddress) val device = bluetoothAdapter!!.getRemoteDevice(deviceAddress)
Log.d("ESP32_BLE", "2. BluetoothDevice Objekt erfolgreich erstellt.")
val gattCallback = object : BluetoothGattCallback() { val gattCallback = object : BluetoothGattCallback() {
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d("ESP32_BLE", "3. onConnectionStateChange aufgerufen! Status: $status, NewState: $newState")
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.d("ESP32_BLE", "4. Verbunden! Starte discoverServices()...") Log.d("ESP32_BLE", "Connected! Start discoverServices()...")
gatt.discoverServices() gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
isConnected = false isConnected = false
gatt.close() gatt.close()
bluetoothGatt = null bluetoothGatt = null
Log.d("ESP32_BLE", "5. Getrennt worden.") Log.d("ESP32_BLE", "Disconnected.")
} }
} else { } else {
Log.d("ESP32_BLE", "Fehler in onConnectionStateChange! Status-Code: $status") Log.d("ESP32_BLE", "error in onConnectionStateChange! Status-Code: $status")
isConnected = false isConnected = false
} }
} }
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
Log.d("ESP32_BLE", "6. onServicesDiscovered aufgerufen mit Status: $status")
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
isConnected = true isConnected = true
writeToEsp32("Hallo ESP32", deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_DATE_UUID) Log.d("ESP32_BLE", "connected successfully.")
Log.d("ESP32_BLE", "7. Services erfolgreich gefunden! Verbindung komplett erfolgreich.")
} }
} }
} }
// Berechtigungsprüfung
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
Log.d("ESP32_BLE", "8. Berechtigung vorhanden, rufe connectGatt auf...")
bluetoothGatt = device.connectGatt(context, false, gattCallback) bluetoothGatt = device.connectGatt(context, false, gattCallback)
} else { } else {
Log.d("ESP32_BLE", "FEHLER: Keine BLUETOOTH_CONNECT Berechtigung erteilt!") Log.d("ESP32_BLE", "error: no BLUETOOTH_CONNECT permission!")
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.d("ESP32_BLE", "Exception gefangen: ${e.message}") Log.d("ESP32_BLE", "Exception: ${e.message}")
} }
} }
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
fun disconnectEsp32() { fun disconnectEsp32() {
// Nur den Disconnect-Befehl senden.
// Das Schließen (close) überlassen wir dem Callback, wenn es wirklich so weit ist!
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
bluetoothGatt?.disconnect() bluetoothGatt?.disconnect()
Log.d("ESP32_BLE","disconnectEsp32") Log.d("ESP32_BLE","disconnectEsp32")
} }
else { else {
Log.d("BLE_DEBUG", "Permission BLUETOOTH_CONNECT fehlt!") Log.d("BLE_DEBUG", "Permission BLUETOOTH_CONNECT error!")
} }
} }
fun writeToEsp32(dataString: String, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) { fun writeToEsp32(dataString: String, SERVICE_UUID: UUID?, CHARACTERISTIC_UUID: UUID?) {
// Prüfen, ob die Verbindung steht und bluetoothGatt nicht null ist
val gatt = bluetoothGatt val gatt = bluetoothGatt
if (gatt == null) { if (gatt == null) {
Log.d("ESP32_BLE", "Fehler: Kein GATT-Objekt vorhanden (nicht verbunden).") Log.d("ESP32_BLE", "error: no GATT-Objekt existing (not connected).")
return return
} }
// 1. Service holen
val service = gatt.getService(SERVICE_UUID) val service = gatt.getService(SERVICE_UUID)
if (service == null) { if (service == null) {
Log.d("ESP32_BLE", "Fehler: Service UUID nicht gefunden!") Log.d("ESP32_BLE", "error: Service UUID not found!")
return return
} }
// 2. Characteristic holen
val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID)
if (characteristic == null) { if (characteristic == null) {
Log.d("ESP32_BLE", "Fehler: Characteristic UUID nicht gefunden!") Log.d("ESP32_BLE", "error: Characteristic UUID not found!")
return return
} }
// 3. Daten in Bytes umwandeln (z.B. einen String wie "LED_ON" in UTF-8 Bytes)
val bytes = dataString.toByteArray(Charsets.UTF_8) val bytes = dataString.toByteArray(Charsets.UTF_8)
characteristic.value = bytes characteristic.value = bytes
// 4. Schreibtyp festlegen ( WRITE_TYPE_DEFAULT wartet auf Antwort vom ESP32, WRITE_TYPE_NO_RESPONSE ist schneller )
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
// 5. Berechtigung prüfen und Schreibvorgang starten
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
val success = gatt.writeCharacteristic(characteristic) val success = gatt.writeCharacteristic(characteristic)
Log.d("ESP32_BLE", "Schreibvorgang gestartet: $success") Log.d("ESP32_BLE", "started writing: $success")
} }
} }
fun syncTime() { fun syncTime() {
val currentTime = LocalTime.now()
val formatterTime = DateTimeFormatter.ofPattern("HH mm ss") val formatterTime = DateTimeFormatter.ofPattern("HH mm ss")
val formattedTime = currentTime.format(formatterTime) val formattedTime = currentTime.format(formatterTime)
val formatter = DateTimeFormatter.ofPattern("dd MM yyyy") val formatter = DateTimeFormatter.ofPattern("dd MM yyyy")
val formattedDate = currentDate.format(formatter) val formattedDate = currentDate?.format(formatter)
Log.d("Time/Date","$formattedDate $formattedTime") Log.d("Time/Date","$formattedDate $formattedTime")
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
writeToEsp32(formattedTime, deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_TIME_UUID) writeToEsp32(formattedTime, deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_TIME_UUID)
delay(150.milliseconds) // Kurze Pause für den BLE-Stack (z. B. 150 ms) delay(150.milliseconds)
writeToEsp32("$formattedDate", deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_DATE_UUID) writeToEsp32("$formattedDate", deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_DATE_UUID)
} }
@@ -0,0 +1,47 @@
package com.example.smart_watch_android
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
class BluetoothForegroundService : Service() {
companion object {
const val CHANNEL_ID = "BluetoothServiceChannel"
const val NOTIFICATION_ID = 1
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Bluetooth connection active")
.setContentText("the app keeps the connection in the background.")
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.build()
startForeground(NOTIFICATION_ID, notification)
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun createNotificationChannel() {
val serviceChannel = NotificationChannel(
CHANNEL_ID,
"Bluetooth Service Channel",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(serviceChannel)
}
}
@@ -1,38 +1,44 @@
package com.example.smart_watch_android package com.example.smart_watch_android
import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.example.smart_watch_android.BLE.BleManager import com.example.smart_watch_android.Screens.AppNavigation
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
val bleManager = BleManager(context = this) private val viewModel: MainViewModel by viewModels()
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@androidx.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
bleManager.init() viewModel.initBleManager(this)
bleManager.checkAndRequestBluetoothPermissions() viewModel.bleManager.checkAndRequestBluetoothPermissions()
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
MaterialTheme { MaterialTheme {
Surface( Surface(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background color = MaterialTheme.colorScheme.background
) { ) {
// Hier wird deine AppNavigation aufgerufen, AppNavigation(bleManager = viewModel.bleManager)
// die die Screens steuert!
AppNavigation(bleManager)
} }
} }
} }
startBluetoothService()
}
private fun startBluetoothService() {
val serviceIntent = Intent(this, BluetoothForegroundService::class.java)
startForegroundService(serviceIntent)
} }
} }
@@ -0,0 +1,20 @@
package com.example.smart_watch_android
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.example.smart_watch_android.BLE.BleManager
class MainViewModel(
application: Application,
) : AndroidViewModel(application) {
lateinit var bleManager: BleManager
private set
fun initBleManager(activityContext: android.content.Context) {
if (!::bleManager.isInitialized) {
bleManager = BleManager(context = activityContext)
bleManager.init()
}
}
}
@@ -0,0 +1,60 @@
package com.example.smart_watch_android.Screens
import android.Manifest
import androidx.annotation.RequiresPermission
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavController
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.MainViewModel
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@Composable
fun AppNavigation(bleManager: BleManager) {
val navController = rememberNavController()
LaunchedEffect(bleManager.isConnected) {
if (bleManager.isConnected) {
navController.navigate("control_panel") {
popUpTo("connect") { inclusive = true }
}
} else {
navController.navigate("connect") {
popUpTo("control_panel") { inclusive = true }
}
}
}
val startDestination = if (bleManager.isConnected) {
"control_panel"
} else {
"connect"
}
NavHost(
navController = navController,
startDestination = startDestination
) {
// Connect Screen
composable("connect") {
ConnectScreen(
isConnected = bleManager.isConnected,
onConnectClicked = {
bleManager.connectToEsp32("10:BD:A3:8F:01:0E")
}
)
}
composable("control_panel") {
ControlPanelScreen(
bleManager = bleManager,
onDisconnectClicked = {
bleManager.disconnectEsp32()
}
)
}
}
}
@@ -2,6 +2,8 @@ package com.example.smart_watch_android.Screens
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -9,6 +11,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable @Composable
fun ConnectScreen(isConnected: Boolean, onConnectClicked: () -> Unit) { fun ConnectScreen(isConnected: Boolean, onConnectClicked: () -> Unit) {
@@ -18,13 +22,14 @@ fun ConnectScreen(isConnected: Boolean, onConnectClicked: () -> Unit) {
) { ) {
Button( Button(
onClick = onConnectClicked, onClick = onConnectClicked,
colors = if (isConnected) { modifier = Modifier.size(300.dp),
ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50)) // Grün shape = CircleShape,
} else { colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5E18BE))
ButtonDefaults.buttonColors()
}
) { ) {
Text(text = if (isConnected) "Verbunden" else "Connect") Text(
text = "Connect",
fontSize = 30.sp
)
} }
} }
} }
@@ -37,11 +37,10 @@ fun ControlPanelScreen(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// Eingabefeld für Text / Uhrzeit / Befehle
OutlinedTextField( OutlinedTextField(
value = textInput, value = textInput,
onValueChange = { textInput = it }, onValueChange = { textInput = it },
label = { Text("Nachricht an ESP32") }, label = { Text("Massage to ESP32") },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
@@ -52,12 +51,14 @@ fun ControlPanelScreen(
onClick = { onClick = {
if (textInput.isNotEmpty()) { if (textInput.isNotEmpty()) {
bleManager.writeToEsp32( bleManager.writeToEsp32(
textInput, DeviceUUIDs.SERVICE_UUID, DeviceUUIDs.CHAR_DATE_UUID textInput, DeviceUUIDs.SERVICE_UUID, DeviceUUIDs.CHAR_MESSAGE_UUID
) )
textInput = "" // Feld danach leeren textInput = ""
} }
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF6C24B6)),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Send") Text("Send")
} }
@@ -68,6 +69,7 @@ fun ControlPanelScreen(
onClick = { onClick = {
bleManager.syncTime() bleManager.syncTime()
}, },
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF50128F)),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("sync Time") Text("sync Time")
@@ -75,7 +77,6 @@ fun ControlPanelScreen(
Spacer(modifier = Modifier.height(64.dp)) Spacer(modifier = Modifier.height(64.dp))
// Disconnect Button (z.B. in Rot, um ihn abzusetzen)
Button( Button(
onClick = onDisconnectClicked, onClick = onDisconnectClicked,
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)), colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)),
@@ -1,67 +0,0 @@
package com.example.smart_watch_android
import android.Manifest
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.unit.dp
import com.example.smart_watch_android.BLE.BleManager
import com.example.smart_watch_android.BLE.DeviceUUIDs
import com.example.smart_watch_android.Screens.ConnectScreen
import com.example.smart_watch_android.Screens.ControlPanelScreen
enum class Screen {
CONNECT,
CONTROL_PANEL
}
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
@Composable
fun AppNavigation(BLEManager: BleManager) {
var currentScreen by remember { mutableStateOf(Screen.CONNECT) }
val bleManager = BLEManager
if (bleManager.isConnected && currentScreen == Screen.CONNECT) {
currentScreen = Screen.CONTROL_PANEL
} else if (!bleManager.isConnected && currentScreen == Screen.CONTROL_PANEL) {
currentScreen = Screen.CONNECT
}
// Je nach Zustand den entsprechenden Screen anzeigen
when (currentScreen) {
Screen.CONNECT -> {
ConnectScreen(
isConnected = bleManager.isConnected,
onConnectClicked = {
bleManager.connectToEsp32("10:BD:A3:8F:01:0E")
}
)
}
Screen.CONTROL_PANEL -> {
ControlPanelScreen(
bleManager,
onDisconnectClicked = {
bleManager.disconnectEsp32()
// Screen wechselt automatisch über den Check oben
}
)
}
}
}
+4
View File
@@ -8,6 +8,8 @@ lifecycleRuntimeKtx = "2.11.0"
activityCompose = "1.13.0" activityCompose = "1.13.0"
kotlin = "2.2.10" kotlin = "2.2.10"
composeBom = "2026.02.01" composeBom = "2026.02.01"
firebaseCrashlyticsBuildtools = "3.0.8"
runtime = "1.12.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -24,6 +26,8 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
firebase-crashlytics-buildtools = { group = "com.google.firebase", name = "firebase-crashlytics-buildtools", version.ref = "firebaseCrashlyticsBuildtools" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }