time sync implemantation. app is open in the background
This commit is contained in:
1 parent
f8337eef46
commit
c53eefaf79
11 files changed
+217
-121
No files matched your search
@@ -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,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,26 +1,28 @@
|
|||||||
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 {
|
||||||
@@ -28,11 +30,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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" }
|
||||||
|
|||||||
Reference in new issue
Block a user