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 12088bb3..d6b67b9e 100644 --- a/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/BTScanModel.kt @@ -24,6 +24,7 @@ import com.geeksville.mesh.util.anonymize import com.hoho.android.usbserial.driver.UsbSerialDriver import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn @@ -45,24 +46,27 @@ class BTScanModel @Inject constructor( val devices = MutableLiveData>(mutableMapOf()) val errorText = MutableLiveData(null) - val isMockInterfaceAddressValid: Boolean by lazy { - radioInterfaceService.isAddressValid(radioInterfaceService.mockInterfaceAddress) + private val showMockInterface = MutableStateFlow(radioInterfaceService.isMockInterface) + + fun showMockInterface() { + showMockInterface.value = true } init { combine( bluetoothRepository.state, networkRepository.resolvedList, - usbRepository.serialDevicesWithDrivers - ) { ble, tcp, usb -> + usbRepository.serialDevicesWithDrivers, + showMockInterface, + ) { ble, tcp, usb, showMockInterface -> devices.value = mutableMapOf().apply { fun addDevice(entry: DeviceListEntry) { this[entry.fullAddress] = entry } // Include a placeholder for "None" addDevice(DeviceListEntry(context.getString(R.string.none), "n", true)) - if (isMockInterfaceAddressValid) { - addDevice(DeviceListEntry("Included simulator", "m", true)) + if (showMockInterface) { + addDevice(DeviceListEntry("Demo Mode", "m", true)) } // Include paired Bluetooth devices diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterfaceSpec.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterfaceSpec.kt index 71995577..726911ce 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterfaceSpec.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/MockInterfaceSpec.kt @@ -1,15 +1,11 @@ package com.geeksville.mesh.repository.radio -import android.app.Application -import com.geeksville.mesh.android.BuildUtils -import com.geeksville.mesh.android.GeeksvilleApplication import javax.inject.Inject /** * Mock interface backend implementation. */ class MockInterfaceSpec @Inject constructor( - private val application: Application, private val factory: MockInterfaceFactory ): InterfaceSpec { override fun createInterface(rest: String): MockInterface { @@ -17,6 +13,5 @@ class MockInterfaceSpec @Inject constructor( } /** Return true if this address is still acceptable. For BLE that means, still bonded */ - override fun addressValid(rest: String): Boolean = - BuildUtils.isEmulator || ((application as GeeksvilleApplication).isInTestLab) -} \ No newline at end of file + override fun addressValid(rest: String): Boolean = true +} diff --git a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt index bf760c12..03df22c2 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/radio/RadioInterfaceService.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope import com.geeksville.mesh.CoroutineDispatchers import com.geeksville.mesh.android.BinaryLogFile +import com.geeksville.mesh.android.BuildUtils import com.geeksville.mesh.android.GeeksvilleApplication import com.geeksville.mesh.android.Logging import com.geeksville.mesh.concurrent.handledLaunch @@ -104,8 +105,8 @@ class RadioInterfaceService @Inject constructor( return interfaceFactory.toInterfaceAddress(interfaceId, rest) } - fun isAddressValid(address: String?): Boolean { - return interfaceFactory.addressValid(address) + val isMockInterface: Boolean by lazy { + BuildUtils.isEmulator || (context as GeeksvilleApplication).isInTestLab } /** Return the device we are configured to use, or null for none @@ -121,7 +122,7 @@ class RadioInterfaceService @Inject constructor( var address = prefs.getString(DEVADDR_KEY, null) // If we are running on the emulator we default to the mock interface, so we can have some data to show to the user - if (address == null && isAddressValid(mockInterfaceAddress)) { + if (address == null && isMockInterface) { address = mockInterfaceAddress } diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index eb1799ad..00660d39 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -290,6 +290,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { .show() } + private var tapCount = 0 + private var lastTapTime: Long = 0 + private fun addDeviceButton(device: BTScanModel.DeviceListEntry, enabled: Boolean) { val b = RadioButton(requireActivity()) b.text = device.name @@ -299,6 +302,19 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { binding.deviceRadioGroup.addView(b) b.setOnClickListener { + if (device.fullAddress == "n") { + val currentTapTime = System.currentTimeMillis() + if (currentTapTime - lastTapTime > TAP_THRESHOLD) { + tapCount = 0 + } + lastTapTime = currentTapTime + tapCount++ + + if (tapCount >= TAP_TRIGGER) { + model.showSnackbar("Demo Mode enabled") + scanModel.showMockInterface() + } + } if (!device.bonded) // If user just clicked on us, try to bond binding.scanStatusText.setText(R.string.starting_pairing) b.isChecked = scanModel.onSelected(device) @@ -354,7 +370,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // If we are running on an emulator, always leave this message showing so we can test the worst case layout val curRadio = scanModel.selectedAddress - if (curRadio != null && !scanModel.isMockInterfaceAddressValid) { + if (curRadio != null && curRadio != "m") { binding.warningNotPaired.visibility = View.GONE } else if (bluetoothViewModel.enabled.value == true) { binding.warningNotPaired.visibility = View.VISIBLE @@ -449,6 +465,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { companion object { const val SCAN_PERIOD: Long = 10000 // Stops scanning after 10 seconds + private const val TAP_TRIGGER: Int = 7 + private const val TAP_THRESHOLD: Long = 500 // max 500 ms between taps + } private fun Editable.isIPAddress(): Boolean {