Refactor NsdManager and improve service display (#2292)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
pull/2309/head
James Rich 2025-06-29 14:18:14 +00:00 zatwierdzone przez GitHub
rodzic ec74bbfe19
commit 8b095aba09
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
5 zmienionych plików z 30 dodań i 23 usunięć

Wyświetl plik

@ -68,8 +68,9 @@ class BTScanModel @Inject constructor(
val devices = MutableLiveData<MutableMap<String, DeviceListEntry>>(mutableMapOf()) val devices = MutableLiveData<MutableMap<String, DeviceListEntry>>(mutableMapOf())
val errorText = MutableLiveData<String?>(null) val errorText = MutableLiveData<String?>(null)
private val showMockInterface: StateFlow<Boolean> get() = private val showMockInterface: StateFlow<Boolean>
MutableStateFlow(radioInterfaceService.isMockInterface()).asStateFlow() get() =
MutableStateFlow(radioInterfaceService.isMockInterface()).asStateFlow()
init { init {
combine( combine(
@ -84,7 +85,13 @@ class BTScanModel @Inject constructor(
} }
// Include a placeholder for "None" // Include a placeholder for "None"
addDevice(DeviceListEntry(context.getString(R.string.none), NO_DEVICE_SELECTED, true)) addDevice(
DeviceListEntry(
context.getString(R.string.none),
NO_DEVICE_SELECTED,
true
)
)
if (showMockInterface) { if (showMockInterface) {
addDevice(DeviceListEntry("Demo Mode", "m", true)) addDevice(DeviceListEntry("Demo Mode", "m", true))
@ -97,7 +104,19 @@ class BTScanModel @Inject constructor(
// Include Network Service Discovery // Include Network Service Discovery
tcp.forEach { service -> tcp.forEach { service ->
val address = service.toAddressString() val address = service.toAddressString()
addDevice(DeviceListEntry(address, "t$address", true)) val txtRecords = service.attributes // Map<String, ByteArray?>
val shortNameBytes = txtRecords["shortname"]
val idBytes = txtRecords["id"]
val shortName = shortNameBytes?.let { String(it, Charsets.UTF_8) }
?: context.getString(R.string.meshtastic)
val deviceId =
idBytes?.let { String(it, Charsets.UTF_8) }?.replace("!", "")
var displayName = shortName
if (deviceId != null) {
displayName += "_$deviceId"
}
addDevice(DeviceListEntry(displayName, "t$address", true))
} }
usb.forEach { (_, d) -> usb.forEach { (_, d) ->
@ -160,10 +179,6 @@ class BTScanModel @Inject constructor(
private var scanJob: Job? = null private var scanJob: Job? = null
val selectedAddress get() = radioInterfaceService.getDeviceAddress()
val selectedBluetooth: Boolean get() = selectedAddress?.getOrNull(0) == 'x'
// / Use the string for the NopInterface
val selectedAddressFlow: StateFlow<String?> = radioInterfaceService.currentDeviceAddressFlow val selectedAddressFlow: StateFlow<String?> = radioInterfaceService.currentDeviceAddressFlow
val selectedNotNullFlow: StateFlow<String> = selectedAddressFlow val selectedNotNullFlow: StateFlow<String> = selectedAddressFlow

Wyświetl plik

@ -41,16 +41,13 @@ class NetworkRepository @Inject constructor(
.conflate() .conflate()
val resolvedList: Flow<List<NsdServiceInfo>> val resolvedList: Flow<List<NsdServiceInfo>>
get() = nsdManagerLazy.get().serviceList(SERVICE_TYPES, SERVICE_NAME) get() = nsdManagerLazy.get().serviceList(SERVICE_TYPE)
.flowOn(dispatchers.io) .flowOn(dispatchers.io)
.conflate() .conflate()
companion object { companion object {
// To find all available services use SERVICE_TYPE = "_services._dns-sd._udp"
internal const val SERVICE_NAME = "Meshtastic"
internal const val SERVICE_PORT = 4403 internal const val SERVICE_PORT = 4403
private const val SERVICE_TYPE = "_meshtastic._tcp" private const val SERVICE_TYPE = "_meshtastic._tcp"
internal val SERVICE_TYPES = setOf("_http._tcp", SERVICE_TYPE)
fun NsdServiceInfo.toAddressString() = buildString { fun NsdServiceInfo.toAddressString() = buildString {
append(@Suppress("DEPRECATION") host.toString().substring(1)) append(@Suppress("DEPRECATION") host.toString().substring(1))

Wyświetl plik

@ -24,27 +24,16 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import kotlin.coroutines.resume import kotlin.coroutines.resume
internal fun NsdManager.serviceList(
serviceTypes: Set<String>,
serviceName: String,
): Flow<List<NsdServiceInfo>> {
val flows = serviceTypes.map { serviceType -> serviceList(serviceType, serviceName) }
return combine(flows) { lists -> lists.flatMap { it } }
}
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
internal fun NsdManager.serviceList( internal fun NsdManager.serviceList(
serviceType: String, serviceType: String,
serviceName: String,
): Flow<List<NsdServiceInfo>> = discoverServices(serviceType).mapLatest { serviceList -> ): Flow<List<NsdServiceInfo>> = discoverServices(serviceType).mapLatest { serviceList ->
serviceList serviceList
.filter { it.serviceName.contains(serviceName) }
.mapNotNull { resolveService(it) } .mapNotNull { resolveService(it) }
} }

Wyświetl plik

@ -81,6 +81,11 @@ fun DeviceListItem(
contentDescription contentDescription
) )
}, },
supportingContent = {
if (device.isTCP) {
Text(device.address)
}
},
trailingContent = { trailingContent = {
if (selected) { if (selected) {
Icon( Icon(

Wyświetl plik

@ -705,4 +705,5 @@
<string name="no_network_devices">No Network devices found.</string> <string name="no_network_devices">No Network devices found.</string>
<string name="no_usb_devices">No USB Serial devices found.</string> <string name="no_usb_devices">No USB Serial devices found.</string>
<string name="scroll_to_bottom">Scroll to bottom</string> <string name="scroll_to_bottom">Scroll to bottom</string>
<string name="meshtastic">Meshtastic</string>
</resources> </resources>