kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: add demo mode for testing and review purposes
- Activates by tapping "None (disable)" 7 times. - Displays a confirmation message when `Demo Mode` is enabled. - Simulates a connection to a Meshtastic device and allows the app to function without requiring real hardware.pull/1279/head^2
rodzic
16e91c0ebf
commit
ffb402acde
|
@ -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<MutableMap<String, DeviceListEntry>>(mutableMapOf())
|
||||
val errorText = MutableLiveData<String?>(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<String, DeviceListEntry>().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
|
||||
|
|
|
@ -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<MockInterface> {
|
||||
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)
|
||||
}
|
||||
override fun addressValid(rest: String): Boolean = true
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Ładowanie…
Reference in New Issue