diff --git a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt index fa79a5d71..fe9d5f933 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -21,6 +21,7 @@ import android.annotation.SuppressLint import android.app.Application import android.bluetooth.BluetoothDevice import android.content.Context +import android.content.SharedPreferences import android.hardware.usb.UsbManager import android.os.RemoteException import androidx.lifecycle.MutableLiveData @@ -51,8 +52,10 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import javax.inject.Inject +import org.json.JSONArray @HiltViewModel +@Suppress("LongParameterList", "TooManyFunctions") class BTScanModel @Inject constructor( private val application: Application, private val serviceRepository: ServiceRepository, @@ -61,12 +64,15 @@ class BTScanModel @Inject constructor( private val usbManagerLazy: dagger.Lazy, private val networkRepository: NetworkRepository, private val radioInterfaceService: RadioInterfaceService, + private val preferences: SharedPreferences, ) : ViewModel(), Logging { private val context: Context get() = application.applicationContext val devices = MutableLiveData>(mutableMapOf()) val errorText = MutableLiveData(null) + private val recentIpAddresses = MutableStateFlow(getRecentAddresses()) + private val showMockInterface: StateFlow get() = MutableStateFlow(radioInterfaceService.isMockInterface()).asStateFlow() @@ -75,9 +81,10 @@ class BTScanModel @Inject constructor( combine( bluetoothRepository.state, networkRepository.resolvedList, + recentIpAddresses.asStateFlow(), usbRepository.serialDevicesWithDrivers, showMockInterface, - ) { ble, tcp, usb, showMockInterface -> + ) { ble, tcp, recent, usb, showMockInterface -> devices.value = mutableMapOf().apply { fun addDevice(entry: DeviceListEntry) { this[entry.fullAddress] = entry @@ -118,6 +125,11 @@ class BTScanModel @Inject constructor( addDevice(DeviceListEntry(displayName, "t$address", true)) } + // Include saved IP connections + recent?.forEach { address -> + addDevice(DeviceListEntry(context.getString(R.string.meshtastic), address, true)) + } + usb.forEach { (_, d) -> addDevice(USBDeviceListEntry(radioInterfaceService, usbManagerLazy.get(), d)) } @@ -282,11 +294,36 @@ class BTScanModel @Inject constructor( }.launchIn(viewModelScope) } + private fun getRecentAddresses(): List { + val jsonAddresses = preferences.getString("recent-ip-addresses", "[]") ?: "[]" + val listAddresses = JSONArray(jsonAddresses).let { jsonArray -> + List(jsonArray.length()) { index -> jsonArray.getString(index) } + } + return listAddresses + } + + private fun setRecentAddresses(addresses: List) { + val editor = preferences.edit() + editor.putString("recent-ip-addresses", addresses.toString()) + editor.apply() + recentIpAddresses.value = addresses + } + + fun addRecentAddress(address: String) { + if (!address.startsWith("t")) return + val existingItems = getRecentAddresses() + val updatedList = mutableListOf() + updatedList.add(address) + updatedList.addAll(existingItems.filter { it != address }.take(2)) + setRecentAddresses(updatedList) + } + // Called by the GUI when a new device has been selected by the user // @returns true if we were able to change to that item fun onSelected(it: DeviceListEntry): Boolean { // If the device is paired, let user select it, otherwise start the pairing flow if (it.bonded) { + addRecentAddress(it.fullAddress) changeDeviceAddress(it.fullAddress) return true } else {