mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-04 22:23:47 -04:00
refactor(settings)!: standardize radio config screens (#3167)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -2,11 +2,8 @@
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ChainWrapping:Channel.kt$Channel$&&</ID>
|
||||
<ID>CommentSpacing:BLEException.kt$BLEConnectionClosing$/// Our interface is being shut down</ID>
|
||||
<ID>CommentSpacing:Constants.kt$/// a bool true means we expect this condition to continue until, false means device might come back</ID>
|
||||
<ID>CommentSpacing:ContextExtensions.kt$/// Utility function to hide the soft keyboard per stack overflow</ID>
|
||||
<ID>CommentSpacing:ContextExtensions.kt$/// show a toast</ID>
|
||||
<ID>CommentSpacing:Coroutines.kt$/// Wrap launch with an exception handler, FIXME, move into a utility lib</ID>
|
||||
<ID>CommentSpacing:DeferredExecution.kt$DeferredExecution$/// Queue some new work</ID>
|
||||
<ID>CommentSpacing:DeferredExecution.kt$DeferredExecution$/// run all work in the queue and clear it to be ready to accept new work</ID>
|
||||
@@ -18,8 +15,6 @@
|
||||
<ID>ComposableParamOrder:AlertDialogs.kt$SimpleAlertDialog</ID>
|
||||
<ID>ComposableParamOrder:BatteryInfo.kt$BatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:ChannelSettingsItemList.kt$ChannelSettingsItemList</ID>
|
||||
<ID>ComposableParamOrder:Connections.kt$ConnectionsScreen</ID>
|
||||
<ID>ComposableParamOrder:CurrentlyConnectedCard.kt$CurrentlyConnectedCard</ID>
|
||||
<ID>ComposableParamOrder:Debug.kt$DebugMenuActions</ID>
|
||||
<ID>ComposableParamOrder:Debug.kt$DecodedPayloadBlock</ID>
|
||||
<ID>ComposableParamOrder:DebugSearch.kt$DebugSearchState</ID>
|
||||
@@ -51,7 +46,6 @@
|
||||
<ID>ComposableParamOrder:NodeDetail.kt$EnvironmentMetrics</ID>
|
||||
<ID>ComposableParamOrder:NodeDetail.kt$NodeActionButton</ID>
|
||||
<ID>ComposableParamOrder:NodeDetail.kt$NodeDetailList</ID>
|
||||
<ID>ComposableParamOrder:NodeDetail.kt$NodeDetailScreen</ID>
|
||||
<ID>ComposableParamOrder:NodeFilterTextField.kt$NodeFilterTextField</ID>
|
||||
<ID>ComposableParamOrder:NodeItem.kt$NodeItem</ID>
|
||||
<ID>ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon</ID>
|
||||
@@ -59,11 +53,9 @@
|
||||
<ID>ComposableParamOrder:NodeScreen.kt$NodeScreen</ID>
|
||||
<ID>ComposableParamOrder:PaxMetrics.kt$PaxMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:PermissionScreenLayout.kt$PermissionScreenLayout</ID>
|
||||
<ID>ComposableParamOrder:PositionConfigItemList.kt$PositionConfigItemList</ID>
|
||||
<ID>ComposableParamOrder:PowerMetrics.kt$PowerMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:QuickChat.kt$OutlinedTextFieldWithCounter</ID>
|
||||
<ID>ComposableParamOrder:SatelliteCountInfo.kt$SatelliteCountInfo</ID>
|
||||
<ID>ComposableParamOrder:SecurityConfigItemList.kt$SecurityConfigItemList</ID>
|
||||
<ID>ComposableParamOrder:SettingsItem.kt$SettingsItem</ID>
|
||||
<ID>ComposableParamOrder:SignalInfo.kt$SignalInfo</ID>
|
||||
<ID>ComposableParamOrder:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
@@ -80,6 +72,8 @@
|
||||
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "received_time") val received_time: Long</ID>
|
||||
<ID>ContentSlotReused:AdaptiveTwoPane.kt$second</ID>
|
||||
<ID>CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>EmptyCatchBlock:MeshLog.kt$MeshLog${ }</ID>
|
||||
<ID>EmptyClassBlock:DebugLogFile.kt$BinaryLogFile${ }</ID>
|
||||
@@ -117,7 +111,6 @@
|
||||
<ID>FunctionNaming:QuickChatActionDao.kt$QuickChatActionDao$@Query("Delete from quick_chat where uuid=:uuid") fun _delete(uuid: Long)</ID>
|
||||
<ID>ImplicitDefaultLocale:NodeInfo.kt$NodeInfo$String.format("%d%%", batteryLevel)</ID>
|
||||
<ID>LambdaParameterEventTrailing:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterEventTrailing:CurrentlyConnectedCard.kt$onClickDisconnect</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onAction</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onSendMessage</ID>
|
||||
@@ -128,15 +121,24 @@
|
||||
<ID>LambdaParameterInRestartableEffect:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterInRestartableEffect:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>LargeClass:MeshService.kt$MeshService : ServiceLogging</ID>
|
||||
<ID>LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigItemList( ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig, enabled: Boolean, onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigItemList( messages: String, cannedMessageConfig: CannedMessageConfig, enabled: Boolean, onSaveClicked: (messages: String, config: CannedMessageConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:DropDownPreference.kt$@Composable fun <T> DropDownPreference( title: String, enabled: Boolean, items: List<Pair<T, String>>, selectedItem: T, onItemSelected: (T) -> Unit, modifier: Modifier = Modifier, summary: String? = null, )</ID>
|
||||
<ID>LongMethod:EditListPreference.kt$@Composable inline fun <reified T> EditListPreference( title: String, list: List<T>, maxCount: Int, enabled: Boolean, keyboardActions: KeyboardActions, crossinline onValuesChanged: (List<T>) -> Unit, modifier: Modifier = Modifier, )</ID>
|
||||
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigItemList( ringtone: String, extNotificationConfig: ExternalNotificationConfig, enabled: Boolean, onSaveClicked: (ringtone: String, config: ExternalNotificationConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:DetectionSensorConfigItemList.kt$@Composable fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:DisplayConfigItemList.kt$@Composable fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:LoRaConfigItemList.kt$@Composable fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>LongMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:PowerConfigItemList.kt$@Composable fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigItemList( storeForwardConfig: StoreForwardConfig, enabled: Boolean, onSaveClicked: (StoreForwardConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigItemList( telemetryConfig: TelemetryConfig, enabled: Boolean, onSaveClicked: (TelemetryConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:SecurityConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>LongMethod:UserConfigItemList.kt$@Composable fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$100</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$101</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$14</ID>
|
||||
@@ -150,30 +152,6 @@
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$1000</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$500</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$512</ID>
|
||||
<ID>MagicNumber:Channel.kt$0xff</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.03125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.203125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.40625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.8125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1.6250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1000f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1600</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$200</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$3.25f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$31</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$400</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$5</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$62</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$800</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_MODERATE$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_SLOW$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.VERY_LONG_SLOW$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$40</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$960</ID>
|
||||
<ID>MagicNumber:Contacts.kt$7</ID>
|
||||
@@ -239,6 +217,7 @@
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e7</ID>
|
||||
<ID>MagicNumber:PacketRepository.kt$PacketRepository$500</ID>
|
||||
<ID>MagicNumber:PacketResponseStateDialog.kt$100</ID>
|
||||
<ID>MagicNumber:PowerConfigItemList.kt$3600</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$21972</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$32809</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$6790</ID>
|
||||
@@ -266,7 +245,6 @@
|
||||
<ID>MatchingDeclarationName:SortOption.kt$NodeSortOption</ID>
|
||||
<ID>MaxLineLength:BluetoothInterface.kt$/* Info for the esp32 device side code. See that source for the 'gold' standard docs on this interface. MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best FIXME - in the esp32 mesh management code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there. FIXME - make sure this protocol is guaranteed robust and won't drop packets "According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)). In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes." MAXPACKET is 256? look into what the lora lib uses. FIXME Characteristics: UUID properties description 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 read fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet). After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this mailbox. f75c76d2-129e-4dad-a1dd-7866124401e7 write toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len) ed9da18c-a800-4f66-a670-aa7547e34453 read|notify|write fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages until it catches up with this number. The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio. Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted. Re: queue management Not all messages are kept in the fromradio queue (filtered based on SubPacket): * only the most recent Position and User messages for a particular node are kept * all Data SubPackets are kept * No WantNodeNum / DenyNodeNum messages are kept A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) */</ID>
|
||||
<ID>MaxLineLength:BluetoothState.kt$BluetoothState$"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map { it.anonymize }})"</ID>
|
||||
<ID>MaxLineLength:Channel.kt$Channel$// We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name</ID>
|
||||
<ID>MaxLineLength:DataPacket.kt$DataPacket$val dataType: Int</ID>
|
||||
<ID>MaxLineLength:LocationRepository.kt$LocationRepository$info("Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m")</ID>
|
||||
<ID>MaxLineLength:MQTTRepository.kt$MQTTRepository.Companion$*</ID>
|
||||
@@ -275,65 +253,49 @@
|
||||
<ID>ModifierClickableOrder:Channel.kt$clickable(onClick = onClick)</ID>
|
||||
<ID>ModifierListSpacing:Packet.kt$Packet$@Entity( tableName = "packet", indices = [ Index(value = ["myNodeNum"]), Index(value = ["port_num"]), Index(value = ["contact_key"]), ] ) data</ID>
|
||||
<ID>ModifierMissing:AdaptiveTwoPane.kt$AdaptiveTwoPane</ID>
|
||||
<ID>ModifierMissing:AmbientLightingConfigItemList.kt$AmbientLightingConfigItemList</ID>
|
||||
<ID>ModifierMissing:AudioConfigItemList.kt$AudioConfigItemList</ID>
|
||||
<ID>ModifierMissing:BLEDevices.kt$BLEDevices</ID>
|
||||
<ID>ModifierMissing:BluetoothConfigItemList.kt$BluetoothConfigItemList</ID>
|
||||
<ID>ModifierMissing:CannedMessageConfigItemList.kt$CannedMessageConfigItemList</ID>
|
||||
<ID>ModifierMissing:Channel.kt$ChannelScreen</ID>
|
||||
<ID>ModifierMissing:ChannelSettingsItemList.kt$ChannelSelection</ID>
|
||||
<ID>ModifierMissing:CleanNodeDatabaseScreen.kt$CleanNodeDatabaseScreen</ID>
|
||||
<ID>ModifierMissing:CommonCharts.kt$ChartHeader</ID>
|
||||
<ID>ModifierMissing:CommonCharts.kt$Legend</ID>
|
||||
<ID>ModifierMissing:CommonCharts.kt$TimeLabels</ID>
|
||||
<ID>ModifierMissing:Connections.kt$ConnectionsScreen</ID>
|
||||
<ID>ModifierMissing:ContactSharing.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$ContactListView</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$ContactsScreen</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$SelectionToolbar</ID>
|
||||
<ID>ModifierMissing:DetectionSensorConfigItemList.kt$DetectionSensorConfigItemList</ID>
|
||||
<ID>ModifierMissing:DeviceConfigItemList.kt$DeviceConfigItemList</ID>
|
||||
<ID>ModifierMissing:DeviceMetrics.kt$DeviceMetricsScreen</ID>
|
||||
<ID>ModifierMissing:DisplayConfigItemList.kt$DisplayConfigItemList</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPicker</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPickerDialog</ID>
|
||||
<ID>ModifierMissing:EmptyStateContent.kt$EmptyStateContent</ID>
|
||||
<ID>ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen</ID>
|
||||
<ID>ModifierMissing:ExternalNotificationConfigItemList.kt$ExternalNotificationConfigItemList</ID>
|
||||
<ID>ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen</ID>
|
||||
<ID>ModifierMissing:IndoorAirQuality.kt$IndoorAirQuality</ID>
|
||||
<ID>ModifierMissing:LoRaConfigItemList.kt$LoRaConfigItemList</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$LoraSignalIndicator</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$Rssi</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$Snr</ID>
|
||||
<ID>ModifierMissing:LoraSignalIndicator.kt$SnrAndRssi</ID>
|
||||
<ID>ModifierMissing:MQTTConfigItemList.kt$MQTTConfigItemList</ID>
|
||||
<ID>ModifierMissing:Main.kt$MainScreen</ID>
|
||||
<ID>ModifierMissing:MapReportingPreference.kt$MapReportingPreference</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$MessageStatusButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReactionButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReplyButton</ID>
|
||||
<ID>ModifierMissing:NeighborInfoConfigItemList.kt$NeighborInfoConfigItemList</ID>
|
||||
<ID>ModifierMissing:NetworkConfigItemList.kt$NetworkConfigItemList</ID>
|
||||
<ID>ModifierMissing:NetworkConfigItemList.kt$NetworkConfigScreen</ID>
|
||||
<ID>ModifierMissing:NetworkDevices.kt$NetworkDevices</ID>
|
||||
<ID>ModifierMissing:NodeMenu.kt$NodeMenu</ID>
|
||||
<ID>ModifierMissing:NodeScreen.kt$NodeScreen</ID>
|
||||
<ID>ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons</ID>
|
||||
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsItem</ID>
|
||||
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsScreen</ID>
|
||||
<ID>ModifierMissing:PaxcounterConfigItemList.kt$PaxcounterConfigItemList</ID>
|
||||
<ID>ModifierMissing:PositionConfigItemList.kt$PositionConfigItemList</ID>
|
||||
<ID>ModifierMissing:PositionConfigItemList.kt$PositionConfigScreen</ID>
|
||||
<ID>ModifierMissing:PositionLog.kt$PositionItem</ID>
|
||||
<ID>ModifierMissing:PositionLog.kt$PositionLogScreen</ID>
|
||||
<ID>ModifierMissing:PowerConfigItemList.kt$PowerConfigItemList</ID>
|
||||
<ID>ModifierMissing:PowerMetrics.kt$PowerMetricsScreen</ID>
|
||||
<ID>ModifierMissing:RadioConfig.kt$RadioConfigItemList</ID>
|
||||
<ID>ModifierMissing:RangeTestConfigItemList.kt$RangeTestConfigItemList</ID>
|
||||
<ID>ModifierMissing:RadioConfigScreenList.kt$RadioConfigScreenList</ID>
|
||||
<ID>ModifierMissing:Reaction.kt$ReactionDialog</ID>
|
||||
<ID>ModifierMissing:RemoteHardwareConfigItemList.kt$RemoteHardwareConfigItemList</ID>
|
||||
<ID>ModifierMissing:SecurityConfigItemList.kt$SecurityConfigItemList</ID>
|
||||
<ID>ModifierMissing:SecurityConfigItemList.kt$SecurityConfigScreen</ID>
|
||||
<ID>ModifierMissing:SecurityIcon.kt$SecurityIcon</ID>
|
||||
<ID>ModifierMissing:SerialConfigItemList.kt$SerialConfigItemList</ID>
|
||||
<ID>ModifierMissing:SettingsItem.kt$SettingsItem</ID>
|
||||
<ID>ModifierMissing:SettingsItem.kt$SettingsItemDetail</ID>
|
||||
<ID>ModifierMissing:SettingsItem.kt$SettingsItemSwitch</ID>
|
||||
@@ -342,20 +304,12 @@
|
||||
<ID>ModifierMissing:SignalMetrics.kt$SignalMetricsScreen</ID>
|
||||
<ID>ModifierMissing:SimpleAlertDialog.kt$SimpleAlertDialog</ID>
|
||||
<ID>ModifierMissing:SlidingSelector.kt$OptionLabel</ID>
|
||||
<ID>ModifierMissing:StoreForwardConfigItemList.kt$StoreForwardConfigItemList</ID>
|
||||
<ID>ModifierMissing:TelemetryConfigItemList.kt$TelemetryConfigItemList</ID>
|
||||
<ID>ModifierMissing:TopLevelNavIcon.kt$TopLevelNavIcon</ID>
|
||||
<ID>ModifierMissing:UserConfigItemList.kt$UserConfigItemList</ID>
|
||||
<ID>ModifierNotUsedAtRoot:BitwisePreference.kt$modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:BitwisePreference.kt$modifier = modifier.fillMaxWidth()</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DropDownPreference.kt$modifier = modifier .background( color = if (selectedItem == item.first) { MaterialTheme.colorScheme.primary.copy(alpha = 0.3f) } else { Color.Unspecified }, )</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EditChannelDialog.kt$modifier = modifier.weight(1f)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EditDeviceProfileDialog.kt$modifier = modifier.weight(1f)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EditListPreference.kt$modifier = modifier.fillMaxWidth()</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EditListPreference.kt$modifier = modifier.padding(16.dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:NodeChip.kt$modifier = modifier.width(IntrinsicSize.Min).defaultMinSize(minWidth = 72.dp).semantics { contentDescription = node.user.shortName.ifEmpty { "Node" } }</ID>
|
||||
@@ -370,17 +324,9 @@
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier .fillMaxWidth() .padding(all = 16.dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End)</ID>
|
||||
<ID>ModifierReused:BitwisePreference.kt$Checkbox( modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End), checked = value and item.first != 0, onCheckedChange = { onItemSelected(value xor item.first) }, enabled = enabled, )</ID>
|
||||
<ID>ModifierReused:BitwisePreference.kt$DropdownMenuItem( onClick = { onItemSelected(value xor item.first) }, modifier = modifier.fillMaxWidth(), text = { Text( text = item.second, overflow = TextOverflow.Ellipsis, ) Checkbox( modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End), checked = value and item.first != 0, onCheckedChange = { onItemSelected(value xor item.first) }, enabled = enabled, ) } )</ID>
|
||||
<ID>ModifierReused:DeviceMetrics.kt$Canvas(modifier = modifier.width(dp)) { val height = size.height val width = size.width for (i in telemetries.indices) { val telemetry = telemetries[i] /* x-value time */ val xRatio = (telemetry.time - oldest.time).toFloat() / timeDiff val x = xRatio * width /* Channel Utilization */ plotPoint( drawContext = drawContext, color = Device.CH_UTIL.color, x = x, value = telemetry.deviceMetrics.channelUtilization, divisor = MAX_PERCENT_VALUE, ) /* Air Utilization Transmit */ plotPoint( drawContext = drawContext, color = Device.AIR_UTIL.color, x = x, value = telemetry.deviceMetrics.airUtilTx, divisor = MAX_PERCENT_VALUE, ) } /* Battery Line */ var index = 0 while (index < telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -> val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = telemetry.deviceMetrics.batteryLevel / MAX_PERCENT_VALUE val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = Device.BATTERY.color, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } }</ID>
|
||||
<ID>ModifierReused:DeviceMetrics.kt$HorizontalLinesOverlay( modifier.width(dp), lineColors = listOf(graphColor, Color.Yellow, Color.Red, graphColor, graphColor), )</ID>
|
||||
<ID>ModifierReused:DeviceMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval())</ID>
|
||||
<ID>ModifierReused:EditListPreference.kt$Column(modifier = modifier) { Text(modifier = modifier.padding(16.dp), text = title, style = MaterialTheme.typography.bodyMedium) listState.forEachIndexed { index, value -> val trailingIcon = @Composable { IconButton( onClick = { focusManager.clearFocus() listState.removeAt(index) onValuesChanged(listState) }, ) { Icon( imageVector = Icons.TwoTone.Close, contentDescription = stringResource(R.string.delete), modifier = Modifier.wrapContentSize(), ) } } // handle lora.ignoreIncoming: List<Int> if (value is Int) { EditTextPreference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, ) } // handle security.adminKey: List<ByteString> if (value is ByteString) { EditBase64Preference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChange = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, ) } // handle remoteHardware.availablePins: List<RemoteHardwarePin> if (value is RemoteHardwarePin) { EditTextPreference( title = stringResource(R.string.gpio_pin), value = value.gpioPin, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { if (it in 0..255) { listState[index] = value.copy { gpioPin = it } as T onValuesChanged(listState) } }, ) EditTextPreference( title = stringResource(R.string.name), value = value.name, maxSize = 14, // name max_size:15 enabled = enabled, isError = false, keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done), keyboardActions = keyboardActions, onValueChanged = { listState[index] = value.copy { name = it } as T onValuesChanged(listState) }, trailingIcon = trailingIcon, ) DropDownPreference( title = stringResource(R.string.type), enabled = enabled, items = RemoteHardwarePinType.entries .filter { it != RemoteHardwarePinType.UNRECOGNIZED } .map { it to it.name }, selectedItem = value.type, onItemSelected = { listState[index] = value.copy { type = it } as T onValuesChanged(listState) }, ) } } OutlinedButton( modifier = Modifier.fillMaxWidth(), onClick = { // Add element based on the type T val newElement = when (T::class) { Int::class -> 0 as T ByteString::class -> ByteString.EMPTY as T RemoteHardwarePin::class -> remoteHardwarePin {} as T else -> throw IllegalArgumentException("Unsupported type: ${T::class}") } listState.add(listState.size, newElement) }, enabled = maxCount > listState.size, ) { Text(text = stringResource(R.string.add)) } }</ID>
|
||||
<ID>ModifierReused:EditListPreference.kt$EditBase64Preference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChange = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
|
||||
<ID>ModifierReused:EditListPreference.kt$EditTextPreference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
|
||||
<ID>ModifierReused:EditListPreference.kt$Text(modifier = modifier.padding(16.dp), text = title, style = MaterialTheme.typography.bodyMedium)</ID>
|
||||
<ID>ModifierReused:EditTextPreference.kt$Box( contentAlignment = Alignment.BottomEnd, modifier = modifier.fillMaxWidth() ) { Text( text = "${value.toByteArray().size}/$maxSize", style = MaterialTheme.typography.bodySmall, color = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground, modifier = Modifier.padding(end = 8.dp, bottom = 4.dp) ) }</ID>
|
||||
<ID>ModifierReused:EditTextPreference.kt$TextField( value = value, singleLine = true, modifier = modifier .fillMaxWidth() .onFocusEvent { isFocused = it.isFocused; onFocusChanged(it) }, enabled = enabled, isError = isError, onValueChange = { if (maxSize > 0) { if (it.toByteArray().size <= maxSize) { onValueChanged(it) } } else onValueChanged(it) }, label = { Text(title) }, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, visualTransformation = visualTransformation, trailingIcon = { if (trailingIcon != null) { trailingIcon() } else if (isError) { Icon( imageVector = Icons.TwoTone.Info, contentDescription = stringResource(id = R.string.error), tint = MaterialTheme.colorScheme.error ) } }, )</ID>
|
||||
<ID>ModifierReused:EnvironmentCharts.kt$Box( contentAlignment = Alignment.TopStart, modifier = modifier.horizontalScroll(state = scrollState, reverseScrolling = true), ) { HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) TimeAxisOverlay(modifier = modifier.width(dp), oldest = oldest, newest = newest, selectedTime.lineInterval()) MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, ) }</ID>
|
||||
<ID>ModifierReused:EnvironmentCharts.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor })</ID>
|
||||
<ID>ModifierReused:EnvironmentCharts.kt$MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, )</ID>
|
||||
@@ -408,18 +354,10 @@
|
||||
<ID>ModifierReused:TextDividerPreference.kt$Row( modifier = modifier .fillMaxWidth() .padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon( trailingIcon, "trailingIcon", modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End), ) } }</ID>
|
||||
<ID>ModifierWithoutDefault:CommonCharts.kt$modifier</ID>
|
||||
<ID>ModifierWithoutDefault:EnvironmentCharts.kt$modifier</ID>
|
||||
<ID>MultiLineIfElse:Channel.kt$Channel$"Custom"</ID>
|
||||
<ID>MultiLineIfElse:Channel.kt$Channel$when (loraConfig.modemPreset) { ModemPreset.SHORT_TURBO -> "ShortTurbo" ModemPreset.SHORT_FAST -> "ShortFast" ModemPreset.SHORT_SLOW -> "ShortSlow" ModemPreset.MEDIUM_FAST -> "MediumFast" ModemPreset.MEDIUM_SLOW -> "MediumSlow" ModemPreset.LONG_FAST -> "LongFast" ModemPreset.LONG_SLOW -> "LongSlow" ModemPreset.LONG_MODERATE -> "LongMod" ModemPreset.VERY_LONG_SLOW -> "VLongSlow" else -> "Invalid" }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toDoubleOrNull()?.let { double -> valueState = it onValueChanged(double) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toFloatOrNull()?.let { float -> valueState = it onValueChanged(float) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toUIntOrNull()?.toInt()?.let { int -> valueState = it onValueChanged(int) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$onValueChanged(it)</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$valueState = it</ID>
|
||||
<ID>MultiLineIfElse:Exceptions.kt$Exceptions.errormsg("ignoring exception", ex)</ID>
|
||||
<ID>MultipleEmitters:CleanNodeDatabaseScreen.kt$NodesDeletionPreview</ID>
|
||||
<ID>MultipleEmitters:CommonCharts.kt$LegendLabel</ID>
|
||||
<ID>MultipleEmitters:DeviceMetrics.kt$DeviceMetricsChart</ID>
|
||||
<ID>MultipleEmitters:EditTextPreference.kt$EditTextPreference</ID>
|
||||
<ID>MultipleEmitters:EnvironmentCharts.kt$EnvironmentMetricsChart</ID>
|
||||
<ID>MultipleEmitters:NodeDetail.kt$EncryptionErrorContent</ID>
|
||||
<ID>MultipleEmitters:NodeDetail.kt$MetricsSection</ID>
|
||||
@@ -430,7 +368,6 @@
|
||||
<ID>MultipleEmitters:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
<ID>MutableStateAutoboxing:Contacts.kt$mutableStateOf(2)</ID>
|
||||
<ID>MutableStateParam:MessageList.kt$selectedIds</ID>
|
||||
<ID>NestedBlockDepth:LanguageUtils.kt$LanguageUtils$fun getLanguageTags(context: Context): Map<String, String></ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedAdmin(fromNodeNum: Int, a: AdminProtos.AdminMessage)</ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>NestedBlockDepth:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
@@ -472,54 +409,32 @@
|
||||
<ID>NoSemicolons:DateUtils.kt$DateUtils$;</ID>
|
||||
<ID>NoWildcardImports:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
<ID>OptionalAbstractKeyword:SyncContinuation.kt$Continuation$abstract</ID>
|
||||
<ID>ParameterNaming:AmbientLightingConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:AudioConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:BitwisePreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:BluetoothConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:CannedMessageConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:ChannelSettingsItemList.kt$onPositiveClicked</ID>
|
||||
<ID>ParameterNaming:ChannelSettingsItemList.kt$onSelected</ID>
|
||||
<ID>ParameterNaming:CleanNodeDatabaseScreen.kt$onCheckedChanged</ID>
|
||||
<ID>ParameterNaming:CleanNodeDatabaseScreen.kt$onDaysChanged</ID>
|
||||
<ID>ParameterNaming:Contacts.kt$onDeleteSelected</ID>
|
||||
<ID>ParameterNaming:Contacts.kt$onMuteSelected</ID>
|
||||
<ID>ParameterNaming:DetectionSensorConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:DeviceConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:DisplayConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:DropDownPreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:EditIPv4Preference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditListPreference.kt$onValuesChanged</ID>
|
||||
<ID>ParameterNaming:EditPasswordPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditTextPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:ExternalNotificationConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:LoRaConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:MQTTConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:MQTTConfigItemList.kt$onShouldReportLocationChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onMapReportingEnabledChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onPositionPrecisionChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onPublishIntervalSecsChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onShouldReportLocationChanged</ID>
|
||||
<ID>ParameterNaming:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>ParameterNaming:NeighborInfoConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:NetworkConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:NodeDetail.kt$onFirmwareSelected</ID>
|
||||
<ID>ParameterNaming:NodeFilterTextField.kt$onToggleShowIgnored</ID>
|
||||
<ID>ParameterNaming:PaxcounterConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:PositionConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:PositionPrecisionPreference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:PowerConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onCancelClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onNegativeClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onPositiveClicked</ID>
|
||||
<ID>ParameterNaming:PreferenceFooter.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:RangeTestConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:RemoteHardwareConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:SerialConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:SlidingSelector.kt$onOptionSelected</ID>
|
||||
<ID>ParameterNaming:StoreForwardConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:TelemetryConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:UsbDevices.kt$onDeviceSelected</ID>
|
||||
<ID>ParameterNaming:UserConfigItemList.kt$onSaveClicked</ID>
|
||||
<ID>ParameterNaming:WelcomeScreen.kt$onGetStarted</ID>
|
||||
<ID>PreviewAnnotationNaming:LargeFontPreview.kt$LargeFontPreview$LargeFontPreview</ID>
|
||||
<ID>PreviewPublic:BatteryInfo.kt$BatteryInfoPreview</ID>
|
||||
@@ -596,19 +511,18 @@
|
||||
<ID>TooManyFunctions:SafeBluetooth.kt$SafeBluetooth : LoggingCloseable</ID>
|
||||
<ID>TooManyFunctions:UIState.kt$UIViewModel : ViewModelLogging</ID>
|
||||
<ID>TopLevelPropertyNaming:Constants.kt$const val prefix = "com.geeksville.mesh"</ID>
|
||||
<ID>UnusedParameter:ChannelSettingsItemList.kt$onBack: () -> Unit</ID>
|
||||
<ID>UnusedParameter:ChannelSettingsItemList.kt$title: String</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$MainAppBar( viewModel = uIViewModel, navController = navController, onAction = { action -> when (action) { is NodeMenuAction.MoreDetails -> { navController.navigate( NodesRoutes.NodeDetailGraph(action.node.num), { launchSingleTop = true restoreState = true }, ) } is NodeMenuAction.Share -> sharedContact = action.node else -> {} } }, )</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$NavGraph( modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(), uIViewModel = uIViewModel, bluetoothViewModel = bluetoothViewModel, navController = navController, )</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$ScannedQrCodeDialog(uIViewModel, newChannelSet)</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
|
||||
<ID>ViewModelForwarding:Message.kt$MessageList( modifier = Modifier.fillMaxSize(), listState = listState, messages = messages, selectedIds = selectedMessageIds, onUnreadChanged = { messageId -> onEvent(MessageScreenEvent.ClearUnreadCount(messageId)) }, onSendReaction = { emoji, id -> onEvent(MessageScreenEvent.SendReaction(emoji, id)) }, viewModel = viewModel, contactKey = contactKey, onReply = { message -> replyingToPacketId = message?.packetId }, onNodeMenuAction = { action -> onEvent(MessageScreenEvent.HandleNodeMenuAction(action)) }, )</ID>
|
||||
<ID>ViewModelForwarding:NodeDetail.kt$NodeDetailContent( node = node, ourNode = ourNode, metricsState = state, lastTracerouteTime = lastTracerouteTime, availableLogs = availableLogs, uiViewModel = uiViewModel, onAction = { action -> handleNodeAction( action = action, uiViewModel = uiViewModel, node = node, navigateToMessages = navigateToMessages, onNavigateUp = onNavigateUp, onNavigate = onNavigate, viewModel = viewModel, ) }, modifier = modifier, )</ID>
|
||||
<ID>ViewModelForwarding:NodeScreen.kt$AddContactFAB( modifier = Modifier.animateFloatingActionButton( visible = !isScrollInProgress && connectionState == ConnectionState.CONNECTED && shareCapable, alignment = Alignment.BottomEnd, ), model = model, onSharedContactImport = { contact -> model.addSharedContact(contact) }, )</ID>
|
||||
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>
|
||||
<ID>WildcardImport:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
<ID>Wrapping:DebugFilters.kt$(</ID>
|
||||
<ID>Wrapping:DebugFilters.kt$if (filter in filterTexts) { Icon( imageVector = Icons.Filled.Done, contentDescription = stringResource(id = R.string.debug_filter_included), ) }</ID>
|
||||
<ID>Wrapping:EditTextPreference.kt$;</ID>
|
||||
<ID>Wrapping:MQTTRepository.kt$MQTTRepository.<no name provided>$(</ID>
|
||||
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.HandleNodeMenuAction -> { when (val action = event.action) { is NodeMenuAction.DirectMessage -> { val hasPKC = ourNode?.hasPKC == true && action.node.hasPKC val targetChannel = if (hasPKC) { DataPacket.PKC_CHANNEL_INDEX } else { action.node.channel } navigateToMessages("$targetChannel${action.node.user.id}") } is NodeMenuAction.MoreDetails -> navigateToNodeDetails(action.node.num) is NodeMenuAction.Share -> sharedContact = action.node else -> viewModel.handleNodeMenuAction(action) } } is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
|
||||
<ID>Wrapping:SerialConnectionImpl.kt$SerialConnectionImpl$(</ID>
|
||||
@@ -47,6 +47,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
@@ -153,7 +154,7 @@ fun NavDestination.isConfigRoute(): Boolean =
|
||||
private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenComposable(
|
||||
navController: NavHostController,
|
||||
routeNameString: String,
|
||||
crossinline screenContent: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
crossinline screenContent: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
composable<R>(
|
||||
deepLinks =
|
||||
@@ -167,7 +168,7 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
|
||||
screenContent(viewModel)
|
||||
screenContent(navController, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,71 +307,71 @@ enum class ConfigRoute(
|
||||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
|
||||
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { nc, vm -> UserConfigScreen(nc, vm) }),
|
||||
CHANNELS(
|
||||
R.string.channels,
|
||||
SettingsRoutes.ChannelConfig,
|
||||
Icons.AutoMirrored.Default.List,
|
||||
0,
|
||||
{ vm -> ChannelConfigScreen(vm) },
|
||||
{ nc, vm -> ChannelConfigScreen(nc, vm) },
|
||||
),
|
||||
DEVICE(
|
||||
R.string.device,
|
||||
SettingsRoutes.Device,
|
||||
Icons.Default.Router,
|
||||
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
|
||||
{ vm -> DeviceConfigScreen(vm) },
|
||||
{ nc, vm -> DeviceConfigScreen(nc, vm) },
|
||||
),
|
||||
POSITION(
|
||||
R.string.position,
|
||||
SettingsRoutes.Position,
|
||||
Icons.Default.LocationOn,
|
||||
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
|
||||
{ vm -> PositionConfigScreen(vm) },
|
||||
{ nc, vm -> PositionConfigScreen(nc, vm) },
|
||||
),
|
||||
POWER(
|
||||
R.string.power,
|
||||
SettingsRoutes.Power,
|
||||
Icons.Default.Power,
|
||||
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
|
||||
{ vm -> PowerConfigScreen(vm) },
|
||||
{ nc, vm -> PowerConfigScreen(nc, vm) },
|
||||
),
|
||||
NETWORK(
|
||||
R.string.network,
|
||||
SettingsRoutes.Network,
|
||||
Icons.Default.Wifi,
|
||||
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
|
||||
{ vm -> NetworkConfigScreen(vm) },
|
||||
{ nc, vm -> NetworkConfigScreen(nc, vm) },
|
||||
),
|
||||
DISPLAY(
|
||||
R.string.display,
|
||||
SettingsRoutes.Display,
|
||||
Icons.Default.DisplaySettings,
|
||||
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
|
||||
{ vm -> DisplayConfigScreen(vm) },
|
||||
{ nc, vm -> DisplayConfigScreen(nc, vm) },
|
||||
),
|
||||
LORA(
|
||||
R.string.lora,
|
||||
SettingsRoutes.LoRa,
|
||||
Icons.Default.CellTower,
|
||||
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
|
||||
{ vm -> LoRaConfigScreen(vm) },
|
||||
{ nc, vm -> LoRaConfigScreen(nc, vm) },
|
||||
),
|
||||
BLUETOOTH(
|
||||
R.string.bluetooth,
|
||||
SettingsRoutes.Bluetooth,
|
||||
Icons.Default.Bluetooth,
|
||||
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
|
||||
{ vm -> BluetoothConfigScreen(vm) },
|
||||
{ nc, vm -> BluetoothConfigScreen(nc, vm) },
|
||||
),
|
||||
SECURITY(
|
||||
R.string.security,
|
||||
SettingsRoutes.Security,
|
||||
Icons.Default.Security,
|
||||
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
|
||||
{ vm -> SecurityConfigScreen(vm) },
|
||||
{ nc, vm -> SecurityConfigScreen(nc, vm) },
|
||||
),
|
||||
;
|
||||
|
||||
@@ -397,98 +398,98 @@ enum class ModuleRoute(
|
||||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
MQTT(
|
||||
R.string.mqtt,
|
||||
SettingsRoutes.MQTT,
|
||||
Icons.Default.Cloud,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
|
||||
{ vm -> MQTTConfigScreen(vm) },
|
||||
{ nc, vm -> MQTTConfigScreen(nc, vm) },
|
||||
),
|
||||
SERIAL(
|
||||
R.string.serial,
|
||||
SettingsRoutes.Serial,
|
||||
Icons.Default.Usb,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
|
||||
{ vm -> SerialConfigScreen(vm) },
|
||||
{ nc, vm -> SerialConfigScreen(nc, vm) },
|
||||
),
|
||||
EXT_NOTIFICATION(
|
||||
R.string.external_notification,
|
||||
SettingsRoutes.ExtNotification,
|
||||
Icons.Default.Notifications,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
|
||||
{ vm -> ExternalNotificationConfigScreen(vm) },
|
||||
{ nc, vm -> ExternalNotificationConfigScreen(nc, vm) },
|
||||
),
|
||||
STORE_FORWARD(
|
||||
R.string.store_forward,
|
||||
SettingsRoutes.StoreForward,
|
||||
Icons.AutoMirrored.Default.Forward,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
|
||||
{ vm -> StoreForwardConfigScreen(vm) },
|
||||
{ nc, vm -> StoreForwardConfigScreen(nc, vm) },
|
||||
),
|
||||
RANGE_TEST(
|
||||
R.string.range_test,
|
||||
SettingsRoutes.RangeTest,
|
||||
Icons.Default.Speed,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
|
||||
{ vm -> RangeTestConfigScreen(vm) },
|
||||
{ nc, vm -> RangeTestConfigScreen(nc, vm) },
|
||||
),
|
||||
TELEMETRY(
|
||||
R.string.telemetry,
|
||||
SettingsRoutes.Telemetry,
|
||||
Icons.Default.DataUsage,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
|
||||
{ vm -> TelemetryConfigScreen(vm) },
|
||||
{ nc, vm -> TelemetryConfigScreen(nc, vm) },
|
||||
),
|
||||
CANNED_MESSAGE(
|
||||
R.string.canned_message,
|
||||
SettingsRoutes.CannedMessage,
|
||||
Icons.AutoMirrored.Default.Message,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
|
||||
{ vm -> CannedMessageConfigScreen(vm) },
|
||||
{ nc, vm -> CannedMessageConfigScreen(nc, vm) },
|
||||
),
|
||||
AUDIO(
|
||||
R.string.audio,
|
||||
SettingsRoutes.Audio,
|
||||
Icons.AutoMirrored.Default.VolumeUp,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
|
||||
{ vm -> AudioConfigScreen(vm) },
|
||||
{ nc, vm -> AudioConfigScreen(nc, vm) },
|
||||
),
|
||||
REMOTE_HARDWARE(
|
||||
R.string.remote_hardware,
|
||||
SettingsRoutes.RemoteHardware,
|
||||
Icons.Default.SettingsRemote,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
|
||||
{ vm -> RemoteHardwareConfigScreen(vm) },
|
||||
{ nc, vm -> RemoteHardwareConfigScreen(nc, vm) },
|
||||
),
|
||||
NEIGHBOR_INFO(
|
||||
R.string.neighbor_info,
|
||||
SettingsRoutes.NeighborInfo,
|
||||
Icons.Default.People,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
|
||||
{ vm -> NeighborInfoConfigScreen(vm) },
|
||||
{ nc, vm -> NeighborInfoConfigScreen(nc, vm) },
|
||||
),
|
||||
AMBIENT_LIGHTING(
|
||||
R.string.ambient_lighting,
|
||||
SettingsRoutes.AmbientLighting,
|
||||
Icons.Default.LightMode,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
|
||||
{ vm -> AmbientLightingConfigScreen(vm) },
|
||||
{ nc, vm -> AmbientLightingConfigScreen(nc, vm) },
|
||||
),
|
||||
DETECTION_SENSOR(
|
||||
R.string.detection_sensor,
|
||||
SettingsRoutes.DetectionSensor,
|
||||
Icons.Default.Sensors,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
|
||||
{ vm -> DetectionSensorConfigScreen(vm) },
|
||||
{ nc, vm -> DetectionSensorConfigScreen(nc, vm) },
|
||||
),
|
||||
PAXCOUNTER(
|
||||
R.string.paxcounter,
|
||||
SettingsRoutes.Paxcounter,
|
||||
Icons.Default.PermScanWifi,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
|
||||
{ vm -> PaxcounterConfigScreen(vm) },
|
||||
{ nc, vm -> PaxcounterConfigScreen(nc, vm) },
|
||||
),
|
||||
;
|
||||
|
||||
|
||||
@@ -133,7 +133,6 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector,
|
||||
companion object {
|
||||
fun NavDestination.isTopLevel(): Boolean = listOf<KClass<out Route>>(
|
||||
ContactsRoutes.Contacts::class,
|
||||
NodesRoutes.Nodes::class,
|
||||
MapRoutes.Map::class,
|
||||
ConnectionsRoutes.Connections::class,
|
||||
)
|
||||
@@ -356,10 +355,34 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
||||
NodesRoutes.Nodes::class,
|
||||
NodesRoutes.NodeDetail::class,
|
||||
SettingsRoutes.Settings::class,
|
||||
SettingsRoutes.AmbientLighting::class,
|
||||
SettingsRoutes.LoRa::class,
|
||||
SettingsRoutes.Security::class,
|
||||
SettingsRoutes.Audio::class,
|
||||
SettingsRoutes.Bluetooth::class,
|
||||
SettingsRoutes.ChannelConfig::class,
|
||||
SettingsRoutes.DetectionSensor::class,
|
||||
SettingsRoutes.Display::class,
|
||||
SettingsRoutes.Telemetry::class,
|
||||
SettingsRoutes.Network::class,
|
||||
SettingsRoutes.Paxcounter::class,
|
||||
SettingsRoutes.Power::class,
|
||||
SettingsRoutes.Position::class,
|
||||
SettingsRoutes.User::class,
|
||||
SettingsRoutes.StoreForward::class,
|
||||
SettingsRoutes.MQTT::class,
|
||||
SettingsRoutes.Serial::class,
|
||||
SettingsRoutes.ExtNotification::class,
|
||||
SettingsRoutes.CleanNodeDb::class,
|
||||
SettingsRoutes.DebugPanel::class,
|
||||
SettingsRoutes.RangeTest::class,
|
||||
SettingsRoutes.CannedMessage::class,
|
||||
SettingsRoutes.RemoteHardware::class,
|
||||
SettingsRoutes.NeighborInfo::class,
|
||||
)
|
||||
.none { this.hasRoute(it) }
|
||||
|
||||
AnimatedVisibility(visible = currentDestination?.hasGlobalAppBar() ?: true) {
|
||||
AnimatedVisibility(visible = currentDestination?.hasGlobalAppBar() ?: false) {
|
||||
MainAppBar(
|
||||
viewModel = uIViewModel,
|
||||
navController = navController,
|
||||
|
||||
@@ -186,9 +186,16 @@ fun SettingsScreen(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = stringResource(R.string.bottom_nav_settings),
|
||||
subtitle =
|
||||
if (state.isLocal) {
|
||||
ourNode?.user?.longName
|
||||
} else {
|
||||
val remoteName = viewModel.destNode.value?.user?.longName ?: ""
|
||||
stringResource(R.string.remotely_administrating, remoteName)
|
||||
},
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = ourNode != null && isConnected,
|
||||
showNodeChip = ourNode != null && isConnected && state.isLocal,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
|
||||
@@ -17,67 +17,50 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val ambientLightingConfig = state.moduleConfig.ambientLighting
|
||||
val formState = rememberConfigState(initialValue = ambientLightingConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AmbientLightingConfigItemList(
|
||||
ambientLightingConfig = state.moduleConfig.ambientLighting,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.ambient_lighting),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { ambientLightingInput ->
|
||||
val config = moduleConfig { ambientLighting = ambientLightingInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { ambientLighting = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigItemList(
|
||||
ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var ambientLightingInput by rememberSaveable { mutableStateOf(ambientLightingConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ambient_lighting_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_state),
|
||||
checked = ambientLightingInput.ledState,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { ambientLightingInput = ambientLightingInput.copy { ledState = it } },
|
||||
checked = formState.value.ledState,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ledState = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,65 +68,41 @@ fun AmbientLightingConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.current),
|
||||
value = ambientLightingInput.current,
|
||||
enabled = enabled,
|
||||
value = formState.value.current,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { current = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { current = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.red),
|
||||
value = ambientLightingInput.red,
|
||||
enabled = enabled,
|
||||
value = formState.value.red,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { red = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.green),
|
||||
value = ambientLightingInput.green,
|
||||
enabled = enabled,
|
||||
value = formState.value.green,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { green = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.blue),
|
||||
value = ambientLightingInput.blue,
|
||||
enabled = enabled,
|
||||
value = formState.value.blue,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && ambientLightingInput != ambientLightingConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
ambientLightingInput = ambientLightingConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ambientLightingInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { blue = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AmbientLightingConfigPreview() {
|
||||
AmbientLightingConfigItemList(
|
||||
ambientLightingConfig = ModuleConfigProtos.ModuleConfig.AmbientLightingConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,66 +17,52 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.AudioConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val audioConfig = state.moduleConfig.audio
|
||||
val formState = rememberConfigState(initialValue = audioConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AudioConfigItemList(
|
||||
audioConfig = state.moduleConfig.audio,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.audio),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { audioInput ->
|
||||
val config = moduleConfig { audio = audioInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { audio = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var audioInput by rememberSaveable { mutableStateOf(audioConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.audio_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.codec_2_enabled),
|
||||
checked = audioInput.codec2Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } },
|
||||
checked = formState.value.codec2Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { codec2Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -84,85 +70,65 @@ fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicke
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ptt_pin),
|
||||
value = audioInput.pttPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.pttPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { pttPin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { pttPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.codec2_sample_rate),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
AudioConfig.Audio_Baud.entries
|
||||
.filter { it != AudioConfig.Audio_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = audioInput.bitrate,
|
||||
onItemSelected = { audioInput = audioInput.copy { bitrate = it } },
|
||||
selectedItem = formState.value.bitrate,
|
||||
onItemSelected = { formState.value = formState.value.copy { bitrate = it } },
|
||||
)
|
||||
}
|
||||
item { Divider() }
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_word_select),
|
||||
value = audioInput.i2SWs,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SWs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SWs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_data_in),
|
||||
value = audioInput.i2SSd,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SSd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SSd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_data_out),
|
||||
value = audioInput.i2SDin,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SDin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SDin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_clock),
|
||||
value = audioInput.i2SSck,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SSck,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && audioInput != audioConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
audioInput = audioConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(audioInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SSck = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AudioConfigPreview() {
|
||||
AudioConfigItemList(audioConfig = AudioConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,68 +17,52 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.BluetoothConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun BluetoothConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val bluetoothConfig = state.radioConfig.bluetooth
|
||||
val formState = rememberConfigState(initialValue = bluetoothConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
BluetoothConfigItemList(
|
||||
bluetoothConfig = state.radioConfig.bluetooth,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.bluetooth),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { bluetoothInput ->
|
||||
val config = config { bluetooth = bluetoothInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { bluetooth = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigItemList(
|
||||
bluetoothConfig: BluetoothConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (BluetoothConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var bluetoothInput by rememberSaveable { mutableStateOf(bluetoothConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.bluetooth_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.bluetooth_enabled),
|
||||
checked = bluetoothInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -86,13 +70,13 @@ fun BluetoothConfigItemList(
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.pairing_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
BluetoothConfig.PairingMode.entries
|
||||
.filter { it != BluetoothConfig.PairingMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = bluetoothInput.mode,
|
||||
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } },
|
||||
selectedItem = formState.value.mode,
|
||||
onItemSelected = { formState.value = formState.value.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -100,35 +84,15 @@ fun BluetoothConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.fixed_pin),
|
||||
value = bluetoothInput.fixedPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.fixedPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
if (it.toString().length == 6) { // ensure 6 digits
|
||||
bluetoothInput = bluetoothInput.copy { fixedPin = it }
|
||||
formState.value = formState.value.copy { fixedPin = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && bluetoothInput != bluetoothConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
bluetoothInput = bluetoothConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(bluetoothInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BluetoothConfigPreview() {
|
||||
BluetoothConfigItemList(bluetoothConfig = BluetoothConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -27,69 +25,57 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.CannedMessageConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val cannedMessageConfig = state.moduleConfig.cannedMessage
|
||||
val messages = state.cannedMessageMessages
|
||||
val formState = rememberConfigState(initialValue = cannedMessageConfig)
|
||||
var messagesInput by rememberSaveable(messages) { mutableStateOf(messages) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
CannedMessageConfigItemList(
|
||||
messages = state.cannedMessageMessages,
|
||||
cannedMessageConfig = state.moduleConfig.cannedMessage,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.canned_message),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { messagesInput, cannedMessageInput ->
|
||||
if (messagesInput != state.cannedMessageMessages) {
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (messagesInput != messages) {
|
||||
viewModel.setCannedMessages(messagesInput)
|
||||
}
|
||||
if (cannedMessageInput != state.moduleConfig.cannedMessage) {
|
||||
val config = moduleConfig { cannedMessage = cannedMessageInput }
|
||||
if (formState.value != cannedMessageConfig) {
|
||||
val config = moduleConfig { cannedMessage = formState.value }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigItemList(
|
||||
messages: String,
|
||||
cannedMessageConfig: CannedMessageConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (messages: String, config: CannedMessageConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var messagesInput by rememberSaveable { mutableStateOf(messages) }
|
||||
var cannedMessageInput by rememberSaveable { mutableStateOf(cannedMessageConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.canned_message_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.canned_message_enabled),
|
||||
checked = cannedMessageInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -97,9 +83,9 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.rotary_encoder_1_enabled),
|
||||
checked = cannedMessageInput.rotary1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } },
|
||||
checked = formState.value.rotary1Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { rotary1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -107,43 +93,43 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_a_port),
|
||||
value = cannedMessageInput.inputbrokerPinA,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinA,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinA = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_b_port),
|
||||
value = cannedMessageInput.inputbrokerPinB,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinB,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinB = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_press_port),
|
||||
value = cannedMessageInput.inputbrokerPinPress,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinPress,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinPress = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_press),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventPress,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } },
|
||||
selectedItem = formState.value.inputbrokerEventPress,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -151,13 +137,13 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_cw),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCw,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } },
|
||||
selectedItem = formState.value.inputbrokerEventCw,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventCw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -165,13 +151,13 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_ccw),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCcw,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } },
|
||||
selectedItem = formState.value.inputbrokerEventCcw,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventCcw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -179,9 +165,9 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.up_down_select_input_enabled),
|
||||
checked = cannedMessageInput.updown1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } },
|
||||
checked = formState.value.updown1Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { updown1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -189,23 +175,23 @@ fun CannedMessageConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.allow_input_source),
|
||||
value = cannedMessageInput.allowInputSource,
|
||||
value = formState.value.allowInputSource,
|
||||
maxSize = 63, // allow_input_source max_size:16
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { allowInputSource = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.send_bell),
|
||||
checked = cannedMessageInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { sendBell = it } },
|
||||
checked = formState.value.sendBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -215,7 +201,7 @@ fun CannedMessageConfigItemList(
|
||||
title = stringResource(R.string.messages),
|
||||
value = messagesInput,
|
||||
maxSize = 200, // messages max_size:201
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
@@ -223,31 +209,5 @@ fun CannedMessageConfigItemList(
|
||||
onValueChanged = { messagesInput = it },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && cannedMessageInput != cannedMessageConfig || messagesInput != messages,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
messagesInput = messages
|
||||
cannedMessageInput = cannedMessageConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(messagesInput, cannedMessageInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CannedMessageConfigPreview() {
|
||||
CannedMessageConfigItemList(
|
||||
messages = "",
|
||||
cannedMessageConfig = CannedMessageConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -68,6 +69,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.channelSettings
|
||||
@@ -168,7 +170,7 @@ fun ChannelSelection(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun ChannelConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
@@ -176,6 +178,8 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
}
|
||||
|
||||
ChannelSettingsItemList(
|
||||
title = stringResource(id = R.string.channels),
|
||||
onBack = { navController.popBackStack() },
|
||||
settingsList = state.channelList,
|
||||
loraConfig = state.radioConfig.lora,
|
||||
maxChannels = viewModel.maxChannels,
|
||||
@@ -188,6 +192,8 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun ChannelSettingsItemList(
|
||||
title: String,
|
||||
onBack: () -> Unit,
|
||||
settingsList: List<ChannelSettings>,
|
||||
loraConfig: LoRaConfig,
|
||||
maxChannels: Int = 8,
|
||||
@@ -243,104 +249,116 @@ private fun ChannelSettingsItemList(
|
||||
ChannelLegendDialog(fwVersion) { showChannelLegendDialog = false }
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().clickable(onClick = {}, enabled = false)) {
|
||||
Column {
|
||||
ChannelsConfigHeader(
|
||||
frequency =
|
||||
if (loraConfig.overrideFrequency != 0f) {
|
||||
loraConfig.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
slot =
|
||||
if (loraConfig.channelNum != 0) {
|
||||
loraConfig.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.press_and_drag),
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
|
||||
ChannelLegend { showChannelLegendDialog = true }
|
||||
|
||||
val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList())
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
dragDropItemsIndexed(items = settingsListInput, dragDropState = dragDropState) {
|
||||
index,
|
||||
channel,
|
||||
isDragging,
|
||||
->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
channelSettings = channel,
|
||||
loraConfig = loraConfig,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) },
|
||||
sharesLocation = locationChannel == index,
|
||||
)
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
},
|
||||
)
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
settingsListInput.add(channelSettings { psk = Channel.default.settings.psk })
|
||||
showEditChannelDialog = settingsListInput.lastIndex
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(Icons.TwoTone.Add, stringResource(R.string.add))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
Column {
|
||||
ChannelsConfigHeader(
|
||||
frequency =
|
||||
if (loraConfig.overrideFrequency != 0f) {
|
||||
loraConfig.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
slot =
|
||||
if (loraConfig.channelNum != 0) {
|
||||
loraConfig.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.press_and_drag),
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = maxChannels > settingsListInput.size,
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
enter =
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
exit =
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
settingsListInput.add(channelSettings { psk = Channel.default.settings.psk })
|
||||
showEditChannelDialog = settingsListInput.lastIndex
|
||||
ChannelLegend { showChannelLegendDialog = true }
|
||||
|
||||
val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList())
|
||||
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
dragDropItemsIndexed(items = settingsListInput, dragDropState = dragDropState) {
|
||||
index,
|
||||
channel,
|
||||
isDragging,
|
||||
->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
channelSettings = channel,
|
||||
loraConfig = loraConfig,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) },
|
||||
sharesLocation = locationChannel == index,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(Icons.TwoTone.Add, stringResource(R.string.add))
|
||||
item { Spacer(modifier = Modifier.weight(1f)) }
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = maxChannels > settingsListInput.size,
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
enter =
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
exit =
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelsConfigHeader(frequency: Float, slot: Int) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PreferenceCategory(text = stringResource(R.string.channels))
|
||||
Column {
|
||||
Text(text = "${stringResource(R.string.freq)}: ${frequency}MHz", fontSize = 11.sp)
|
||||
@@ -380,6 +398,8 @@ private fun determineLocationSharingChannel(firmwareVersion: DeviceVersion, sett
|
||||
@Composable
|
||||
private fun ChannelSettingsPreview() {
|
||||
ChannelSettingsItemList(
|
||||
title = "Channels",
|
||||
onBack = {},
|
||||
settingsList =
|
||||
listOf(
|
||||
channelSettings {
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.google.protobuf.MessageLite
|
||||
|
||||
/**
|
||||
* A state holder for managing config data within a Composable.
|
||||
*
|
||||
* This class encapsulates the common logic for handling editable state that is derived from an initial value. It tracks
|
||||
* whether the current value has been modified ("dirty"), and provides simple methods to save the changes or reset to
|
||||
* the initial state.
|
||||
*
|
||||
* @param T The type of the data being managed, typically a Protobuf message.
|
||||
* @property initialValue The original, unmodified value of the config data.
|
||||
*/
|
||||
class ConfigState<T : MessageLite>(private val initialValue: T) {
|
||||
var value by mutableStateOf(initialValue)
|
||||
|
||||
val isDirty: Boolean
|
||||
get() = value != initialValue
|
||||
|
||||
fun reset() {
|
||||
value = initialValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : MessageLite> saver(initialValue: T): Saver<ConfigState<T>, ByteArray> = Saver(
|
||||
save = { it.value.toByteArray() },
|
||||
restore = {
|
||||
ConfigState(initialValue).apply {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
value = initialValue.parserForType.parseFrom(it) as T
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and remembers a [ConfigState] instance, correctly handling process death and recomposition. When the
|
||||
* `initialValue` changes, the config state will be reset.
|
||||
*
|
||||
* @param initialValue The initial value to populate the config with. The config will be reset if this value changes
|
||||
* across recompositions.
|
||||
*/
|
||||
@Composable
|
||||
fun <T : MessageLite> rememberConfigState(initialValue: T): ConfigState<T> =
|
||||
rememberSaveable(initialValue, saver = ConfigState.saver(initialValue)) { ConfigState(initialValue) }
|
||||
@@ -17,72 +17,55 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val detectionSensorConfig = state.moduleConfig.detectionSensor
|
||||
val formState = rememberConfigState(initialValue = detectionSensorConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DetectionSensorConfigItemList(
|
||||
detectionSensorConfig = state.moduleConfig.detectionSensor,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.detection_sensor),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { detectionSensorInput ->
|
||||
val config = moduleConfig { detectionSensor = detectionSensorInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { detectionSensor = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DetectionSensorConfigItemList(
|
||||
detectionSensorConfig: ModuleConfig.DetectionSensorConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfig.DetectionSensorConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var detectionSensorInput by rememberSaveable { mutableStateOf(detectionSensorConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.detection_sensor_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.detection_sensor_enabled),
|
||||
checked = detectionSensorInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -90,29 +73,29 @@ fun DetectionSensorConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_broadcast_seconds),
|
||||
value = detectionSensorInput.minimumBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.minimumBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { minimumBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.state_broadcast_seconds),
|
||||
value = detectionSensorInput.stateBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.stateBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { stateBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.send_bell_with_alert_message),
|
||||
checked = detectionSensorInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { sendBell = it } },
|
||||
checked = formState.value.sendBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -120,37 +103,37 @@ fun DetectionSensorConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.friendly_name),
|
||||
value = detectionSensorInput.name,
|
||||
value = formState.value.name,
|
||||
maxSize = 19, // name max_size:20
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { name = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { name = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_to_monitor),
|
||||
value = detectionSensorInput.monitorPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.monitorPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { monitorPin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { monitorPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.detection_trigger_type),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
ModuleConfig.DetectionSensorConfig.TriggerType.entries
|
||||
.filter { it != ModuleConfig.DetectionSensorConfig.TriggerType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = detectionSensorInput.detectionTriggerType,
|
||||
onItemSelected = { detectionSensorInput = detectionSensorInput.copy { detectionTriggerType = it } },
|
||||
selectedItem = formState.value.detectionTriggerType,
|
||||
onItemSelected = { formState.value = formState.value.copy { detectionTriggerType = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -158,35 +141,11 @@ fun DetectionSensorConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_input_pullup_mode),
|
||||
checked = detectionSensorInput.usePullup,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { usePullup = it } },
|
||||
checked = formState.value.usePullup,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePullup = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && detectionSensorInput != detectionSensorConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
detectionSensorInput = detectionSensorConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(detectionSensorInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DetectionSensorConfigPreview() {
|
||||
DetectionSensorConfigItemList(
|
||||
detectionSensorConfig = ModuleConfig.DetectionSensorConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@ package com.geeksville.mesh.ui.settings.radio.components
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -46,16 +44,15 @@ import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
@@ -91,24 +88,135 @@ private val DeviceConfig.RebroadcastMode.description: Int
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
val deviceConfig = state.radioConfig.device
|
||||
val formState = rememberConfigState(initialValue = deviceConfig)
|
||||
var selectedRole by rememberSaveable { mutableStateOf(formState.value.role) }
|
||||
val infrastructureRoles = listOf(DeviceConfig.Role.ROUTER, DeviceConfig.Role.REPEATER)
|
||||
if (selectedRole != formState.value.role) {
|
||||
if (selectedRole in infrastructureRoles) {
|
||||
RouterRoleConfirmationDialog(
|
||||
onDismiss = { selectedRole = formState.value.role },
|
||||
onConfirm = { formState.value = formState.value.copy { role = selectedRole } },
|
||||
)
|
||||
} else {
|
||||
formState.value = formState.value.copy { role = selectedRole }
|
||||
}
|
||||
}
|
||||
|
||||
DeviceConfigItemList(
|
||||
deviceConfig = state.radioConfig.device,
|
||||
val focusManager = LocalFocusManager.current
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.device),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { deviceInput ->
|
||||
val config = config { device = deviceInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { device = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.role),
|
||||
enabled = state.connected,
|
||||
selectedItem = formState.value.role,
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = formState.value.role.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.rebroadcast_mode),
|
||||
enabled = state.connected,
|
||||
selectedItem = formState.value.rebroadcastMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { rebroadcastMode = it } },
|
||||
summary = stringResource(id = formState.value.rebroadcastMode.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval),
|
||||
value = formState.value.nodeInfoBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.hardware)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.double_tap_as_button_press),
|
||||
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
|
||||
checked = formState.value.doubleTapAsButtonPress,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.triple_click_adhoc_ping),
|
||||
summary = stringResource(id = R.string.config_device_tripleClickAsAdHocPing_summary),
|
||||
checked = !formState.value.disableTripleClick,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { disableTripleClick = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatEnabled_summary),
|
||||
checked = !formState.value.ledHeartbeatDisabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ledHeartbeatDisabled = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.debug)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.time_zone),
|
||||
value = formState.value.tzdef,
|
||||
summary = stringResource(id = R.string.config_device_tzdef_summary),
|
||||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { tzdef = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.gpio)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.button_gpio),
|
||||
value = formState.value.buttonGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.buzzer_gpio),
|
||||
value = formState.value.buzzerGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
||||
val dialogTitle = stringResource(R.string.are_you_sure)
|
||||
@@ -141,140 +249,3 @@ fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var deviceInput by rememberSaveable { mutableStateOf(deviceConfig) }
|
||||
var selectedRole by rememberSaveable { mutableStateOf(deviceInput.role) }
|
||||
val infrastructureRoles = listOf(DeviceConfig.Role.ROUTER, DeviceConfig.Role.REPEATER)
|
||||
if (selectedRole != deviceInput.role) {
|
||||
if (selectedRole in infrastructureRoles) {
|
||||
RouterRoleConfirmationDialog(
|
||||
onDismiss = { selectedRole = deviceInput.role },
|
||||
onConfirm = { deviceInput = deviceInput.copy { role = selectedRole } },
|
||||
)
|
||||
} else {
|
||||
deviceInput = deviceInput.copy { role = selectedRole }
|
||||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.role),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.role,
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = deviceInput.role.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.rebroadcast_mode),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.rebroadcastMode,
|
||||
onItemSelected = { deviceInput = deviceInput.copy { rebroadcastMode = it } },
|
||||
summary = stringResource(id = deviceInput.rebroadcastMode.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval),
|
||||
value = deviceInput.nodeInfoBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.hardware)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.double_tap_as_button_press),
|
||||
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
|
||||
checked = deviceInput.doubleTapAsButtonPress,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.triple_click_adhoc_ping),
|
||||
summary = stringResource(id = R.string.config_device_tripleClickAsAdHocPing_summary),
|
||||
checked = !deviceInput.disableTripleClick,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatEnabled_summary),
|
||||
checked = !deviceInput.ledHeartbeatDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.debug)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.time_zone),
|
||||
value = deviceInput.tzdef,
|
||||
summary = stringResource(id = R.string.config_device_tzdef_summary),
|
||||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { tzdef = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.gpio)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.button_gpio),
|
||||
value = deviceInput.buttonGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.buzzer_gpio),
|
||||
value = deviceInput.buzzerGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && deviceInput != deviceConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
deviceInput = deviceConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(deviceInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DeviceConfigPreview() {
|
||||
DeviceConfigItemList(deviceConfig = DeviceConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,65 +17,52 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val displayConfig = state.radioConfig.display
|
||||
val formState = rememberConfigState(initialValue = displayConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DisplayConfigItemList(
|
||||
displayConfig = state.radioConfig.display,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.display),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { displayInput ->
|
||||
val config = config { display = displayInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { display = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSaveClicked: (DisplayConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var displayInput by rememberSaveable { mutableStateOf(displayConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.display_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.always_point_north),
|
||||
summary = stringResource(id = R.string.config_display_compass_north_top_summary),
|
||||
checked = displayInput.compassNorthTop,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } },
|
||||
checked = formState.value.compassNorthTop,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { compassNorthTop = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -83,9 +70,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_12h_format),
|
||||
summary = stringResource(R.string.display_time_in_12h_format),
|
||||
enabled = enabled,
|
||||
checked = displayInput.use12HClock,
|
||||
onCheckedChange = { displayInput = displayInput.copy { use12HClock = it } },
|
||||
enabled = state.connected,
|
||||
checked = formState.value.use12HClock,
|
||||
onCheckedChange = { formState.value = formState.value.copy { use12HClock = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -93,9 +80,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.bold_heading),
|
||||
summary = stringResource(id = R.string.config_display_heading_bold_summary),
|
||||
checked = displayInput.headingBold,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { headingBold = it } },
|
||||
checked = formState.value.headingBold,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { headingBold = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -103,13 +90,13 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.display_units),
|
||||
summary = stringResource(id = R.string.config_display_units_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.DisplayUnits.entries
|
||||
.filter { it != DisplayConfig.DisplayUnits.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.units,
|
||||
onItemSelected = { displayInput = displayInput.copy { units = it } },
|
||||
selectedItem = formState.value.units,
|
||||
onItemSelected = { formState.value = formState.value.copy { units = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -119,10 +106,10 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.screen_on_for),
|
||||
summary = stringResource(id = R.string.config_display_screen_on_secs_summary),
|
||||
value = displayInput.screenOnSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.screenOnSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { screenOnSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -131,10 +118,10 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.carousel_interval),
|
||||
summary = stringResource(id = R.string.config_display_auto_screen_carousel_secs_summary),
|
||||
value = displayInput.autoScreenCarouselSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.autoScreenCarouselSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { autoScreenCarouselSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { autoScreenCarouselSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -142,9 +129,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wake_on_tap_or_motion),
|
||||
summary = stringResource(id = R.string.config_display_wake_on_tap_or_motion_summary),
|
||||
checked = displayInput.wakeOnTapOrMotion,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } },
|
||||
checked = formState.value.wakeOnTapOrMotion,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { wakeOnTapOrMotion = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -152,9 +139,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.flip_screen),
|
||||
summary = stringResource(id = R.string.config_display_flip_screen_summary),
|
||||
checked = displayInput.flipScreen,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } },
|
||||
checked = formState.value.flipScreen,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { flipScreen = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -162,13 +149,13 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.display_mode),
|
||||
summary = stringResource(id = R.string.config_display_displaymode_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.DisplayMode.entries
|
||||
.filter { it != DisplayConfig.DisplayMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.displaymode,
|
||||
onItemSelected = { displayInput = displayInput.copy { displaymode = it } },
|
||||
selectedItem = formState.value.displaymode,
|
||||
onItemSelected = { formState.value = formState.value.copy { displaymode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -176,48 +163,28 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.oled_type),
|
||||
summary = stringResource(id = R.string.config_display_oled_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.OledType.entries
|
||||
.filter { it != DisplayConfig.OledType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.oled,
|
||||
onItemSelected = { displayInput = displayInput.copy { oled = it } },
|
||||
selectedItem = formState.value.oled,
|
||||
onItemSelected = { formState.value = formState.value.copy { oled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.compass_orientation),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.CompassOrientation.entries
|
||||
.filter { it != DisplayConfig.CompassOrientation.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.compassOrientation,
|
||||
onItemSelected = { displayInput = displayInput.copy { compassOrientation = it } },
|
||||
selectedItem = formState.value.compassOrientation,
|
||||
onItemSelected = { formState.value = formState.value.copy { compassOrientation = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && displayInput != displayConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
displayInput = displayConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(displayInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DisplayConfigPreview() {
|
||||
DisplayConfigItemList(displayConfig = DisplayConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
@@ -27,80 +25,69 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.ExternalNotificationConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.common.components.TextDividerPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val extNotificationConfig = state.moduleConfig.externalNotification
|
||||
val ringtone = state.ringtone
|
||||
val formState = rememberConfigState(initialValue = extNotificationConfig)
|
||||
var ringtoneInput by rememberSaveable(ringtone) { mutableStateOf(ringtone) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
ExternalNotificationConfigItemList(
|
||||
ringtone = state.ringtone,
|
||||
extNotificationConfig = state.moduleConfig.externalNotification,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.external_notification),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { ringtoneInput, extNotificationInput ->
|
||||
if (ringtoneInput != state.ringtone) {
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (ringtoneInput != ringtone) {
|
||||
viewModel.setRingtone(ringtoneInput)
|
||||
}
|
||||
if (extNotificationInput != state.moduleConfig.externalNotification) {
|
||||
val config = moduleConfig { externalNotification = extNotificationInput }
|
||||
if (formState.value != extNotificationConfig) {
|
||||
val config = moduleConfig { externalNotification = formState.value }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigItemList(
|
||||
ringtone: String,
|
||||
extNotificationConfig: ExternalNotificationConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ringtone: String, config: ExternalNotificationConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var ringtoneInput by rememberSaveable { mutableStateOf(ringtone) }
|
||||
var externalNotificationInput by rememberSaveable { mutableStateOf(extNotificationConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.external_notification_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.external_notification_enabled),
|
||||
checked = externalNotificationInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { TextDividerPreference(stringResource(R.string.notifications_on_message_receipt), enabled = enabled) }
|
||||
item {
|
||||
TextDividerPreference(stringResource(R.string.notifications_on_message_receipt), enabled = state.connected)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_led),
|
||||
checked = externalNotificationInput.alertMessage,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertMessage = it } },
|
||||
checked = formState.value.alertMessage,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessage = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -108,11 +95,9 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_buzzer),
|
||||
checked = externalNotificationInput.alertMessageBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageBuzzer = it }
|
||||
},
|
||||
checked = formState.value.alertMessageBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessageBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -120,22 +105,25 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_vibra),
|
||||
checked = externalNotificationInput.alertMessageVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageVibra = it }
|
||||
},
|
||||
checked = formState.value.alertMessageVibra,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessageVibra = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { TextDividerPreference(stringResource(R.string.notifications_on_alert_bell_receipt), enabled = enabled) }
|
||||
item {
|
||||
TextDividerPreference(
|
||||
stringResource(R.string.notifications_on_alert_bell_receipt),
|
||||
enabled = state.connected,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_led),
|
||||
checked = externalNotificationInput.alertBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertBell = it } },
|
||||
checked = formState.value.alertBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -143,11 +131,9 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_buzzer),
|
||||
checked = externalNotificationInput.alertBellBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellBuzzer = it }
|
||||
},
|
||||
checked = formState.value.alertBellBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBellBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -155,11 +141,9 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_vibra),
|
||||
checked = externalNotificationInput.alertBellVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellVibra = it }
|
||||
},
|
||||
checked = formState.value.alertBellVibra,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBellVibra = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -167,20 +151,20 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_led_gpio),
|
||||
value = externalNotificationInput.output,
|
||||
enabled = enabled,
|
||||
value = formState.value.output,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { output = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { output = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.output != 0) {
|
||||
if (formState.value.output != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.output_led_active_high),
|
||||
checked = externalNotificationInput.active,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { active = it } },
|
||||
checked = formState.value.active,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { active = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -189,20 +173,20 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_buzzer_gpio),
|
||||
value = externalNotificationInput.outputBuzzer,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputBuzzer,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputBuzzer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.outputBuzzer != 0) {
|
||||
if (formState.value.outputBuzzer != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_pwm_buzzer),
|
||||
checked = externalNotificationInput.usePwm,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { usePwm = it } },
|
||||
checked = formState.value.usePwm,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePwm = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -211,30 +195,30 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_vibra_gpio),
|
||||
value = externalNotificationInput.outputVibra,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputVibra,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputVibra = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputVibra = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_duration_milliseconds),
|
||||
value = externalNotificationInput.outputMs,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputMs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputMs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputMs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nag_timeout_seconds),
|
||||
value = externalNotificationInput.nagTimeout,
|
||||
enabled = enabled,
|
||||
value = formState.value.nagTimeout,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { nagTimeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,7 +227,7 @@ fun ExternalNotificationConfigItemList(
|
||||
title = stringResource(R.string.ringtone),
|
||||
value = ringtoneInput,
|
||||
maxSize = 230, // ringtone max_size:231
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
@@ -255,39 +239,11 @@ fun ExternalNotificationConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_i2s_as_buzzer),
|
||||
checked = externalNotificationInput.useI2SAsBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { useI2SAsBuzzer = it }
|
||||
},
|
||||
checked = formState.value.useI2SAsBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { useI2SAsBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && externalNotificationInput != extNotificationConfig || ringtoneInput != ringtone,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
ringtoneInput = ringtone
|
||||
externalNotificationInput = extNotificationConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ringtoneInput, externalNotificationInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ExternalNotificationConfigPreview() {
|
||||
ExternalNotificationConfigItemList(
|
||||
ringtone = "",
|
||||
extNotificationConfig = ExternalNotificationConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,30 +17,24 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
@@ -50,73 +44,61 @@ import org.meshtastic.core.model.numChannels
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun LoRaConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val loraConfig = state.radioConfig.lora
|
||||
val primarySettings = state.channelList.getOrNull(0) ?: return
|
||||
val formState = rememberConfigState(initialValue = loraConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
val primaryChannel by remember(formState.value) { mutableStateOf(Channel(primarySettings, formState.value)) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LoRaConfigItemList(
|
||||
loraConfig = state.radioConfig.lora,
|
||||
primarySettings = state.channelList.getOrNull(0) ?: return,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.lora),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { loraInput ->
|
||||
val config = config { lora = loraInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { lora = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
hasPaFan = viewModel.hasPaFan,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun LoRaConfigItemList(
|
||||
loraConfig: LoRaConfig,
|
||||
primarySettings: ChannelSettings,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (LoRaConfig) -> Unit,
|
||||
hasPaFan: Boolean = false,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var loraInput by rememberSaveable { mutableStateOf(loraConfig) }
|
||||
val primaryChannel by remember(loraInput) { mutableStateOf(Channel(primarySettings, loraInput)) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
summary = stringResource(id = R.string.config_lora_region_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } },
|
||||
selectedItem = formState.value.region,
|
||||
onItemSelected = { formState.value = formState.value.copy { region = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_modem_preset),
|
||||
checked = loraInput.usePreset,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } },
|
||||
checked = formState.value.usePreset,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (loraInput.usePreset) {
|
||||
if (formState.value.usePreset) {
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
summary = stringResource(id = R.string.config_lora_modem_preset_summary),
|
||||
enabled = enabled && loraInput.usePreset,
|
||||
enabled = state.connected && formState.value.usePreset,
|
||||
items =
|
||||
LoRaConfig.ModemPreset.entries
|
||||
.filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = loraInput.modemPreset,
|
||||
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } },
|
||||
selectedItem = formState.value.modemPreset,
|
||||
onItemSelected = { formState.value = formState.value.copy { modemPreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -124,30 +106,30 @@ fun LoRaConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.bandwidth),
|
||||
value = loraInput.bandwidth,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.bandwidth,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { bandwidth = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.spread_factor),
|
||||
value = loraInput.spreadFactor,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.spreadFactor,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { spreadFactor = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.coding_rate),
|
||||
value = loraInput.codingRate,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.codingRate,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { codingRate = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { codingRate = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -156,18 +138,18 @@ fun LoRaConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = loraInput.ignoreMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
|
||||
checked = formState.value.ignoreMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = loraInput.configOkToMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
|
||||
checked = formState.value.configOkToMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -175,9 +157,9 @@ fun LoRaConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tx_enabled),
|
||||
checked = loraInput.txEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } },
|
||||
checked = formState.value.txEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { txEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -185,10 +167,10 @@ fun LoRaConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
summary = stringResource(id = R.string.config_lora_hop_limit_summary),
|
||||
value = loraInput.hopLimit,
|
||||
enabled = enabled,
|
||||
value = formState.value.hopLimit,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { hopLimit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -198,13 +180,18 @@ fun LoRaConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_slot),
|
||||
summary = stringResource(id = R.string.config_lora_frequency_slot_summary),
|
||||
value = if (isFocused || loraInput.channelNum != 0) loraInput.channelNum else primaryChannel.channelNum,
|
||||
enabled = enabled,
|
||||
value =
|
||||
if (isFocused || formState.value.channelNum != 0) {
|
||||
formState.value.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = {
|
||||
if (it <= loraInput.numChannels) { // total num of LoRa channels
|
||||
loraInput = loraInput.copy { channelNum = it }
|
||||
if (it <= formState.value.numChannels) { // total num of LoRa channels
|
||||
formState.value = formState.value.copy { channelNum = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -213,9 +200,9 @@ fun LoRaConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
checked = loraInput.sx126XRxBoostedGain,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } },
|
||||
checked = formState.value.sx126XRxBoostedGain,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sx126XRxBoostedGain = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -224,63 +211,38 @@ fun LoRaConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.override_frequency_mhz),
|
||||
value =
|
||||
if (isFocused || loraInput.overrideFrequency != 0f) {
|
||||
loraInput.overrideFrequency
|
||||
if (isFocused || formState.value.overrideFrequency != 0f) {
|
||||
formState.value.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { overrideFrequency = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = loraInput.txPower,
|
||||
enabled = enabled,
|
||||
value = formState.value.txPower,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { txPower = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txPower = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (hasPaFan) {
|
||||
if (viewModel.hasPaFan) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.pa_fan_disabled),
|
||||
checked = loraInput.paFanDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } },
|
||||
checked = formState.value.paFanDisabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { paFanDisabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && loraInput != loraConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
loraInput = loraConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(loraInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LoRaConfigPreview() {
|
||||
LoRaConfigItemList(
|
||||
loraConfig = Channel.default.loraConfig,
|
||||
primarySettings = Channel.default.settings,
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,83 +19,72 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.MQTTConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditPasswordPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun MQTTConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val destNode by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
val destNum = destNode?.num
|
||||
val mqttConfig = state.moduleConfig.mqtt
|
||||
val formState = rememberConfigState(initialValue = mqttConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
if (!formState.value.mapReportSettings.shouldReportLocation) {
|
||||
val settings =
|
||||
formState.value.mapReportSettings.copy {
|
||||
this.shouldReportLocation = viewModel.shouldReportLocation(destNum)
|
||||
}
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
}
|
||||
|
||||
MQTTConfigItemList(
|
||||
mqttConfig = state.moduleConfig.mqtt,
|
||||
enabled = state.connected,
|
||||
shouldReportLocation = viewModel.shouldReportLocation(destNum),
|
||||
onShouldReportLocationChanged = { shouldReportLocation ->
|
||||
viewModel.setShouldReportLocation(destNum, shouldReportLocation)
|
||||
},
|
||||
onSaveClicked = { mqttInput ->
|
||||
val config = moduleConfig { mqtt = mqttInput }
|
||||
val consentValid =
|
||||
if (formState.value.mapReportingEnabled) {
|
||||
formState.value.mapReportSettings.shouldReportLocation &&
|
||||
mqttConfig.mapReportSettings.publishIntervalSecs >= MIN_INTERVAL_SECS
|
||||
} else {
|
||||
true
|
||||
}
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.mqtt),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected && consentValid,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { mqtt = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigItemList(
|
||||
mqttConfig: MQTTConfig,
|
||||
enabled: Boolean,
|
||||
shouldReportLocation: Boolean,
|
||||
onShouldReportLocationChanged: (shouldReportLocation: Boolean) -> Unit,
|
||||
onSaveClicked: (MQTTConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var mqttInput by rememberSaveable { mutableStateOf(mqttConfig) }
|
||||
if (!mqttInput.mapReportSettings.shouldReportLocation) {
|
||||
val settings = mqttInput.mapReportSettings.copy { this.shouldReportLocation = shouldReportLocation }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.mqtt_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.mqtt_enabled),
|
||||
checked = mqttInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -103,48 +92,48 @@ fun MQTTConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.address),
|
||||
value = mqttInput.address,
|
||||
value = formState.value.address,
|
||||
maxSize = 63, // address max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { address = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { address = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.username),
|
||||
value = mqttInput.username,
|
||||
value = formState.value.username,
|
||||
maxSize = 63, // username max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { username = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { username = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.password),
|
||||
value = mqttInput.password,
|
||||
value = formState.value.password,
|
||||
maxSize = 63, // password max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { password = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { password = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.encryption_enabled),
|
||||
checked = mqttInput.encryptionEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { encryptionEnabled = it } },
|
||||
checked = formState.value.encryptionEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { encryptionEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -152,22 +141,22 @@ fun MQTTConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.json_output_enabled),
|
||||
checked = mqttInput.jsonEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { jsonEnabled = it } },
|
||||
checked = formState.value.jsonEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { jsonEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
val defaultAddress = stringResource(R.string.default_mqtt_address)
|
||||
val isDefault = mqttInput.address.isEmpty() || mqttInput.address.contains(defaultAddress)
|
||||
val enforceTls = isDefault && mqttInput.proxyToClientEnabled
|
||||
val isDefault = formState.value.address.isEmpty() || formState.value.address.contains(defaultAddress)
|
||||
val enforceTls = isDefault && formState.value.proxyToClientEnabled
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tls_enabled),
|
||||
checked = mqttInput.tlsEnabled || enforceTls,
|
||||
enabled = enabled && !enforceTls,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { tlsEnabled = it } },
|
||||
checked = formState.value.tlsEnabled || enforceTls,
|
||||
enabled = state.connected && !enforceTls,
|
||||
onCheckedChange = { formState.value = formState.value.copy { tlsEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -175,23 +164,23 @@ fun MQTTConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.root_topic),
|
||||
value = mqttInput.root,
|
||||
value = formState.value.root,
|
||||
maxSize = 31, // root max_size:32
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { root = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { root = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.proxy_to_client_enabled),
|
||||
checked = mqttInput.proxyToClientEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { proxyToClientEnabled = it } },
|
||||
checked = formState.value.proxyToClientEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { proxyToClientEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -200,63 +189,30 @@ fun MQTTConfigItemList(
|
||||
|
||||
item {
|
||||
MapReportingPreference(
|
||||
mapReportingEnabled = mqttInput.mapReportingEnabled,
|
||||
onMapReportingEnabledChanged = { mqttInput = mqttInput.copy { mapReportingEnabled = it } },
|
||||
shouldReportLocation = mqttInput.mapReportSettings.shouldReportLocation,
|
||||
mapReportingEnabled = formState.value.mapReportingEnabled,
|
||||
onMapReportingEnabledChanged = { formState.value = formState.value.copy { mapReportingEnabled = it } },
|
||||
shouldReportLocation = formState.value.mapReportSettings.shouldReportLocation,
|
||||
onShouldReportLocationChanged = {
|
||||
onShouldReportLocationChanged(it)
|
||||
val settings = mqttInput.mapReportSettings.copy { this.shouldReportLocation = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
viewModel.setShouldReportLocation(destNum, it)
|
||||
val settings = formState.value.mapReportSettings.copy { this.shouldReportLocation = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
positionPrecision = mqttInput.mapReportSettings.positionPrecision,
|
||||
positionPrecision = formState.value.mapReportSettings.positionPrecision,
|
||||
onPositionPrecisionChanged = {
|
||||
val settings = mqttInput.mapReportSettings.copy { positionPrecision = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
val settings = formState.value.mapReportSettings.copy { positionPrecision = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
publishIntervalSecs = mqttInput.mapReportSettings.publishIntervalSecs,
|
||||
publishIntervalSecs = formState.value.mapReportSettings.publishIntervalSecs,
|
||||
onPublishIntervalSecsChanged = {
|
||||
val settings = mqttInput.mapReportSettings.copy { publishIntervalSecs = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
val settings = formState.value.mapReportSettings.copy { publishIntervalSecs = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
focusManager = focusManager,
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
val consentValid =
|
||||
if (mqttInput.mapReportingEnabled) {
|
||||
mqttInput.mapReportSettings.shouldReportLocation &&
|
||||
mqttConfig.mapReportSettings.publishIntervalSecs >= MIN_INTERVAL_SECS
|
||||
} else {
|
||||
true
|
||||
}
|
||||
PreferenceFooter(
|
||||
enabled = enabled && mqttInput != mqttConfig && consentValid,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
mqttInput = mqttConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(mqttInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MIN_INTERVAL_SECS = 3600
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MQTTConfigPreview() {
|
||||
MQTTConfigItemList(
|
||||
mqttConfig = MQTTConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
shouldReportLocation = true,
|
||||
onShouldReportLocationChanged = { _ -> },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,67 +17,50 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun NeighborInfoConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun NeighborInfoConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val neighborInfoConfig = state.moduleConfig.neighborInfo
|
||||
val formState = rememberConfigState(initialValue = neighborInfoConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NeighborInfoConfigItemList(
|
||||
neighborInfoConfig = state.moduleConfig.neighborInfo,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.neighbor_info),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { neighborInfoInput ->
|
||||
val config = moduleConfig { neighborInfo = neighborInfoInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { neighborInfo = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NeighborInfoConfigItemList(
|
||||
neighborInfoConfig: ModuleConfigProtos.ModuleConfig.NeighborInfoConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.NeighborInfoConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var neighborInfoInput by rememberSaveable { mutableStateOf(neighborInfoConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.neighbor_info_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.neighbor_info_enabled),
|
||||
checked = neighborInfoInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,10 +68,10 @@ fun NeighborInfoConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval_seconds),
|
||||
value = neighborInfoInput.updateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.updateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { neighborInfoInput = neighborInfoInput.copy { updateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { updateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -96,35 +79,11 @@ fun NeighborInfoConfigItemList(
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.transmit_over_lora),
|
||||
summary = stringResource(id = R.string.config_device_transmitOverLora_summary),
|
||||
checked = neighborInfoInput.transmitOverLora,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it } },
|
||||
checked = formState.value.transmitOverLora,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { transmitOverLora = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && neighborInfoInput != neighborInfoConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
neighborInfoInput = neighborInfoConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(neighborInfoInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NeighborInfoConfigPreview() {
|
||||
NeighborInfoConfigItemList(
|
||||
neighborInfoConfig = ModuleConfigProtos.ModuleConfig.NeighborInfoConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
@@ -38,10 +36,10 @@ import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.NetworkConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
@@ -50,7 +48,6 @@ import com.geeksville.mesh.ui.common.components.EditIPv4Preference
|
||||
import com.geeksville.mesh.ui.common.components.EditPasswordPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
@@ -63,40 +60,10 @@ private fun ScanErrorDialog(onDismiss: () -> Unit = {}) =
|
||||
SimpleAlertDialog(title = R.string.error, text = R.string.wifi_qr_code_error, onDismiss = onDismiss)
|
||||
|
||||
@Composable
|
||||
fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NetworkConfigItemList(
|
||||
hasWifi = state.metadata?.hasWifi ?: true,
|
||||
hasEthernet = state.metadata?.hasEthernet ?: true,
|
||||
networkConfig = state.radioConfig.network,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { networkInput ->
|
||||
val config = config { network = networkInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractWifiCredentials(qrCode: String) =
|
||||
Regex("""WIFI:S:(.*?);.*?P:(.*?);""").find(qrCode)?.destructured?.let { (ssid, password) -> ssid to password }
|
||||
?: (null to null)
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun NetworkConfigItemList(
|
||||
hasWifi: Boolean,
|
||||
hasEthernet: Boolean,
|
||||
networkConfig: NetworkConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (NetworkConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var networkInput by rememberSaveable { mutableStateOf(networkConfig) }
|
||||
val networkConfig = state.radioConfig.network
|
||||
val formState = rememberConfigState(initialValue = networkConfig)
|
||||
|
||||
var showScanErrorDialog: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
if (showScanErrorDialog) {
|
||||
@@ -108,8 +75,8 @@ fun NetworkConfigItemList(
|
||||
if (result.contents != null) {
|
||||
val (ssid, psk) = extractWifiCredentials(result.contents)
|
||||
if (ssid != null && psk != null) {
|
||||
networkInput =
|
||||
networkInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
wifiSsid = ssid
|
||||
wifiPsk = psk
|
||||
}
|
||||
@@ -129,17 +96,29 @@ fun NetworkConfigItemList(
|
||||
}
|
||||
barcodeLauncher.launch(zxingScan)
|
||||
}
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
if (hasWifi) {
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.network),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { network = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
if (state.metadata?.hasWifi == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.wifi_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wifi_enabled),
|
||||
summary = stringResource(id = R.string.config_network_wifi_enabled_summary),
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
|
||||
checked = formState.value.wifiEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { wifiEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
@@ -147,25 +126,25 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ssid),
|
||||
value = networkInput.wifiSsid,
|
||||
value = formState.value.wifiSsid,
|
||||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiSsid = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiSsid = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.password),
|
||||
value = networkInput.wifiPsk,
|
||||
value = formState.value.wifiPsk,
|
||||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiPsk = it } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,37 +152,38 @@ fun NetworkConfigItemList(
|
||||
Button(
|
||||
onClick = { zxingScan() },
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasEthernet) {
|
||||
if (state.metadata?.hasEthernet == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ethernet_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ethernet_enabled),
|
||||
summary = stringResource(id = R.string.config_network_eth_enabled_summary),
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
|
||||
checked = formState.value.ethEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ethEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEthernet || hasWifi) {
|
||||
if (state.metadata?.hasEthernet == true || state.metadata?.hasWifi == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.udp_enabled),
|
||||
summary = stringResource(id = R.string.config_network_udp_enabled_summary),
|
||||
checked = networkInput.enabledProtocols == 1,
|
||||
enabled = enabled,
|
||||
checked = formState.value.enabledProtocols == 1,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
formState.value =
|
||||
formState.value.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -215,41 +195,41 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ntp_server),
|
||||
value = networkInput.ntpServer,
|
||||
value = formState.value.ntpServer,
|
||||
maxSize = 32, // ntp_server max_size:33
|
||||
enabled = enabled,
|
||||
isError = networkInput.ntpServer.isEmpty(),
|
||||
enabled = state.connected,
|
||||
isError = formState.value.ntpServer.isEmpty(),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { ntpServer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { ntpServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.rsyslog_server),
|
||||
value = networkInput.rsyslogServer,
|
||||
value = formState.value.rsyslogServer,
|
||||
maxSize = 32, // rsyslog_server max_size:33
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { rsyslogServer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rsyslogServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.ipv4_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
NetworkConfig.AddressMode.entries
|
||||
.filter { it != NetworkConfig.AddressMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = networkInput.addressMode,
|
||||
onItemSelected = { networkInput = networkInput.copy { addressMode = it } },
|
||||
selectedItem = formState.value.addressMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { addressMode = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
@@ -257,12 +237,12 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.ip),
|
||||
value = networkInput.ipv4Config.ip,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.ip,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { ip = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { ip = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -270,12 +250,12 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.gateway),
|
||||
value = networkInput.ipv4Config.gateway,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.gateway,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { gateway = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { gateway = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -283,12 +263,12 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.subnet),
|
||||
value = networkInput.ipv4Config.subnet,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.subnet,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { subnet = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { subnet = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -296,47 +276,19 @@ fun NetworkConfigItemList(
|
||||
item {
|
||||
EditIPv4Preference(
|
||||
title = "DNS",
|
||||
value = networkInput.ipv4Config.dns,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.dns,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { dns = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { dns = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && networkInput != networkConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
networkInput = networkConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(networkInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NetworkConfigPreview() {
|
||||
NetworkConfigItemList(
|
||||
hasWifi = true,
|
||||
hasEthernet = true,
|
||||
networkConfig = NetworkConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun QrCodeErrorDialogPreview() {
|
||||
ScanErrorDialog()
|
||||
}
|
||||
private fun extractWifiCredentials(qrCode: String) =
|
||||
Regex("""WIFI:S:(.*?);.*?P:(.*?);""").find(qrCode)?.destructured?.let { (ssid, password) -> ssid to password }
|
||||
?: (null to null)
|
||||
|
||||
@@ -17,69 +17,51 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PaxcounterConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val paxcounterConfig = state.moduleConfig.paxcounter
|
||||
val formState = rememberConfigState(initialValue = paxcounterConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PaxcounterConfigItemList(
|
||||
paxcounterConfig = state.moduleConfig.paxcounter,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.paxcounter),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { paxcounterConfigInput ->
|
||||
val config = moduleConfig { paxcounter = paxcounterConfigInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { paxcounter = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PaxcounterConfigItemList(
|
||||
paxcounterConfig: ModuleConfigProtos.ModuleConfig.PaxcounterConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.PaxcounterConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var paxcounterInput by rememberSaveable { mutableStateOf(paxcounterConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.paxcounter_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.paxcounter_enabled),
|
||||
checked = paxcounterInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { paxcounterInput = paxcounterInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -87,55 +69,31 @@ fun PaxcounterConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval_seconds),
|
||||
value = paxcounterInput.paxcounterUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.paxcounterUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { paxcounterUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.wifi_rssi_threshold_defaults_to_80),
|
||||
value = paxcounterInput.wifiThreshold,
|
||||
enabled = enabled,
|
||||
value = formState.value.wifiThreshold,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { wifiThreshold = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.ble_rssi_threshold_defaults_to_80),
|
||||
value = paxcounterInput.bleThreshold,
|
||||
enabled = enabled,
|
||||
value = formState.value.bleThreshold,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { bleThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && paxcounterInput != paxcounterConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
paxcounterInput = paxcounterConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(paxcounterInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { bleThreshold = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PaxcounterConfigPreview() {
|
||||
PaxcounterConfigItemList(
|
||||
paxcounterConfig = ModuleConfigProtos.ModuleConfig.PaxcounterConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,8 +21,6 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
@@ -35,13 +33,12 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.location.LocationCompat
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.ConfigProtos.Config.PositionConfig
|
||||
import com.geeksville.mesh.Position
|
||||
@@ -51,7 +48,6 @@ import com.geeksville.mesh.ui.common.components.BitwisePreference
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
@@ -61,7 +57,7 @@ import org.meshtastic.core.strings.R
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var phoneLocation: Location? by remember { mutableStateOf(null) }
|
||||
@@ -73,120 +69,104 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
altitude = node?.position?.altitude ?: 0,
|
||||
time = 1, // ignore time for fixed_position
|
||||
)
|
||||
val positionConfig = state.radioConfig.position
|
||||
val formState = rememberConfigState(initialValue = positionConfig)
|
||||
var locationInput by rememberSaveable { mutableStateOf(currentPosition) }
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PositionConfigItemList(
|
||||
phoneLocation = phoneLocation,
|
||||
location = currentPosition,
|
||||
positionConfig = state.radioConfig.position,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { locationInput, positionInput ->
|
||||
if (positionInput.fixedPosition) {
|
||||
if (locationInput != currentPosition) {
|
||||
viewModel.setFixedPosition(locationInput)
|
||||
}
|
||||
} else {
|
||||
if (state.radioConfig.position.fixedPosition) {
|
||||
// fixed position changed from enabled to disabled
|
||||
viewModel.removeFixedPosition()
|
||||
}
|
||||
}
|
||||
val config = config { position = positionInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onUseCurrentLocation = {
|
||||
@SuppressLint("MissingPermission")
|
||||
coroutineScope.launch { phoneLocation = viewModel.getCurrentLocation() }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun PositionConfigItemList(
|
||||
phoneLocation: Location? = null,
|
||||
location: Position,
|
||||
positionConfig: PositionConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (position: Position, config: PositionConfig) -> Unit,
|
||||
onUseCurrentLocation: suspend () -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val locationPermissionState =
|
||||
rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) { granted ->
|
||||
if (granted) {
|
||||
coroutineScope.launch { onUseCurrentLocation() }
|
||||
@SuppressLint("MissingPermission")
|
||||
coroutineScope.launch { phoneLocation = viewModel.getCurrentLocation() }
|
||||
}
|
||||
}
|
||||
var locationInput by rememberSaveable { mutableStateOf(location) }
|
||||
var positionInput by rememberSaveable { mutableStateOf(positionConfig) }
|
||||
|
||||
LaunchedEffect(phoneLocation) {
|
||||
if (phoneLocation != null) {
|
||||
phoneLocation?.let { phoneLoc ->
|
||||
locationInput =
|
||||
Position(
|
||||
latitude = phoneLocation.latitude,
|
||||
longitude = phoneLocation.longitude,
|
||||
latitude = phoneLoc.latitude,
|
||||
longitude = phoneLoc.longitude,
|
||||
altitude =
|
||||
LocationCompat.hasMslAltitude(phoneLocation).let {
|
||||
LocationCompat.hasMslAltitude(phoneLoc).let {
|
||||
if (it && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
phoneLocation.mslAltitudeMeters.toInt()
|
||||
phoneLoc.mslAltitudeMeters.toInt()
|
||||
} else {
|
||||
phoneLocation.altitude.toInt()
|
||||
phoneLoc.altitude.toInt()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.position),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (formState.value.fixedPosition) {
|
||||
if (locationInput != currentPosition) {
|
||||
viewModel.setFixedPosition(locationInput)
|
||||
}
|
||||
} else {
|
||||
if (positionConfig.fixedPosition) {
|
||||
// fixed position changed from enabled to disabled
|
||||
viewModel.removeFixedPosition()
|
||||
}
|
||||
}
|
||||
val config = config { position = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_packet)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.broadcast_interval),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_secs_summary),
|
||||
value = positionInput.positionBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.positionBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { positionBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { positionBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.smart_position),
|
||||
checked = positionInput.positionBroadcastSmartEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { positionBroadcastSmartEnabled = it } },
|
||||
checked = formState.value.positionBroadcastSmartEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { positionBroadcastSmartEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (positionInput.positionBroadcastSmartEnabled) {
|
||||
if (formState.value.positionBroadcastSmartEnabled) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_interval),
|
||||
summary =
|
||||
stringResource(id = R.string.config_position_broadcast_smart_minimum_interval_secs_summary),
|
||||
value = positionInput.broadcastSmartMinimumIntervalSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.broadcastSmartMinimumIntervalSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumIntervalSecs = it } },
|
||||
onValueChanged = {
|
||||
formState.value = formState.value.copy { broadcastSmartMinimumIntervalSecs = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_distance),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_smart_minimum_distance_summary),
|
||||
value = positionInput.broadcastSmartMinimumDistance,
|
||||
enabled = enabled,
|
||||
value = formState.value.broadcastSmartMinimumDistance,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { broadcastSmartMinimumDistance = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -194,19 +174,19 @@ fun PositionConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.fixed_position),
|
||||
checked = positionInput.fixedPosition,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } },
|
||||
checked = formState.value.fixedPosition,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { fixedPosition = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (positionInput.fixedPosition) {
|
||||
if (formState.value.fixedPosition) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.latitude),
|
||||
value = locationInput.latitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value >= -90 && value <= 90.0) {
|
||||
@@ -219,7 +199,7 @@ fun PositionConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.longitude),
|
||||
value = locationInput.longitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value >= -180 && value <= 180.0) {
|
||||
@@ -232,14 +212,14 @@ fun PositionConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.altitude),
|
||||
value = locationInput.altitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value -> locationInput = locationInput.copy(altitude = value) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextButton(
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
onClick = { coroutineScope.launch { locationPermissionState.launchPermissionRequest() } },
|
||||
) {
|
||||
Text(text = stringResource(R.string.position_config_set_fixed_from_phone))
|
||||
@@ -250,13 +230,13 @@ fun PositionConfigItemList(
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.gps_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
ConfigProtos.Config.PositionConfig.GpsMode.entries
|
||||
.filter { it != ConfigProtos.Config.PositionConfig.GpsMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = positionInput.gpsMode,
|
||||
onItemSelected = { positionInput = positionInput.copy { gpsMode = it } },
|
||||
selectedItem = formState.value.gpsMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { gpsMode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -265,10 +245,10 @@ fun PositionConfigItemList(
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval),
|
||||
summary = stringResource(id = R.string.config_position_gps_update_interval_summary),
|
||||
value = positionInput.gpsUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.gpsUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { gpsUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_flags)) }
|
||||
@@ -276,15 +256,15 @@ fun PositionConfigItemList(
|
||||
BitwisePreference(
|
||||
title = stringResource(R.string.position_flags),
|
||||
summary = stringResource(id = R.string.config_position_flags_summary),
|
||||
value = positionInput.positionFlags,
|
||||
enabled = enabled,
|
||||
value = formState.value.positionFlags,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
ConfigProtos.Config.PositionConfig.PositionFlags.entries
|
||||
.filter {
|
||||
it != PositionConfig.PositionFlags.UNSET && it != PositionConfig.PositionFlags.UNRECOGNIZED
|
||||
}
|
||||
.map { it.number to it.name },
|
||||
onItemSelected = { positionInput = positionInput.copy { positionFlags = it } },
|
||||
onItemSelected = { formState.value = formState.value.copy { positionFlags = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -293,58 +273,31 @@ fun PositionConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_receive_gpio),
|
||||
value = positionInput.rxGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.rxGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { rxGpio = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rxGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_transmit_gpio),
|
||||
value = positionInput.txGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.txGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { txGpio = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_en_gpio),
|
||||
value = positionInput.gpsEnGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.gpsEnGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { gpsEnGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && positionInput != positionConfig || locationInput != location,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
locationInput = location
|
||||
positionInput = positionConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(locationInput, positionInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { gpsEnGpio = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PositionConfigPreview() {
|
||||
PositionConfigItemList(
|
||||
location = Position(0.0, 0.0, 0),
|
||||
positionConfig = PositionConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
onUseCurrentLocation = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,67 +17,51 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ConfigProtos.Config.PowerConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val powerConfig = state.radioConfig.power
|
||||
val formState = rememberConfigState(initialValue = powerConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PowerConfigItemList(
|
||||
powerConfig = state.radioConfig.power,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.power),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { powerInput ->
|
||||
val config = config { power = powerInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { power = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicked: (PowerConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var powerInput by rememberSaveable { mutableStateOf(powerConfig) }
|
||||
var shutdownOnPowerLoss by rememberSaveable { mutableStateOf(powerConfig.onBatteryShutdownAfterSecs > 0) }
|
||||
var adcOverride by rememberSaveable { mutableStateOf(powerConfig.adcMultiplierOverride > 0f) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.power_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.enable_power_saving_mode),
|
||||
summary = stringResource(id = R.string.config_power_is_power_saving_summary),
|
||||
checked = powerInput.isPowerSaving,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } },
|
||||
checked = formState.value.isPowerSaving,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isPowerSaving = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,23 +69,22 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.shutdown_on_power_loss),
|
||||
checked = shutdownOnPowerLoss,
|
||||
enabled = enabled,
|
||||
checked = formState.value.onBatteryShutdownAfterSecs > 0,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
shutdownOnPowerLoss = it
|
||||
if (!it) powerInput = powerInput.copy { onBatteryShutdownAfterSecs = 0 }
|
||||
formState.value = formState.value.copy { onBatteryShutdownAfterSecs = if (it) 3600 else 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (shutdownOnPowerLoss) {
|
||||
if (formState.value.onBatteryShutdownAfterSecs > 0) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.shutdown_on_battery_delay_seconds),
|
||||
value = powerInput.onBatteryShutdownAfterSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.onBatteryShutdownAfterSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { onBatteryShutdownAfterSecs = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -111,23 +94,22 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override),
|
||||
checked = adcOverride,
|
||||
enabled = enabled,
|
||||
checked = formState.value.adcMultiplierOverride > 0f,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
adcOverride = it
|
||||
if (!it) powerInput = powerInput.copy { adcMultiplierOverride = 0f }
|
||||
formState.value = formState.value.copy { adcMultiplierOverride = if (it) 1.0f else 0.0f }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (adcOverride) {
|
||||
if (formState.value.adcMultiplierOverride > 0f) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override_ratio),
|
||||
value = powerInput.adcMultiplierOverride,
|
||||
enabled = enabled,
|
||||
value = formState.value.adcMultiplierOverride,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { adcMultiplierOverride = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -137,61 +119,41 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.wait_for_bluetooth_duration_seconds),
|
||||
value = powerInput.waitBluetoothSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.waitBluetoothSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { waitBluetoothSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { waitBluetoothSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.super_deep_sleep_duration_seconds),
|
||||
value = powerInput.sdsSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.sdsSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { sdsSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { sdsSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_wake_time_seconds),
|
||||
value = powerInput.minWakeSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.minWakeSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { minWakeSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { minWakeSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.battery_ina_2xx_i2c_address),
|
||||
value = powerInput.deviceBatteryInaAddress,
|
||||
enabled = enabled,
|
||||
value = formState.value.deviceBatteryInaAddress,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { deviceBatteryInaAddress = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && powerInput != powerConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
powerInput = powerConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(powerInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { deviceBatteryInaAddress = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PowerConfigPreview() {
|
||||
PowerConfigItemList(powerConfig = PowerConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.settings.radio.ResponseState
|
||||
import com.google.protobuf.MessageLite
|
||||
|
||||
@Composable
|
||||
fun <T : MessageLite> RadioConfigScreenList(
|
||||
title: String,
|
||||
onBack: () -> Unit,
|
||||
responseState: ResponseState<Any>,
|
||||
onDismissPacketResponse: () -> Unit,
|
||||
configState: ConfigState<T>,
|
||||
enabled: Boolean,
|
||||
onSave: (T) -> Unit,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = responseState, onDismiss = onDismissPacketResponse)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = title,
|
||||
canNavigateUp = true,
|
||||
onNavigateUp = onBack,
|
||||
ourNode = null,
|
||||
isConnected = false,
|
||||
showNodeChip = false,
|
||||
actions = {},
|
||||
onAction = {},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
content()
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && configState.isDirty,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
configState.reset()
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSave(configState.value)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,67 +17,50 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RangeTestConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun RangeTestConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val rangeTestConfig = state.moduleConfig.rangeTest
|
||||
val formState = rememberConfigState(initialValue = rangeTestConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RangeTestConfigItemList(
|
||||
rangeTestConfig = state.moduleConfig.rangeTest,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.range_test),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { rangeTestInput ->
|
||||
val config = moduleConfig { rangeTest = rangeTestInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { rangeTest = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RangeTestConfigItemList(
|
||||
rangeTestConfig: RangeTestConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (RangeTestConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var rangeTestInput by rememberSaveable { mutableStateOf(rangeTestConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.range_test_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.range_test_enabled),
|
||||
checked = rangeTestInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,41 +68,21 @@ fun RangeTestConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.sender_message_interval_seconds),
|
||||
value = rangeTestInput.sender,
|
||||
enabled = enabled,
|
||||
value = formState.value.sender,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { sender = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.save_csv_in_storage_esp32_only),
|
||||
checked = rangeTestInput.save,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } },
|
||||
checked = formState.value.save,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { save = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && rangeTestInput != rangeTestConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
rangeTestInput = rangeTestConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(rangeTestInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RangeTestConfig() {
|
||||
RangeTestConfigItemList(rangeTestConfig = RangeTestConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,67 +17,50 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RemoteHardwareConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditListPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun RemoteHardwareConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun RemoteHardwareConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val remoteHardwareConfig = state.moduleConfig.remoteHardware
|
||||
val formState = rememberConfigState(initialValue = remoteHardwareConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig = state.moduleConfig.remoteHardware,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.remote_hardware),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { remoteHardwareInput ->
|
||||
val config = moduleConfig { remoteHardware = remoteHardwareInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { remoteHardware = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig: RemoteHardwareConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (RemoteHardwareConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var remoteHardwareInput by rememberSaveable { mutableStateOf(remoteHardwareConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.remote_hardware_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.remote_hardware_enabled),
|
||||
checked = remoteHardwareInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,9 +68,9 @@ fun RemoteHardwareConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.allow_undefined_pin_access),
|
||||
checked = remoteHardwareInput.allowUndefinedPinAccess,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it } },
|
||||
checked = formState.value.allowUndefinedPinAccess,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { allowUndefinedPinAccess = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -95,42 +78,18 @@ fun RemoteHardwareConfigItemList(
|
||||
item {
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.available_pins),
|
||||
list = remoteHardwareInput.availablePinsList,
|
||||
list = formState.value.availablePinsList,
|
||||
maxCount = 4, // available_pins max_count:4
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = { list ->
|
||||
remoteHardwareInput =
|
||||
remoteHardwareInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
availablePins.clear()
|
||||
availablePins.addAll(list)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && remoteHardwareInput != remoteHardwareConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
remoteHardwareInput = remoteHardwareConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(remoteHardwareInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RemoteHardwareConfigPreview() {
|
||||
RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig = RemoteHardwareConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,12 +19,9 @@ package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
@@ -42,19 +39,17 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.SecurityConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.CopyIconButton
|
||||
import com.geeksville.mesh.ui.common.components.EditBase64Preference
|
||||
import com.geeksville.mesh.ui.common.components.EditListPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.node.NodeActionButton
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
@@ -64,45 +59,19 @@ import com.google.protobuf.ByteString
|
||||
import org.meshtastic.core.strings.R
|
||||
import java.security.SecureRandom
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val node by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
val securityConfig = state.radioConfig.security
|
||||
val formState = rememberConfigState(initialValue = securityConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SecurityConfigItemList(
|
||||
user = node?.user,
|
||||
securityConfig = state.radioConfig.security,
|
||||
enabled = state.connected,
|
||||
onConfirm = { securityInput ->
|
||||
val config = config { security = securityInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onExport = { uri, securityConfig -> viewModel.exportSecurityConfig(uri, securityConfig) },
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SecurityConfigItemList(
|
||||
user: MeshProtos.User? = null,
|
||||
securityConfig: SecurityConfig,
|
||||
enabled: Boolean,
|
||||
onConfirm: (config: SecurityConfig) -> Unit,
|
||||
onExport: (uri: Uri, securityConfig: SecurityConfig) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var securityInput by rememberSaveable { mutableStateOf(securityConfig) }
|
||||
|
||||
var publicKey by rememberSaveable { mutableStateOf(securityInput.publicKey) }
|
||||
LaunchedEffect(securityInput.privateKey) {
|
||||
if (securityInput.privateKey != securityConfig.privateKey) {
|
||||
var publicKey by rememberSaveable { mutableStateOf(formState.value.publicKey) }
|
||||
LaunchedEffect(formState.value.privateKey) {
|
||||
if (formState.value.privateKey != securityConfig.privateKey) {
|
||||
publicKey = "".toByteString()
|
||||
} else if (securityInput.privateKey == securityConfig.privateKey) {
|
||||
} else if (formState.value.privateKey == securityConfig.privateKey) {
|
||||
publicKey = securityConfig.publicKey
|
||||
}
|
||||
}
|
||||
@@ -110,18 +79,18 @@ fun SecurityConfigItemList(
|
||||
val exportConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
|
||||
it.data?.data?.let { uri -> viewModel.exportSecurityConfig(uri, securityConfig) }
|
||||
}
|
||||
}
|
||||
|
||||
var showKeyGenerationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog = showKeyGenerationDialog,
|
||||
config = securityInput,
|
||||
onConfirm = { newConfig ->
|
||||
securityInput = newConfig
|
||||
onConfirm = {
|
||||
formState.value = it
|
||||
showKeyGenerationDialog = false
|
||||
onConfirm(securityInput)
|
||||
val config = config { security = formState.value }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onDismiss = { showKeyGenerationDialog = false },
|
||||
)
|
||||
@@ -141,7 +110,7 @@ fun SecurityConfigItemList(
|
||||
type = "application/*"
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
"${user?.shortName}_keys_${System.currentTimeMillis()}.json",
|
||||
"${node?.user?.shortName}_keys_${System.currentTimeMillis()}.json",
|
||||
)
|
||||
}
|
||||
exportConfigLauncher.launch(intent)
|
||||
@@ -153,7 +122,19 @@ fun SecurityConfigItemList(
|
||||
)
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.security),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { security = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.direct_message_key)) }
|
||||
|
||||
item {
|
||||
@@ -161,15 +142,15 @@ fun SecurityConfigItemList(
|
||||
title = stringResource(R.string.public_key),
|
||||
summary = stringResource(id = R.string.config_security_public_key),
|
||||
value = publicKey,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
readOnly = true,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChange = {
|
||||
if (it.size() == 32) {
|
||||
securityInput = securityInput.copy { this.publicKey = it }
|
||||
formState.value = formState.value.copy { this.publicKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.publicKey.encodeToString()) },
|
||||
trailingIcon = { CopyIconButton(valueToCopy = formState.value.publicKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -177,15 +158,15 @@ fun SecurityConfigItemList(
|
||||
EditBase64Preference(
|
||||
title = stringResource(R.string.private_key),
|
||||
summary = stringResource(id = R.string.config_security_private_key),
|
||||
value = securityInput.privateKey,
|
||||
enabled = enabled,
|
||||
value = formState.value.privateKey,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChange = {
|
||||
if (it.size() == 32) {
|
||||
securityInput = securityInput.copy { privateKey = it }
|
||||
formState.value = formState.value.copy { privateKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.privateKey.encodeToString()) },
|
||||
trailingIcon = { CopyIconButton(valueToCopy = formState.value.privateKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -193,7 +174,7 @@ fun SecurityConfigItemList(
|
||||
NodeActionButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
title = stringResource(R.string.regenerate_private_key),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = { showKeyGenerationDialog = true },
|
||||
)
|
||||
@@ -203,7 +184,7 @@ fun SecurityConfigItemList(
|
||||
NodeActionButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
title = stringResource(R.string.export_keys),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = { showEditSecurityConfigDialog = true },
|
||||
)
|
||||
@@ -213,13 +194,13 @@ fun SecurityConfigItemList(
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.admin_key),
|
||||
summary = stringResource(id = R.string.config_security_admin_key),
|
||||
list = securityInput.adminKeyList,
|
||||
list = formState.value.adminKeyList,
|
||||
maxCount = 3,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = {
|
||||
securityInput =
|
||||
securityInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
adminKey.clear()
|
||||
adminKey.addAll(it)
|
||||
}
|
||||
@@ -231,9 +212,9 @@ fun SecurityConfigItemList(
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.serial_console),
|
||||
summary = stringResource(id = R.string.config_security_serial_enabled),
|
||||
checked = securityInput.serialEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } },
|
||||
checked = formState.value.serialEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { serialEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -242,9 +223,9 @@ fun SecurityConfigItemList(
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.debug_log_api_enabled),
|
||||
summary = stringResource(id = R.string.config_security_debug_log_api_enabled),
|
||||
checked = securityInput.debugLogApiEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { debugLogApiEnabled = it } },
|
||||
checked = formState.value.debugLogApiEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { debugLogApiEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -253,9 +234,9 @@ fun SecurityConfigItemList(
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.managed_mode),
|
||||
summary = stringResource(id = R.string.config_security_is_managed),
|
||||
checked = securityInput.isManaged,
|
||||
enabled = enabled && securityInput.adminKeyCount > 0,
|
||||
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
|
||||
checked = formState.value.isManaged,
|
||||
enabled = state.connected && formState.value.adminKeyCount > 0,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isManaged = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -263,26 +244,12 @@ fun SecurityConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.legacy_admin_channel),
|
||||
checked = securityInput.adminChannelEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { adminChannelEnabled = it } },
|
||||
checked = formState.value.adminChannelEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { adminChannelEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && securityInput != securityConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
securityInput = securityConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,11 +257,9 @@ fun SecurityConfigItemList(
|
||||
@Composable
|
||||
fun PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog: Boolean,
|
||||
config: SecurityConfig,
|
||||
onConfirm: (SecurityConfig) -> Unit,
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
var securityInput by rememberSaveable { mutableStateOf(config) }
|
||||
if (showKeyGenerationDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
@@ -303,20 +268,22 @@ fun PrivateKeyRegenerateDialog(
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
securityInput =
|
||||
securityInput.copy {
|
||||
clearPrivateKey()
|
||||
clearPublicKey()
|
||||
// Generate a random "f" value
|
||||
val f = ByteArray(32).apply { SecureRandom().nextBytes(this) }
|
||||
// Adjust the value to make it valid as an "s" value for eval().
|
||||
// According to the specification we need to mask off the 3
|
||||
// right-most bits of f[0], mask off the left-most bit of f[31],
|
||||
// and set the second to left-most bit of f[31].
|
||||
f[0] = (f[0].toInt() and 0xF8).toByte()
|
||||
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
|
||||
privateKey = ByteString.copyFrom(f)
|
||||
}
|
||||
val securityInput =
|
||||
SecurityConfig.newBuilder()
|
||||
.apply {
|
||||
clearPrivateKey()
|
||||
clearPublicKey()
|
||||
// Generate a random "f" value
|
||||
val f = ByteArray(32).apply { SecureRandom().nextBytes(this) }
|
||||
// Adjust the value to make it valid as an "s" value for eval().
|
||||
// According to the specification we need to mask off the 3
|
||||
// right-most bits of f[0], mask off the left-most bit of f[31],
|
||||
// and set the second to left-most bit of f[31].
|
||||
f[0] = (f[0].toInt() and 0xF8).toByte()
|
||||
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
|
||||
privateKey = ByteString.copyFrom(f)
|
||||
}
|
||||
.build()
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
) {
|
||||
@@ -327,9 +294,3 @@ fun PrivateKeyRegenerateDialog(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SecurityConfigPreview() {
|
||||
SecurityConfigItemList(securityConfig = SecurityConfig.getDefaultInstance(), enabled = true, onConfirm = {})
|
||||
}
|
||||
|
||||
@@ -17,65 +17,52 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.SerialConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val serialConfig = state.moduleConfig.serial
|
||||
val formState = rememberConfigState(initialValue = serialConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SerialConfigItemList(
|
||||
serialConfig = state.moduleConfig.serial,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.serial),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { serialInput ->
|
||||
val config = moduleConfig { serial = serialInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { serial = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveClicked: (SerialConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var serialInput by rememberSaveable { mutableStateOf(serialConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.serial_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.serial_enabled),
|
||||
checked = serialInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -83,9 +70,9 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.echo_enabled),
|
||||
checked = serialInput.echo,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { echo = it } },
|
||||
checked = formState.value.echo,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { echo = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -93,33 +80,33 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "RX",
|
||||
value = serialInput.rxd,
|
||||
enabled = enabled,
|
||||
value = formState.value.rxd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { rxd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rxd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "TX",
|
||||
value = serialInput.txd,
|
||||
enabled = enabled,
|
||||
value = formState.value.txd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { txd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_baud_rate),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
SerialConfig.Serial_Baud.entries
|
||||
.filter { it != SerialConfig.Serial_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.baud,
|
||||
onItemSelected = { serialInput = serialInput.copy { baud = it } },
|
||||
selectedItem = formState.value.baud,
|
||||
onItemSelected = { formState.value = formState.value.copy { baud = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -127,23 +114,23 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.timeout),
|
||||
value = serialInput.timeout,
|
||||
enabled = enabled,
|
||||
value = formState.value.timeout,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { timeout = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { timeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
SerialConfig.Serial_Mode.entries
|
||||
.filter { it != SerialConfig.Serial_Mode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.mode,
|
||||
onItemSelected = { serialInput = serialInput.copy { mode = it } },
|
||||
selectedItem = formState.value.mode,
|
||||
onItemSelected = { formState.value = formState.value.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -151,31 +138,11 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.override_console_serial_port),
|
||||
checked = serialInput.overrideConsoleSerialPort,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { overrideConsoleSerialPort = it } },
|
||||
checked = formState.value.overrideConsoleSerialPort,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { overrideConsoleSerialPort = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && serialInput != serialConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
serialInput = serialConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(serialInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SerialConfigPreview() {
|
||||
SerialConfigItemList(serialConfig = SerialConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,67 +17,50 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.StoreForwardConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val storeForwardConfig = state.moduleConfig.storeForward
|
||||
val formState = rememberConfigState(initialValue = storeForwardConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
StoreForwardConfigItemList(
|
||||
storeForwardConfig = state.moduleConfig.storeForward,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.store_forward),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { storeForwardInput ->
|
||||
val config = moduleConfig { storeForward = storeForwardInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { storeForward = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoreForwardConfigItemList(
|
||||
storeForwardConfig: StoreForwardConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (StoreForwardConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var storeForwardInput by rememberSaveable { mutableStateOf(storeForwardConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.store_forward_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.store_forward_enabled),
|
||||
checked = storeForwardInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -85,9 +68,9 @@ fun StoreForwardConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.heartbeat),
|
||||
checked = storeForwardInput.heartbeat,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } },
|
||||
checked = formState.value.heartbeat,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { heartbeat = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -95,65 +78,41 @@ fun StoreForwardConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.number_of_records),
|
||||
value = storeForwardInput.records,
|
||||
enabled = enabled,
|
||||
value = formState.value.records,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { records = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.history_return_max),
|
||||
value = storeForwardInput.historyReturnMax,
|
||||
enabled = enabled,
|
||||
value = formState.value.historyReturnMax,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnMax = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { historyReturnMax = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.history_return_window),
|
||||
value = storeForwardInput.historyReturnWindow,
|
||||
enabled = enabled,
|
||||
value = formState.value.historyReturnWindow,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { historyReturnWindow = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.server),
|
||||
checked = storeForwardInput.isServer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } },
|
||||
checked = formState.value.isServer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isServer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && storeForwardInput != storeForwardConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
storeForwardInput = storeForwardConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(storeForwardInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun StoreForwardConfigPreview() {
|
||||
StoreForwardConfigItemList(
|
||||
storeForwardConfig = StoreForwardConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,87 +17,70 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.TelemetryConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val telemetryConfig = state.moduleConfig.telemetry
|
||||
val formState = rememberConfigState(initialValue = telemetryConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
TelemetryConfigItemList(
|
||||
telemetryConfig = state.moduleConfig.telemetry,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.telemetry),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { telemetryInput ->
|
||||
val config = moduleConfig { telemetry = telemetryInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { telemetry = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TelemetryConfigItemList(
|
||||
telemetryConfig: TelemetryConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (TelemetryConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var telemetryInput by rememberSaveable { mutableStateOf(telemetryConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.telemetry_config)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.device_metrics_update_interval_seconds),
|
||||
value = telemetryInput.deviceUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.deviceUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { deviceUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.environment_metrics_update_interval_seconds),
|
||||
value = telemetryInput.environmentUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.environmentUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { environmentUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_module_enabled),
|
||||
checked = telemetryInput.environmentMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } },
|
||||
checked = formState.value.environmentMeasurementEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -105,9 +88,9 @@ fun TelemetryConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.environmentScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } },
|
||||
checked = formState.value.environmentScreenEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -115,9 +98,9 @@ fun TelemetryConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_use_fahrenheit),
|
||||
checked = telemetryInput.environmentDisplayFahrenheit,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } },
|
||||
checked = formState.value.environmentDisplayFahrenheit,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentDisplayFahrenheit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -125,9 +108,9 @@ fun TelemetryConfigItemList(
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.air_quality_metrics_module_enabled),
|
||||
checked = telemetryInput.airQualityEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { airQualityEnabled = it } },
|
||||
checked = formState.value.airQualityEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { airQualityEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -135,19 +118,19 @@ fun TelemetryConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.air_quality_metrics_update_interval_seconds),
|
||||
value = telemetryInput.airQualityInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.airQualityInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { airQualityInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { airQualityInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.power_metrics_module_enabled),
|
||||
checked = telemetryInput.powerMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it } },
|
||||
checked = formState.value.powerMeasurementEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { powerMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
@@ -155,41 +138,21 @@ fun TelemetryConfigItemList(
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.power_metrics_update_interval_seconds),
|
||||
value = telemetryInput.powerUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.powerUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { powerUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { powerUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.power_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.powerScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerScreenEnabled = it } },
|
||||
checked = formState.value.powerScreenEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { powerScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && telemetryInput != telemetryConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
telemetryInput = telemetryConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(telemetryInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TelemetryConfigPreview() {
|
||||
TelemetryConfigItemList(telemetryConfig = TelemetryConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
||||
@@ -17,32 +17,23 @@
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.deviceMetadata
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.isUnmessageableRole
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.RegularPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
@@ -50,74 +41,65 @@ import com.geeksville.mesh.user
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val userConfig = state.userConfig
|
||||
val formState = rememberConfigState(initialValue = userConfig)
|
||||
val firmwareVersion = DeviceVersion(state.metadata?.firmwareVersion ?: "")
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
UserConfigItemList(
|
||||
userConfig = state.userConfig,
|
||||
enabled = true,
|
||||
onSaveClicked = viewModel::setOwner,
|
||||
metadata = state.metadata,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun UserConfigItemList(
|
||||
metadata: MeshProtos.DeviceMetadata?,
|
||||
userConfig: MeshProtos.User,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (MeshProtos.User) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var userInput by rememberSaveable { mutableStateOf(userConfig) }
|
||||
val firmwareVersion = DeviceVersion(metadata?.firmwareVersion ?: "")
|
||||
|
||||
val validLongName = userInput.longName.isNotBlank()
|
||||
val validShortName = userInput.shortName.isNotBlank()
|
||||
val validLongName = formState.value.longName.isNotBlank()
|
||||
val validShortName = formState.value.shortName.isNotBlank()
|
||||
val validNames = validLongName && validShortName
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.user),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected && validNames,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = viewModel::setOwner,
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.user_config)) }
|
||||
|
||||
item { RegularPreference(title = stringResource(R.string.node_id), subtitle = userInput.id, onClick = {}) }
|
||||
item {
|
||||
RegularPreference(title = stringResource(R.string.node_id), subtitle = formState.value.id, onClick = {})
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.long_name),
|
||||
value = userInput.longName,
|
||||
value = formState.value.longName,
|
||||
maxSize = 39, // long_name max_size:40
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = !validLongName,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { userInput = userInput.copy { longName = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { longName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.short_name),
|
||||
value = userInput.shortName,
|
||||
value = formState.value.shortName,
|
||||
maxSize = 4, // short_name max_size:5
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = !validShortName,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { userInput = userInput.copy { shortName = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { shortName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
RegularPreference(
|
||||
title = stringResource(R.string.hardware_model),
|
||||
subtitle = userInput.hwModel.name,
|
||||
subtitle = formState.value.hwModel.name,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
@@ -128,10 +110,10 @@ fun UserConfigItemList(
|
||||
title = stringResource(R.string.unmessageable),
|
||||
summary = stringResource(R.string.unmonitored_or_infrastructure),
|
||||
checked =
|
||||
userInput.isUnmessagable ||
|
||||
(firmwareVersion < DeviceVersion("2.6.9") && userInput.role.isUnmessageableRole()),
|
||||
enabled = userInput.hasIsUnmessagable() || firmwareVersion >= DeviceVersion("2.6.9"),
|
||||
onCheckedChange = { userInput = userInput.copy { isUnmessagable = it } },
|
||||
formState.value.isUnmessagable ||
|
||||
(firmwareVersion < DeviceVersion("2.6.9") && formState.value.role.isUnmessageableRole()),
|
||||
enabled = formState.value.hasIsUnmessagable() || firmwareVersion >= DeviceVersion("2.6.9"),
|
||||
onCheckedChange = { formState.value = formState.value.copy { isUnmessagable = it } },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -141,43 +123,11 @@ fun UserConfigItemList(
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.licensed_amateur_radio),
|
||||
summary = stringResource(R.string.licensed_amateur_radio_text),
|
||||
checked = userInput.isLicensed,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { userInput = userInput.copy { isLicensed = it } },
|
||||
checked = formState.value.isLicensed,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isLicensed = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && userInput != userConfig && validNames,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
userInput = userConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(userInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun UserConfigPreview() {
|
||||
UserConfigItemList(
|
||||
userConfig =
|
||||
user {
|
||||
id = "!a280d9c8"
|
||||
longName = "Meshtastic d9c8"
|
||||
shortName = "d9c8"
|
||||
hwModel = MeshProtos.HardwareModel.RAK4631
|
||||
isLicensed = false
|
||||
},
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
metadata = deviceMetadata { firmwareVersion = "2.8.0" },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ internal fun Project.configureDetekt(extension: DetektExtension) = extension.app
|
||||
config.setFrom("$rootDir/config/detekt/detekt.yml")
|
||||
buildUponDefaultConfig = true
|
||||
allRules = false
|
||||
baseline = file("$rootDir/config/detekt/baseline.xml")
|
||||
source.setFrom(
|
||||
files(
|
||||
"src/main/java",
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<!--
|
||||
~ Copyright (c) 2025 Meshtastic LLC
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>CommentSpacing:NodeInfo.kt$NodeInfo$/// @return a nice human readable string for the distance, or null for unknown</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$NodeInfo$/// @return bearing to the other position in degrees</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$NodeInfo$/// @return distance in meters to some other node (or null if unknown)</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$NodeInfo$/// return the position if it is valid, else null</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$Position$/// @return bearing to the other position in degrees</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$Position$/// @return distance in meters to some other node (or null if unknown)</ID>
|
||||
<ID>CommentSpacing:NodeInfo.kt$Position.Companion$/// Convert to a double representation of degrees</ID>
|
||||
<ID>FinalNewline:DataPacket.kt$com.geeksville.mesh.DataPacket.kt</ID>
|
||||
<ID>FinalNewline:MyNodeInfo.kt$com.geeksville.mesh.MyNodeInfo.kt</ID>
|
||||
<ID>FinalNewline:NodeInfo.kt$com.geeksville.mesh.NodeInfo.kt</ID>
|
||||
<ID>FunctionParameterNaming:LocationUtils.kt$_degIn: Double</ID>
|
||||
<ID>FunctionParameterNaming:LocationUtils.kt$lat_a: Double</ID>
|
||||
<ID>FunctionParameterNaming:LocationUtils.kt$lat_b: Double</ID>
|
||||
<ID>FunctionParameterNaming:LocationUtils.kt$lng_a: Double</ID>
|
||||
<ID>FunctionParameterNaming:LocationUtils.kt$lng_b: Double</ID>
|
||||
<ID>ImplicitDefaultLocale:LocationUtils.kt$GPSFormat$String.format( "%s%s %.6s %.7s", UTM.zone, UTM.toMGRS().band, UTM.easting, UTM.northing )</ID>
|
||||
<ID>ImplicitDefaultLocale:LocationUtils.kt$GPSFormat$String.format( "%s%s %s%s %05d %05d", MGRS.zone, MGRS.band, MGRS.column, MGRS.row, MGRS.easting, MGRS.northing )</ID>
|
||||
<ID>ImplicitDefaultLocale:LocationUtils.kt$GPSFormat$String.format("%.5f %.5f", p.latitude, p.longitude)</ID>
|
||||
<ID>ImplicitDefaultLocale:LocationUtils.kt$GPSFormat$String.format("%s°%s'%.5s\"%s", a[0], a[1], a[2], a[3])</ID>
|
||||
<ID>ImplicitDefaultLocale:NodeInfo.kt$NodeInfo$String.format("%d%%", batteryLevel)</ID>
|
||||
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1440000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$24</ID>
|
||||
<ID>MagicNumber:Extensions.kt$2880</ID>
|
||||
<ID>MagicNumber:Extensions.kt$60</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$0.8</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$110540</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$111320</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$180</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$1e-7</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$360</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$360.0</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$3600.0</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$60</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$60.0</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$6366000</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$GPSFormat$3</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.114</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.299</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.587</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x0000FF</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x00FF00</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0xFF0000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$15</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$16</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609.34</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$255</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$3.281</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$60</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$8</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$180</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e-7</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e7</ID>
|
||||
<ID>MatchingDeclarationName:LocationUtils.kt$GPSFormat</ID>
|
||||
<ID>MaxLineLength:DataPacket.kt$DataPacket$val dataType: Int</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$NodeInfo$prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist < 1609 -> "%.0f ft".format(dist.toDouble()*3.281)</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$NodeInfo$prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE && dist >= 1609 -> "%.1f mi".format(dist / 1609.34)</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$NodeInfo$prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist < 1000 -> "%.0f m".format(dist.toDouble())</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$NodeInfo$prefUnits == ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE && dist >= 1000 -> "%.1f km".format(dist / 1000.0)</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$Position$/**</ID>
|
||||
<ID>MaxLineLength:NodeInfo.kt$Position$return "Position(lat=${latitude.anonymize}, lon=${longitude.anonymize}, alt=${altitude.anonymize}, time=${time})"</ID>
|
||||
<ID>MultiLineIfElse:NodeInfo.kt$MeshUser$hwModel.name.replace('_', '-').replace('p', '.').lowercase()</ID>
|
||||
<ID>MultiLineIfElse:NodeInfo.kt$MeshUser$null</ID>
|
||||
<ID>NewLineAtEndOfFile:DataPacket.kt$com.geeksville.mesh.DataPacket.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:MyNodeInfo.kt$com.geeksville.mesh.MyNodeInfo.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:NodeInfo.kt$com.geeksville.mesh.NodeInfo.kt</ID>
|
||||
<ID>NoConsecutiveBlankLines:NodeInfo.kt$ </ID>
|
||||
<ID>SpacingAroundOperators:NodeInfo.kt$NodeInfo$*</ID>
|
||||
<ID>StringTemplate:NodeInfo.kt$Position$${time}</ID>
|
||||
<ID>TooManyFunctions:LocationUtils.kt$com.geeksville.mesh.util.LocationUtils.kt</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
@@ -1,378 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues>
|
||||
<ID>TooManyFunctions:ContactSharing.kt$com.geeksville.mesh.ui.ContactSharing.kt</ID>
|
||||
<ID>TooManyFunctions:NodeDetail.kt$com.geeksville.mesh.ui.NodeDetail.kt</ID>
|
||||
</ManuallySuppressedIssues>
|
||||
<CurrentIssues>
|
||||
<ID>ChainWrapping:Channel.kt$Channel$&&</ID>
|
||||
<ID>CommentSpacing:BLEException.kt$BLEConnectionClosing$/// Our interface is being shut down</ID>
|
||||
<ID>CommentSpacing:Constants.kt$/// a bool true means we expect this condition to continue until, false means device might come back</ID>
|
||||
<ID>CommentSpacing:ContextExtensions.kt$/// Utility function to hide the soft keyboard per stack overflow</ID>
|
||||
<ID>CommentSpacing:ContextExtensions.kt$/// show a toast</ID>
|
||||
<ID>CommentSpacing:Coroutines.kt$/// Wrap launch with an exception handler, FIXME, move into a utility lib</ID>
|
||||
<ID>CommentSpacing:DeferredExecution.kt$DeferredExecution$/// Queue some new work</ID>
|
||||
<ID>CommentSpacing:DeferredExecution.kt$DeferredExecution$/// run all work in the queue and clear it to be ready to accept new work</ID>
|
||||
<ID>CommentSpacing:Exceptions.kt$/// Convert any exceptions in this service call into a RemoteException that the client can</ID>
|
||||
<ID>CommentSpacing:Exceptions.kt$/// then handle</ID>
|
||||
<ID>CommentSpacing:Exceptions.kt$Exceptions$/// Set in Application.onCreate</ID>
|
||||
<ID>CommentSpacing:MockInterface.kt$MockInterface$/// Generate a fake node info entry</ID>
|
||||
<ID>CommentSpacing:MockInterface.kt$MockInterface$/// Generate a fake text message from a node</ID>
|
||||
<ID>CommentSpacing:MockInterface.kt$MockInterface$/// Send a fake ack packet back if the sender asked for want_ack</ID>
|
||||
<ID>ConstructorParameterNaming:MeshLog.kt$MeshLog$@ColumnInfo(name = "message") val raw_message: String</ID>
|
||||
<ID>ConstructorParameterNaming:MeshLog.kt$MeshLog$@ColumnInfo(name = "received_date") val received_date: Long</ID>
|
||||
<ID>ConstructorParameterNaming:MeshLog.kt$MeshLog$@ColumnInfo(name = "type") val message_type: String</ID>
|
||||
<ID>ConstructorParameterNaming:Packet.kt$ContactSettings$@PrimaryKey val contact_key: String</ID>
|
||||
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "contact_key") val contact_key: String</ID>
|
||||
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "port_num") val port_num: Int</ID>
|
||||
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "received_time") val received_time: Long</ID>
|
||||
<ID>CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>EmptyCatchBlock:MeshLog.kt$MeshLog${ }</ID>
|
||||
<ID>EmptyClassBlock:DebugLogFile.kt$BinaryLogFile${ }</ID>
|
||||
<ID>EmptyFunctionBlock:NopInterface.kt$NopInterface${ }</ID>
|
||||
<ID>EmptyFunctionBlock:NsdManager.kt$<no name provided>${ }</ID>
|
||||
<ID>EmptyFunctionBlock:TrustAllX509TrustManager.kt$TrustAllX509TrustManager${}</ID>
|
||||
<ID>FinalNewline:AppPrefs.kt$com.geeksville.mesh.android.AppPrefs.kt</ID>
|
||||
<ID>FinalNewline:ApplicationModule.kt$com.geeksville.mesh.ApplicationModule.kt</ID>
|
||||
<ID>FinalNewline:BLEException.kt$com.geeksville.mesh.service.BLEException.kt</ID>
|
||||
<ID>FinalNewline:BluetoothInterfaceFactory.kt$com.geeksville.mesh.repository.radio.BluetoothInterfaceFactory.kt</ID>
|
||||
<ID>FinalNewline:BluetoothRepositoryModule.kt$com.geeksville.mesh.repository.bluetooth.BluetoothRepositoryModule.kt</ID>
|
||||
<ID>FinalNewline:BootCompleteReceiver.kt$com.geeksville.mesh.service.BootCompleteReceiver.kt</ID>
|
||||
<ID>FinalNewline:CoroutineDispatchers.kt$com.geeksville.mesh.CoroutineDispatchers.kt</ID>
|
||||
<ID>FinalNewline:Coroutines.kt$com.geeksville.mesh.concurrent.Coroutines.kt</ID>
|
||||
<ID>FinalNewline:DateUtils.kt$com.geeksville.mesh.android.DateUtils.kt</ID>
|
||||
<ID>FinalNewline:DebugLogFile.kt$com.geeksville.mesh.android.DebugLogFile.kt</ID>
|
||||
<ID>FinalNewline:DeferredExecution.kt$com.geeksville.mesh.concurrent.DeferredExecution.kt</ID>
|
||||
<ID>FinalNewline:DeviceVersion.kt$com.geeksville.mesh.model.DeviceVersion.kt</ID>
|
||||
<ID>FinalNewline:InterfaceId.kt$com.geeksville.mesh.repository.radio.InterfaceId.kt</ID>
|
||||
<ID>FinalNewline:InterfaceSpec.kt$com.geeksville.mesh.repository.radio.InterfaceSpec.kt</ID>
|
||||
<ID>FinalNewline:MockInterfaceFactory.kt$com.geeksville.mesh.repository.radio.MockInterfaceFactory.kt</ID>
|
||||
<ID>FinalNewline:NopInterface.kt$com.geeksville.mesh.repository.radio.NopInterface.kt</ID>
|
||||
<ID>FinalNewline:NopInterfaceFactory.kt$com.geeksville.mesh.repository.radio.NopInterfaceFactory.kt</ID>
|
||||
<ID>FinalNewline:ProbeTableProvider.kt$com.geeksville.mesh.repository.usb.ProbeTableProvider.kt</ID>
|
||||
<ID>FinalNewline:QuickChatActionRepository.kt$com.geeksville.mesh.database.QuickChatActionRepository.kt</ID>
|
||||
<ID>FinalNewline:RadioNotConnectedException.kt$com.geeksville.mesh.service.RadioNotConnectedException.kt</ID>
|
||||
<ID>FinalNewline:RadioRepositoryModule.kt$com.geeksville.mesh.repository.radio.RadioRepositoryModule.kt</ID>
|
||||
<ID>FinalNewline:RegularPreference.kt$com.geeksville.mesh.ui.common.components.RegularPreference.kt</ID>
|
||||
<ID>FinalNewline:SerialConnection.kt$com.geeksville.mesh.repository.usb.SerialConnection.kt</ID>
|
||||
<ID>FinalNewline:SerialConnectionListener.kt$com.geeksville.mesh.repository.usb.SerialConnectionListener.kt</ID>
|
||||
<ID>FinalNewline:SerialInterface.kt$com.geeksville.mesh.repository.radio.SerialInterface.kt</ID>
|
||||
<ID>FinalNewline:SerialInterfaceFactory.kt$com.geeksville.mesh.repository.radio.SerialInterfaceFactory.kt</ID>
|
||||
<ID>FinalNewline:TCPInterfaceFactory.kt$com.geeksville.mesh.repository.radio.TCPInterfaceFactory.kt</ID>
|
||||
<ID>FinalNewline:UsbBroadcastReceiver.kt$com.geeksville.mesh.repository.usb.UsbBroadcastReceiver.kt</ID>
|
||||
<ID>FinalNewline:UsbRepositoryModule.kt$com.geeksville.mesh.repository.usb.UsbRepositoryModule.kt</ID>
|
||||
<ID>ForbiddenComment:SafeBluetooth.kt$SafeBluetooth$// TODO: display some kind of UI about restarting BLE</ID>
|
||||
<ID>FunctionNaming:PacketDao.kt$PacketDao$@Query("DELETE FROM packet WHERE uuid=:uuid") suspend fun _delete(uuid: Long)</ID>
|
||||
<ID>FunctionNaming:QuickChatActionDao.kt$QuickChatActionDao$@Query("Delete from quick_chat where uuid=:uuid") fun _delete(uuid: Long)</ID>
|
||||
<ID>ImplicitDefaultLocale:NodeInfo.kt$NodeInfo$String.format("%d%%", batteryLevel)</ID>
|
||||
<ID>LargeClass:MeshService.kt$MeshService : ServiceLogging</ID>
|
||||
<ID>LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigItemList( ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig, enabled: Boolean, onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigItemList( audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigItemList( messages: String, cannedMessageConfig: CannedMessageConfig, enabled: Boolean, onSaveClicked: (messages: String, config: CannedMessageConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigItemList( deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:DropDownPreference.kt$@Composable fun <T> DropDownPreference( title: String, enabled: Boolean, items: List<Pair<T, String>>, selectedItem: T, onItemSelected: (T) -> Unit, modifier: Modifier = Modifier, summary: String? = null, )</ID>
|
||||
<ID>LongMethod:EditListPreference.kt$@Composable inline fun <reified T> EditListPreference( title: String, list: List<T>, maxCount: Int, enabled: Boolean, keyboardActions: KeyboardActions, crossinline onValuesChanged: (List<T>) -> Unit, modifier: Modifier = Modifier, )</ID>
|
||||
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigItemList( ringtone: String, extNotificationConfig: ExternalNotificationConfig, enabled: Boolean, onSaveClicked: (ringtone: String, config: ExternalNotificationConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>LongMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigItemList( serialConfig: SerialConfig, enabled: Boolean, onSaveClicked: (SerialConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigItemList( storeForwardConfig: StoreForwardConfig, enabled: Boolean, onSaveClicked: (StoreForwardConfig) -> Unit, )</ID>
|
||||
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigItemList( telemetryConfig: TelemetryConfig, enabled: Boolean, onSaveClicked: (TelemetryConfig) -> Unit, )</ID>
|
||||
<ID>LongParameterList:NOAAWmsTileSource.kt$NOAAWmsTileSource$( aName: String, aBaseUrl: Array<String>, layername: String, version: String, time: String?, srs: String, style: String?, format: String, )</ID>
|
||||
<ID>LongParameterList:RadioInterfaceService.kt$RadioInterfaceService$( private val context: Application, private val dispatchers: CoroutineDispatchers, private val bluetoothRepository: BluetoothRepository, private val networkRepository: NetworkRepository, private val processLifecycle: Lifecycle, @RadioRepositoryQualifier private val prefs: SharedPreferences, private val interfaceFactory: InterfaceFactory, )</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$100</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$101</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$14</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$15</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$34</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$35</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$4</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$5</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$79</ID>
|
||||
<ID>MagicNumber:BatteryInfo.kt$80</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$1000</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$500</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$512</ID>
|
||||
<ID>MagicNumber:Channel.kt$0xff</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.03125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.203125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.40625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.8125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1.6250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1000f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1600</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$200</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$3.25f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$31</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$400</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$5</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$62</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$800</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_MODERATE$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_SLOW$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.VERY_LONG_SLOW$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$40</ID>
|
||||
<ID>MagicNumber:ChannelSet.kt$960</ID>
|
||||
<ID>MagicNumber:Contacts.kt$7</ID>
|
||||
<ID>MagicNumber:Contacts.kt$8</ID>
|
||||
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID>
|
||||
<ID>MagicNumber:Debug.kt$3</ID>
|
||||
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$100</ID>
|
||||
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$10000</ID>
|
||||
<ID>MagicNumber:DownloadButton.kt$1.25f</ID>
|
||||
<ID>MagicNumber:EditChannelDialog.kt$16</ID>
|
||||
<ID>MagicNumber:EditChannelDialog.kt$32</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$0xff</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$16</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$24</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$8</ID>
|
||||
<ID>MagicNumber:EditListPreference.kt$12</ID>
|
||||
<ID>MagicNumber:EditListPreference.kt$12345</ID>
|
||||
<ID>MagicNumber:EditListPreference.kt$67890</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1440000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$24</ID>
|
||||
<ID>MagicNumber:Extensions.kt$2880</ID>
|
||||
<ID>MagicNumber:Extensions.kt$60</ID>
|
||||
<ID>MagicNumber:LazyColumnDragAndDropDemo.kt$50</ID>
|
||||
<ID>MagicNumber:LocationRepository.kt$LocationRepository$1000L</ID>
|
||||
<ID>MagicNumber:LocationRepository.kt$LocationRepository$30</ID>
|
||||
<ID>MagicNumber:LocationRepository.kt$LocationRepository$31</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$1e-7</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$360</ID>
|
||||
<ID>MagicNumber:MQTTRepository.kt$MQTTRepository$512</ID>
|
||||
<ID>MagicNumber:MapView.kt$0.5f</ID>
|
||||
<ID>MagicNumber:MapView.kt$1.3</ID>
|
||||
<ID>MagicNumber:MapView.kt$1024.0</ID>
|
||||
<ID>MagicNumber:MapView.kt$128205</ID>
|
||||
<ID>MagicNumber:MapView.kt$12F</ID>
|
||||
<ID>MagicNumber:MapView.kt$<no name provided>$1e7</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$1e-5</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$1e-7</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$3.0f</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$40f</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$60f</ID>
|
||||
<ID>MagicNumber:MapViewExtensions.kt$80f</ID>
|
||||
<ID>MagicNumber:MarkerWithLabel.kt$MarkerWithLabel$3</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$0xffffffff</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$100</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$1000</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$1000.0</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$1000L</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$16</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$30</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$32</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$60000</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$8</ID>
|
||||
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1000L</ID>
|
||||
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-5</ID>
|
||||
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-7</ID>
|
||||
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$180</ID>
|
||||
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$256</ID>
|
||||
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$360.0</ID>
|
||||
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$4</ID>
|
||||
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$5</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.114</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.299</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.587</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x0000FF</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x00FF00</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0xFF0000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$16</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609.34</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$255</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$3.281</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$8</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$180</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e-7</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e7</ID>
|
||||
<ID>MagicNumber:PacketRepository.kt$PacketRepository$500</ID>
|
||||
<ID>MagicNumber:PacketResponseStateDialog.kt$100</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$21972</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$32809</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$6790</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$9114</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$10</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$100</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$1000</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$2500</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth.<no name provided>$2500</ID>
|
||||
<ID>MagicNumber:SerialConnectionImpl.kt$SerialConnectionImpl$115200</ID>
|
||||
<ID>MagicNumber:SerialConnectionImpl.kt$SerialConnectionImpl$200</ID>
|
||||
<ID>MagicNumber:ServiceClient.kt$ServiceClient$500</ID>
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$0xff</ID>
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$3</ID>
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$4</ID>
|
||||
<ID>MagicNumber:StreamInterface.kt$StreamInterface$8</ID>
|
||||
<ID>MagicNumber:TCPInterface.kt$TCPInterface$1000</ID>
|
||||
<ID>MagicNumber:TCPInterface.kt$TCPInterface$180</ID>
|
||||
<ID>MagicNumber:TCPInterface.kt$TCPInterface$500</ID>
|
||||
<ID>MagicNumber:UIState.kt$4</ID>
|
||||
<ID>MatchingDeclarationName:AnalyticsClient.kt$AnalyticsProvider</ID>
|
||||
<ID>MatchingDeclarationName:DistanceExtensions.kt$DistanceUnit</ID>
|
||||
<ID>MatchingDeclarationName:LocationUtils.kt$GPSFormat</ID>
|
||||
<ID>MatchingDeclarationName:MeshServiceStarter.kt$ServiceStarter : Worker</ID>
|
||||
<ID>MatchingDeclarationName:SortOption.kt$NodeSortOption</ID>
|
||||
<ID>MaxLineLength:AppPrefs.kt$FloatPref$fun get(thisRef: AppPrefs, prop: KProperty<Float>): Float</ID>
|
||||
<ID>MaxLineLength:AppPrefs.kt$StringPref$fun get(thisRef: AppPrefs, prop: KProperty<String>): String</ID>
|
||||
<ID>MaxLineLength:BluetoothInterface.kt$/* Info for the esp32 device side code. See that source for the 'gold' standard docs on this interface. MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best FIXME - in the esp32 mesh management code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there. FIXME - make sure this protocol is guaranteed robust and won't drop packets "According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)). In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes." MAXPACKET is 256? look into what the lora lib uses. FIXME Characteristics: UUID properties description 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 read fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet). After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this mailbox. f75c76d2-129e-4dad-a1dd-7866124401e7 write toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len) ed9da18c-a800-4f66-a670-aa7547e34453 read|notify|write fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages until it catches up with this number. The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio. Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted. Re: queue management Not all messages are kept in the fromradio queue (filtered based on SubPacket): * only the most recent Position and User messages for a particular node are kept * all Data SubPackets are kept * No WantNodeNum / DenyNodeNum messages are kept A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) */</ID>
|
||||
<ID>MaxLineLength:BluetoothState.kt$BluetoothState$"BluetoothState(hasPermissions=$hasPermissions, enabled=$enabled, bondedDevices=${bondedDevices.map { it.anonymize }})"</ID>
|
||||
<ID>MaxLineLength:Channel.kt$Channel$// We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name</ID>
|
||||
<ID>MaxLineLength:DataPacket.kt$DataPacket$val dataType: Int</ID>
|
||||
<ID>MaxLineLength:LoRaConfigItemList.kt$value = if (isFocused || loraInput.overrideFrequency != 0f) loraInput.overrideFrequency else primaryChannel.radioFreq</ID>
|
||||
<ID>MaxLineLength:LocationRepository.kt$LocationRepository$info("Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m")</ID>
|
||||
<ID>MaxLineLength:MQTTRepository.kt$MQTTRepository.Companion$*</ID>
|
||||
<ID>MaxLineLength:ServiceClient.kt$ServiceClient$// Some phones seem to ahve a race where if you unbind and quickly rebind bindService returns false. Try</ID>
|
||||
<ID>MaxLineLength:ServiceClient.kt$ServiceClient.<no name provided>$// If we start to close a service, it seems that there is a possibility a onServiceConnected event is the queue</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$*</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$* An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP probably)</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$// Note: we have to check if ptr +1 is equal to packet length (for example, for a 1 byte packetlen, this code will be run with ptr of4</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$deliverPacket()</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$lostSync()</ID>
|
||||
<ID>MaxLineLength:StreamInterface.kt$StreamInterface$service.onDisconnect(isPermanent = true)</ID>
|
||||
<ID>MayBeConst:AppPrefs.kt$AppPrefs.Companion$private val baseName = "appPrefs_"</ID>
|
||||
<ID>MultiLineIfElse:Channel.kt$Channel$"Custom"</ID>
|
||||
<ID>MultiLineIfElse:Channel.kt$Channel$when (loraConfig.modemPreset) { ModemPreset.SHORT_TURBO -> "ShortTurbo" ModemPreset.SHORT_FAST -> "ShortFast" ModemPreset.SHORT_SLOW -> "ShortSlow" ModemPreset.MEDIUM_FAST -> "MediumFast" ModemPreset.MEDIUM_SLOW -> "MediumSlow" ModemPreset.LONG_FAST -> "LongFast" ModemPreset.LONG_SLOW -> "LongSlow" ModemPreset.LONG_MODERATE -> "LongMod" ModemPreset.VERY_LONG_SLOW -> "VLongSlow" else -> "Invalid" }</ID>
|
||||
<ID>MultiLineIfElse:EditListPreference.kt$EditBase64Preference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChange = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
|
||||
<ID>MultiLineIfElse:EditListPreference.kt$EditTextPreference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toDoubleOrNull()?.let { double -> valueState = it onValueChanged(double) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toFloatOrNull()?.let { float -> valueState = it onValueChanged(float) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$it.toUIntOrNull()?.toInt()?.let { int -> valueState = it onValueChanged(int) }</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$onValueChanged(it)</ID>
|
||||
<ID>MultiLineIfElse:EditTextPreference.kt$valueState = it</ID>
|
||||
<ID>MultiLineIfElse:Exceptions.kt$Exceptions.errormsg("ignoring exception", ex)</ID>
|
||||
<ID>NestedBlockDepth:LanguageUtils.kt$LanguageUtils$fun getLanguageTags(context: Context): Map<String, String></ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedAdmin(fromNodeNum: Int, a: AdminProtos.AdminMessage)</ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>NestedBlockDepth:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>NewLineAtEndOfFile:AppPrefs.kt$com.geeksville.mesh.android.AppPrefs.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:ApplicationModule.kt$com.geeksville.mesh.ApplicationModule.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:BLEException.kt$com.geeksville.mesh.service.BLEException.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:BluetoothInterfaceFactory.kt$com.geeksville.mesh.repository.radio.BluetoothInterfaceFactory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:BluetoothRepositoryModule.kt$com.geeksville.mesh.repository.bluetooth.BluetoothRepositoryModule.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:BootCompleteReceiver.kt$com.geeksville.mesh.service.BootCompleteReceiver.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:CoroutineDispatchers.kt$com.geeksville.mesh.CoroutineDispatchers.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:Coroutines.kt$com.geeksville.mesh.concurrent.Coroutines.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:DateUtils.kt$com.geeksville.mesh.android.DateUtils.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:DebugLogFile.kt$com.geeksville.mesh.android.DebugLogFile.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:DeferredExecution.kt$com.geeksville.mesh.concurrent.DeferredExecution.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:DeviceVersion.kt$com.geeksville.mesh.model.DeviceVersion.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:InterfaceId.kt$com.geeksville.mesh.repository.radio.InterfaceId.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:InterfaceSpec.kt$com.geeksville.mesh.repository.radio.InterfaceSpec.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:MockInterfaceFactory.kt$com.geeksville.mesh.repository.radio.MockInterfaceFactory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:NopInterface.kt$com.geeksville.mesh.repository.radio.NopInterface.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:NopInterfaceFactory.kt$com.geeksville.mesh.repository.radio.NopInterfaceFactory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:ProbeTableProvider.kt$com.geeksville.mesh.repository.usb.ProbeTableProvider.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:QuickChatActionRepository.kt$com.geeksville.mesh.database.QuickChatActionRepository.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:RadioNotConnectedException.kt$com.geeksville.mesh.service.RadioNotConnectedException.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:RadioRepositoryModule.kt$com.geeksville.mesh.repository.radio.RadioRepositoryModule.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:RegularPreference.kt$com.geeksville.mesh.ui.common.components.RegularPreference.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:SerialConnection.kt$com.geeksville.mesh.repository.usb.SerialConnection.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:SerialConnectionListener.kt$com.geeksville.mesh.repository.usb.SerialConnectionListener.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:SerialInterface.kt$com.geeksville.mesh.repository.radio.SerialInterface.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:SerialInterfaceFactory.kt$com.geeksville.mesh.repository.radio.SerialInterfaceFactory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:TCPInterfaceFactory.kt$com.geeksville.mesh.repository.radio.TCPInterfaceFactory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:UsbBroadcastReceiver.kt$com.geeksville.mesh.repository.usb.UsbBroadcastReceiver.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:UsbRepositoryModule.kt$com.geeksville.mesh.repository.usb.UsbRepositoryModule.kt</ID>
|
||||
<ID>NoBlankLineBeforeRbrace:DebugLogFile.kt$BinaryLogFile$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:NopInterface.kt$NopInterface$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:AppPrefs.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:BootCompleteReceiver.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:Constants.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:DebugLogFile.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:DeferredExecution.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:Exceptions.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:IRadioInterface.kt$ </ID>
|
||||
<ID>NoEmptyClassBody:DebugLogFile.kt$BinaryLogFile${ }</ID>
|
||||
<ID>NoSemicolons:DateUtils.kt$DateUtils$;</ID>
|
||||
<ID>NoWildcardImports:MockInterface.kt$import com.geeksville.mesh.*</ID>
|
||||
<ID>NoWildcardImports:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
<ID>OptionalAbstractKeyword:SyncContinuation.kt$Continuation$abstract</ID>
|
||||
<ID>ParameterListWrapping:AppPrefs.kt$FloatPref$(thisRef: AppPrefs, prop: KProperty<Float>)</ID>
|
||||
<ID>ParameterListWrapping:AppPrefs.kt$StringPref$(thisRef: AppPrefs, prop: KProperty<String>)</ID>
|
||||
<ID>RethrowCaughtException:SyncContinuation.kt$Continuation$throw ex</ID>
|
||||
<ID>ReturnCount:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
|
||||
<ID>SpacingAroundCurly:AppPrefs.kt$FloatPref$}</ID>
|
||||
<ID>SpacingAroundKeyword:AppPrefs.kt$AppPrefs$if</ID>
|
||||
<ID>SpacingAroundKeyword:Exceptions.kt$if</ID>
|
||||
<ID>SpacingAroundKeyword:Exceptions.kt$when</ID>
|
||||
<ID>SpacingAroundRangeOperator:BatteryInfo.kt$..</ID>
|
||||
<ID>SwallowedException:BluetoothInterface.kt$BluetoothInterface$ex: CancellationException</ID>
|
||||
<ID>SwallowedException:ChannelSet.kt$ex: Throwable</ID>
|
||||
<ID>SwallowedException:DeviceVersion.kt$DeviceVersion$e: Exception</ID>
|
||||
<ID>SwallowedException:Exceptions.kt$ex: Throwable</ID>
|
||||
<ID>SwallowedException:MeshLog.kt$MeshLog$e: IOException</ID>
|
||||
<ID>SwallowedException:MeshService.kt$MeshService$e: Exception</ID>
|
||||
<ID>SwallowedException:MeshService.kt$MeshService$e: TimeoutException</ID>
|
||||
<ID>SwallowedException:MeshService.kt$MeshService$ex: BLEException</ID>
|
||||
<ID>SwallowedException:MeshService.kt$MeshService$ex: CancellationException</ID>
|
||||
<ID>SwallowedException:NsdManager.kt$ex: IllegalArgumentException</ID>
|
||||
<ID>SwallowedException:SafeBluetooth.kt$SafeBluetooth$ex: DeadObjectException</ID>
|
||||
<ID>SwallowedException:SafeBluetooth.kt$SafeBluetooth$ex: NullPointerException</ID>
|
||||
<ID>SwallowedException:ServiceClient.kt$ServiceClient$ex: IllegalArgumentException</ID>
|
||||
<ID>SwallowedException:TCPInterface.kt$TCPInterface$ex: SocketTimeoutException</ID>
|
||||
<ID>TooGenericExceptionCaught:BTScanModel.kt$BTScanModel$ex: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:BluetoothInterface.kt$BluetoothInterface$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:ChannelSet.kt$ex: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:DeviceVersion.kt$DeviceVersion$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:Exceptions.kt$ex: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:LanguageUtils.kt$LanguageUtils$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:LocationRepository.kt$LocationRepository$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MQTTRepository.kt$MQTTRepository$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MainActivity.kt$MainActivity$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MapView.kt$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MapViewModel.kt$MapViewModel$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MeshService.kt$MeshService$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MeshService.kt$MeshService$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MeshService.kt$MeshService.<no name provided>$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:MeshServiceStarter.kt$ServiceStarter$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:RadioConfigViewModel.kt$RadioConfigViewModel$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:SafeBluetooth.kt$SafeBluetooth$ex: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:SafeBluetooth.kt$SafeBluetooth$ex: NullPointerException</ID>
|
||||
<ID>TooGenericExceptionCaught:SqlTileWriterExt.kt$SqlTileWriterExt$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:SyncContinuation.kt$Continuation$ex: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:TCPInterface.kt$TCPInterface$ex: Throwable</ID>
|
||||
<ID>TooGenericExceptionThrown:DeviceVersion.kt$DeviceVersion$throw Exception("Can't parse version $s")</ID>
|
||||
<ID>TooGenericExceptionThrown:MeshService.kt$MeshService$throw Exception("Can't set user without a NodeInfo")</ID>
|
||||
<ID>TooGenericExceptionThrown:MeshService.kt$MeshService.<no name provided>$throw Exception("Port numbers must be non-zero!")</ID>
|
||||
<ID>TooGenericExceptionThrown:ServiceClient.kt$ServiceClient$throw Exception("Haven't called connect")</ID>
|
||||
<ID>TooGenericExceptionThrown:ServiceClient.kt$ServiceClient$throw Exception("Service not bound")</ID>
|
||||
<ID>TooGenericExceptionThrown:SyncContinuation.kt$SyncContinuation$throw Exception("SyncContinuation timeout")</ID>
|
||||
<ID>TooGenericExceptionThrown:SyncContinuation.kt$SyncContinuation$throw Exception("This shouldn't happen")</ID>
|
||||
<ID>TooManyFunctions:AppPrefs.kt$AppPrefs</ID>
|
||||
<ID>TooManyFunctions:BluetoothInterface.kt$BluetoothInterface : IRadioInterfaceLogging</ID>
|
||||
<ID>TooManyFunctions:MainActivity.kt$MainActivity : AppCompatActivityLogging</ID>
|
||||
<ID>TooManyFunctions:MeshService.kt$MeshService : ServiceLogging</ID>
|
||||
<ID>TooManyFunctions:MeshService.kt$MeshService$<no name provided> : Stub</ID>
|
||||
<ID>TooManyFunctions:NodeDetail.kt$com.geeksville.mesh.ui.node.NodeDetail.kt</ID>
|
||||
<ID>TooManyFunctions:PacketDao.kt$PacketDao</ID>
|
||||
<ID>TooManyFunctions:PacketRepository.kt$PacketRepository</ID>
|
||||
<ID>TooManyFunctions:RadioConfigRepository.kt$RadioConfigRepository</ID>
|
||||
<ID>TooManyFunctions:RadioConfigViewModel.kt$RadioConfigViewModel : ViewModelLogging</ID>
|
||||
<ID>TooManyFunctions:RadioInterfaceService.kt$RadioInterfaceService : Logging</ID>
|
||||
<ID>TooManyFunctions:SafeBluetooth.kt$SafeBluetooth : LoggingCloseable</ID>
|
||||
<ID>TooManyFunctions:UIState.kt$UIViewModel : ViewModelLogging</ID>
|
||||
<ID>TopLevelPropertyNaming:Constants.kt$const val prefix = "com.geeksville.mesh"</ID>
|
||||
<ID>UnusedPrivateMember:NOAAWmsTileSource.kt$NOAAWmsTileSource$private fun tile2lat(y: Int, z: Int): Double</ID>
|
||||
<ID>UnusedPrivateMember:NOAAWmsTileSource.kt$NOAAWmsTileSource$private fun tile2lon(x: Int, z: Int): Double</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:CustomTileSource.kt$CustomTileSource</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID>
|
||||
<ID>WildcardImport:MockInterface.kt$import com.geeksville.mesh.*</ID>
|
||||
<ID>WildcardImport:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
30
core/model/detekt-baseline.xml
Normal file
30
core/model/detekt-baseline.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>MagicNumber:Channel.kt$0xff</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.03125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.0625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.203125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.40625f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$.8125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1.6250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1000f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$1600</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$200</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$3.25f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$31</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$400</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$5</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$62</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$800</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_MODERATE$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.LONG_SLOW$.125f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.MEDIUM_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_FAST$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.SHORT_SLOW$.250f</ID>
|
||||
<ID>MagicNumber:ChannelOption.kt$ChannelOption.VERY_LONG_SLOW$.0625f</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
@@ -898,4 +898,5 @@
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
<string name="device_configuration">Device configuration</string>
|
||||
<string name="remotely_administrating">"[Remote] %1$s"</string>
|
||||
</resources>
|
||||
|
||||
37
mesh_service_example/detekt-baseline.xml
Normal file
37
mesh_service_example/detekt-baseline.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ImplicitDefaultLocale:NodeInfo.kt$NodeInfo$String.format("%d%%", batteryLevel)</ID>
|
||||
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$1440000</ID>
|
||||
<ID>MagicNumber:Extensions.kt$24</ID>
|
||||
<ID>MagicNumber:Extensions.kt$2880</ID>
|
||||
<ID>MagicNumber:Extensions.kt$60</ID>
|
||||
<ID>MagicNumber:LocationUtils.kt$1e-7</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.114</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.299</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.587</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x0000FF</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0x00FF00</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0xFF0000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1000.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$16</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$1609.34</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$255</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$3.281</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$NodeInfo$8</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$180</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position$90.0</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e-7</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e7</ID>
|
||||
<ID>MatchingDeclarationName:LocationUtils.kt$GPSFormat</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
Reference in New Issue
Block a user