From c24df6189cd107251e2b330b8d80c561487f615a Mon Sep 17 00:00:00 2001 From: cstef Date: Sun, 11 Dec 2022 22:21:39 +0100 Subject: [PATCH] Move settings to SettingsScreen.kt, add new settings --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 4 + .../java/com/cstef/meshlink/BleService.kt | 106 +++++-- .../java/com/cstef/meshlink/MainActivity.kt | 268 +++++++++++------- .../java/com/cstef/meshlink/db/Database.kt | 2 +- .../com/cstef/meshlink/db/entities/Device.kt | 1 + .../com/cstef/meshlink/managers/BleManager.kt | 63 +++- .../meshlink/managers/ClientBleManager.kt | 118 +++++--- .../meshlink/managers/ServerBleManager.kt | 65 ++--- .../cstef/meshlink/screens/AddDeviceScreen.kt | 78 ++--- .../cstef/meshlink/screens/SettingsScreen.kt | 227 +++++++++++++++ .../cstef/meshlink/screens/UserInfoScreen.kt | 122 +------- .../java/com/cstef/meshlink/util/constants.kt | 4 - 13 files changed, 689 insertions(+), 370 deletions(-) create mode 100644 app/src/main/java/com/cstef/meshlink/screens/SettingsScreen.kt diff --git a/app/build.gradle b/app/build.gradle index 6a23d57..d276633 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,4 +84,5 @@ dependencies { implementation "androidx.sqlite:sqlite:2.2.0" implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05" + implementation 'io.github.g0dkar:qrcode-kotlin-android:3.2.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 76a9e14..5b0e841 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,10 @@ + + diff --git a/app/src/main/java/com/cstef/meshlink/BleService.kt b/app/src/main/java/com/cstef/meshlink/BleService.kt index 07bb800..c2fb279 100644 --- a/app/src/main/java/com/cstef/meshlink/BleService.kt +++ b/app/src/main/java/com/cstef/meshlink/BleService.kt @@ -7,6 +7,7 @@ import android.os.Binder import android.os.Handler import android.os.HandlerThread import android.os.IBinder +import android.util.Base64 import android.util.Log import android.widget.Toast import androidx.core.app.NotificationCompat @@ -23,7 +24,6 @@ import com.cstef.meshlink.util.struct.Message import com.daveanthonythomas.moshipack.MoshiPack import java.security.PublicKey import java.util.* -import android.util.Base64 class BleService : Service() { @@ -43,12 +43,15 @@ class BleService : Service() { } inner class BleServiceBinder : Binder() { + val isAdvertising + get() = bleManager.isAdvertising val service: BleService get() = this@BleService val isBleStarted get() = bleManager.isStarted - + val isScanning + get() = bleManager.isScanning val isDatabaseOpen: LiveData get() = this@BleService.isDatabaseOpen val isDatabaseOpening: LiveData @@ -57,10 +60,14 @@ class BleService : Service() { get() = this@BleService.databaseError val allMessages: LiveData> get() = this@BleService.allMessages ?: throw Exception("Database not open") - val allDevices: LiveData> get() = this@BleService.allDevices ?: throw Exception("Database not open") + fun setUserId(id: String) { + userId = id + bleManager.setUserId(id) + } + fun sendMessage(receiverId: String, message: String, type: String = Message.Type.TEXT) { this@BleService.sendMessage( Message( @@ -75,8 +82,9 @@ class BleService : Service() { ) } - fun addDevice(userId: String) { - bleDataExchangeManager.onUserConnected(userId) + fun addDevice(userId: String, address: String, publicKey: PublicKey) { + bleDataExchangeManager.onUserConnected(userId, address) + bleDataExchangeManager.onUserPublicKeyReceived(userId, address, publicKey) } // fun sendIsWriting(userId: String, isWriting: Boolean) { @@ -84,17 +92,21 @@ class BleService : Service() { // } fun getPublicKeySignature(otherUserId: String): String { - val publicKey = - if (otherUserId == userId) - encryptionManager.publicKey - else encryptionManager.getPublicKey( - Base64.decode( - allDevices.value?.find { it.userId == otherUserId }?.publicKey, - Base64.DEFAULT + val device = allDevices.value?.find { it.userId == otherUserId } + return if (otherUserId == userId) { + encryptionManager.getPublicKeySignature(encryptionManager.publicKey) + } else if (device != null) { + encryptionManager.getPublicKeySignature( + encryptionManager.getPublicKey( + Base64.decode( + device.publicKey, + Base64.DEFAULT + ) ) ) - Log.d("BleService", "publicKey: $publicKey") - return encryptionManager.getPublicKeySignature(publicKey) + } else { + return "No public key for user $otherUserId" + } } fun blockUser(userId: String) { @@ -150,8 +162,37 @@ class BleService : Service() { this@BleService.openDatabase(masterPassword) } - fun start(userId: String) { - this@BleService.start(userId) + fun openServer() { + bleManager.openServer() + } + + fun closeServer() { + bleManager.closeServer() + } + + fun startScanning() { + // check permissions + bleManager.startScanning() + } + + fun stopScanning() { + bleManager.stopScanning() + } + + fun startAdvertising() { + bleManager.startAdvertising() + } + + fun stopAdvertising() { + bleManager.stopAdvertising() + } + + fun startConnectOrUpdateKnownDevices() { + bleManager.startConnectOrUpdateKnownDevices() + } + + fun stopConnectOrUpdateKnownDevices() { + bleManager.stopConnectOrUpdateKnownDevices() } } @@ -160,11 +201,19 @@ class BleService : Service() { mutableMapOf() // userId -> messageId -> chunks private val bleDataExchangeManager = object : BleManager.BleDataExchangeManager { - override fun onUserConnected(userId: String) { + override fun getKnownDevices(): List { + return allDevices?.value ?: emptyList() + } + + override fun getAddressForUserId(userId: String): String { + return allDevices?.value?.find { it.userId == userId }?.address ?: "" + } + + override fun onUserConnected(userId: String, address: String) { Log.d("BleService", "onUserConnected: $userId") val devices = allDevices?.value ?: emptyList() if (!devices.any { it.userId == userId }) { - deviceRepository?.insert(Device(userId, 0, System.currentTimeMillis(), true)) + deviceRepository?.insert(Device(userId, address, 0, System.currentTimeMillis(), true)) val intent = Intent(ACTION_DEVICE) sendBroadcast(intent) } else { @@ -179,7 +228,7 @@ class BleService : Service() { } } - override fun onUserPublicKeyReceived(userId: String, publicKey: PublicKey) { + override fun onUserPublicKeyReceived(userId: String, address: String, publicKey: PublicKey) { Log.d("BleService", "onUserPublicKeyReceived: $userId") // if the public key is already known, ask the user if they want to update it val devices = allDevices?.value ?: emptyList() @@ -197,6 +246,7 @@ class BleService : Service() { deviceRepository?.insert( Device( userId, + address, publicKey = Base64.encodeToString( publicKey.encoded, Base64.DEFAULT @@ -221,6 +271,13 @@ class BleService : Service() { "BleService", "onChunkReceived: chunk.index: ${chunk.index}, chunk.data.size: ${chunk.data.size}, chunk.messageId: ${chunk.messageId}" ) + // is user blocked? + val devices = allDevices?.value ?: emptyList() + val device = devices.find { it.userId == getUserIdForAddress(address) } + if (device != null && device.blocked) { + Log.d("BleService", "onChunkReceived: user is blocked") + return + } if (chunks.containsKey(address)) { chunks[address]?.let { chunks -> if (chunks.containsKey(chunk.messageId)) { @@ -255,6 +312,7 @@ class BleService : Service() { if (message.recipientId == userId) { //Log.d("BleService", "onDataReceived: $bleData") messagesHashes[message.senderId]?.add(hash) + // Check if the user is blocked message.content = encryptionManager.decrypt(message.content) messageRepository?.insert( @@ -404,16 +462,6 @@ class BleService : Service() { allDevices = deviceRepository?.allDevices } - fun start(userId: String) { - Log.d("BleService", "startBle") - this.userId = userId - if (!bleManager.isStarted.value) bleManager.start(userId) - } - - fun stop() { - if (bleManager.isStarted.value) bleManager.stop() - } - fun sendMessage(message: Message) { if (message.recipientId != null) { messageRepository?.insert( diff --git a/app/src/main/java/com/cstef/meshlink/MainActivity.kt b/app/src/main/java/com/cstef/meshlink/MainActivity.kt index 879a003..e0a528d 100644 --- a/app/src/main/java/com/cstef/meshlink/MainActivity.kt +++ b/app/src/main/java/com/cstef/meshlink/MainActivity.kt @@ -25,11 +25,9 @@ import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material.icons.rounded.Add -import androidx.compose.material.icons.rounded.Close import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -47,14 +45,10 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.cstef.meshlink.managers.isBleOn -import com.cstef.meshlink.screens.AddDeviceScreen -import com.cstef.meshlink.screens.ChatScreen -import com.cstef.meshlink.screens.ScanScreen -import com.cstef.meshlink.screens.UserInfoScreen +import com.cstef.meshlink.screens.* import com.cstef.meshlink.ui.theme.AppTheme import com.cstef.meshlink.ui.theme.DarkColors import com.cstef.meshlink.ui.theme.LightColors -import com.cstef.meshlink.util.RequestCode import com.cstef.meshlink.util.generateFriendlyId class MainActivity : AppCompatActivity() { @@ -124,6 +118,7 @@ class MainActivity : AppCompatActivity() { ) ) { val started by bleBinder?.isBleStarted!! + val isScanning by bleBinder?.isScanning!! val navController = rememberNavController() val isDatabaseOpen by bleBinder!!.isDatabaseOpen.observeAsState(false) val isDatabaseOpening by bleBinder!!.isDatabaseOpening.observeAsState(false) @@ -207,8 +202,17 @@ class MainActivity : AppCompatActivity() { editor.putBoolean("first_time", false) editor.apply() } - if (!started) { - bleBinder?.start(userId) + val scan = sharedPreferences.getBoolean("is_scanning", true) + val advertise = sharedPreferences.getBoolean("is_advertising", true) + + openServer() + startConnectOrUpdateKnownDevices() + + if (advertise) { + startAdvertising() + } + if (scan) { + startScanning() } } NavHost(navController = navController, startDestination = "scan") { @@ -236,25 +240,6 @@ class MainActivity : AppCompatActivity() { contentDescription = "Add device", ) } - ExtendedFloatingActionButton( - onClick = { - if (started) { - stopBle() - } else { - startBle() - } - }, - icon = { - Icon( - imageVector = if (started) Icons.Rounded.Close else Icons.Filled.PlayArrow, - contentDescription = "Start/Stop" - ) - }, - text = { Text(text = if (started) "Stop" else "Start") }, - modifier = Modifier - .padding(24.dp) - .align(Alignment.BottomEnd), - ) } } } @@ -286,6 +271,27 @@ class MainActivity : AppCompatActivity() { it, backStackEntry.arguments?.getString("deviceId"), backStackEntry.arguments?.getString("deviceId") == userId + ) { + navController.navigate("settings") + } + } + } + composable("settings") { + bleBinder?.let { binder -> + SettingsScreen( + binder, + startScanning = { + startScanning() + }, + stopScanning = { + stopScanning() + }, + startAdvertising = { + startAdvertising() + }, + stopAdvertising = { + stopAdvertising() + }, ) } } @@ -295,110 +301,167 @@ class MainActivity : AppCompatActivity() { } } - override fun onDestroy() { - super.onDestroy() - unbindService() + private fun openServer() { + requestPermissions { + bleBinder?.openServer() + } } - override fun onRequestPermissionsResult( - requestCode: Int, permissions: Array, grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == RequestCode.ACCESS_COARSE_LOCATION) { - if (grantResults.isEmpty() || grantResults.first() != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(this, "Location Permission required to scan", Toast.LENGTH_SHORT).show() - requestPermissions() - } + private fun startConnectOrUpdateKnownDevices() { + requestPermissions { + bleBinder?.startConnectOrUpdateKnownDevices() } } - private var requestBluetooth = + override fun onDestroy() { + super.onDestroy() + unbindService() + } + + private val requestEnableBluetooth = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode != RESULT_OK) { Toast.makeText(this, "Bluetooth is required to scan", Toast.LENGTH_SHORT).show() requestPermissions() } } - private val requestMultiplePermissions = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> - permissions.entries.forEach { - Log.d("test006", "${it.key} = ${it.value}") - } - if (!permissions.filter { it.key != Manifest.permission.BLUETOOTH_ADMIN } - .all { it.value } && permissions[Manifest.permission.BLUETOOTH_ADMIN] == false) { - Toast.makeText( - this, - "Please allow every permission for MeshLink to work correctly", - Toast.LENGTH_SHORT - ).show() + private val _requestLocation = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> + // Check if all permissions are granted + if (result.values.all { it }) { + // TODO + } else { + Toast.makeText(this, "Location Permission required to scan", Toast.LENGTH_SHORT).show() requestPermissions() } } + private val requestLocation = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + _requestLocation.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)) + } else { + _requestLocation.launch(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)) + } + } + + private val checkLocation = { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } else { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + } + private val _requestBluetooth = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> + // Check if all permissions are granted + if (result.values.all { it }) { + // TODO + } else { + Toast.makeText(this, "Bluetooth Permission required to scan", Toast.LENGTH_SHORT).show() + requestPermissions() + } + } - private fun requestPermissions() { + private val requestBluetooth = { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - if (ContextCompat.checkSelfPermission( - this, Manifest.permission.BLUETOOTH_ADMIN - ) != PackageManager.PERMISSION_GRANTED - ) { - requestMultiplePermissions.launch( - arrayOf( - Manifest.permission.BLUETOOTH_ADMIN - ) + _requestBluetooth.launch( + arrayOf( + Manifest.permission.BLUETOOTH_ADMIN ) - return - } + ) } else { - if (ContextCompat.checkSelfPermission( - this, Manifest.permission.BLUETOOTH_ADVERTISE - ) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( - this, Manifest.permission.BLUETOOTH_CONNECT - ) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( - this, Manifest.permission.BLUETOOTH_SCAN - ) != PackageManager.PERMISSION_GRANTED - ) { - requestMultiplePermissions.launch( - arrayOf( - Manifest.permission.BLUETOOTH_ADVERTISE, - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.BLUETOOTH_CONNECT - ) + _requestBluetooth.launch( + arrayOf( + Manifest.permission.BLUETOOTH_ADVERTISE, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT ) - return - } + ) + } + } + + private val checkBluetooth = { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.BLUETOOTH_ADMIN + ) == PackageManager.PERMISSION_GRANTED + } else { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.BLUETOOTH_ADVERTISE + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.BLUETOOTH_SCAN + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.BLUETOOTH_CONNECT + ) == PackageManager.PERMISSION_GRANTED } + } + private fun requestPermissions(onSuccess: () -> Unit = {}) { + if (!checkBluetooth()) { + Log.d("MainActivity", "Requesting bluetooth permissions") + requestBluetooth() + } val adapter = (getSystemService(BLUETOOTH_SERVICE) as BluetoothManager).adapter if (!adapter.isBleOn) { - val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) - requestBluetooth.launch(enableBtIntent) - return + Log.d("MainActivity", "Requesting Bluetooth") + requestEnableBluetooth.launch(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)) + } + if (!checkLocation()) { + Log.d("MainActivity", "Requesting location permission") + requestLocation() } + if (checkBluetooth() && adapter.isBleOn && checkLocation()) { + Log.d("MainActivity", "All permissions granted") + onSuccess() + } + } - if (ContextCompat.checkSelfPermission( - this, Manifest.permission.ACCESS_COARSE_LOCATION - ) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( - this, Manifest.permission.ACCESS_FINE_LOCATION - ) != PackageManager.PERMISSION_GRANTED - ) { - requestMultiplePermissions.launch( - arrayOf( - Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION - ) - ) - return + private fun startAdvertising() { + requestPermissions { + val sharedPreferences = getSharedPreferences("USER_SETTINGS", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putBoolean("is_advertising", true) + editor.apply() + bleBinder?.startAdvertising() } } - fun startBle() { - bleService?.start(userId) - Toast.makeText(this, "Service Started", Toast.LENGTH_SHORT).show() + private fun stopAdvertising() { + val sharedPreferences = getSharedPreferences("USER_SETTINGS", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putBoolean("is_advertising", false) + editor.apply() + bleBinder?.stopAdvertising() } - fun stopBle() { - bleService?.stop() - Toast.makeText(this, "Service Stopped", Toast.LENGTH_SHORT).show() + private fun startScanning() { + requestPermissions { + val sharedPreferences = getSharedPreferences("USER_SETTINGS", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putBoolean("is_scanning", true) + editor.apply() + bleBinder?.startScanning() + } + } + + private fun stopScanning() { + val sharedPreferences = getSharedPreferences("USER_SETTINGS", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putBoolean("is_scanning", false) + editor.apply() + bleBinder?.stopScanning() } private fun createNotificationChannels() { @@ -442,10 +505,11 @@ class MainActivity : AppCompatActivity() { Log.d("test006", "onServiceConnected") bleService = (service as BleService.BleServiceBinder).service bleBinder = service - requestPermissions() + bleBinder!!.setUserId(userId) // get isFirstTime from shared preferences val sharedPreferences = getSharedPreferences("USER_SETTINGS", Context.MODE_PRIVATE) val isFirstTime = sharedPreferences.getBoolean("first_time", true) + setContent { App(isFirstTime) } diff --git a/app/src/main/java/com/cstef/meshlink/db/Database.kt b/app/src/main/java/com/cstef/meshlink/db/Database.kt index 6a952ff..fce1e53 100644 --- a/app/src/main/java/com/cstef/meshlink/db/Database.kt +++ b/app/src/main/java/com/cstef/meshlink/db/Database.kt @@ -16,7 +16,7 @@ import net.sqlcipher.database.SupportFactory Device::class, Message::class, ], - version = 12, + version = 13, exportSchema = false, ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/cstef/meshlink/db/entities/Device.kt b/app/src/main/java/com/cstef/meshlink/db/entities/Device.kt index cfd1db9..4d63e2a 100644 --- a/app/src/main/java/com/cstef/meshlink/db/entities/Device.kt +++ b/app/src/main/java/com/cstef/meshlink/db/entities/Device.kt @@ -7,6 +7,7 @@ import androidx.room.PrimaryKey @Entity(tableName = "devices") data class Device( @PrimaryKey val userId: String, + @ColumnInfo(name = "address") val address: String, @ColumnInfo(name = "rssi") val rssi: Int = 0, @ColumnInfo(name = "last_seen") val lastSeen: Long, @ColumnInfo(name = "connected") val connected: Boolean, diff --git a/app/src/main/java/com/cstef/meshlink/managers/BleManager.kt b/app/src/main/java/com/cstef/meshlink/managers/BleManager.kt index d6e1c0a..2760f4a 100644 --- a/app/src/main/java/com/cstef/meshlink/managers/BleManager.kt +++ b/app/src/main/java/com/cstef/meshlink/managers/BleManager.kt @@ -12,6 +12,7 @@ import android.os.Looper import android.util.Log import androidx.compose.runtime.mutableStateOf import com.cstef.meshlink.BleService +import com.cstef.meshlink.db.entities.Device import com.cstef.meshlink.util.struct.Chunk import com.cstef.meshlink.util.struct.Message import java.security.PublicKey @@ -30,7 +31,9 @@ class BleManager( serviceHandler: Handler ) { - var isStarted = mutableStateOf(false) + val isAdvertising = mutableStateOf(false) + val isStarted = mutableStateOf(false) + val isScanning = mutableStateOf(false) private val tag = BleManager::class.java.canonicalName private val adapter get() = (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter @@ -45,10 +48,11 @@ class BleManager( dataExchangeManager, callbackHandler, encryptionManager, - serviceHandler + serviceHandler, + this ) private val serverManager = - ServerBleManager(context, dataExchangeManager, callbackHandler, encryptionManager) + ServerBleManager(context, dataExchangeManager, callbackHandler, encryptionManager, this) private val canBeClient: Boolean = adapter != null && context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) @@ -57,20 +61,11 @@ class BleManager( private val canBeServer: Boolean get() = adapter.bluetoothLeAdvertiser != null - fun start(userId: String) { - Log.d(tag, "BleManager started") - Log.d(tag, "canBeClient: $canBeClient") - Log.d(tag, "canBeServer: $canBeServer") - if (canBeClient) clientManager.start(userId) - if (canBeServer) serverManager.start(userId) - isStarted.value = true - } - @SuppressLint("MissingPermission") fun stop() { Log.d(tag, "BleManager stopped") if (canBeClient) clientManager.stop() - if (canBeServer) serverManager.stop() + if (canBeServer) serverManager.stopAdvertising() isStarted.value = false } @@ -110,6 +105,42 @@ class BleManager( clientManager.disconnect(userId) } + fun startScanning() { + if (canBeClient) clientManager.startScanning() + } + + fun stopScanning() { + clientManager.stopScanning() + } + + fun startAdvertising() { + if (canBeServer) serverManager.startAdvertising() + } + + fun stopAdvertising() { + serverManager.stopAdvertising() + } + + fun setUserId(id: String) { + clientManager.setUserId(id) + serverManager.setUserId(id) + } + + fun openServer() { + if (canBeServer) serverManager.openServer() + } + + fun closeServer() { + serverManager.closeServer() + } + + fun startConnectOrUpdateKnownDevices() { + if (canBeClient) clientManager.startConnectOrUpdateKnownDevicesLoop() + } + + fun stopConnectOrUpdateKnownDevices() { + clientManager.stopConnectOrUpdateKnownDevicesLoop() + } // fun sendIsWriting(userId: String, writing: Boolean) { // val deviceAddress = clientManager.connectedServersAddresses[userId] // if (deviceAddress != null && clientManager.connectedGattServers.containsKey(deviceAddress)) { @@ -136,13 +167,13 @@ class BleManager( /** * @param userId ID of the remote BLE device */ - fun onUserConnected(userId: String) {} + fun onUserConnected(userId: String, address: String) {} /** * @param userId ID of the remote BLE device */ fun onUserDisconnected(userId: String) {} - fun onUserPublicKeyReceived(userId: String, publicKey: PublicKey) {} + fun onUserPublicKeyReceived(userId: String, address: String, publicKey: PublicKey) {} fun getUsername(): String = "" fun getPublicKeyForUser(recipientId: String): PublicKey? fun onUserRssiReceived(userId: String, rssi: Int) {} @@ -150,5 +181,7 @@ class BleManager( fun onUserWriting(userId: String, isWriting: Boolean) {} fun getUserIdForAddress(address: String): String? = "" fun onMessageSendFailed(userId: String?, reason: String?) {} + fun getKnownDevices(): List = emptyList() + fun getAddressForUserId(userId: String): String = "" } } diff --git a/app/src/main/java/com/cstef/meshlink/managers/ClientBleManager.kt b/app/src/main/java/com/cstef/meshlink/managers/ClientBleManager.kt index 194c039..8c6267e 100644 --- a/app/src/main/java/com/cstef/meshlink/managers/ClientBleManager.kt +++ b/app/src/main/java/com/cstef/meshlink/managers/ClientBleManager.kt @@ -18,7 +18,6 @@ import android.os.Handler import android.os.ParcelUuid import android.util.Base64 import android.util.Log -import android.widget.Toast import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -26,6 +25,7 @@ import com.cstef.meshlink.R import com.cstef.meshlink.util.BleUuid import com.cstef.meshlink.util.struct.* import com.daveanthonythomas.moshipack.MoshiPack +import kotlinx.coroutines.* import java.math.RoundingMode import java.security.KeyFactory import java.security.spec.X509EncodedKeySpec @@ -38,9 +38,11 @@ class ClientBleManager( private val dataExchangeManager: BleManager.BleDataExchangeManager, private val callbackHandler: Handler, private val encryptionManager: EncryptionManager, - handler: Handler + handler: Handler, + private val parentManager: BleManager ) { + private var connectLoopJob: Job? = null private var userId: String? = null private val moshi = MoshiPack() @@ -176,12 +178,16 @@ class ClientBleManager( gatt.close() } } + else -> { + Log.w("ClientBleManager", "onConnectionStateChange: unknown state $newState") + } } } @SuppressLint("MissingPermission") override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) { super.onMtuChanged(gatt, mtu, status) + Log.d("ClientBleManager", "onMtuChanged: $mtu") operationQueue.operationComplete() if (status == BluetoothGatt.GATT_SUCCESS) { if (gatt != null) operationQueue.execute { @@ -196,6 +202,10 @@ class ClientBleManager( @SuppressLint("MissingPermission") override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { super.onServicesDiscovered(gatt, status) + Log.d( + "ClientBleManager", + "onServicesDiscovered: $status, success: ${status == BluetoothGatt.GATT_SUCCESS}" + ) operationQueue.operationComplete() if (status == BluetoothGatt.GATT_SUCCESS) { // request data from remote BLE server @@ -229,9 +239,11 @@ class ClientBleManager( if (gatt != null) { connectedServersAddresses[userId] = gatt.device.address operationQueue.execute { gatt.readPublicKey() } - } - callbackHandler.post { - userId?.let { dataExchangeManager.onUserConnected(it) } + callbackHandler.post { + userId?.let { dataExchangeManager.onUserConnected(it, gatt.device.address) } + } + } else { + Log.e("ClientBleManager", "onCharacteristicRead: gatt == null") } } BleUuid.USER_PUBLIC_KEY_UUID -> { @@ -243,10 +255,14 @@ class ClientBleManager( "ClientBleManager", "onCharacteristicRead: publicKey = $publicKey gatt == null: ${gatt == null}" ) - callbackHandler.post { - dataExchangeManager.onUserPublicKeyReceived( - msg.userId, publicKey - ) + if (gatt != null) { + callbackHandler.post { + dataExchangeManager.onUserPublicKeyReceived( + msg.userId, gatt.device.address, publicKey + ) + } + } else { + Log.e("ClientBleManager", "onCharacteristicRead: gatt == null") } } } @@ -328,32 +344,6 @@ class ClientBleManager( } } - fun start(myUserId: String) { - userId = myUserId - if (adapter.isBleOn) { - if (ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_SCAN - ) == PackageManager.PERMISSION_GRANTED - || - (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_ADMIN - ) == PackageManager.PERMISSION_GRANTED) - ) { - scanner?.startScan(scanFilters, scanSettings, scanCallback) - Log.d("ClientBleManager", "Started scanning") - } else { - Log.e("ClientBleManager", "Permission not granted") - Toast.makeText(context, "Permission not granted", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText(context, "Bluetooth LE is not enabled", Toast.LENGTH_SHORT).show() - Log.d( - "ClientBleManager", - "start: Bluetooth is not on: adapter=$adapter isEnabled=${adapter?.isEnabled}" - ) - } - } - fun stop() { if (adapter.isBleOn) { if (ActivityCompat.checkSelfPermission( @@ -517,6 +507,64 @@ class ClientBleManager( } } + @SuppressLint("MissingPermission") + fun startScanning() { + if (adapter.isBleOn) { + scanner?.startScan(scanFilters, scanSettings, scanCallback) + parentManager.isScanning.value = true + Log.d("ClientBleManager", "startScanning: started") + } else { + Log.w("ClientBleManager", "startScanning: BLE is off") + } + } + + @SuppressLint("MissingPermission") + fun stopScanning() { + scanner?.stopScan(scanCallback) + parentManager.isScanning.value = false + Log.d("ClientBleManager", "stopScanning: stopped") + } + + fun startConnectOrUpdateKnownDevicesLoop() { + if (connectLoopJob == null) { + connectLoopJob = CoroutineScope(Dispatchers.IO).launch { + while (true) { + delay(15000) + val knownDevices = dataExchangeManager.getKnownDevices() + knownDevices.forEach { device -> + if (connectedServersAddresses[device.userId] == null) { + Log.d( + "ClientBleManager", + "startConnectOrUpdateKnownDevicesLoop: Connecting to ${device.userId}" + ) + connect(device.address) + } + } + } + } + } + } + + fun stopConnectOrUpdateKnownDevicesLoop() { + connectLoopJob?.cancel() + connectLoopJob = null + } + + @SuppressLint("MissingPermission") + fun connect(address: String) { + val device = adapter?.getRemoteDevice(address) + if (device != null) { + device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE) + Log.d("ClientBleManager", "connect: connecting to $address") + } else { + Log.w("ClientBleManager", "connect: device is null") + } + } + + fun setUserId(id: String) { + userId = id + } + // @SuppressLint("MissingPermission") // @Suppress("DEPRECATION") // fun sendIsWriting(userId: String, writing: Boolean) { diff --git a/app/src/main/java/com/cstef/meshlink/managers/ServerBleManager.kt b/app/src/main/java/com/cstef/meshlink/managers/ServerBleManager.kt index fe407b3..82693ef 100644 --- a/app/src/main/java/com/cstef/meshlink/managers/ServerBleManager.kt +++ b/app/src/main/java/com/cstef/meshlink/managers/ServerBleManager.kt @@ -1,19 +1,15 @@ package com.cstef.meshlink.managers -import android.Manifest import android.annotation.SuppressLint import android.bluetooth.* import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseData import android.bluetooth.le.AdvertiseSettings import android.content.Context -import android.content.pm.PackageManager -import android.os.Build import android.os.Handler import android.os.ParcelUuid import android.util.Base64 import android.util.Log -import androidx.core.app.ActivityCompat import com.cstef.meshlink.util.BleUuid import com.cstef.meshlink.util.struct.Chunk import com.cstef.meshlink.util.struct.KeyData @@ -25,7 +21,8 @@ class ServerBleManager( private val context: Context, private val dataExchangeManager: BleManager.BleDataExchangeManager, private val callbackHandler: Handler, - private val encryptionManager: EncryptionManager + private val encryptionManager: EncryptionManager, + private val parentManager: BleManager ) { private val moshi = MoshiPack() @@ -81,10 +78,19 @@ class ServerBleManager( private val advertiseData = AdvertiseData.Builder().addServiceUuid(ParcelUuid.fromString(BleUuid.SERVICE_UUID)).build() - private val advertiseCallback = object : AdvertiseCallback() {} + private val advertiseCallback = object : AdvertiseCallback() { + override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) { + super.onStartSuccess(settingsInEffect) + Log.d("ServerBleManager", "Advertising start success") + } - private val serverCallback = object : BluetoothGattServerCallback() { + override fun onStartFailure(errorCode: Int) { + super.onStartFailure(errorCode) + Log.d("ServerBleManager", "Advertising start failure: $errorCode") + } + } + private val serverCallback = object : BluetoothGattServerCallback() { @SuppressLint("MissingPermission") override fun onCharacteristicReadRequest( device: BluetoothDevice?, @@ -177,38 +183,15 @@ class ServerBleManager( } } } - } - fun start(myUserId: String) { - if (ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_CONNECT - ) == PackageManager.PERMISSION_GRANTED || (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_ADMIN - ) == PackageManager.PERMISSION_GRANTED) - ) { - userId = myUserId - openServer() - startAdvertising() - } - } - - fun stop() { - if ((ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_CONNECT - ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_ADVERTISE - ) == PackageManager.PERMISSION_GRANTED) || (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && ActivityCompat.checkSelfPermission( - context, Manifest.permission.BLUETOOTH_ADMIN - ) == PackageManager.PERMISSION_GRANTED) - ) { - Log.d("ServerBleManager", "Closing server") - stopAdvertising() - closeServer() + override fun onMtuChanged(device: BluetoothDevice?, mtu: Int) { + super.onMtuChanged(device, mtu) + Log.d("ServerBleManager", "onMtuChanged: $mtu") } } @SuppressLint("MissingPermission") - private fun openServer() { + fun openServer() { if (adapter.isBleOn && gattServer == null) { gattServer = bluetoothManager.openGattServer(context, serverCallback) gattServer?.addService(bleService) @@ -216,23 +199,31 @@ class ServerBleManager( } @SuppressLint("MissingPermission") - private fun closeServer() { + fun closeServer() { gattServer?.clearServices() gattServer?.close() gattServer = null } @SuppressLint("MissingPermission") - private fun startAdvertising() { + fun startAdvertising() { if (adapter.isBleOn) { advertiser?.startAdvertising(advertiseSettings, advertiseData, advertiseCallback) + parentManager.isAdvertising.value = true + Log.d("ServerBleManager", "startAdvertising: started") } } @SuppressLint("MissingPermission") - private fun stopAdvertising() { + fun stopAdvertising() { if (adapter.isBleOn) { advertiser?.stopAdvertising(advertiseCallback) + parentManager.isAdvertising.value = false + Log.d("ServerBleManager", "stopAdvertising: stopped") } } + + fun setUserId(id: String) { + userId = id + } } diff --git a/app/src/main/java/com/cstef/meshlink/screens/AddDeviceScreen.kt b/app/src/main/java/com/cstef/meshlink/screens/AddDeviceScreen.kt index 6a99428..fae9ee6 100644 --- a/app/src/main/java/com/cstef/meshlink/screens/AddDeviceScreen.kt +++ b/app/src/main/java/com/cstef/meshlink/screens/AddDeviceScreen.kt @@ -1,9 +1,6 @@ package com.cstef.meshlink.screens -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material3.* @@ -28,40 +25,47 @@ fun AddDeviceScreen( val (error, setError) = remember { mutableStateOf("") } - Row(modifier = Modifier.fillMaxSize()) { - OutlinedTextField( - value = userId, - onValueChange = { - setUserId(it) - setError("") - }, - label = { Text("User ID") }, - modifier = Modifier - .padding(16.dp) - .align(Alignment.CenterVertically) - .weight(1f) - .fillMaxWidth(), - isError = error.isNotEmpty(), - singleLine = true, - + Column(modifier = Modifier.fillMaxSize()) { + TopAppBar( + title = { Text(text = "Add device") }, ) - FloatingActionButton( - onClick = { - // User Id format: 2 words and a 3 digits number separated by either a dot or a dash - if (bleBinder != null && userId.isNotEmpty() && userId != myUserId && Regex( - "^[a-zA-Z]+[\\.-]?[a-zA-Z]+[\\.-]?[0-9]{3}\$" - ).matches(userId) - ) { - bleBinder.addDevice(userId) - onBack() - } else { - setError("Please enter a valid user ID") - } - }, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(end = 16.dp, top = 8.dp) - ) { - Icon(Icons.Rounded.Add, contentDescription = "Add device") + Row(modifier = Modifier.fillMaxSize()) { + OutlinedTextField( + value = userId, + onValueChange = { + setUserId(it) + setError("") + }, + label = { Text("User ID") }, + modifier = Modifier + .padding(16.dp) + .align(Alignment.CenterVertically) + .weight(1f) + .fillMaxWidth(), + isError = error.isNotEmpty(), + singleLine = true, + + ) + FloatingActionButton( + onClick = { + // User Id format: 2 words and a 3 digits number separated by either a dot or a dash + if (bleBinder != null && userId.isNotEmpty() && userId != myUserId && Regex( + "^[a-zA-Z]+[\\.-]?[a-zA-Z]+[\\.-]?[0-9]{3}\$" + ).matches(userId) + ) { + // bleBinder.addDevice(userId) + onBack() + } else { + setError("Please enter a valid user ID") + } + }, modifier = Modifier + .align(Alignment.CenterVertically) + .padding(end = 16.dp, top = 8.dp) + ) { + Icon(Icons.Rounded.Add, contentDescription = "Add device") + } } + // QR code scanner + } } diff --git a/app/src/main/java/com/cstef/meshlink/screens/SettingsScreen.kt b/app/src/main/java/com/cstef/meshlink/screens/SettingsScreen.kt new file mode 100644 index 0000000..538c091 --- /dev/null +++ b/app/src/main/java/com/cstef/meshlink/screens/SettingsScreen.kt @@ -0,0 +1,227 @@ +package com.cstef.meshlink.screens + +import android.widget.Toast +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.cstef.meshlink.BleService +import com.cstef.meshlink.ui.theme.DarkColors +import com.cstef.meshlink.ui.theme.LightColors + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + bleBinder: BleService.BleServiceBinder, + startScanning: () -> Unit, + stopScanning: () -> Unit, + startAdvertising: () -> Unit, + stopAdvertising: () -> Unit, +) { + val colors = if (isSystemInDarkTheme()) DarkColors else LightColors + val devices by bleBinder.allDevices.observeAsState(listOf()) + val context = LocalContext.current + val isAdvertising by bleBinder.isAdvertising + val isScanning by bleBinder.isScanning + TopAppBar( + title = { Text(text = "Settings") }, + modifier = Modifier + .height(56.dp) + .padding(top = 16.dp), + ) + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 64.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Discoverable", + modifier = Modifier.padding(start = 16.dp), + color = colors.onBackground, + style = MaterialTheme.typography.bodyLarge + ) + Switch( + checked = isAdvertising, + onCheckedChange = { + if (it) { + startAdvertising() + } else { + stopAdvertising() + } + }, + modifier = Modifier.padding(end = 16.dp) + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = "Scan for devices", + modifier = Modifier.padding(start = 16.dp), + color = colors.onBackground, + style = MaterialTheme.typography.bodyLarge + ) + Switch( + checked = isScanning, + onCheckedChange = { + if (it) { + startScanning() + } else { + stopScanning() + } + }, + modifier = Modifier.padding(end = 16.dp) + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // Delete all data button + val (confirmDelete, setConfirmDelete) = remember { mutableStateOf(false) } + if (confirmDelete) { + AlertDialog(onDismissRequest = { + setConfirmDelete(false) + }, + title = { Text("Delete data") }, + text = { Text("Are you sure you want to delete all data?") }, + confirmButton = { + Button(onClick = { + bleBinder.deleteAllData() + setConfirmDelete(false) + }) { + Text("Delete") + } + }, + dismissButton = { + TextButton(onClick = { + setConfirmDelete(false) + }) { + Text("Cancel") + } + }) + } + Button( + onClick = { + setConfirmDelete(true) + }, modifier = Modifier + .padding(start = 16.dp, end = 8.dp) + ) { + Text("Delete all data") + } + // Delete specific data button + val (isDeleteSpecificDataEnabled, setDeleteSpecificDataEnabled) = remember { + mutableStateOf(false) + } + if (isDeleteSpecificDataEnabled) { + AlertDialog(onDismissRequest = { setDeleteSpecificDataEnabled(false) }, + title = { Text(text = "Delete specific data") }, + text = { + DropdownMenu(expanded = true, + onDismissRequest = { setDeleteSpecificDataEnabled(false) }, + content = { + DropdownMenuItem(onClick = { /*TODO*/ }, text = { Text(text = "Device 1") }) + DropdownMenuItem(onClick = { /*TODO*/ }, text = { Text(text = "Device 2") }) + DropdownMenuItem(onClick = { /*TODO*/ }, text = { Text(text = "Device 3") }) + }) + }, + confirmButton = { Button(onClick = { /*TODO*/ }) { Text(text = "Yes") } }, + dismissButton = { Button(onClick = { setDeleteSpecificDataEnabled(false) }) { Text(text = "No") } }) + } + Button( + onClick = { + setDeleteSpecificDataEnabled(true) + }, + modifier = Modifier + .padding(start = 8.dp, end = 16.dp) + ) { Text(text = "Delete device data") } + } + + // Change password + val (passwordDialogVisible, setPasswordDialogVisible) = remember { mutableStateOf(false) } + if (passwordDialogVisible) { + val (newPassword, setNewPassword) = remember { mutableStateOf("") } + AlertDialog(onDismissRequest = { + setPasswordDialogVisible(false) + }, title = { Text("Enter password") }, text = { + Column { + OutlinedTextField(value = newPassword, + onValueChange = { + setNewPassword(it) + }, + label = { Text("New password") }, + modifier = Modifier + .padding(top = 16.dp, bottom = 16.dp) + .align(Alignment.CenterHorizontally), + singleLine = true, + trailingIcon = { + IconButton(onClick = { + setNewPassword("") + }) { + Icon( + imageVector = Icons.Default.Clear, contentDescription = "Clear" + ) + } + }) + } + }, confirmButton = { + Button(onClick = { + val success = bleBinder.changeDatabasePassword(newPassword) + if (success) { + setPasswordDialogVisible(false) + } else { + Toast.makeText(context, "Wrong password", Toast.LENGTH_SHORT).show() + } + }) { + Text("Set") + } + }, dismissButton = { + TextButton(onClick = { + setPasswordDialogVisible(false) + }) { + Text("Cancel") + } + }) + } + Button( + onClick = { + setPasswordDialogVisible(true) + }, + modifier = Modifier + .padding(start = 16.dp, end = 16.dp) + .align(Alignment.CenterHorizontally), + ) { + Text(text = "Change password") + } + // About button + TextButton( + onClick = { /*TODO*/ }, modifier = Modifier + .padding(start = 16.dp, end = 16.dp) + .align( + Alignment.CenterHorizontally + ) + ) { Text(text = "About") } + } +} diff --git a/app/src/main/java/com/cstef/meshlink/screens/UserInfoScreen.kt b/app/src/main/java/com/cstef/meshlink/screens/UserInfoScreen.kt index 5cf6cee..50106de 100644 --- a/app/src/main/java/com/cstef/meshlink/screens/UserInfoScreen.kt +++ b/app/src/main/java/com/cstef/meshlink/screens/UserInfoScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Done import androidx.compose.material3.* import androidx.compose.runtime.Composable @@ -28,6 +27,7 @@ fun UserInfoScreen( bleBinder: BleService.BleServiceBinder, userId: String?, isMe: Boolean, + openSettings: () -> Unit ) { val context = LocalContext.current val colors = if (isSystemInDarkTheme()) DarkColors else LightColors @@ -57,7 +57,7 @@ fun UserInfoScreen( overflow = TextOverflow.Ellipsis, ) Text( - text = userId, + text = "", style = MaterialTheme.typography.bodyLarge, modifier = Modifier .padding(bottom = 16.dp) @@ -68,7 +68,7 @@ fun UserInfoScreen( ) } else { Text( - text = userId, + text = "$userId ${if (isMe) "(me)" else if (device?.blocked == true) "(blocked)" else ""}", style = MaterialTheme.typography.titleLarge, modifier = Modifier .padding(top = 16.dp, bottom = 16.dp) @@ -79,7 +79,7 @@ fun UserInfoScreen( ) } - bleBinder.getPublicKeySignature(userId)?.let { + bleBinder.getPublicKeySignature(userId).let { Text( text = "Public key", style = MaterialTheme.typography.titleMedium, @@ -106,14 +106,7 @@ fun UserInfoScreen( color = colors.onBackground, style = MaterialTheme.typography.bodySmall ) - } ?: Text( - text = "No public key available", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .padding(top = 16.dp, bottom = 16.dp) - .align(Alignment.CenterHorizontally), - color = colors.onBackground - ) + } val (newName, setNewName) = remember { mutableStateOf(device?.name ?: "") } OutlinedTextField(value = newName, onValueChange = { @@ -186,106 +179,15 @@ fun UserInfoScreen( } } } else { - val (confirmDelete, setConfirmDelete) = remember { mutableStateOf(false) } - val (passwordDialogVisible, setPasswordDialogVisible) = remember { mutableStateOf(false) } - - if (confirmDelete) { - AlertDialog(onDismissRequest = { - setConfirmDelete(false) - }, - title = { Text("Delete data") }, - text = { Text("Are you sure you want to delete all data?") }, - confirmButton = { - Button(onClick = { - bleBinder.deleteAllData() - setConfirmDelete(false) - }) { - Text("Delete") - } - }, - dismissButton = { - TextButton(onClick = { - setConfirmDelete(false) - }) { - Text("Cancel") - } - }) - } - if (passwordDialogVisible) { - val (newPassword, setNewPassword) = remember { mutableStateOf("") } - AlertDialog(onDismissRequest = { - setPasswordDialogVisible(false) - }, - title = { Text("Enter password") }, - text = { - Column { - OutlinedTextField(value = newPassword, - onValueChange = { - setNewPassword(it) - }, - label = { Text("New password") }, - modifier = Modifier - .padding(top = 16.dp, bottom = 16.dp) - .align(Alignment.CenterHorizontally), - singleLine = true, - trailingIcon = { - IconButton(onClick = { - setNewPassword("") - }) { - Icon( - imageVector = Icons.Default.Clear, contentDescription = "Clear" - ) - } - }) - } - }, - confirmButton = { - Button(onClick = { - val success = bleBinder.changeDatabasePassword(newPassword) - if (success) { - setPasswordDialogVisible(false) - } else { - Toast - .makeText(context, "Wrong password", Toast.LENGTH_SHORT) - .show() - } - }) { - Text("Set") - } - }, - dismissButton = { - TextButton(onClick = { - setPasswordDialogVisible(false) - }) { - Text("Cancel") - } - }) - } - Row( - modifier = Modifier - .align(Alignment.CenterHorizontally) + // Settings button + Button( + onClick = { + openSettings() + }, modifier = Modifier .padding(top = 16.dp, bottom = 16.dp) + .align(Alignment.CenterHorizontally) ) { - Button( - onClick = { - setConfirmDelete(true) - }, modifier = Modifier - - ) { - Text( - text = "Delete all data", - ) - } - Button( - onClick = { - setPasswordDialogVisible(true) - }, modifier = Modifier.padding(start = 16.dp) - - ) { - Text( - text = "Set password", - ) - } + Text("Settings") } } } diff --git a/app/src/main/java/com/cstef/meshlink/util/constants.kt b/app/src/main/java/com/cstef/meshlink/util/constants.kt index cee87d1..02a2657 100644 --- a/app/src/main/java/com/cstef/meshlink/util/constants.kt +++ b/app/src/main/java/com/cstef/meshlink/util/constants.kt @@ -9,10 +9,6 @@ object BleUuid { const val USER_WRITING_UUID = "00000007-1000-4000-9000-a16b52011896" } -object RequestCode { - const val ACCESS_COARSE_LOCATION = 101 -} - const val AVATAR_SIZE = 36 // awesome-horse-123