mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: implement XModem file transfers and enhance BLE connection robustness (#4959)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run
This commit is contained in:
parent
ae4465d7c8
commit
c75c9b34d6
43 changed files with 1100 additions and 120 deletions
|
|
@ -71,6 +71,8 @@ import org.meshtastic.proto.Config
|
|||
import org.meshtastic.proto.DeviceConnectionStatus
|
||||
import org.meshtastic.proto.DeviceMetadata
|
||||
import org.meshtastic.proto.DeviceProfile
|
||||
import org.meshtastic.proto.DeviceUIConfig
|
||||
import org.meshtastic.proto.FileInfo
|
||||
import org.meshtastic.proto.HardwareModel
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
|
|
@ -92,6 +94,8 @@ data class RadioConfigState(
|
|||
val ringtone: String = "",
|
||||
val cannedMessageMessages: String = "",
|
||||
val deviceConnectionStatus: DeviceConnectionStatus? = null,
|
||||
val deviceUIConfig: DeviceUIConfig? = null,
|
||||
val fileManifest: List<FileInfo> = emptyList(),
|
||||
val responseState: ResponseState<Boolean> = ResponseState.Empty,
|
||||
val analyticsAvailable: Boolean = true,
|
||||
val analyticsEnabled: Boolean = false,
|
||||
|
|
@ -188,6 +192,14 @@ open class RadioConfigViewModel(
|
|||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
radioConfigRepository.deviceUIConfigFlow
|
||||
.onEach { uiConfig -> _radioConfigState.update { it.copy(deviceUIConfig = uiConfig) } }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
radioConfigRepository.fileManifestFlow
|
||||
.onEach { manifest -> _radioConfigState.update { it.copy(fileManifest = manifest) } }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
serviceRepository.meshPacketFlow.onEach(::processPacketResponse).launchIn(viewModelScope)
|
||||
|
||||
combine(serviceRepository.connectionState, radioConfigState) { connState, _ ->
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
|
|
@ -54,6 +55,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.model.util.isDebug
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.accept
|
||||
import org.meshtastic.core.resources.are_you_sure
|
||||
|
|
@ -66,11 +68,16 @@ import org.meshtastic.core.resources.config_device_tripleClickAsAdHocPing_summar
|
|||
import org.meshtastic.core.resources.config_device_tzdef_summary
|
||||
import org.meshtastic.core.resources.config_device_use_phone_tz
|
||||
import org.meshtastic.core.resources.device
|
||||
import org.meshtastic.core.resources.device_storage_ui_title
|
||||
import org.meshtastic.core.resources.device_theme_language
|
||||
import org.meshtastic.core.resources.double_tap_as_button_press
|
||||
import org.meshtastic.core.resources.file_entry
|
||||
import org.meshtastic.core.resources.files_available
|
||||
import org.meshtastic.core.resources.gpio
|
||||
import org.meshtastic.core.resources.hardware
|
||||
import org.meshtastic.core.resources.i_know_what_i_m_doing
|
||||
import org.meshtastic.core.resources.led_heartbeat
|
||||
import org.meshtastic.core.resources.no_files_manifested
|
||||
import org.meshtastic.core.resources.nodeinfo_broadcast_interval
|
||||
import org.meshtastic.core.resources.options
|
||||
import org.meshtastic.core.resources.rebroadcast_mode
|
||||
|
|
@ -143,6 +150,7 @@ private val Config.DeviceConfig.RebroadcastMode.description: StringResource
|
|||
Config.DeviceConfig.RebroadcastMode.NONE -> Res.string.rebroadcast_mode_none_desc
|
||||
Config.DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY ->
|
||||
Res.string.rebroadcast_mode_core_portnums_only_desc
|
||||
|
||||
else -> Res.string.unrecognized
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +160,7 @@ fun DeviceConfigScreenCommon(viewModel: RadioConfigViewModel, onBack: () -> Unit
|
|||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val deviceConfig = state.radioConfig.device ?: Config.DeviceConfig()
|
||||
val formState = rememberConfigState(initialValue = deviceConfig)
|
||||
var selectedRole by rememberSaveable { mutableStateOf(formState.value.role) }
|
||||
var selectedRole by rememberSaveable(formState.value.role) { mutableStateOf(formState.value.role) }
|
||||
val infrastructureRoles =
|
||||
listOf(Config.DeviceConfig.Role.ROUTER, Config.DeviceConfig.Role.ROUTER_LATE, Config.DeviceConfig.Role.REPEATER)
|
||||
if (selectedRole != formState.value.role) {
|
||||
|
|
@ -309,6 +317,42 @@ fun DeviceConfigScreenCommon(viewModel: RadioConfigViewModel, onBack: () -> Unit
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
if ((state.deviceUIConfig != null || state.fileManifest.isNotEmpty()) && isDebug) {
|
||||
item {
|
||||
TitledCard(title = stringResource(Res.string.device_storage_ui_title)) {
|
||||
state.deviceUIConfig?.let { uiConfig ->
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text =
|
||||
stringResource(
|
||||
Res.string.device_theme_language,
|
||||
uiConfig.theme.toString(),
|
||||
uiConfig.language.toString(),
|
||||
),
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp))
|
||||
}
|
||||
if (state.fileManifest.isNotEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = stringResource(Res.string.files_available, state.fileManifest.size),
|
||||
)
|
||||
state.fileManifest.forEach { file ->
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = stringResource(Res.string.file_entry, file.file_name, file.size_bytes),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = stringResource(Res.string.no_files_manifested),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ fun PositionConfigScreenCommon(viewModel: RadioConfigViewModel, onBack: () -> Un
|
|||
updated
|
||||
}
|
||||
val formState = rememberConfigState(initialValue = sanitizedPositionConfig)
|
||||
var locationInput by rememberSaveable { mutableStateOf(currentPosition) }
|
||||
var locationInput by rememberSaveable(currentPosition) { mutableStateOf(currentPosition) }
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
RadioConfigScreenList(
|
||||
|
|
|
|||
|
|
@ -111,6 +111,12 @@ class RadioConfigViewModelTest {
|
|||
every { radioConfigRepository.localConfigFlow } returns MutableStateFlow(LocalConfig())
|
||||
every { radioConfigRepository.channelSetFlow } returns MutableStateFlow(ChannelSet())
|
||||
every { radioConfigRepository.moduleConfigFlow } returns MutableStateFlow(LocalModuleConfig())
|
||||
every { radioConfigRepository.deviceUIConfigFlow } returns MutableStateFlow(null)
|
||||
every { radioConfigRepository.fileManifestFlow } returns MutableStateFlow(emptyList())
|
||||
|
||||
every { analyticsPrefs.analyticsAllowed } returns MutableStateFlow(false)
|
||||
every { homoglyphEncodingPrefs.homoglyphEncodingEnabled } returns MutableStateFlow(false)
|
||||
|
||||
every { serviceRepository.meshPacketFlow } returns MutableSharedFlow()
|
||||
every { serviceRepository.connectionState } returns
|
||||
MutableStateFlow(org.meshtastic.core.model.ConnectionState.Connected)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue