feat: add api configuration settings
This commit is contained in:
@@ -64,4 +64,5 @@ dependencies {
|
||||
implementation group: 'commons-codec', name: 'commons-codec', version: '1.16.0'
|
||||
|
||||
implementation "com.android.volley:volley:1.2.1"
|
||||
implementation "androidx.datastore:datastore-preferences:1.0.0"
|
||||
}
|
||||
@@ -212,8 +212,6 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
||||
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
||||
.build()
|
||||
|
||||
private val scanFilters = listOf( ScanFilter.Builder().setServiceUuid(ParcelUuid.fromString(BuildConfig.SERVICE_UUID)).build())
|
||||
|
||||
private val bleScanner by lazy {
|
||||
bleAdapter.bluetoothLeScanner
|
||||
}
|
||||
@@ -353,6 +351,9 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
||||
}
|
||||
|
||||
fun discoverServices(device: BluetoothDevice) {
|
||||
if(operationQueue.any { it.device.address === device.address && it is DiscoverServicesRequest }) {
|
||||
return
|
||||
}
|
||||
enqueueOperation(DiscoverServicesRequest(device))
|
||||
}
|
||||
|
||||
@@ -377,6 +378,9 @@ class ConnectionManager(val context: Context, bleAdapter: BluetoothAdapter) {
|
||||
}
|
||||
|
||||
fun readRemoteRssi(device: BluetoothDevice) {
|
||||
if(operationQueue.any { it.device.address === device.address && it is ReadRemoteRssi }) {
|
||||
return
|
||||
}
|
||||
enqueueOperation(ReadRemoteRssi(device))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.logitech.vc.kirbytest
|
||||
|
||||
import SettingsRepository
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothDevice.BOND_BONDED
|
||||
@@ -38,9 +39,11 @@ class KirbyDevice(
|
||||
private val connectionManager: ConnectionManager,
|
||||
private val bleDevice: BluetoothDevice,
|
||||
private val loggerDb: LoggerContract.LoggerDb,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val onStateChange: (device: KirbyDevice) -> Unit,
|
||||
|
||||
|
||||
|
||||
) : BleListener(bleDevice.address), DeviceListEntry {
|
||||
private val queue: RequestQueue = Volley.newRequestQueue(context)
|
||||
|
||||
@@ -164,10 +167,11 @@ class KirbyDevice(
|
||||
}
|
||||
|
||||
private fun publishMeasurement(payload: String) {
|
||||
val accessKey = BuildConfig.API_KEY
|
||||
val url = BuildConfig.API_BASE_URL
|
||||
val settings = settingsRepository.readSettings()
|
||||
val accessKey = settings.apiKey
|
||||
val url = settings.apiUrl
|
||||
|
||||
if(url.isEmpty()) {
|
||||
if(url.isEmpty() || accessKey.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.logitech.vc.kirbytest
|
||||
|
||||
import SettingsRepository
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
@@ -15,7 +16,6 @@ import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -23,7 +23,6 @@ import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
@@ -38,6 +37,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import settingsDialog
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Timer
|
||||
@@ -69,6 +69,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var loggerDb: LoggerContract.LoggerDb
|
||||
private lateinit var createFileLauncher: ActivityResultLauncher<String>
|
||||
private val bondedDevices = HashSet<String>()
|
||||
private lateinit var settings: SettingsRepository
|
||||
|
||||
private val bluetoothAdapter: BluetoothAdapter by lazy {
|
||||
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
@@ -93,7 +94,7 @@ class MainActivity : AppCompatActivity() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
||||
settings = SettingsRepository(applicationContext)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
@@ -153,6 +154,13 @@ class MainActivity : AppCompatActivity() {
|
||||
loggerDb.exportToMultipleCSV()
|
||||
}
|
||||
}, 1000, 10000)
|
||||
|
||||
|
||||
|
||||
|
||||
lifecycleScope.launch {
|
||||
settings.loadSettings()
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleScanning(): Unit {
|
||||
@@ -239,7 +247,8 @@ class MainActivity : AppCompatActivity() {
|
||||
Log.i("KirbyDevice", "Connecting to " + result.device.address)
|
||||
|
||||
connectionManager.connect(result.device)
|
||||
connectionManager.readRemoteRssi(result.device)
|
||||
|
||||
//connectionManager.readRemoteRssi(result.device)
|
||||
connectionManager.discoverServices(result.device)
|
||||
}
|
||||
|
||||
@@ -253,7 +262,8 @@ class MainActivity : AppCompatActivity() {
|
||||
bleDevice: BluetoothDevice
|
||||
): KirbyDevice {
|
||||
|
||||
val device = KirbyDevice(this.applicationContext, connectionManager, bleDevice, loggerDb) {
|
||||
val device =
|
||||
KirbyDevice(this.applicationContext, connectionManager, bleDevice, loggerDb, settings) {
|
||||
val i = kirbyDevices.indexOfFirst { d -> d === it }
|
||||
runOnUiThread {
|
||||
deviceListAdapter.notifyItemChanged(i)
|
||||
@@ -303,6 +313,12 @@ class MainActivity : AppCompatActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.action_settings -> {
|
||||
val settingsDialog = settingsDialog(this, settings)
|
||||
settingsDialog.show()
|
||||
return true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
117
app/src/main/java/com/logitech/vc/kirbytest/Settings.kt
Normal file
117
app/src/main/java/com/logitech/vc/kirbytest/Settings.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
import androidx.datastore.preferences.core.edit
|
||||
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.logitech.vc.kirbytest.BuildConfig
|
||||
import com.logitech.vc.kirbytest.R
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
|
||||
val Context.dataStore by preferencesDataStore(name = "app_settings")
|
||||
|
||||
class SettingsRepository(context: Context) {
|
||||
private val dataStore = context.dataStore
|
||||
|
||||
private val settings = Settings(apiUrl = BuildConfig.API_BASE_URL, apiKey = BuildConfig.API_KEY)
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
companion object {
|
||||
val API_URL = stringPreferencesKey("api_url")
|
||||
val API_KEY = stringPreferencesKey("api_key")
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun saveSettings(apiUrl: String, apiKey: String) {
|
||||
settings.apiKey = apiKey
|
||||
settings.apiUrl = apiUrl
|
||||
|
||||
coroutineScope.launch(Dispatchers.Main) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[API_URL] = apiUrl
|
||||
preferences[API_KEY] = apiKey
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun readSettings(): Settings {
|
||||
return settings
|
||||
}
|
||||
|
||||
private val settingsFlow: Flow<Settings> = dataStore.data.map {
|
||||
Settings(
|
||||
apiUrl = it[API_URL] ?: BuildConfig.API_BASE_URL,
|
||||
apiKey = it[API_KEY] ?: BuildConfig.API_KEY
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loadSettings() {
|
||||
val result = settingsFlow.firstOrNull()
|
||||
if (result != null) {
|
||||
settings.apiKey = result.apiKey
|
||||
settings.apiUrl = result.apiUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Settings(var apiUrl: String, var apiKey: String)
|
||||
|
||||
|
||||
fun settingsDialog(context: Context, settingsRepo: SettingsRepository): AlertDialog {
|
||||
|
||||
val layoutInflater = LayoutInflater.from(context)
|
||||
|
||||
val root = layoutInflater.inflate(R.layout.settings_dialog, null)
|
||||
val urlField = root.findViewById<EditText>(R.id.apiUrl)
|
||||
val keyField = root.findViewById<EditText>(R.id.apiKey)
|
||||
|
||||
val settings = settingsRepo.readSettings()
|
||||
urlField.setText(settings.apiUrl)
|
||||
keyField.setText(settings.apiKey)
|
||||
|
||||
return androidx.appcompat.app.AlertDialog.Builder(context)
|
||||
.setTitle(R.string.settings)
|
||||
.setView(root)
|
||||
.setPositiveButton(R.string.save) { dialog: DialogInterface?, whichButton: Int ->
|
||||
Log.d("SettingsDialog", "save settings")
|
||||
|
||||
val url = urlField.text.toString()
|
||||
val key = keyField.text.toString()
|
||||
if (isFullPath(url) || url.isEmpty()) {
|
||||
settingsRepo.saveSettings(url, key)
|
||||
}
|
||||
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog: DialogInterface?, whichButton: Int ->
|
||||
//Do something
|
||||
Log.d("SettingsDialog", "cancel settings")
|
||||
}
|
||||
|
||||
.create()
|
||||
|
||||
}
|
||||
|
||||
fun isFullPath(potentialUrl: String): Boolean {
|
||||
try {
|
||||
URL(potentialUrl).toURI()
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="match_parent"
|
||||
android:text='Start Scan'
|
||||
android:text='@string/start_scan'
|
||||
app:icon="@drawable/action_icon_scan"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
56
app/src/main/res/layout/settings_dialog.xml
Normal file
56
app/src/main/res/layout/settings_dialog.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/apiUrlLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="10"
|
||||
android:text="API url" />
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/apiUrl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="10"
|
||||
android:ems="10"
|
||||
android:inputType="textUri"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/apiKeyLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="10"
|
||||
android:text="API key" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/apiKey"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="20"
|
||||
android:autofillHints="password"
|
||||
android:ems="10"
|
||||
android:inputType="text"
|
||||
android:text="" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -12,4 +12,9 @@
|
||||
android:orderInCategory="200"
|
||||
android:title="@string/action_reset_log"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="300"
|
||||
android:title="@string/settings"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
@@ -7,5 +7,11 @@
|
||||
<string name="second_fragment_label">Second Fragment</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="api_url">API url</string>
|
||||
<string name="api_key">API key</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="start_scan">Start Scan</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user