From c53eefaf79537f3d5cd59967169d7d0e64de973f Mon Sep 17 00:00:00 2001 From: Gitea Date: Sat, 17 Oct 2026 22:28:13 +0200 Subject: [PATCH] time sync implemantation. app is open in the background --- app/build.gradle.kts | 4 + app/src/main/AndroidManifest.xml | 7 +- .../smart_watch_android/BLE/BleManager.kt | 79 +++++++++++-------- .../BluetoothForgroundService.kt | 47 +++++++++++ .../smart_watch_android/MainActivity.kt | 24 +++--- .../smart_watch_android/MainViewModel.kt | 20 +++++ .../Screens/AppNavigation.kt | 60 ++++++++++++++ .../Screens/ConnectScreen.kt | 17 ++-- .../Screens/ControlPanelScreen.kt | 11 +-- .../smart_watch_android/Screens/Screen.kt | 67 ---------------- gradle/libs.versions.toml | 4 + 11 files changed, 218 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt create mode 100644 app/src/main/java/com/example/smart_watch_android/MainViewModel.kt create mode 100644 app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt delete mode 100644 app/src/main/java/com/example/smart_watch_android/Screens/Screen.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index edcdb5d..9bda6b3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,11 +39,13 @@ dependencies { implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.firebase.crashlytics.buildtools) testImplementation(libs.junit) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) @@ -51,4 +53,6 @@ dependencies { androidTestImplementation(libs.androidx.junit) debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.tooling) + + implementation("androidx.navigation:navigation-compose:2.10.2") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 765f47c..dd3bbc2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + + diff --git a/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt b/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt index 56937eb..3bb532b 100644 --- a/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt +++ b/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt @@ -29,8 +29,7 @@ import java.util.UUID import kotlin.time.Duration.Companion.milliseconds class BleManager(private val context: Context) { - val currentTime = LocalTime.now() - val currentDate = LocalDate.now() + val currentDate: LocalDate? = LocalDate.now() val deviceUUIDs = DeviceUUIDs private var bluetoothGatt: BluetoothGatt? = null @@ -40,9 +39,31 @@ class BleManager(private val context: Context) { var isConnected by mutableStateOf(false) private set + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) fun init() { bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager 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) fun checkAndRequestBluetoothPermissions() { @@ -52,132 +73,122 @@ class BleManager(private val context: Context) { Manifest.permission.ACCESS_FINE_LOCATION ) - // Prüfen, ob die Berechtigungen bereits erteilt wurden val missingPermissions = permissions.filter { ActivityCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED } if (missingPermissions.isNotEmpty()) { - // Wenn Berechtigungen fehlen, frage den Nutzer danach - ActivityCompat.requestPermissions(context as Activity, missingPermissions.toTypedArray(), 101) + 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!") + } } } 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) { - Log.d("ESP32_BLE", "Fehler: Bluetooth wird von diesem Gerät nicht unterstützt!") + Log.d("ESP32_BLE", "error: Bluetooth is not supported!") return } try { val device = bluetoothAdapter!!.getRemoteDevice(deviceAddress) - Log.d("ESP32_BLE", "2. BluetoothDevice Objekt erfolgreich erstellt.") val gattCallback = object : BluetoothGattCallback() { @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) 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 (newState == BluetoothProfile.STATE_CONNECTED) { - Log.d("ESP32_BLE", "4. Verbunden! Starte discoverServices()...") + Log.d("ESP32_BLE", "Connected! Start discoverServices()...") gatt.discoverServices() } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { isConnected = false gatt.close() bluetoothGatt = null - Log.d("ESP32_BLE", "5. Getrennt worden.") + Log.d("ESP32_BLE", "Disconnected.") } } else { - Log.d("ESP32_BLE", "Fehler in onConnectionStateChange! Status-Code: $status") + Log.d("ESP32_BLE", "error in onConnectionStateChange! Status-Code: $status") isConnected = false } } override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - Log.d("ESP32_BLE", "6. onServicesDiscovered aufgerufen mit Status: $status") if (status == BluetoothGatt.GATT_SUCCESS) { isConnected = true - writeToEsp32("Hallo ESP32", deviceUUIDs.SERVICE_UUID, deviceUUIDs.CHAR_DATE_UUID) - Log.d("ESP32_BLE", "7. Services erfolgreich gefunden! Verbindung komplett erfolgreich.") + Log.d("ESP32_BLE", "connected successfully.") } } } - // Berechtigungsprüfung 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) } else { - Log.d("ESP32_BLE", "FEHLER: Keine BLUETOOTH_CONNECT Berechtigung erteilt!") + Log.d("ESP32_BLE", "error: no BLUETOOTH_CONNECT permission!") } } catch (e: Exception) { - Log.d("ESP32_BLE", "Exception gefangen: ${e.message}") + Log.d("ESP32_BLE", "Exception: ${e.message}") } } @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) 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) { bluetoothGatt?.disconnect() Log.d("ESP32_BLE","disconnectEsp32") } 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?) { - // Prüfen, ob die Verbindung steht und bluetoothGatt nicht null ist val gatt = bluetoothGatt 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 } - // 1. Service holen val service = gatt.getService(SERVICE_UUID) if (service == null) { - Log.d("ESP32_BLE", "Fehler: Service UUID nicht gefunden!") + Log.d("ESP32_BLE", "error: Service UUID not found!") return } - // 2. Characteristic holen val characteristic = service.getCharacteristic(CHARACTERISTIC_UUID) if (characteristic == null) { - Log.d("ESP32_BLE", "Fehler: Characteristic UUID nicht gefunden!") + Log.d("ESP32_BLE", "error: Characteristic UUID not found!") return } - // 3. Daten in Bytes umwandeln (z.B. einen String wie "LED_ON" in UTF-8 Bytes) val bytes = dataString.toByteArray(Charsets.UTF_8) 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 - // 5. Berechtigung prüfen und Schreibvorgang starten if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { val success = gatt.writeCharacteristic(characteristic) - Log.d("ESP32_BLE", "Schreibvorgang gestartet: $success") + Log.d("ESP32_BLE", "started writing: $success") } } fun syncTime() { + val currentTime = LocalTime.now() val formatterTime = DateTimeFormatter.ofPattern("HH mm ss") val formattedTime = currentTime.format(formatterTime) val formatter = DateTimeFormatter.ofPattern("dd MM yyyy") - val formattedDate = currentDate.format(formatter) + val formattedDate = currentDate?.format(formatter) Log.d("Time/Date","$formattedDate $formattedTime") CoroutineScope(Dispatchers.IO).launch { 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) } diff --git a/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt b/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt new file mode 100644 index 0000000..ab1ac07 --- /dev/null +++ b/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/MainActivity.kt b/app/src/main/java/com/example/smart_watch_android/MainActivity.kt index f3de9c7..eae7c0f 100644 --- a/app/src/main/java/com/example/smart_watch_android/MainActivity.kt +++ b/app/src/main/java/com/example/smart_watch_android/MainActivity.kt @@ -1,38 +1,44 @@ package com.example.smart_watch_android +import android.content.Intent import android.os.Build import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier -import com.example.smart_watch_android.BLE.BleManager +import com.example.smart_watch_android.Screens.AppNavigation class MainActivity : ComponentActivity() { - val bleManager = BleManager(context = this) + private val viewModel: MainViewModel by viewModels() @RequiresApi(Build.VERSION_CODES.S) + @androidx.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - bleManager.init() - bleManager.checkAndRequestBluetoothPermissions() + viewModel.initBleManager(this) + viewModel.bleManager.checkAndRequestBluetoothPermissions() enableEdgeToEdge() setContent { MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background - ) { - // Hier wird deine AppNavigation aufgerufen, - // die die Screens steuert! - AppNavigation(bleManager) + ) { + AppNavigation(bleManager = viewModel.bleManager) } } } + startBluetoothService() + } + private fun startBluetoothService() { + val serviceIntent = Intent(this, BluetoothForegroundService::class.java) + + startForegroundService(serviceIntent) } } diff --git a/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt b/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt new file mode 100644 index 0000000..2dcc5e8 --- /dev/null +++ b/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt b/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt new file mode 100644 index 0000000..f4334e0 --- /dev/null +++ b/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt @@ -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() + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt b/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt index 01cd996..6626d88 100644 --- a/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt +++ b/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt @@ -2,6 +2,8 @@ package com.example.smart_watch_android.Screens import androidx.compose.foundation.layout.Box 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.ButtonDefaults import androidx.compose.material3.Text @@ -9,6 +11,8 @@ 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.unit.dp +import androidx.compose.ui.unit.sp @Composable fun ConnectScreen(isConnected: Boolean, onConnectClicked: () -> Unit) { @@ -18,13 +22,14 @@ fun ConnectScreen(isConnected: Boolean, onConnectClicked: () -> Unit) { ) { Button( onClick = onConnectClicked, - colors = if (isConnected) { - ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50)) // Grün - } else { - ButtonDefaults.buttonColors() - } + modifier = Modifier.size(300.dp), + shape = CircleShape, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5E18BE)) ) { - Text(text = if (isConnected) "Verbunden" else "Connect") + Text( + text = "Connect", + fontSize = 30.sp + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/ControlPanelScreen.kt b/app/src/main/java/com/example/smart_watch_android/Screens/ControlPanelScreen.kt index 3ec16c3..573108d 100644 --- a/app/src/main/java/com/example/smart_watch_android/Screens/ControlPanelScreen.kt +++ b/app/src/main/java/com/example/smart_watch_android/Screens/ControlPanelScreen.kt @@ -37,11 +37,10 @@ fun ControlPanelScreen( verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - // Eingabefeld für Text / Uhrzeit / Befehle OutlinedTextField( value = textInput, onValueChange = { textInput = it }, - label = { Text("Nachricht an ESP32") }, + label = { Text("Massage to ESP32") }, modifier = Modifier.fillMaxWidth() ) @@ -52,12 +51,14 @@ fun ControlPanelScreen( onClick = { if (textInput.isNotEmpty()) { 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() + ) { Text("Send") } @@ -68,6 +69,7 @@ fun ControlPanelScreen( onClick = { bleManager.syncTime() }, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF50128F)), modifier = Modifier.fillMaxWidth() ) { Text("sync Time") @@ -75,7 +77,6 @@ fun ControlPanelScreen( Spacer(modifier = Modifier.height(64.dp)) - // Disconnect Button (z.B. in Rot, um ihn abzusetzen) Button( onClick = onDisconnectClicked, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE53935)), diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/Screen.kt b/app/src/main/java/com/example/smart_watch_android/Screens/Screen.kt deleted file mode 100644 index f6a417d..0000000 --- a/app/src/main/java/com/example/smart_watch_android/Screens/Screen.kt +++ /dev/null @@ -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 - } - ) - } - } -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7ee047..263aaa1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,8 @@ lifecycleRuntimeKtx = "2.11.0" activityCompose = "1.13.0" kotlin = "2.2.10" composeBom = "2026.02.01" +firebaseCrashlyticsBuildtools = "3.0.8" +runtime = "1.12.1" [libraries] 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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } 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] android-application = { id = "com.android.application", version.ref = "agp" }