mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Add :core:ui (#3203)
This commit is contained in:
parent
b139c7edd7
commit
c5360086b7
128 changed files with 594 additions and 750 deletions
|
|
@ -185,6 +185,7 @@ dependencies {
|
|||
implementation(projects.core.prefs)
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.strings)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.feature.map)
|
||||
|
||||
// Bundles
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@
|
|||
<ID>CommentSpacing:Exceptions.kt$Exceptions$/// Set in Application.onCreate</ID>
|
||||
<ID>CommentWrapping:SignalMetrics.kt$Metric.SNR$/* Selected 12 as the max to get 4 equal vertical sections. */</ID>
|
||||
<ID>ComposableNaming:NodeDetail.kt$notesSection</ID>
|
||||
<ID>ComposableParamOrder:AlertDialogs.kt$SimpleAlertDialog</ID>
|
||||
<ID>ComposableParamOrder:BatteryInfo.kt$BatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:ChannelSettingsItemList.kt$ChannelSettingsItemList</ID>
|
||||
<ID>ComposableParamOrder:Debug.kt$DebugMenuActions</ID>
|
||||
<ID>ComposableParamOrder:Debug.kt$DecodedPayloadBlock</ID>
|
||||
|
|
@ -21,7 +19,6 @@
|
|||
<ID>ComposableParamOrder:DebugSearch.kt$DebugSearchStateviewModelDefaults</ID>
|
||||
<ID>ComposableParamOrder:DeviceMetrics.kt$DeviceMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:EditBase64Preference.kt$EditBase64Preference</ID>
|
||||
<ID>ComposableParamOrder:EditTextPreference.kt$EditTextPreference</ID>
|
||||
<ID>ComposableParamOrder:ElevationInfo.kt$ElevationInfo</ID>
|
||||
<ID>ComposableParamOrder:EmptyStateContent.kt$EmptyStateContent</ID>
|
||||
<ID>ComposableParamOrder:EnvironmentCharts.kt$ChartContent</ID>
|
||||
|
|
@ -33,7 +30,6 @@
|
|||
<ID>ComposableParamOrder:LinkedCoordinates.kt$LinkedCoordinates</ID>
|
||||
<ID>ComposableParamOrder:MainAppBar.kt$MainAppBar</ID>
|
||||
<ID>ComposableParamOrder:MapReportingPreference.kt$MapReportingPreference</ID>
|
||||
<ID>ComposableParamOrder:MaterialBatteryInfo.kt$MaterialBatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:Message.kt$MessageScreen</ID>
|
||||
<ID>ComposableParamOrder:Message.kt$QuickChatRow</ID>
|
||||
<ID>ComposableParamOrder:MessageActions.kt$MessageActions</ID>
|
||||
|
|
@ -59,23 +55,13 @@
|
|||
<ID>ComposableParamOrder:SettingsItem.kt$SettingsItem</ID>
|
||||
<ID>ComposableParamOrder:SignalInfo.kt$SignalInfo</ID>
|
||||
<ID>ComposableParamOrder:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:SwitchPreference.kt$SwitchPreference</ID>
|
||||
<ID>ComposableParamOrder:TopLevelNavIcon.kt$ConnectionsNavIcon</ID>
|
||||
<ID>ComposableParamOrder:TracerouteButton.kt$TracerouteButton</ID>
|
||||
<ID>ComposableParamOrder:WarningDialog.kt$WarningDialog</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>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>
|
||||
<ID>EmptyFunctionBlock:NopInterface.kt$NopInterface${ }</ID>
|
||||
<ID>EmptyFunctionBlock:NsdManager.kt$<no name provided>${ }</ID>
|
||||
|
|
@ -89,16 +75,13 @@
|
|||
<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: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>
|
||||
|
|
@ -107,9 +90,6 @@
|
|||
<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>LambdaParameterEventTrailing:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onAction</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
|
||||
|
|
@ -139,16 +119,6 @@
|
|||
<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>
|
||||
<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>
|
||||
|
|
@ -156,30 +126,15 @@
|
|||
<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: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:MeshService.kt$MeshService$0xffffffff</ID>
|
||||
<ID>MagicNumber:MeshService.kt$MeshService$1000</ID>
|
||||
|
|
@ -193,28 +148,6 @@
|
|||
<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: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:PowerConfigItemList.kt$3600</ID>
|
||||
|
|
@ -239,20 +172,12 @@
|
|||
<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: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: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>
|
||||
<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>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:BLEDevices.kt$BLEDevices</ID>
|
||||
<ID>ModifierMissing:Channel.kt$ChannelScreen</ID>
|
||||
<ID>ModifierMissing:ChannelSettingsItemList.kt$ChannelSelection</ID>
|
||||
|
|
@ -270,11 +195,6 @@
|
|||
<ID>ModifierMissing:EmptyStateContent.kt$EmptyStateContent</ID>
|
||||
<ID>ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen</ID>
|
||||
<ID>ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen</ID>
|
||||
<ID>ModifierMissing:IndoorAirQuality.kt$IndoorAirQuality</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:Main.kt$MainScreen</ID>
|
||||
<ID>ModifierMissing:MapReportingPreference.kt$MapReportingPreference</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$MessageStatusButton</ID>
|
||||
|
|
@ -302,8 +222,6 @@
|
|||
<ID>ModifierMissing:SettingsScreen.kt$SettingsScreen</ID>
|
||||
<ID>ModifierMissing:Share.kt$ShareScreen</ID>
|
||||
<ID>ModifierMissing:SignalMetrics.kt$SignalMetricsScreen</ID>
|
||||
<ID>ModifierMissing:SimpleAlertDialog.kt$SimpleAlertDialog</ID>
|
||||
<ID>ModifierMissing:SlidingSelector.kt$OptionLabel</ID>
|
||||
<ID>ModifierMissing:TopLevelNavIcon.kt$TopLevelNavIcon</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
|
|
@ -317,13 +235,10 @@
|
|||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PreferenceFooter.kt$modifier = modifier .height(48.dp) .weight(1f)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalInfo.kt$modifier = modifier</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
<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: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>
|
||||
|
|
@ -339,19 +254,11 @@
|
|||
<ID>ModifierReused:PowerMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval())</ID>
|
||||
<ID>ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Power.CURRENT.color, minValue = Power.CURRENT.min, maxValue = Power.CURRENT.max, )</ID>
|
||||
<ID>ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), VOLTAGE_COLOR, minValue = voltageMin, maxValue = voltageMax, )</ID>
|
||||
<ID>ModifierReused:PreferenceCategory.kt$Card( modifier = modifier.padding(bottom = 8.dp), ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { ProvideTextStyle(MaterialTheme.typography.bodyLarge) { content() } } }</ID>
|
||||
<ID>ModifierReused:PreferenceCategory.kt$Text( text, modifier = modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp, end = 16.dp), style = MaterialTheme.typography.titleLarge, )</ID>
|
||||
<ID>ModifierReused:PreferenceFooter.kt$OutlinedButton( modifier = modifier .height(48.dp) .weight(1f), enabled = enabled, onClick = onPositiveClicked, ) { Text( text = stringResource(id = positiveText), ) }</ID>
|
||||
<ID>ModifierReused:PreferenceFooter.kt$OutlinedButton( modifier = modifier .height(48.dp) .weight(1f), onClick = onNegativeClicked, ) { Text( text = stringResource(id = negativeText), ) }</ID>
|
||||
<ID>ModifierReused:PreferenceFooter.kt$Row( modifier = modifier .fillMaxWidth() .height(64.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { OutlinedButton( modifier = modifier .height(48.dp) .weight(1f), onClick = onNegativeClicked, ) { Text( text = stringResource(id = negativeText), ) } OutlinedButton( modifier = modifier .height(48.dp) .weight(1f), enabled = enabled, onClick = onPositiveClicked, ) { Text( text = stringResource(id = positiveText), ) } }</ID>
|
||||
<ID>ModifierReused:SignalMetrics.kt$Canvas(modifier = modifier.width(dp)) { val width = size.width /* Plot */ for (packet in meshPackets) { val xRatio = (packet.rxTime - oldest.rxTime).toFloat() / timeDiff val x = xRatio * width /* SNR */ plotPoint( drawContext = drawContext, color = Metric.SNR.color, x = x, value = packet.rxSnr - Metric.SNR.min, divisor = snrDiff, ) /* RSSI */ plotPoint( drawContext = drawContext, color = Metric.RSSI.color, x = x, value = packet.rxRssi - Metric.RSSI.min, divisor = rssiDiff, ) } }</ID>
|
||||
<ID>ModifierReused:SignalMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor })</ID>
|
||||
<ID>ModifierReused:SignalMetrics.kt$TimeAxisOverlay( modifier.width(dp), oldest = oldest.rxTime, newest = newest.rxTime, selectedTime.lineInterval(), )</ID>
|
||||
<ID>ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.RSSI.color, minValue = Metric.RSSI.min, maxValue = Metric.RSSI.max, )</ID>
|
||||
<ID>ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.SNR.color, minValue = Metric.SNR.min, maxValue = Metric.SNR.max, )</ID>
|
||||
<ID>ModifierReused:TextDividerPreference.kt$Card( modifier = modifier.fillMaxWidth(), ) { 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>ModifierReused:TextDividerPreference.kt$Icon( trailingIcon, "trailingIcon", modifier = modifier .fillMaxWidth() .wrapContentWidth(Alignment.End), )</ID>
|
||||
<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:Exceptions.kt$Exceptions.errormsg("ignoring exception", ex)</ID>
|
||||
|
|
@ -363,7 +270,6 @@
|
|||
<ID>MultipleEmitters:NodeDetail.kt$MetricsSection</ID>
|
||||
<ID>MultipleEmitters:PaxMetrics.kt$PaxMetricsChart</ID>
|
||||
<ID>MultipleEmitters:PowerMetrics.kt$PowerMetricsChart</ID>
|
||||
<ID>MultipleEmitters:PreferenceCategory.kt$PreferenceCategory</ID>
|
||||
<ID>MultipleEmitters:RadioConfig.kt$RadioConfigItemList</ID>
|
||||
<ID>MultipleEmitters:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
<ID>MutableStateAutoboxing:Contacts.kt$mutableStateOf(2)</ID>
|
||||
|
|
@ -380,16 +286,13 @@
|
|||
<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: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>
|
||||
|
|
@ -409,7 +312,6 @@
|
|||
<ID>NoSemicolons:DateUtils.kt$DateUtils$;</ID>
|
||||
<ID>NoWildcardImports:UsbRepository.kt$import kotlinx.coroutines.flow.*</ID>
|
||||
<ID>OptionalAbstractKeyword:SyncContinuation.kt$Continuation$abstract</ID>
|
||||
<ID>ParameterNaming:BitwisePreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:ChannelSettingsItemList.kt$onPositiveClicked</ID>
|
||||
<ID>ParameterNaming:ChannelSettingsItemList.kt$onSelected</ID>
|
||||
<ID>ParameterNaming:CleanNodeDatabaseScreen.kt$onCheckedChanged</ID>
|
||||
|
|
@ -417,10 +319,7 @@
|
|||
<ID>ParameterNaming:Contacts.kt$onDeleteSelected</ID>
|
||||
<ID>ParameterNaming:Contacts.kt$onMuteSelected</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:MapReportingPreference.kt$onMapReportingEnabledChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onPositionPrecisionChanged</ID>
|
||||
<ID>ParameterNaming:MapReportingPreference.kt$onPublishIntervalSecsChanged</ID>
|
||||
|
|
@ -429,25 +328,15 @@
|
|||
<ID>ParameterNaming:NodeDetail.kt$onFirmwareSelected</ID>
|
||||
<ID>ParameterNaming:NodeFilterTextField.kt$onToggleShowIgnored</ID>
|
||||
<ID>ParameterNaming:PositionPrecisionPreference.kt$onValueChanged</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:SlidingSelector.kt$onOptionSelected</ID>
|
||||
<ID>ParameterNaming:UsbDevices.kt$onDeviceSelected</ID>
|
||||
<ID>ParameterNaming:WelcomeScreen.kt$onGetStarted</ID>
|
||||
<ID>PreviewAnnotationNaming:LargeFontPreview.kt$LargeFontPreview$LargeFontPreview</ID>
|
||||
<ID>PreviewPublic:BatteryInfo.kt$BatteryInfoPreview</ID>
|
||||
<ID>PreviewPublic:BatteryInfo.kt$BatteryInfoPreviewSimple</ID>
|
||||
<ID>PreviewPublic:Channel.kt$ModemPresetInfoPreview</ID>
|
||||
<ID>PreviewPublic:ElevationInfo.kt$ElevationInfoPreview</ID>
|
||||
<ID>PreviewPublic:EmptyStateContent.kt$EmptyStateContentPreview</ID>
|
||||
<ID>PreviewPublic:IndoorAirQuality.kt$IAQScalePreview</ID>
|
||||
<ID>PreviewPublic:LastHeardInfo.kt$LastHeardInfoPreview</ID>
|
||||
<ID>PreviewPublic:LazyColumnDragAndDropDemo.kt$LazyColumnDragAndDropDemo</ID>
|
||||
<ID>PreviewPublic:LinkedCoordinates.kt$LinkedCoordinatesPreview</ID>
|
||||
<ID>PreviewPublic:MapReportingPreference.kt$MapReportingPreview</ID>
|
||||
<ID>PreviewPublic:MaterialBatteryInfo.kt$MaterialBatteryInfoPreview</ID>
|
||||
<ID>PreviewPublic:NodeChip.kt$NodeChipPreview</ID>
|
||||
<ID>PreviewPublic:NodeItem.kt$NodeInfoPreview</ID>
|
||||
<ID>PreviewPublic:NodeItem.kt$NodeInfoSimplePreview</ID>
|
||||
|
|
@ -458,17 +347,13 @@
|
|||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoPreview</ID>
|
||||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoSelfPreview</ID>
|
||||
<ID>PreviewPublic:SignalInfo.kt$SignalInfoSimplePreview</ID>
|
||||
<ID>PreviewPublic:SlidingSelector.kt$SlidingSelectorPreview</ID>
|
||||
<ID>RethrowCaughtException:SyncContinuation.kt$Continuation$throw ex</ID>
|
||||
<ID>ReturnCount:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</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$ex: BLEException</ID>
|
||||
<ID>SwallowedException:MeshService.kt$MeshService$ex: CancellationException</ID>
|
||||
<ID>SwallowedException:NsdManager.kt$ex: IllegalArgumentException</ID>
|
||||
|
|
@ -479,7 +364,6 @@
|
|||
<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>
|
||||
|
|
@ -492,7 +376,6 @@
|
|||
<ID>TooGenericExceptionCaught:SafeBluetooth.kt$SafeBluetooth$ex: NullPointerException</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>
|
||||
|
|
@ -503,9 +386,7 @@
|
|||
<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>
|
||||
|
|
@ -521,15 +402,11 @@
|
|||
<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: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>
|
||||
<ID>Wrapping:SerialConnectionImpl.kt$SerialConnectionImpl$(port, object : SerialInputOutputManager.Listener { override fun onNewData(data: ByteArray) { listener.onDataReceived(data) } override fun onRunError(e: Exception?) { closed.set(true) ignoreException { port.dtr = false port.rts = false port.close() } closedLatch.countDown() listener.onDisconnected(e) } })</ID>
|
||||
<ID>Wrapping:SerialInterface.kt$SerialInterface$(</ID>
|
||||
<ID>Wrapping:SerialInterface.kt$SerialInterface$(device, object : SerialConnectionListener { override fun onMissingPermission() { errormsg("Need permissions for port") } override fun onConnected() { onConnect.invoke() } override fun onDataReceived(bytes: ByteArray) { debug("Received ${bytes.size} byte(s)") bytes.forEach(::readChar) } override fun onDisconnected(thrown: Exception?) { thrown?.let { e -> errormsg("Serial error: $e") } debug("$device disconnected") onDeviceDisconnect(false) } })</ID>
|
||||
<ID>Wrapping:ServiceClient.kt$ServiceClient$Closeable, Logging</ID>
|
||||
<ID>Wrapping:SlidingSelector.kt$;</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.geeksville.mesh.MeshProtos.Waypoint
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.EmojiPickerDialog
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.waypoint
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun MapButton(
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.map.MapViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ import com.geeksville.mesh.android.GeeksvilleApplication
|
|||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.MainScreen
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.intro.AppIntroductionScreen
|
||||
import com.geeksville.mesh.ui.sharing.toSharedContact
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ package com.geeksville.mesh.model
|
|||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Green
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.InfantryBlue
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.LightGreen
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Magenta
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Orange
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Pink
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Purple
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Red
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Yellow
|
||||
import org.meshtastic.core.model.util.UnitConversions
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Green
|
||||
import org.meshtastic.core.ui.theme.GraphColors.InfantryBlue
|
||||
import org.meshtastic.core.ui.theme.GraphColors.LightGreen
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Magenta
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Orange
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Pink
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Purple
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Red
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Yellow
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
enum class Environment(val color: Color) {
|
||||
|
|
|
|||
|
|
@ -91,16 +91,7 @@ import com.geeksville.mesh.repository.radio.MeshActivity
|
|||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.icons.Conversations
|
||||
import com.geeksville.mesh.ui.common.icons.Map
|
||||
import com.geeksville.mesh.ui.common.icons.MeshtasticIcons
|
||||
import com.geeksville.mesh.ui.common.icons.Nodes
|
||||
import com.geeksville.mesh.ui.common.icons.Settings
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusBlue
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
import com.geeksville.mesh.ui.connections.components.TopLevelNavIcon
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
|
|
@ -120,6 +111,15 @@ import org.meshtastic.core.navigation.NodesRoutes
|
|||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.icon.Conversations
|
||||
import org.meshtastic.core.ui.icon.Map
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.Nodes
|
||||
import org.meshtastic.core.ui.icon.Settings
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, val route: Route) {
|
||||
|
|
|
|||
|
|
@ -1,49 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun AdaptiveTwoPane(
|
||||
first: @Composable ColumnScope.() -> Unit,
|
||||
second: @Composable ColumnScope.() -> Unit,
|
||||
) = BoxWithConstraints {
|
||||
val compactWidth = maxWidth < 600.dp
|
||||
Row {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
first()
|
||||
|
||||
if (compactWidth) {
|
||||
second()
|
||||
}
|
||||
}
|
||||
|
||||
if (!compactWidth) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
second()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun SimpleAlertDialog(
|
||||
title: String,
|
||||
message: String?,
|
||||
html: String? = null,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirmRequest: () -> Unit = onDismissRequest, // Default confirm to dismiss
|
||||
) {
|
||||
val annotatedString =
|
||||
html?.let {
|
||||
AnnotatedString.fromHtml(
|
||||
html,
|
||||
linkStyles =
|
||||
TextLinkStyles(
|
||||
style =
|
||||
SpanStyle(
|
||||
textDecoration = TextDecoration.Underline,
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
if (annotatedString != null) {
|
||||
Text(text = annotatedString)
|
||||
} else {
|
||||
Text(text = message.orEmpty())
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onConfirmRequest) { Text(stringResource(id = R.string.okay)) } },
|
||||
)
|
||||
}
|
||||
|
||||
// For Rationale Dialogs
|
||||
@Composable
|
||||
fun MultipleChoiceAlertDialog(
|
||||
title: String,
|
||||
message: String?,
|
||||
choices: Map<String, () -> Unit>,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(text = title) },
|
||||
text = {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
message?.let { Text(text = it, modifier = Modifier.padding(bottom = 8.dp)) }
|
||||
choices.forEach { (choice, action) ->
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||
onClick = {
|
||||
action()
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = choice)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,92 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun BatteryInfo(modifier: Modifier = Modifier, batteryLevel: Int?, voltage: Float?) {
|
||||
val infoString = "%d%% %.2fV".format(batteryLevel, voltage)
|
||||
val (image, level) =
|
||||
when (batteryLevel) {
|
||||
in 0..4 -> R.drawable.ic_battery_alert to " $infoString"
|
||||
in 5..14 -> R.drawable.ic_battery_outline to infoString
|
||||
in 15..34 -> R.drawable.ic_battery_low to infoString
|
||||
in 35..79 -> R.drawable.ic_battery_medium to infoString
|
||||
in 80..100 -> R.drawable.ic_battery_high to infoString
|
||||
101 -> R.drawable.ic_power_plug_24 to "%.2fV".format(voltage)
|
||||
else -> R.drawable.ic_battery_unknown to (voltage?.let { "%.2fV".format(it) } ?: "")
|
||||
}
|
||||
|
||||
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
modifier = Modifier.height(18.dp),
|
||||
imageVector = ImageVector.vectorResource(id = image),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
text = level,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun BatteryInfoPreview(@PreviewParameter(BatteryInfoPreviewParameterProvider::class) batteryInfo: Pair<Int?, Float?>) {
|
||||
AppTheme { BatteryInfo(batteryLevel = batteryInfo.first, voltage = batteryInfo.second) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun BatteryInfoPreviewSimple() {
|
||||
AppTheme { BatteryInfo(batteryLevel = 85, voltage = 3.7F) }
|
||||
}
|
||||
|
||||
class BatteryInfoPreviewParameterProvider : PreviewParameterProvider<Pair<Int?, Float?>> {
|
||||
override val values: Sequence<Pair<Int?, Float?>>
|
||||
get() =
|
||||
sequenceOf(
|
||||
85 to 3.7F,
|
||||
2 to 3.7F,
|
||||
12 to 3.7F,
|
||||
28 to 3.7F,
|
||||
50 to 3.7F,
|
||||
101 to 4.9F,
|
||||
null to 4.5F,
|
||||
null to null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,113 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BitwisePreference(
|
||||
title: String,
|
||||
value: Int,
|
||||
enabled: Boolean,
|
||||
items: List<Pair<Int, String>>,
|
||||
onItemSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
if (enabled) {
|
||||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
modifier = modifier.padding(vertical = 8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = value.toString(),
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
enabled = enabled,
|
||||
supportingText = { if (summary != null) Text(text = summary) },
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items.forEach { item ->
|
||||
DropdownMenuItem(
|
||||
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,
|
||||
)
|
||||
},
|
||||
onClick = { onItemSelected(value xor item.first) },
|
||||
)
|
||||
}
|
||||
PreferenceFooter(
|
||||
enabled = enabled,
|
||||
negativeText = R.string.clear,
|
||||
onNegativeClicked = { onItemSelected(0) },
|
||||
positiveText = R.string.close,
|
||||
onPositiveClicked = { expanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BitwisePreferencePreview() {
|
||||
BitwisePreference(
|
||||
title = "Settings",
|
||||
value = 3,
|
||||
summary = "This is a summary",
|
||||
enabled = true,
|
||||
items = listOf(1 to "TEST1", 2 to "TEST2"),
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,69 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
||||
@Composable
|
||||
fun BottomSheetDialog(
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) = Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
.clickable(
|
||||
onClick = onDismiss,
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 1f),
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@Composable
|
||||
fun ClickableTextField(
|
||||
@StringRes label: Int,
|
||||
enabled: Boolean,
|
||||
trailingIcon: ImageVector,
|
||||
value: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
isError: Boolean = false,
|
||||
) {
|
||||
val source = remember { MutableInteractionSource() }
|
||||
val isPressed by source.collectIsPressedAsState()
|
||||
if (isPressed) onClick()
|
||||
|
||||
OutlinedTextField(
|
||||
value,
|
||||
onValueChange = {},
|
||||
enabled = enabled,
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(label)) },
|
||||
trailingIcon = { Icon(trailingIcon, null) },
|
||||
isError = isError,
|
||||
interactionSource = source,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,54 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import android.content.ClipData
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.ContentCopy
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun CopyIconButton(
|
||||
valueToCopy: String,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String = stringResource(id = R.string.copy),
|
||||
) {
|
||||
val clipboardManager = LocalClipboard.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
val clipData = ClipData.newPlainText(label, valueToCopy)
|
||||
val clipEntry = ClipEntry(clipData)
|
||||
clipboardManager.setClipEntry(clipEntry)
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.TwoTone.ContentCopy, contentDescription = label)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun EditIPv4Preference(
|
||||
title: String,
|
||||
value: Int,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val pattern = """\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b""".toRegex()
|
||||
|
||||
fun convertIntToIpAddress(int: Int): String {
|
||||
return "${int and 0xff}.${int shr 8 and 0xff}.${int shr 16 and 0xff}.${int shr 24 and 0xff}"
|
||||
}
|
||||
|
||||
fun convertIpAddressToInt(ipAddress: String): Int? = ipAddress.split(".")
|
||||
.map { it.toIntOrNull() }.reversed() // little-endian byte order
|
||||
.fold(0) { total, next ->
|
||||
if (next == null) return null else total shl 8 or next
|
||||
}
|
||||
|
||||
var valueState by remember(value) { mutableStateOf(convertIntToIpAddress(value)) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
isError = convertIntToIpAddress(value) != valueState,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
valueState = it
|
||||
if (pattern.matches(it)) convertIpAddressToInt(it)?.let { int -> onValueChanged(int) }
|
||||
},
|
||||
onFocusChanged = {},
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EditIPv4PreferencePreview() {
|
||||
EditIPv4Preference(
|
||||
title = "IP Address",
|
||||
value = 16820416,
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {}
|
||||
)
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ import com.geeksville.mesh.copy
|
|||
import com.geeksville.mesh.remoteHardwarePin
|
||||
import com.google.protobuf.ByteString
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,92 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.VisibilityOff
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun EditPasswordPreference(
|
||||
title: String,
|
||||
value: String,
|
||||
maxSize: Int,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var isPasswordVisible by remember { mutableStateOf(false) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = value,
|
||||
maxSize = maxSize,
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = { onValueChanged(it) },
|
||||
onFocusChanged = {},
|
||||
visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
|
||||
Icon(
|
||||
imageVector = if (isPasswordVisible) Icons.TwoTone.VisibilityOff else Icons.TwoTone.VisibilityOff,
|
||||
contentDescription =
|
||||
if (isPasswordVisible) {
|
||||
stringResource(R.string.hide_password)
|
||||
} else {
|
||||
stringResource(R.string.show_password)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EditPasswordPreferencePreview() {
|
||||
EditPasswordPreference(
|
||||
title = "Password",
|
||||
value = "top secret",
|
||||
maxSize = 63,
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -1,291 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusState
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun SignedIntegerEditTextPreference(
|
||||
title: String,
|
||||
value: Int,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = valueState.toIntOrNull() == null,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
valueState = it
|
||||
it.toIntOrNull()?.let { int -> onValueChanged(int) }
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: Int,
|
||||
enabled: Boolean,
|
||||
isError: Boolean = false,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toUInt().toString()) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toUInt().toString() != valueState || isError,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.isEmpty()) {
|
||||
valueState = it
|
||||
} else {
|
||||
it.toUIntOrNull()?.toInt()?.let { int ->
|
||||
valueState = it
|
||||
onValueChanged(int)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: Float,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toString() != valueState,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.isEmpty()) {
|
||||
valueState = it
|
||||
} else {
|
||||
it.toFloatOrNull()?.let { float ->
|
||||
valueState = it
|
||||
onValueChanged(float)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: Double,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Double) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
val decimalSeparators = setOf('.', ',', '٫', '、', '·') // set of possible decimal separators
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toString() != valueState,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.length <= 1 || it.first() in decimalSeparators) {
|
||||
valueState = it
|
||||
} else {
|
||||
it.toDoubleOrNull()?.let { double ->
|
||||
valueState = it
|
||||
onValueChanged(double)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = {},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: String,
|
||||
enabled: Boolean,
|
||||
isError: Boolean,
|
||||
keyboardOptions: KeyboardOptions,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
maxSize: Int = 0, // max_size - 1 (in bytes)
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
) {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
|
||||
Column(modifier = modifier.padding(vertical = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
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,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (maxSize > 0 && isFocused) {
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EditTextPreferencePreview() {
|
||||
Column {
|
||||
EditTextPreference(
|
||||
title = "String",
|
||||
value = "Meshtastic",
|
||||
summary = "This is a summary",
|
||||
maxSize = 39,
|
||||
enabled = true,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {},
|
||||
)
|
||||
EditTextPreference(
|
||||
title = "Advanced Settings",
|
||||
value = UInt.MAX_VALUE.toInt(),
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import androidx.emoji2.emojipicker.RecentEmojiProviderAdapter
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import com.geeksville.mesh.ui.common.EmojiPickerViewModel
|
||||
import com.geeksville.mesh.util.CustomRecentEmojiProvider
|
||||
import org.meshtastic.core.ui.component.BottomSheetDialog
|
||||
|
||||
@Composable
|
||||
fun EmojiPicker(
|
||||
|
|
|
|||
|
|
@ -1,307 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ThumbUp
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQDangerouslyPolluted
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQExcellent
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQExtremelyPolluted
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQGood
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQHeavilyPolluted
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQLightlyPolluted
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQModeratelyPolluted
|
||||
import com.geeksville.mesh.ui.common.theme.IAQColors.IAQSeverelyPolluted
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
enum class Iaq(val color: Color, val description: String, val range: IntRange) {
|
||||
Excellent(IAQExcellent, "Excellent", 0..50),
|
||||
Good(IAQGood, "Good", 51..100),
|
||||
LightlyPolluted(IAQLightlyPolluted, "Lightly Polluted", 101..150),
|
||||
ModeratelyPolluted(IAQModeratelyPolluted, "Moderately Polluted", 151..200),
|
||||
HeavilyPolluted(IAQHeavilyPolluted, "Heavily Polluted", 201..300),
|
||||
SeverelyPolluted(IAQSeverelyPolluted, "Severely Polluted", 301..400),
|
||||
ExtremelyPolluted(IAQExtremelyPolluted, "Extremely Polluted", 401..500),
|
||||
DangerouslyPolluted(IAQDangerouslyPolluted, "Dangerously Polluted", 501..Int.MAX_VALUE),
|
||||
}
|
||||
|
||||
fun getIaq(iaq: Int): Iaq? = when {
|
||||
iaq == Int.MIN_VALUE -> null
|
||||
iaq in Iaq.Excellent.range -> Iaq.Excellent
|
||||
iaq in Iaq.Good.range -> Iaq.Good
|
||||
iaq in Iaq.LightlyPolluted.range -> Iaq.LightlyPolluted
|
||||
iaq in Iaq.ModeratelyPolluted.range -> Iaq.ModeratelyPolluted
|
||||
iaq in Iaq.HeavilyPolluted.range -> Iaq.HeavilyPolluted
|
||||
iaq in Iaq.SeverelyPolluted.range -> Iaq.SeverelyPolluted
|
||||
iaq in Iaq.ExtremelyPolluted.range -> Iaq.ExtremelyPolluted
|
||||
else -> Iaq.DangerouslyPolluted
|
||||
}
|
||||
|
||||
private fun getIaqDescriptionWithRange(iaqEnum: Iaq): String = if (iaqEnum.range.last == Int.MAX_VALUE) {
|
||||
"${iaqEnum.description} (${iaqEnum.range.first}+)"
|
||||
} else {
|
||||
"${iaqEnum.description} (${iaqEnum.range.first}-${iaqEnum.range.last})"
|
||||
}
|
||||
|
||||
enum class IaqDisplayMode {
|
||||
Pill,
|
||||
Dot,
|
||||
Text,
|
||||
Gauge,
|
||||
Gradient,
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "UnusedPrivateProperty")
|
||||
@Composable
|
||||
fun IndoorAirQuality(iaq: Int?, displayMode: IaqDisplayMode = IaqDisplayMode.Pill) {
|
||||
if (iaq == null || iaq == Int.MIN_VALUE) {
|
||||
return
|
||||
}
|
||||
var isLegendOpen by remember { mutableStateOf(false) }
|
||||
val iaqEnum = if (iaq != null) getIaq(iaq) else null
|
||||
val gradient = Brush.linearGradient(colors = Iaq.entries.map { it.color })
|
||||
|
||||
if (iaqEnum != null) {
|
||||
Column {
|
||||
when (displayMode) {
|
||||
IaqDisplayMode.Pill -> {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.clip(RoundedCornerShape(10.dp))
|
||||
.background(iaqEnum.color)
|
||||
.width(125.dp)
|
||||
.height(30.dp)
|
||||
.clickable { isLegendOpen = true },
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(4.dp).align(Alignment.CenterStart),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = "IAQ $iaq", color = Color.White, fontWeight = FontWeight.Bold)
|
||||
Icon(
|
||||
imageVector =
|
||||
if (iaqEnum.range.first < 100) Icons.Default.ThumbUp else Icons.Filled.Warning,
|
||||
contentDescription = stringResource(R.string.air_quality_icon),
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IaqDisplayMode.Dot -> {
|
||||
Column(modifier = Modifier.clickable { isLegendOpen = true }) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = "$iaq")
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Box(modifier = Modifier.size(10.dp).background(iaqEnum.color, shape = CircleShape))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IaqDisplayMode.Text -> {
|
||||
Text(
|
||||
text = getIaqDescriptionWithRange(iaqEnum),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.clickable { isLegendOpen = true },
|
||||
)
|
||||
}
|
||||
|
||||
IaqDisplayMode.Gauge -> {
|
||||
CircularProgressIndicator(
|
||||
progress = iaq / 500f,
|
||||
modifier = Modifier.size(60.dp).clickable { isLegendOpen = true },
|
||||
strokeWidth = 8.dp,
|
||||
color = iaqEnum.color,
|
||||
)
|
||||
Text(text = "${iaqEnum.description}")
|
||||
}
|
||||
|
||||
IaqDisplayMode.Gradient -> {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.clickable { isLegendOpen = true },
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = iaq / 500f,
|
||||
modifier = Modifier.fillMaxWidth().height(20.dp),
|
||||
color = iaqEnum.color,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = iaqEnum.description, fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isLegendOpen) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { isLegendOpen = false },
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
text = { IAQScale() },
|
||||
confirmButton = {
|
||||
TextButton(onClick = { isLegendOpen = false }) {
|
||||
Text(text = stringResource(id = R.string.close))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming Iaq is an enum class with color and description properties
|
||||
// and that it conforms to CaseIterable.
|
||||
// Replace with your actual implementation
|
||||
|
||||
@Composable
|
||||
fun IAQScale(modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.padding(16.dp), horizontalAlignment = Alignment.Start) {
|
||||
Text(
|
||||
text = stringResource(R.string.indoor_air_quality_iaq),
|
||||
style =
|
||||
MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold, textAlign = TextAlign.Center),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
for (iaq in Iaq.entries) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(modifier = Modifier.size(20.dp, 15.dp).clip(RoundedCornerShape(5.dp)).background(iaq.color))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(getIaqDescriptionWithRange(iaq), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun IAQScalePreview() {
|
||||
IAQScale()
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun IndoorAirQualityPreview() {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("Pill", style = MaterialTheme.typography.titleLarge)
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 6)
|
||||
IndoorAirQuality(iaq = 51)
|
||||
}
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 101)
|
||||
IndoorAirQuality(iaq = 201)
|
||||
}
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 350)
|
||||
IndoorAirQuality(iaq = 351)
|
||||
}
|
||||
|
||||
Text("Dot", style = MaterialTheme.typography.titleLarge)
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 6, displayMode = IaqDisplayMode.Dot)
|
||||
IndoorAirQuality(iaq = 51, displayMode = IaqDisplayMode.Dot)
|
||||
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Dot)
|
||||
IndoorAirQuality(iaq = 201, displayMode = IaqDisplayMode.Dot)
|
||||
IndoorAirQuality(iaq = 350, displayMode = IaqDisplayMode.Dot)
|
||||
IndoorAirQuality(iaq = 351, displayMode = IaqDisplayMode.Dot)
|
||||
}
|
||||
|
||||
Text("Text", style = MaterialTheme.typography.titleLarge)
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 6, displayMode = IaqDisplayMode.Text)
|
||||
IndoorAirQuality(iaq = 51, displayMode = IaqDisplayMode.Text)
|
||||
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Text)
|
||||
}
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 201, displayMode = IaqDisplayMode.Text)
|
||||
IndoorAirQuality(iaq = 350, displayMode = IaqDisplayMode.Text)
|
||||
IndoorAirQuality(iaq = 500, displayMode = IaqDisplayMode.Text)
|
||||
}
|
||||
|
||||
Text("Gauge", style = MaterialTheme.typography.titleLarge)
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 6, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 51, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 151, displayMode = IaqDisplayMode.Gauge)
|
||||
}
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 201, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 251, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 301, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 351, displayMode = IaqDisplayMode.Gauge)
|
||||
}
|
||||
Row {
|
||||
IndoorAirQuality(iaq = 401, displayMode = IaqDisplayMode.Gauge)
|
||||
IndoorAirQuality(iaq = 500, displayMode = IaqDisplayMode.Gauge)
|
||||
}
|
||||
|
||||
Text("Gradient", style = MaterialTheme.typography.titleLarge)
|
||||
IndoorAirQuality(iaq = 6, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 51, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 101, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 201, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 351, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 401, displayMode = IaqDisplayMode.Gradient)
|
||||
IndoorAirQuality(iaq = 500, displayMode = IaqDisplayMode.Gradient)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,285 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListItemInfo
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// Derived in part from: https://github.com/androidx/androidx/blob/c92ad2941368202b2d78b8d14c71bf81e9525944/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/LazyColumnDragAndDropDemo.kt
|
||||
@Preview
|
||||
@Composable
|
||||
fun LazyColumnDragAndDropDemo() {
|
||||
var list by remember { mutableStateOf(List(50) { it }) }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val dragDropState = rememberDragDropState(listState, headerCount = 1) { fromIndex, toIndex ->
|
||||
if (fromIndex in list.indices && toIndex in list.indices) {
|
||||
list = list.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(
|
||||
dragDropState = dragDropState,
|
||||
haptics = LocalHapticFeedback.current,
|
||||
),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
item {
|
||||
Text("Header", Modifier.fillMaxWidth().padding(20.dp))
|
||||
}
|
||||
|
||||
itemsIndexed(list, key = { _, item -> item }) { index, item ->
|
||||
DraggableItem(dragDropState, index + 1) { isDragging ->
|
||||
Card {
|
||||
Text("Item $item", Modifier.fillMaxWidth().padding(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Text("Footer", Modifier.fillMaxWidth().padding(20.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberDragDropState(
|
||||
lazyListState: LazyListState,
|
||||
headerCount: Int = 0,
|
||||
onMove: (Int, Int) -> Unit
|
||||
): DragDropState {
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = remember(lazyListState) { DragDropState(lazyListState, headerCount, scope, onMove) }
|
||||
LaunchedEffect(state) {
|
||||
while (true) {
|
||||
val diff = state.scrollChannel.receive()
|
||||
lazyListState.scrollBy(diff)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
class DragDropState
|
||||
internal constructor(
|
||||
private val state: LazyListState,
|
||||
private val headerCount: Int,
|
||||
private val scope: CoroutineScope,
|
||||
private val onMove: (Int, Int) -> Unit
|
||||
) {
|
||||
private var draggingItemIndex by mutableStateOf<Int?>(null)
|
||||
val adjustedItemIndex get() = draggingItemIndex?.minus(headerCount)
|
||||
|
||||
internal val scrollChannel = Channel<Float>()
|
||||
|
||||
private var draggingItemDraggedDelta by mutableFloatStateOf(0f)
|
||||
private var draggingItemInitialOffset by mutableIntStateOf(0)
|
||||
internal val draggingItemOffset: Float
|
||||
get() = draggingItemLayoutInfo?.let { item ->
|
||||
draggingItemInitialOffset + draggingItemDraggedDelta - item.offset
|
||||
} ?: 0f
|
||||
|
||||
private val draggingItemLayoutInfo: LazyListItemInfo?
|
||||
get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
|
||||
|
||||
internal var previousIndexOfDraggedItem by mutableStateOf<Int?>(null)
|
||||
private set
|
||||
|
||||
internal var previousItemOffset = Animatable(0f)
|
||||
private set
|
||||
|
||||
internal fun onDragStart(offset: Offset): LazyListItemInfo? = state.layoutInfo.visibleItemsInfo
|
||||
.filter { it.contentType == DragDropContentType }
|
||||
.firstOrNull { item -> offset.y.toInt() in item.offset..(item.offset + item.size) }
|
||||
?.also {
|
||||
draggingItemIndex = it.index
|
||||
draggingItemInitialOffset = it.offset
|
||||
}
|
||||
|
||||
internal fun onDragInterrupted() {
|
||||
if (draggingItemIndex != null) {
|
||||
previousIndexOfDraggedItem = draggingItemIndex
|
||||
val startOffset = draggingItemOffset
|
||||
scope.launch {
|
||||
previousItemOffset.snapTo(startOffset)
|
||||
previousItemOffset.animateTo(
|
||||
0f,
|
||||
spring(stiffness = Spring.StiffnessMediumLow, visibilityThreshold = 1f)
|
||||
)
|
||||
previousIndexOfDraggedItem = null
|
||||
}
|
||||
}
|
||||
draggingItemDraggedDelta = 0f
|
||||
draggingItemIndex = null
|
||||
draggingItemInitialOffset = 0
|
||||
}
|
||||
|
||||
internal fun onDrag(offset: Offset) {
|
||||
draggingItemDraggedDelta += offset.y
|
||||
|
||||
val draggingItem = draggingItemLayoutInfo ?: return
|
||||
val startOffset = draggingItem.offset + draggingItemOffset
|
||||
val endOffset = startOffset + draggingItem.size
|
||||
val middleOffset = startOffset + (endOffset - startOffset) / 2f
|
||||
|
||||
val targetItem = state.layoutInfo.visibleItemsInfo
|
||||
.find { item ->
|
||||
middleOffset.toInt() in item.offset..item.offsetEnd &&
|
||||
draggingItem.index != item.index
|
||||
}
|
||||
if (targetItem != null) {
|
||||
if (
|
||||
draggingItem.index == state.firstVisibleItemIndex ||
|
||||
targetItem.index == state.firstVisibleItemIndex
|
||||
) {
|
||||
state.requestScrollToItem(
|
||||
state.firstVisibleItemIndex,
|
||||
state.firstVisibleItemScrollOffset
|
||||
)
|
||||
}
|
||||
onMove.invoke(draggingItem.index - headerCount, targetItem.index - headerCount)
|
||||
draggingItemIndex = targetItem.index
|
||||
} else {
|
||||
val overscroll = when {
|
||||
draggingItemDraggedDelta > 0 ->
|
||||
(endOffset - state.layoutInfo.viewportEndOffset).coerceAtLeast(0f)
|
||||
|
||||
draggingItemDraggedDelta < 0 ->
|
||||
(startOffset - state.layoutInfo.viewportStartOffset).coerceAtMost(0f)
|
||||
|
||||
else -> 0f
|
||||
}
|
||||
if (overscroll != 0f) {
|
||||
scrollChannel.trySend(overscroll)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val LazyListItemInfo.offsetEnd: Int
|
||||
get() = this.offset + this.size
|
||||
}
|
||||
|
||||
fun Modifier.dragContainer(
|
||||
dragDropState: DragDropState,
|
||||
haptics: HapticFeedback,
|
||||
): Modifier {
|
||||
return this.pointerInput(dragDropState) {
|
||||
detectDragGesturesAfterLongPress(
|
||||
onDrag = { change, offset ->
|
||||
change.consume()
|
||||
dragDropState.onDrag(offset = offset)
|
||||
},
|
||||
onDragStart = { offset ->
|
||||
dragDropState.onDragStart(offset) ?: return@detectDragGesturesAfterLongPress
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onDragEnd = { dragDropState.onDragInterrupted() },
|
||||
onDragCancel = { dragDropState.onDragInterrupted() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LazyItemScope.DraggableItem(
|
||||
dragDropState: DragDropState,
|
||||
index: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.(isDragging: Boolean) -> Unit
|
||||
) {
|
||||
val dragging = index == dragDropState.adjustedItemIndex
|
||||
val draggingModifier = if (dragging) {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer { translationY = dragDropState.draggingItemOffset }
|
||||
} else if (index == dragDropState.previousIndexOfDraggedItem) {
|
||||
Modifier
|
||||
.zIndex(1f)
|
||||
.graphicsLayer { translationY = dragDropState.previousItemOffset.value }
|
||||
} else {
|
||||
Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
}
|
||||
Column(modifier = modifier.then(draggingModifier)) { content(dragging) }
|
||||
}
|
||||
|
||||
const val DragDropContentType = "drag-and-drop"
|
||||
|
||||
/**
|
||||
* Extension function for [LazyListScope] with drag-and-drop functionality for indexed items.
|
||||
*
|
||||
* Wraps [itemsIndexed] function with [detectDragGesturesAfterLongPress] to enable long-press
|
||||
* drag gestures and allow items in the list to be reordered using the provided [DragDropState].
|
||||
*/
|
||||
inline fun <T> LazyListScope.dragDropItemsIndexed(
|
||||
items: List<T>,
|
||||
dragDropState: DragDropState,
|
||||
noinline key: ((index: Int, item: T) -> Any)? = null,
|
||||
crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T, isDragging: Boolean) -> Unit
|
||||
) = itemsIndexed(
|
||||
items = items,
|
||||
key = key,
|
||||
contentType = { _, _ -> DragDropContentType },
|
||||
itemContent = { index, item ->
|
||||
DraggableItem(
|
||||
dragDropState = dragDropState,
|
||||
index = index,
|
||||
content = { isDragging -> itemContent(index, item, isDragging) }
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -1,162 +0,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/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("MagicNumber")
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SignalCellular4Bar
|
||||
import androidx.compose.material.icons.filled.SignalCellularAlt
|
||||
import androidx.compose.material.icons.filled.SignalCellularAlt1Bar
|
||||
import androidx.compose.material.icons.filled.SignalCellularAlt2Bar
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusOrange
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
private const val SNR_GOOD_THRESHOLD = -7f
|
||||
private const val SNR_FAIR_THRESHOLD = -15f
|
||||
|
||||
private const val RSSI_GOOD_THRESHOLD = -115
|
||||
private const val RSSI_FAIR_THRESHOLD = -126
|
||||
|
||||
@Stable
|
||||
private enum class Quality(
|
||||
@Stable val nameRes: Int,
|
||||
@Stable val imageVector: ImageVector,
|
||||
@Stable val color: @Composable () -> Color,
|
||||
) {
|
||||
NONE(R.string.none_quality, Icons.Default.SignalCellularAlt1Bar, { colorScheme.StatusRed }),
|
||||
BAD(R.string.bad, Icons.Default.SignalCellularAlt2Bar, { colorScheme.StatusOrange }),
|
||||
FAIR(R.string.fair, Icons.Default.SignalCellularAlt, { colorScheme.StatusYellow }),
|
||||
GOOD(R.string.good, Icons.Default.SignalCellular4Bar, { colorScheme.StatusGreen }),
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the `snr` and `rssi` color coded based on the signal quality, along with a human readable description and
|
||||
* related icon.
|
||||
*/
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun NodeSignalQuality(snr: Float, rssi: Int, modifier: Modifier = Modifier) {
|
||||
val quality = determineSignalQuality(snr, rssi)
|
||||
FlowRow(modifier = modifier, maxLines = 1) {
|
||||
Snr(snr)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Rssi(rssi)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = "${stringResource(R.string.signal)} ${stringResource(quality.nameRes)}",
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize,
|
||||
maxLines = 1,
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(
|
||||
imageVector = quality.imageVector,
|
||||
contentDescription = stringResource(R.string.signal_quality),
|
||||
tint = quality.color.invoke(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Displays the `snr` and `rssi` with color depending on the values respectively. */
|
||||
@Composable
|
||||
fun SnrAndRssi(snr: Float, rssi: Int) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Snr(snr)
|
||||
Rssi(rssi)
|
||||
}
|
||||
}
|
||||
|
||||
/** Displays a human readable description and icon representing the signal quality. */
|
||||
@Composable
|
||||
fun LoraSignalIndicator(snr: Float, rssi: Int) {
|
||||
val quality = determineSignalQuality(snr, rssi)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize().padding(8.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = quality.imageVector,
|
||||
contentDescription = stringResource(R.string.signal_quality),
|
||||
tint = quality.color.invoke(),
|
||||
)
|
||||
Text(text = "${stringResource(R.string.signal)} ${stringResource(quality.nameRes)}")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Snr(snr: Float, fontSize: TextUnit = MaterialTheme.typography.labelLarge.fontSize) {
|
||||
val color: Color =
|
||||
if (snr > SNR_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color.invoke()
|
||||
} else if (snr > SNR_FAIR_THRESHOLD) {
|
||||
Quality.FAIR.color.invoke()
|
||||
} else {
|
||||
Quality.BAD.color.invoke()
|
||||
}
|
||||
|
||||
Text(text = "%s %.2fdB".format(stringResource(id = R.string.snr), snr), color = color, fontSize = fontSize)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Rssi(rssi: Int, fontSize: TextUnit = MaterialTheme.typography.labelLarge.fontSize) {
|
||||
val color: Color =
|
||||
if (rssi > RSSI_GOOD_THRESHOLD) {
|
||||
Quality.GOOD.color.invoke()
|
||||
} else if (rssi > RSSI_FAIR_THRESHOLD) {
|
||||
Quality.FAIR.color.invoke()
|
||||
} else {
|
||||
Quality.BAD.color.invoke()
|
||||
}
|
||||
Text(text = "%s %ddBm".format(stringResource(id = R.string.rssi), rssi), color = color, fontSize = fontSize)
|
||||
}
|
||||
|
||||
private fun determineSignalQuality(snr: Float, rssi: Int): Quality = when {
|
||||
snr > SNR_GOOD_THRESHOLD && rssi > RSSI_GOOD_THRESHOLD -> Quality.GOOD
|
||||
snr > SNR_GOOD_THRESHOLD && rssi > RSSI_FAIR_THRESHOLD -> Quality.FAIR
|
||||
snr > SNR_FAIR_THRESHOLD && rssi > RSSI_GOOD_THRESHOLD -> Quality.FAIR
|
||||
snr <= SNR_FAIR_THRESHOLD && rssi <= RSSI_FAIR_THRESHOLD -> Quality.NONE
|
||||
else -> Quality.BAD
|
||||
}
|
||||
|
|
@ -1,102 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.geeksville.mesh.ui.common.theme.HyperlinkBlue
|
||||
import com.mikepenz.markdown.compose.components.markdownComponents
|
||||
import com.mikepenz.markdown.m3.Markdown
|
||||
import com.mikepenz.markdown.model.DefaultMarkdownColors
|
||||
import com.mikepenz.markdown.model.DefaultMarkdownTypography
|
||||
|
||||
@Composable
|
||||
fun MDText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
style: TextStyle = MaterialTheme.typography.bodyMedium,
|
||||
color: Color = Color.Unspecified,
|
||||
) {
|
||||
val colors =
|
||||
DefaultMarkdownColors(
|
||||
text = color,
|
||||
codeBackground = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
inlineCodeBackground = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
dividerColor = MaterialTheme.colorScheme.onSurface,
|
||||
tableBackground = MaterialTheme.colorScheme.surfaceContainer,
|
||||
)
|
||||
|
||||
val typography =
|
||||
DefaultMarkdownTypography(
|
||||
// Restrict max size of the text
|
||||
h1 = MaterialTheme.typography.headlineMedium.copy(color = color),
|
||||
h2 = MaterialTheme.typography.headlineMedium.copy(color = color),
|
||||
h3 = MaterialTheme.typography.headlineSmall.copy(color = color),
|
||||
h4 = MaterialTheme.typography.titleLarge.copy(color = color),
|
||||
h5 = MaterialTheme.typography.titleMedium.copy(color = color),
|
||||
h6 = MaterialTheme.typography.titleSmall.copy(color = color),
|
||||
text = style,
|
||||
code =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
inlineCode =
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
background = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
quote = MaterialTheme.typography.bodyLarge.copy(color = color),
|
||||
paragraph = MaterialTheme.typography.bodyMedium.copy(color = color),
|
||||
ordered = MaterialTheme.typography.bodyMedium.copy(color = color),
|
||||
bullet = MaterialTheme.typography.bodyMedium.copy(color = color),
|
||||
list = MaterialTheme.typography.bodyMedium.copy(color = color),
|
||||
textLink =
|
||||
TextLinkStyles(style = SpanStyle(color = HyperlinkBlue, textDecoration = TextDecoration.Underline)),
|
||||
table = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
|
||||
// Custom Markdown components to disable image rendering
|
||||
val customComponents = markdownComponents(image = { /* Empty composable to disable image rendering */ })
|
||||
|
||||
Markdown(
|
||||
content = text,
|
||||
modifier = modifier,
|
||||
colors = colors,
|
||||
typography = typography,
|
||||
components = customComponents, // Use custom components
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AutoLinkTextPreview() {
|
||||
MDText(
|
||||
"A text containing a link https://example.com **bold** _Italics_" +
|
||||
"\n # hello \n ## hello \n ### hello \n #### hello \n ##### hello \n ###### hello \n ```code```",
|
||||
)
|
||||
}
|
||||
|
|
@ -50,7 +50,6 @@ import com.geeksville.mesh.model.UIViewModel
|
|||
import com.geeksville.mesh.navigation.isConfigRoute
|
||||
import com.geeksville.mesh.navigation.isNodeDetailRoute
|
||||
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.debug.DebugMenuActions
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
|
|
@ -59,6 +58,7 @@ import org.meshtastic.core.navigation.ContactsRoutes
|
|||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -1,130 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.BatteryUnknown
|
||||
import androidx.compose.material.icons.rounded.BatteryUnknown
|
||||
import androidx.compose.material.icons.rounded.Power
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.icons.BatteryEmpty
|
||||
import com.geeksville.mesh.ui.common.icons.BatteryUnknown
|
||||
import com.geeksville.mesh.ui.common.icons.MeshtasticIcons
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusOrange
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
|
||||
private const val FORMAT = "%d%%"
|
||||
private const val SIZE_ICON = 20
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
fun MaterialBatteryInfo(modifier: Modifier = Modifier, level: Int) {
|
||||
val levelString = FORMAT.format(level)
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
if (level > 100) {
|
||||
Icon(
|
||||
modifier = Modifier.size(SIZE_ICON.dp).rotate(90f),
|
||||
imageVector = Icons.Rounded.Power,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(text = "PWD", color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelLarge)
|
||||
} else if (level < 0) {
|
||||
Icon(
|
||||
modifier = Modifier.size(SIZE_ICON.dp),
|
||||
imageVector = MeshtasticIcons.BatteryUnknown,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null,
|
||||
)
|
||||
} else {
|
||||
// Map battery percentage to color
|
||||
val fillColor =
|
||||
when (level) {
|
||||
in 0..19 -> MaterialTheme.colorScheme.StatusRed
|
||||
in 20..39 -> MaterialTheme.colorScheme.StatusOrange
|
||||
else -> MaterialTheme.colorScheme.StatusGreen
|
||||
}
|
||||
|
||||
Icon(
|
||||
modifier =
|
||||
Modifier.size(SIZE_ICON.dp).drawBehind {
|
||||
val insetVertical = size.height * .28f
|
||||
val insetLeft = size.width * .11f
|
||||
val insetRight = size.width * .22f
|
||||
|
||||
val availableWidth = size.width - (insetLeft + insetRight)
|
||||
val availableHeight = size.height - (insetVertical * 2)
|
||||
|
||||
// Fill (grow from left to right)
|
||||
val fillWidth = availableWidth * (level / 100f)
|
||||
|
||||
drawRect(
|
||||
color = fillColor,
|
||||
topLeft = Offset(insetLeft, insetVertical),
|
||||
size = Size(fillWidth, availableHeight),
|
||||
)
|
||||
},
|
||||
imageVector = MeshtasticIcons.BatteryEmpty,
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = levelString,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BatteryLevelProvider : PreviewParameterProvider<Int> {
|
||||
override val values: Sequence<Int> = sequenceOf(-1, 19, 39, 90, 101)
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun MaterialBatteryInfoPreview(@PreviewParameter(BatteryLevelProvider::class) batteryLevel: Int) {
|
||||
AppTheme { MaterialBatteryInfo(level = batteryLevel) }
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp
|
|||
import org.meshtastic.core.model.util.DistanceUnit
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
|
|
|||
|
|
@ -1,69 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun PreferenceCategory(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
content: (@Composable ColumnScope.() -> Unit)? = null
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
modifier = modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp, end = 16.dp),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
if (content != null) {
|
||||
Card(
|
||||
modifier = modifier.padding(bottom = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
ProvideTextStyle(MaterialTheme.typography.bodyLarge) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreferenceCategoryPreview() {
|
||||
PreferenceCategory(
|
||||
text = "Advanced settings"
|
||||
)
|
||||
}
|
||||
|
|
@ -1,79 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
onCancelClicked: () -> Unit,
|
||||
onSaveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PreferenceFooter(
|
||||
enabled = enabled,
|
||||
negativeText = R.string.clear_changes,
|
||||
onNegativeClicked = onCancelClicked,
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = onSaveClicked,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
@StringRes negativeText: Int,
|
||||
onNegativeClicked: () -> Unit,
|
||||
@StringRes positiveText: Int,
|
||||
onPositiveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth().height(64.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedButton(modifier = Modifier.height(48.dp).weight(1f), onClick = onNegativeClicked) {
|
||||
Text(text = stringResource(id = negativeText))
|
||||
}
|
||||
OutlinedButton(modifier = Modifier.height(48.dp).weight(1f), enabled = enabled, onClick = onPositiveClicked) {
|
||||
Text(text = stringResource(id = positiveText))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreferenceFooterPreview() {
|
||||
PreferenceFooter(enabled = true, onCancelClicked = {}, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -1,145 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RegularPreference(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
summary: String? = null,
|
||||
trailingIcon: ImageVector? = null,
|
||||
dropdownMenu: @Composable () -> Unit = {},
|
||||
) {
|
||||
RegularPreference(
|
||||
title = title,
|
||||
subtitle = AnnotatedString(text = subtitle),
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
trailingIcon = trailingIcon,
|
||||
dropdownMenu = dropdownMenu,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun RegularPreference(
|
||||
title: String,
|
||||
subtitle: AnnotatedString,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
summary: String? = null,
|
||||
trailingIcon: ImageVector? = null,
|
||||
dropdownMenu: @Composable () -> Unit = {},
|
||||
) {
|
||||
val color = if (enabled) {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = enabled, onClick = onClick)
|
||||
.padding(all = 16.dp),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = if (enabled) {
|
||||
Color.Unspecified
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
|
||||
},
|
||||
)
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
if (trailingIcon != null) {
|
||||
Box {
|
||||
Icon(
|
||||
imageVector = trailingIcon,
|
||||
contentDescription = "trailingIcon",
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.wrapContentWidth(Alignment.End),
|
||||
tint = color,
|
||||
)
|
||||
dropdownMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = color,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RegularPreferencePreview() {
|
||||
RegularPreference(
|
||||
title = "Advanced settings",
|
||||
subtitle = "Text2",
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
|
|
@ -64,11 +64,11 @@ import com.geeksville.mesh.AppOnlyProtos
|
|||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.model.getChannel
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
private const val PRECISE_POSITION_BITS = 32
|
||||
|
||||
|
|
|
|||
|
|
@ -27,9 +27,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.NodeSignalQuality
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
const val MAX_VALID_SNR = 100F
|
||||
const val MAX_VALID_RSSI = 0
|
||||
|
|
|
|||
|
|
@ -1,107 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun SimpleAlertDialog(
|
||||
@StringRes title: Int,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
confirmText: String? = null,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
dismissText: String? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
) = AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface),
|
||||
) {
|
||||
Text(text = dismissText ?: stringResource(id = R.string.cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
onConfirm?.let {
|
||||
TextButton(
|
||||
onClick = onConfirm,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface),
|
||||
) {
|
||||
Text(text = confirmText ?: stringResource(id = R.string.okay))
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(id = title), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||
},
|
||||
text = text,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SimpleAlertDialog(
|
||||
@StringRes title: Int,
|
||||
@StringRes text: Int,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
) = SimpleAlertDialog(
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
title = title,
|
||||
text = {
|
||||
Text(text = stringResource(id = text), modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
|
||||
},
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SimpleAlertDialog(
|
||||
@StringRes title: Int,
|
||||
text: String,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
) = SimpleAlertDialog(
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
title = title,
|
||||
text = { Text(text = text, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center) },
|
||||
)
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun SimpleAlertDialogPreview() {
|
||||
AppTheme { SimpleAlertDialog(title = R.string.message, text = R.string.sample_message) }
|
||||
}
|
||||
|
|
@ -1,429 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.awaitEachGesture
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.horizontalDrag
|
||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
|
||||
import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.PointerInputChange
|
||||
import androidx.compose.ui.input.pointer.changedToUp
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.semantics.onClick
|
||||
import androidx.compose.ui.semantics.role
|
||||
import androidx.compose.ui.semantics.selected
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.stateDescription
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow.Companion.Ellipsis
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
|
||||
private const val NO_OPTION_INDEX = -1
|
||||
|
||||
private val TRACK_PADDING = 2.dp
|
||||
|
||||
private val TRACK_COLOR = Color.LightGray.copy(alpha = .5f)
|
||||
|
||||
private val PRESSED_TRACK_PADDING = 1.dp
|
||||
|
||||
private val OPTION_PADDING = 5.dp
|
||||
|
||||
private const val PRESSED_UNSELECTED_ALPHA = .6f
|
||||
|
||||
private val BACKGROUND_SHAPE = RoundedCornerShape(8.dp)
|
||||
|
||||
/**
|
||||
* Provides the user with a set of options they can choose from.
|
||||
*
|
||||
* (Inspired by https://gist.github.com/zach-klippenstein/7ae8874db304f957d6bb91263e292117)
|
||||
*/
|
||||
@Composable
|
||||
fun <T : Any> SlidingSelector(
|
||||
options: List<T>,
|
||||
selectedOption: T,
|
||||
onOptionSelected: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable (T) -> Unit
|
||||
) {
|
||||
val state = remember { SelectorState() }
|
||||
state.optionCount = options.size
|
||||
state.selectedOption = options.indexOf(selectedOption)
|
||||
state.onOptionSelected = { onOptionSelected(options[it]) }
|
||||
|
||||
/* Animate between whole-number indices so we don't need to do pixel calculations. */
|
||||
val selectedIndexOffset by animateFloatAsState(
|
||||
state.selectedOption.toFloat(),
|
||||
label = "Selected Index Offset"
|
||||
)
|
||||
|
||||
Layout(
|
||||
content = {
|
||||
SelectedIndicator(state)
|
||||
Dividers(state)
|
||||
Options(state, options, content)
|
||||
},
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.then(state.inputModifier)
|
||||
.background(TRACK_COLOR, BACKGROUND_SHAPE)
|
||||
.padding(TRACK_PADDING)
|
||||
) { measurables, constraints ->
|
||||
val (indicatorMeasurable, dividersMeasurable, optionsMeasurable) = measurables
|
||||
|
||||
/* Measure the options first so we know how tall to make the indicator. */
|
||||
val optionsPlaceable = optionsMeasurable.measure(constraints)
|
||||
state.updatePressedScale(optionsPlaceable.height, this)
|
||||
|
||||
/* Measure the indicator and dividers to be the right size. */
|
||||
val indicatorPlaceable = indicatorMeasurable.measure(
|
||||
Constraints.fixed(
|
||||
width = optionsPlaceable.width / options.size,
|
||||
height = optionsPlaceable.height
|
||||
)
|
||||
)
|
||||
|
||||
val dividersPlaceable = dividersMeasurable.measure(
|
||||
Constraints.fixed(
|
||||
width = optionsPlaceable.width,
|
||||
height = optionsPlaceable.height
|
||||
)
|
||||
)
|
||||
|
||||
layout(optionsPlaceable.width, optionsPlaceable.height) {
|
||||
val optionWidth = optionsPlaceable.width / options.size
|
||||
|
||||
/* Place the indicator first so that it's below the option labels. */
|
||||
indicatorPlaceable.placeRelative(
|
||||
x = (selectedIndexOffset * optionWidth).toInt(),
|
||||
y = 0
|
||||
)
|
||||
dividersPlaceable.placeRelative(IntOffset.Zero)
|
||||
optionsPlaceable.placeRelative(IntOffset.Zero)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual representation of the option the user may select.
|
||||
*/
|
||||
@Composable
|
||||
fun OptionLabel(text: String) {
|
||||
Text(text, maxLines = 1, overflow = Ellipsis)
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the selected indicator on the [SlidingSelector] track.
|
||||
*/
|
||||
@Composable
|
||||
private fun SelectedIndicator(state: SelectorState) {
|
||||
Box(
|
||||
Modifier
|
||||
.then(
|
||||
state.optionScaleModifier(
|
||||
pressed = state.pressedOption == state.selectedOption,
|
||||
option = state.selectedOption
|
||||
)
|
||||
)
|
||||
.shadow(4.dp, BACKGROUND_SHAPE)
|
||||
.background(MaterialTheme.colorScheme.background, BACKGROUND_SHAPE)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws dividers between [OptionLabel]s.
|
||||
*/
|
||||
@Composable
|
||||
private fun Dividers(state: SelectorState) {
|
||||
/* Animate each divider independently. */
|
||||
val alphas = (0 until state.optionCount).map { i ->
|
||||
val selectionAdjacent = i == state.selectedOption || i - 1 == state.selectedOption
|
||||
animateFloatAsState(if (selectionAdjacent) 0f else 1f, label = "Dividers")
|
||||
}
|
||||
|
||||
Canvas(Modifier.fillMaxSize()) {
|
||||
val optionWidth = size.width / state.optionCount
|
||||
val dividerPadding = TRACK_PADDING + PRESSED_TRACK_PADDING
|
||||
|
||||
alphas.forEachIndexed { i, alpha ->
|
||||
val x = i * optionWidth
|
||||
drawLine(
|
||||
Color.White,
|
||||
alpha = alpha.value,
|
||||
start = Offset(x, dividerPadding.toPx()),
|
||||
end = Offset(x, size.height - dividerPadding.toPx())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the options available to the user.
|
||||
*/
|
||||
@Composable
|
||||
private fun <T> Options(
|
||||
state: SelectorState,
|
||||
options: List<T>,
|
||||
content: @Composable (T) -> Unit
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides TextStyle(fontWeight = FontWeight.Medium)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = spacedBy(TRACK_PADDING),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectableGroup()
|
||||
) {
|
||||
options.forEachIndexed { i, timeFrame ->
|
||||
val isSelected = i == state.selectedOption
|
||||
val isPressed = i == state.pressedOption
|
||||
|
||||
/* Unselected presses are represented by fading. */
|
||||
val alpha by animateFloatAsState(
|
||||
if (!isSelected && isPressed) PRESSED_UNSELECTED_ALPHA else 1f,
|
||||
label = "Unselected"
|
||||
)
|
||||
|
||||
val semanticsModifier = Modifier.semantics(mergeDescendants = true) {
|
||||
selected = isSelected
|
||||
role = Role.Button
|
||||
onClick { state.onOptionSelected(i); true }
|
||||
stateDescription = if (isSelected) "Selected" else "Not selected"
|
||||
}
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
/* Divide space evenly between all options. */
|
||||
.weight(1f)
|
||||
.then(semanticsModifier)
|
||||
.padding(OPTION_PADDING)
|
||||
/* Draw pressed indication when not selected. */
|
||||
.alpha(alpha)
|
||||
/* Selected presses are represented by scaling. */
|
||||
.then(state.optionScaleModifier(isPressed && isSelected, i))
|
||||
/* Center the option content. */
|
||||
.wrapContentWidth()
|
||||
) {
|
||||
content(timeFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains and handles the state necessary to present the [SlidingSelector] to the user.
|
||||
*/
|
||||
private class SelectorState {
|
||||
var optionCount by mutableIntStateOf(0)
|
||||
var selectedOption by mutableIntStateOf(0)
|
||||
var onOptionSelected: (Int) -> Unit by mutableStateOf({})
|
||||
var pressedOption by mutableIntStateOf(NO_OPTION_INDEX)
|
||||
|
||||
/**
|
||||
* Scale factor that should be used to scale pressed option. When this scale is applied,
|
||||
* exactly [PRESSED_TRACK_PADDING] will be added around the element's usual size.
|
||||
*/
|
||||
var pressedSelectedScale by mutableFloatStateOf(1f)
|
||||
private set
|
||||
|
||||
/**
|
||||
* Calculates the scale factor we need to use for pressed options to get the desired padding.
|
||||
*/
|
||||
fun updatePressedScale(controlHeight: Int, density: Density) {
|
||||
with(density) {
|
||||
val pressedPadding = PRESSED_TRACK_PADDING * 2
|
||||
val pressedHeight = controlHeight - pressedPadding.toPx()
|
||||
pressedSelectedScale = pressedHeight / controlHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [Modifier] that will scale an element so that it gets [PRESSED_TRACK_PADDING] extra
|
||||
* padding around it. The scale will be animated.
|
||||
*
|
||||
* The scale is also performed around either the left or right edge of the element if the option
|
||||
* is the first or last option, respectively. In those cases, the scale will also be translated so
|
||||
* that [PRESSED_TRACK_PADDING] will be added on the left or right edge.
|
||||
*/
|
||||
@SuppressLint("ModifierFactoryExtensionFunction")
|
||||
fun optionScaleModifier(
|
||||
pressed: Boolean,
|
||||
option: Int,
|
||||
): Modifier = Modifier.composed {
|
||||
val scale by animateFloatAsState(if (pressed) pressedSelectedScale else 1f, label = "Scale")
|
||||
val xOffset by animateDpAsState(
|
||||
if (pressed) PRESSED_TRACK_PADDING else 0.dp,
|
||||
label = "x Offset"
|
||||
)
|
||||
|
||||
graphicsLayer {
|
||||
this.scaleX = scale
|
||||
this.scaleY = scale
|
||||
|
||||
/* Scales on the ends should gravitate to that edge. */
|
||||
this.transformOrigin = TransformOrigin(
|
||||
pivotFractionX = when (option) {
|
||||
0 -> 0f
|
||||
optionCount - 1 -> 1f
|
||||
else -> .5f
|
||||
},
|
||||
pivotFractionY = .5f
|
||||
)
|
||||
|
||||
/* But should still move inwards to keep the pressed padding consistent with top and bottom. */
|
||||
this.translationX = when (option) {
|
||||
0 -> xOffset.toPx()
|
||||
optionCount - 1 -> -xOffset.toPx()
|
||||
else -> 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [Modifier] that will listen for touch gestures and update the selected and pressed properties
|
||||
* of this state appropriately.
|
||||
*/
|
||||
val inputModifier = Modifier.pointerInput(optionCount) {
|
||||
val optionWidth = size.width / optionCount
|
||||
|
||||
/* Helper to calculate which option an event occurred in. */
|
||||
fun optionIndex(change: PointerInputChange): Int =
|
||||
((change.position.x / size.width.toFloat()) * optionCount)
|
||||
.toInt()
|
||||
.coerceIn(0, optionCount - 1)
|
||||
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
|
||||
pressedOption = optionIndex(down)
|
||||
val downOnSelected = pressedOption == selectedOption
|
||||
val optionBounds = Rect(
|
||||
left = pressedOption * optionWidth.toFloat(),
|
||||
right = (pressedOption + 1) * optionWidth.toFloat(),
|
||||
top = 0f,
|
||||
bottom = size.height.toFloat()
|
||||
)
|
||||
|
||||
if (downOnSelected) {
|
||||
horizontalDrag(down.id) { change ->
|
||||
pressedOption = optionIndex(change)
|
||||
|
||||
if (pressedOption != selectedOption) {
|
||||
onOptionSelected(pressedOption)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
waitForUpOrCancellation(inBounds = optionBounds)
|
||||
/* Null means the gesture was cancelled (e.g. dragged out of bounds). */
|
||||
?.let { onOptionSelected(pressedOption) }
|
||||
}
|
||||
pressedOption = NO_OPTION_INDEX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Works with bounds that may not be at 0,0.
|
||||
*/
|
||||
@Suppress("ReturnCount")
|
||||
private suspend fun AwaitPointerEventScope.waitForUpOrCancellation(inBounds: Rect): PointerInputChange? {
|
||||
while (true) {
|
||||
val event = awaitPointerEvent(PointerEventPass.Main)
|
||||
if (event.changes.all { it.changedToUp() }) {
|
||||
/* All pointers are up */
|
||||
return event.changes[0]
|
||||
}
|
||||
|
||||
if (event.changes.any { it.isConsumed || !inBounds.contains(it.position) }) {
|
||||
/* Canceled */
|
||||
return null
|
||||
}
|
||||
|
||||
val consumeCheck = awaitPointerEvent(PointerEventPass.Final)
|
||||
if (consumeCheck.changes.any { it.isConsumed }) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SlidingSelectorPreview() {
|
||||
MaterialTheme {
|
||||
Surface {
|
||||
Column(Modifier.padding(8.dp)) {
|
||||
|
||||
var selectedOption by remember { mutableStateOf(TimeFrame.TWENTY_FOUR_HOURS) }
|
||||
SlidingSelector(
|
||||
TimeFrame.entries.toList(),
|
||||
selectedOption,
|
||||
onOptionSelected = { selectedOption = it }
|
||||
) {
|
||||
OptionLabel(stringResource(it.strRes))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.material3.CircularWavyProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SwitchPreference(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
summary: String = "",
|
||||
checked: Boolean,
|
||||
enabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
padding: PaddingValues? = null,
|
||||
containerColor: Color? = null,
|
||||
loading: Boolean = false,
|
||||
) {
|
||||
ListItem(
|
||||
colors =
|
||||
ListItemDefaults.colors()
|
||||
.copy(
|
||||
headlineColor =
|
||||
if (enabled) {
|
||||
ListItemDefaults.colors().headlineColor
|
||||
} else {
|
||||
ListItemDefaults.colors().headlineColor.copy(alpha = 0.5f)
|
||||
},
|
||||
supportingTextColor =
|
||||
if (enabled) {
|
||||
ListItemDefaults.colors().supportingTextColor
|
||||
} else {
|
||||
ListItemDefaults.colors().supportingTextColor.copy(alpha = 0.5f)
|
||||
},
|
||||
containerColor = containerColor ?: ListItemDefaults.colors().containerColor,
|
||||
),
|
||||
modifier =
|
||||
(padding?.let { Modifier.padding(it) } ?: modifier).toggleable(
|
||||
value = checked,
|
||||
enabled = enabled,
|
||||
onValueChange = onCheckedChange,
|
||||
),
|
||||
trailingContent = {
|
||||
AnimatedContent(targetState = loading) { loading ->
|
||||
if (loading) {
|
||||
CircularWavyProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
} else {
|
||||
Switch(enabled = enabled, checked = checked, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
if (summary.isNotEmpty()) {
|
||||
Text(text = summary, modifier = Modifier.padding(bottom = 16.dp))
|
||||
}
|
||||
},
|
||||
headlineContent = { Text(text = title) },
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SwitchPreferencePreview() {
|
||||
SwitchPreference(title = "Setting", checked = true, enabled = true, onCheckedChange = {})
|
||||
}
|
||||
|
|
@ -1,93 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun TextDividerPreference(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
trailingIcon: ImageVector? = null,
|
||||
) {
|
||||
TextDividerPreference(
|
||||
title = AnnotatedString(text = title),
|
||||
enabled = enabled,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextDividerPreference(
|
||||
title: AnnotatedString,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
trailingIcon: ImageVector? = null,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
) {
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TextDividerPreferencePreview() {
|
||||
TextDividerPreference(title = "Advanced settings")
|
||||
}
|
||||
|
|
@ -1,73 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
|
||||
@Composable
|
||||
fun rememberTimeTickWithLifecycle(): Long {
|
||||
val context = LocalContext.current
|
||||
var value by remember { mutableLongStateOf(System.currentTimeMillis()) }
|
||||
val receiver = TimeBroadcastReceiver { value = System.currentTimeMillis() }
|
||||
|
||||
LifecycleResumeEffect(Unit) {
|
||||
receiver.register(context)
|
||||
value = System.currentTimeMillis()
|
||||
|
||||
onPauseOrDispose {
|
||||
receiver.unregister(context)
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private class TimeBroadcastReceiver(
|
||||
val onTimeChanged: () -> Unit,
|
||||
) : BroadcastReceiver() {
|
||||
private var registered = false
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
onTimeChanged()
|
||||
}
|
||||
|
||||
fun register(context: Context) {
|
||||
if (!registered) {
|
||||
val filter = IntentFilter(Intent.ACTION_TIME_TICK)
|
||||
context.registerReceiver(this, filter)
|
||||
registered = true
|
||||
}
|
||||
}
|
||||
|
||||
fun unregister(context: Context) {
|
||||
if (registered) {
|
||||
context.unregisterReceiver(this)
|
||||
registered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun TitledCard(title: String?, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
title?.let {
|
||||
Text(
|
||||
text = it,
|
||||
modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
|
||||
Card(content = content)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun TitledCardPreview() {
|
||||
AppTheme { Surface { TitledCard(title = "Title") { Box(modifier = Modifier.fillMaxWidth().height(100.dp)) {} } } }
|
||||
}
|
||||
|
|
@ -1,184 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [battery_android_0](https://fonts.google.com/icons?icon.query=battery+android+0&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.BatteryEmpty: ImageVector
|
||||
get() {
|
||||
if (batteryEmpty != null) {
|
||||
return batteryEmpty!!
|
||||
}
|
||||
batteryEmpty =
|
||||
ImageVector.Builder(
|
||||
name = "BatteryEmpty",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(160f, 720f)
|
||||
quadToRelative(-50f, 0f, -85f, -35f)
|
||||
reflectiveQuadToRelative(-35f, -85f)
|
||||
verticalLineToRelative(-240f)
|
||||
quadToRelative(0f, -50f, 35f, -85f)
|
||||
reflectiveQuadToRelative(85f, -35f)
|
||||
horizontalLineToRelative(540f)
|
||||
quadToRelative(50f, 0f, 85f, 35f)
|
||||
reflectiveQuadToRelative(35f, 85f)
|
||||
verticalLineToRelative(240f)
|
||||
quadToRelative(0f, 50f, -35f, 85f)
|
||||
reflectiveQuadToRelative(-85f, 35f)
|
||||
lineTo(160f, 720f)
|
||||
close()
|
||||
moveTo(160f, 640f)
|
||||
horizontalLineToRelative(540f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(740f, 600f)
|
||||
verticalLineToRelative(-240f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(700f, 320f)
|
||||
lineTo(160f, 320f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(120f, 360f)
|
||||
verticalLineToRelative(240f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(160f, 640f)
|
||||
close()
|
||||
moveTo(860f, 580f)
|
||||
verticalLineToRelative(-200f)
|
||||
horizontalLineToRelative(20f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(920f, 420f)
|
||||
verticalLineToRelative(120f)
|
||||
quadToRelative(0f, 17f, -11.5f, 28.5f)
|
||||
reflectiveQuadTo(880f, 580f)
|
||||
horizontalLineToRelative(-20f)
|
||||
close()
|
||||
moveTo(120f, 640f)
|
||||
verticalLineToRelative(-320f)
|
||||
verticalLineToRelative(320f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return batteryEmpty!!
|
||||
}
|
||||
|
||||
private var batteryEmpty: ImageVector? = null
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [battery_android_question](https://fonts.google.com/icons?icon.query=battery+android+question&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.BatteryUnknown: ImageVector
|
||||
get() {
|
||||
if (batteryUnknown != null) {
|
||||
return batteryUnknown!!
|
||||
}
|
||||
batteryUnknown =
|
||||
ImageVector.Builder(
|
||||
name = "BatteryUnknown",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(120f, 640f)
|
||||
verticalLineToRelative(-320f)
|
||||
verticalLineToRelative(320f)
|
||||
close()
|
||||
moveTo(726f, 720f)
|
||||
lineTo(160f, 720f)
|
||||
quadToRelative(-50f, 0f, -85f, -35f)
|
||||
reflectiveQuadToRelative(-35f, -85f)
|
||||
verticalLineToRelative(-240f)
|
||||
quadToRelative(0f, -50f, 35f, -85f)
|
||||
reflectiveQuadToRelative(85f, -35f)
|
||||
horizontalLineToRelative(521f)
|
||||
quadToRelative(-20f, 16f, -35f, 36f)
|
||||
reflectiveQuadToRelative(-25f, 44f)
|
||||
lineTo(160f, 320f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(120f, 360f)
|
||||
verticalLineToRelative(240f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(160f, 640f)
|
||||
horizontalLineToRelative(520f)
|
||||
quadToRelative(2f, 25f, 14.5f, 45.5f)
|
||||
reflectiveQuadTo(726f, 720f)
|
||||
close()
|
||||
moveTo(800f, 660f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(840f, 620f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(800f, 580f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(760f, 620f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(800f, 660f)
|
||||
close()
|
||||
moveTo(772f, 538f)
|
||||
horizontalLineToRelative(57f)
|
||||
verticalLineToRelative(-21f)
|
||||
quadToRelative(0f, -10f, 5f, -19f)
|
||||
quadToRelative(6f, -13f, 15.5f, -22f)
|
||||
reflectiveQuadToRelative(19.5f, -19f)
|
||||
quadToRelative(17f, -17f, 28.5f, -37f)
|
||||
reflectiveQuadToRelative(11.5f, -43f)
|
||||
quadToRelative(0f, -42f, -32.5f, -69.5f)
|
||||
reflectiveQuadTo(800f, 280f)
|
||||
quadToRelative(-38f, 0f, -68f, 22f)
|
||||
reflectiveQuadToRelative(-40f, 58f)
|
||||
lineToRelative(51f, 21f)
|
||||
quadToRelative(6f, -20f, 21.5f, -33f)
|
||||
reflectiveQuadToRelative(35.5f, -13f)
|
||||
quadToRelative(21f, 0f, 36.5f, 12f)
|
||||
reflectiveQuadToRelative(15.5f, 32f)
|
||||
quadToRelative(0f, 17f, -10f, 30.5f)
|
||||
reflectiveQuadTo(820f, 434f)
|
||||
quadToRelative(-11f, 11f, -22.5f, 21.5f)
|
||||
reflectiveQuadTo(779f, 480f)
|
||||
quadToRelative(-6f, 14f, -6.5f, 28.5f)
|
||||
reflectiveQuadTo(772f, 538f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return batteryUnknown!!
|
||||
}
|
||||
|
||||
private var batteryUnknown: ImageVector? = null
|
||||
|
|
@ -1,151 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [router](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:router:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=router&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.Device: ImageVector
|
||||
get() {
|
||||
if (device != null) {
|
||||
return device!!
|
||||
}
|
||||
device =
|
||||
ImageVector.Builder(
|
||||
name = "Outlined.Device",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color(0xFFE3E3E3))) {
|
||||
moveTo(200f, 840f)
|
||||
quadToRelative(-33f, 0f, -56.5f, -23.5f)
|
||||
reflectiveQuadTo(120f, 760f)
|
||||
verticalLineToRelative(-160f)
|
||||
quadToRelative(0f, -33f, 23.5f, -56.5f)
|
||||
reflectiveQuadTo(200f, 520f)
|
||||
horizontalLineToRelative(400f)
|
||||
verticalLineToRelative(-120f)
|
||||
quadToRelative(0f, -17f, 11.5f, -28.5f)
|
||||
reflectiveQuadTo(640f, 360f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(680f, 400f)
|
||||
verticalLineToRelative(120f)
|
||||
horizontalLineToRelative(80f)
|
||||
quadToRelative(33f, 0f, 56.5f, 23.5f)
|
||||
reflectiveQuadTo(840f, 600f)
|
||||
verticalLineToRelative(160f)
|
||||
quadToRelative(0f, 33f, -23.5f, 56.5f)
|
||||
reflectiveQuadTo(760f, 840f)
|
||||
lineTo(200f, 840f)
|
||||
close()
|
||||
moveTo(200f, 760f)
|
||||
horizontalLineToRelative(560f)
|
||||
verticalLineToRelative(-160f)
|
||||
lineTo(200f, 600f)
|
||||
verticalLineToRelative(160f)
|
||||
close()
|
||||
moveTo(280f, 720f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(320f, 680f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(280f, 640f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(240f, 680f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(280f, 720f)
|
||||
close()
|
||||
moveTo(420f, 720f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(460f, 680f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(420f, 640f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(380f, 680f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(420f, 720f)
|
||||
close()
|
||||
moveTo(560f, 720f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(600f, 680f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(560f, 640f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(520f, 680f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(560f, 720f)
|
||||
close()
|
||||
moveTo(640f, 300f)
|
||||
quadToRelative(-11f, 0f, -20f, 2f)
|
||||
reflectiveQuadToRelative(-18f, 6f)
|
||||
quadToRelative(-16f, 7f, -32.5f, 6f)
|
||||
reflectiveQuadTo(541f, 301f)
|
||||
quadToRelative(-12f, -12f, -11.5f, -29f)
|
||||
reflectiveQuadToRelative(14.5f, -25f)
|
||||
quadToRelative(21f, -13f, 45.5f, -20f)
|
||||
reflectiveQuadToRelative(50.5f, -7f)
|
||||
quadToRelative(27f, 0f, 51f, 7f)
|
||||
reflectiveQuadToRelative(45f, 20f)
|
||||
quadToRelative(14f, 8f, 14.5f, 25f)
|
||||
reflectiveQuadTo(739f, 301f)
|
||||
quadToRelative(-12f, 12f, -29f, 13f)
|
||||
reflectiveQuadToRelative(-33f, -6f)
|
||||
quadToRelative(-8f, -4f, -17.5f, -6f)
|
||||
reflectiveQuadToRelative(-19.5f, -2f)
|
||||
close()
|
||||
moveTo(640f, 160f)
|
||||
quadToRelative(-39f, 0f, -74.5f, 11.5f)
|
||||
reflectiveQuadTo(500f, 205f)
|
||||
quadToRelative(-14f, 10f, -30.5f, 9f)
|
||||
reflectiveQuadTo(442f, 202f)
|
||||
quadToRelative(-12f, -12f, -12f, -28f)
|
||||
reflectiveQuadToRelative(13f, -26f)
|
||||
quadToRelative(41f, -32f, 91f, -50f)
|
||||
reflectiveQuadToRelative(106f, -18f)
|
||||
quadToRelative(56f, 0f, 106f, 18f)
|
||||
reflectiveQuadToRelative(91f, 50f)
|
||||
quadToRelative(13f, 10f, 13f, 26f)
|
||||
reflectiveQuadToRelative(-12f, 28f)
|
||||
quadToRelative(-11f, 11f, -27.5f, 12f)
|
||||
reflectiveQuadToRelative(-30.5f, -9f)
|
||||
quadToRelative(-30f, -22f, -65.5f, -33.5f)
|
||||
reflectiveQuadTo(640f, 160f)
|
||||
close()
|
||||
moveTo(200f, 760f)
|
||||
verticalLineToRelative(-160f)
|
||||
verticalLineToRelative(160f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return device!!
|
||||
}
|
||||
|
||||
private var device: ImageVector? = null
|
||||
|
|
@ -1,110 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [map](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:map:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=map&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.Map: ImageVector
|
||||
get() {
|
||||
if (map != null) {
|
||||
return map!!
|
||||
}
|
||||
map =
|
||||
ImageVector.Builder(
|
||||
name = "Outlined.Map",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveToRelative(574f, 831f)
|
||||
lineToRelative(-214f, -75f)
|
||||
lineToRelative(-186f, 72f)
|
||||
quadToRelative(-10f, 4f, -19.5f, 2.5f)
|
||||
reflectiveQuadTo(137f, 824f)
|
||||
quadToRelative(-8f, -5f, -12.5f, -13.5f)
|
||||
reflectiveQuadTo(120f, 791f)
|
||||
verticalLineToRelative(-561f)
|
||||
quadToRelative(0f, -13f, 7.5f, -23f)
|
||||
reflectiveQuadToRelative(20.5f, -15f)
|
||||
lineToRelative(186f, -63f)
|
||||
quadToRelative(6f, -2f, 12.5f, -3f)
|
||||
reflectiveQuadToRelative(13.5f, -1f)
|
||||
quadToRelative(7f, 0f, 13.5f, 1f)
|
||||
reflectiveQuadToRelative(12.5f, 3f)
|
||||
lineToRelative(214f, 75f)
|
||||
lineToRelative(186f, -72f)
|
||||
quadToRelative(10f, -4f, 19.5f, -2.5f)
|
||||
reflectiveQuadTo(823f, 136f)
|
||||
quadToRelative(8f, 5f, 12.5f, 13.5f)
|
||||
reflectiveQuadTo(840f, 169f)
|
||||
verticalLineToRelative(561f)
|
||||
quadToRelative(0f, 13f, -7.5f, 23f)
|
||||
reflectiveQuadTo(812f, 768f)
|
||||
lineToRelative(-186f, 63f)
|
||||
quadToRelative(-6f, 2f, -12.5f, 3f)
|
||||
reflectiveQuadToRelative(-13.5f, 1f)
|
||||
quadToRelative(-7f, 0f, -13.5f, -1f)
|
||||
reflectiveQuadToRelative(-12.5f, -3f)
|
||||
close()
|
||||
moveTo(560f, 742f)
|
||||
verticalLineToRelative(-468f)
|
||||
lineToRelative(-160f, -56f)
|
||||
verticalLineToRelative(468f)
|
||||
lineToRelative(160f, 56f)
|
||||
close()
|
||||
moveTo(640f, 742f)
|
||||
lineTo(760f, 702f)
|
||||
verticalLineToRelative(-474f)
|
||||
lineToRelative(-120f, 46f)
|
||||
verticalLineToRelative(468f)
|
||||
close()
|
||||
moveTo(200f, 732f)
|
||||
lineTo(320f, 686f)
|
||||
verticalLineToRelative(-468f)
|
||||
lineToRelative(-120f, 40f)
|
||||
verticalLineToRelative(474f)
|
||||
close()
|
||||
moveTo(640f, 274f)
|
||||
verticalLineToRelative(468f)
|
||||
verticalLineToRelative(-468f)
|
||||
close()
|
||||
moveTo(320f, 218f)
|
||||
verticalLineToRelative(468f)
|
||||
verticalLineToRelative(-468f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return map!!
|
||||
}
|
||||
|
||||
private var map: ImageVector? = null
|
||||
|
|
@ -1,20 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
object MeshtasticIcons
|
||||
|
|
@ -1,101 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [forum](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:forum:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=forum&icon.size=24&icon.color=%23e3e3e3&icon.platform=android&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.Conversations: ImageVector
|
||||
get() {
|
||||
if (conversations != null) {
|
||||
return conversations!!
|
||||
}
|
||||
conversations =
|
||||
ImageVector.Builder(
|
||||
name = "Outlined.Conversations",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(840f, 824f)
|
||||
quadToRelative(-8f, 0f, -15f, -3f)
|
||||
reflectiveQuadToRelative(-13f, -9f)
|
||||
lineToRelative(-92f, -92f)
|
||||
lineTo(320f, 720f)
|
||||
quadToRelative(-33f, 0f, -56.5f, -23.5f)
|
||||
reflectiveQuadTo(240f, 640f)
|
||||
verticalLineToRelative(-40f)
|
||||
horizontalLineToRelative(440f)
|
||||
quadToRelative(33f, 0f, 56.5f, -23.5f)
|
||||
reflectiveQuadTo(760f, 520f)
|
||||
verticalLineToRelative(-280f)
|
||||
horizontalLineToRelative(40f)
|
||||
quadToRelative(33f, 0f, 56.5f, 23.5f)
|
||||
reflectiveQuadTo(880f, 320f)
|
||||
verticalLineToRelative(463f)
|
||||
quadToRelative(0f, 18f, -12f, 29.5f)
|
||||
reflectiveQuadTo(840f, 824f)
|
||||
close()
|
||||
moveTo(160f, 487f)
|
||||
lineToRelative(47f, -47f)
|
||||
horizontalLineToRelative(393f)
|
||||
verticalLineToRelative(-280f)
|
||||
lineTo(160f, 160f)
|
||||
verticalLineToRelative(327f)
|
||||
close()
|
||||
moveTo(120f, 624f)
|
||||
quadToRelative(-16f, 0f, -28f, -11.5f)
|
||||
reflectiveQuadTo(80f, 583f)
|
||||
verticalLineToRelative(-423f)
|
||||
quadToRelative(0f, -33f, 23.5f, -56.5f)
|
||||
reflectiveQuadTo(160f, 80f)
|
||||
horizontalLineToRelative(440f)
|
||||
quadToRelative(33f, 0f, 56.5f, 23.5f)
|
||||
reflectiveQuadTo(680f, 160f)
|
||||
verticalLineToRelative(280f)
|
||||
quadToRelative(0f, 33f, -23.5f, 56.5f)
|
||||
reflectiveQuadTo(600f, 520f)
|
||||
lineTo(240f, 520f)
|
||||
lineToRelative(-92f, 92f)
|
||||
quadToRelative(-6f, 6f, -13f, 9f)
|
||||
reflectiveQuadToRelative(-15f, 3f)
|
||||
close()
|
||||
moveTo(160f, 440f)
|
||||
verticalLineToRelative(-280f)
|
||||
verticalLineToRelative(280f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return conversations!!
|
||||
}
|
||||
|
||||
private var conversations: ImageVector? = null
|
||||
|
|
@ -1,165 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [router_off](https://fonts.google.com/icons?icon.query=router+off&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.NoDevice: ImageVector
|
||||
get() {
|
||||
if (noDevice != null) {
|
||||
return noDevice!!
|
||||
}
|
||||
noDevice =
|
||||
ImageVector.Builder(
|
||||
name = "Outlined.NoDevice",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(806f, 692f)
|
||||
lineTo(600f, 486f)
|
||||
verticalLineToRelative(-86f)
|
||||
quadToRelative(0f, -17f, 11.5f, -28.5f)
|
||||
reflectiveQuadTo(640f, 360f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(680f, 400f)
|
||||
verticalLineToRelative(120f)
|
||||
horizontalLineToRelative(80f)
|
||||
quadToRelative(33f, 0f, 56.5f, 23.5f)
|
||||
reflectiveQuadTo(840f, 600f)
|
||||
verticalLineToRelative(78f)
|
||||
quadToRelative(0f, 14f, -12f, 19f)
|
||||
reflectiveQuadToRelative(-22f, -5f)
|
||||
close()
|
||||
moveTo(200f, 760f)
|
||||
horizontalLineToRelative(446f)
|
||||
lineTo(486f, 600f)
|
||||
lineTo(200f, 600f)
|
||||
verticalLineToRelative(160f)
|
||||
close()
|
||||
moveTo(200f, 840f)
|
||||
quadToRelative(-33f, 0f, -56.5f, -23.5f)
|
||||
reflectiveQuadTo(120f, 760f)
|
||||
verticalLineToRelative(-160f)
|
||||
quadToRelative(0f, -33f, 23.5f, -56.5f)
|
||||
reflectiveQuadTo(200f, 520f)
|
||||
horizontalLineToRelative(206f)
|
||||
lineTo(83f, 197f)
|
||||
quadToRelative(-12f, -12f, -12f, -28.5f)
|
||||
reflectiveQuadTo(83f, 140f)
|
||||
quadToRelative(12f, -12f, 28.5f, -12f)
|
||||
reflectiveQuadToRelative(28.5f, 12f)
|
||||
lineToRelative(680f, 680f)
|
||||
quadToRelative(12f, 12f, 12f, 28f)
|
||||
reflectiveQuadToRelative(-12f, 28f)
|
||||
quadToRelative(-12f, 12f, -28.5f, 12f)
|
||||
reflectiveQuadTo(763f, 876f)
|
||||
lineToRelative(-37f, -36f)
|
||||
lineTo(200f, 840f)
|
||||
close()
|
||||
moveTo(280f, 720f)
|
||||
quadToRelative(-17f, 0f, -28.5f, -11.5f)
|
||||
reflectiveQuadTo(240f, 680f)
|
||||
quadToRelative(0f, -17f, 11.5f, -28.5f)
|
||||
reflectiveQuadTo(280f, 640f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(320f, 680f)
|
||||
quadToRelative(0f, 17f, -11.5f, 28.5f)
|
||||
reflectiveQuadTo(280f, 720f)
|
||||
close()
|
||||
moveTo(420f, 720f)
|
||||
quadToRelative(-17f, 0f, -28.5f, -11.5f)
|
||||
reflectiveQuadTo(380f, 680f)
|
||||
quadToRelative(0f, -17f, 11.5f, -28.5f)
|
||||
reflectiveQuadTo(420f, 640f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(460f, 680f)
|
||||
quadToRelative(0f, 17f, -11.5f, 28.5f)
|
||||
reflectiveQuadTo(420f, 720f)
|
||||
close()
|
||||
moveTo(560f, 720f)
|
||||
quadToRelative(-17f, 0f, -28.5f, -11.5f)
|
||||
reflectiveQuadTo(520f, 680f)
|
||||
quadToRelative(0f, -17f, 11.5f, -28.5f)
|
||||
reflectiveQuadTo(560f, 640f)
|
||||
quadToRelative(17f, 0f, 28.5f, 11.5f)
|
||||
reflectiveQuadTo(600f, 680f)
|
||||
quadToRelative(0f, 17f, -11.5f, 28.5f)
|
||||
reflectiveQuadTo(560f, 720f)
|
||||
close()
|
||||
moveTo(200f, 760f)
|
||||
verticalLineToRelative(-160f)
|
||||
verticalLineToRelative(160f)
|
||||
close()
|
||||
moveTo(640f, 300f)
|
||||
quadToRelative(-11f, 0f, -20f, 2f)
|
||||
reflectiveQuadToRelative(-18f, 6f)
|
||||
quadToRelative(-16f, 7f, -32.5f, 6f)
|
||||
reflectiveQuadTo(541f, 301f)
|
||||
quadToRelative(-12f, -12f, -11.5f, -29f)
|
||||
reflectiveQuadToRelative(14.5f, -25f)
|
||||
quadToRelative(21f, -13f, 45.5f, -20f)
|
||||
reflectiveQuadToRelative(50.5f, -7f)
|
||||
quadToRelative(27f, 0f, 51f, 7f)
|
||||
reflectiveQuadToRelative(45f, 20f)
|
||||
quadToRelative(14f, 8f, 14.5f, 25f)
|
||||
reflectiveQuadTo(739f, 301f)
|
||||
quadToRelative(-12f, 12f, -29f, 13f)
|
||||
reflectiveQuadToRelative(-33f, -6f)
|
||||
quadToRelative(-8f, -4f, -17.5f, -6f)
|
||||
reflectiveQuadToRelative(-19.5f, -2f)
|
||||
close()
|
||||
moveTo(640f, 160f)
|
||||
quadToRelative(-39f, 0f, -74.5f, 11.5f)
|
||||
reflectiveQuadTo(500f, 205f)
|
||||
quadToRelative(-14f, 10f, -30.5f, 9f)
|
||||
reflectiveQuadTo(442f, 202f)
|
||||
quadToRelative(-12f, -12f, -12f, -28f)
|
||||
reflectiveQuadToRelative(13f, -26f)
|
||||
quadToRelative(41f, -32f, 91f, -50f)
|
||||
reflectiveQuadToRelative(106f, -18f)
|
||||
quadToRelative(56f, 0f, 106f, 18f)
|
||||
reflectiveQuadToRelative(91f, 50f)
|
||||
quadToRelative(13f, 10f, 13f, 26f)
|
||||
reflectiveQuadToRelative(-12f, 28f)
|
||||
quadToRelative(-11f, 11f, -27.5f, 12f)
|
||||
reflectiveQuadToRelative(-30.5f, -9f)
|
||||
quadToRelative(-30f, -22f, -65.5f, -33.5f)
|
||||
reflectiveQuadTo(640f, 160f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return noDevice!!
|
||||
}
|
||||
|
||||
private var noDevice: ImageVector? = null
|
||||
|
|
@ -1,166 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [graph_3](https://fonts.google.com/icons?icon.query=graph+3&icon.size=24&icon.color=%23e3e3e3&icon.style=Rounded)
|
||||
*/
|
||||
val MeshtasticIcons.Nodes: ImageVector
|
||||
get() {
|
||||
if (nodes != null) {
|
||||
return nodes!!
|
||||
}
|
||||
nodes =
|
||||
ImageVector.Builder(
|
||||
name = "Outlined.Nodes",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color.Black)) {
|
||||
moveTo(480f, 880f)
|
||||
quadToRelative(-50f, 0f, -85f, -35f)
|
||||
reflectiveQuadToRelative(-35f, -85f)
|
||||
quadToRelative(0f, -5f, 0.5f, -11f)
|
||||
reflectiveQuadToRelative(1.5f, -11f)
|
||||
lineToRelative(-83f, -47f)
|
||||
quadToRelative(-16f, 14f, -36f, 21.5f)
|
||||
reflectiveQuadToRelative(-43f, 7.5f)
|
||||
quadToRelative(-50f, 0f, -85f, -35f)
|
||||
reflectiveQuadToRelative(-35f, -85f)
|
||||
quadToRelative(0f, -50f, 35f, -85f)
|
||||
reflectiveQuadToRelative(85f, -35f)
|
||||
quadToRelative(24f, 0f, 45f, 9f)
|
||||
reflectiveQuadToRelative(38f, 25f)
|
||||
lineToRelative(119f, -60f)
|
||||
quadToRelative(-3f, -23f, 2.5f, -45f)
|
||||
reflectiveQuadToRelative(19.5f, -41f)
|
||||
lineToRelative(-34f, -52f)
|
||||
quadToRelative(-7f, 2f, -14.5f, 3f)
|
||||
reflectiveQuadToRelative(-15.5f, 1f)
|
||||
quadToRelative(-50f, 0f, -85f, -35f)
|
||||
reflectiveQuadToRelative(-35f, -85f)
|
||||
quadToRelative(0f, -50f, 35f, -85f)
|
||||
reflectiveQuadToRelative(85f, -35f)
|
||||
quadToRelative(50f, 0f, 85f, 35f)
|
||||
reflectiveQuadToRelative(35f, 85f)
|
||||
quadToRelative(0f, 20f, -6.5f, 38.5f)
|
||||
reflectiveQuadTo(456f, 272f)
|
||||
lineToRelative(35f, 52f)
|
||||
quadToRelative(8f, -2f, 15f, -3f)
|
||||
reflectiveQuadToRelative(15f, -1f)
|
||||
quadToRelative(17f, 0f, 32f, 4f)
|
||||
reflectiveQuadToRelative(29f, 12f)
|
||||
lineToRelative(66f, -54f)
|
||||
quadToRelative(-4f, -10f, -6f, -20.5f)
|
||||
reflectiveQuadToRelative(-2f, -21.5f)
|
||||
quadToRelative(0f, -50f, 35f, -85f)
|
||||
reflectiveQuadToRelative(85f, -35f)
|
||||
quadToRelative(50f, 0f, 85f, 35f)
|
||||
reflectiveQuadToRelative(35f, 85f)
|
||||
quadToRelative(0f, 50f, -35f, 85f)
|
||||
reflectiveQuadToRelative(-85f, 35f)
|
||||
quadToRelative(-17f, 0f, -32f, -4.5f)
|
||||
reflectiveQuadTo(699f, 343f)
|
||||
lineToRelative(-66f, 55f)
|
||||
quadToRelative(4f, 10f, 6f, 20.5f)
|
||||
reflectiveQuadToRelative(2f, 21.5f)
|
||||
quadToRelative(0f, 50f, -35f, 85f)
|
||||
reflectiveQuadToRelative(-85f, 35f)
|
||||
quadToRelative(-24f, 0f, -45.5f, -9f)
|
||||
reflectiveQuadTo(437f, 526f)
|
||||
lineToRelative(-118f, 59f)
|
||||
quadToRelative(2f, 9f, 1.5f, 18f)
|
||||
reflectiveQuadToRelative(-2.5f, 18f)
|
||||
lineToRelative(84f, 48f)
|
||||
quadToRelative(16f, -14f, 35.5f, -21.5f)
|
||||
reflectiveQuadTo(480f, 640f)
|
||||
quadToRelative(50f, 0f, 85f, 35f)
|
||||
reflectiveQuadToRelative(35f, 85f)
|
||||
quadToRelative(0f, 50f, -35f, 85f)
|
||||
reflectiveQuadToRelative(-85f, 35f)
|
||||
close()
|
||||
moveTo(200f, 640f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(240f, 600f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(200f, 560f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(160f, 600f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(200f, 640f)
|
||||
close()
|
||||
moveTo(360f, 240f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(400f, 200f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(360f, 160f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(320f, 200f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(360f, 240f)
|
||||
close()
|
||||
moveTo(480f, 800f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(520f, 760f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(480f, 720f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(440f, 760f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(480f, 800f)
|
||||
close()
|
||||
moveTo(520f, 480f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(560f, 440f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(520f, 400f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(480f, 440f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(520f, 480f)
|
||||
close()
|
||||
moveTo(760f, 280f)
|
||||
quadToRelative(17f, 0f, 28.5f, -11.5f)
|
||||
reflectiveQuadTo(800f, 240f)
|
||||
quadToRelative(0f, -17f, -11.5f, -28.5f)
|
||||
reflectiveQuadTo(760f, 200f)
|
||||
quadToRelative(-17f, 0f, -28.5f, 11.5f)
|
||||
reflectiveQuadTo(720f, 240f)
|
||||
quadToRelative(0f, 17f, 11.5f, 28.5f)
|
||||
reflectiveQuadTo(760f, 280f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return nodes!!
|
||||
}
|
||||
|
||||
private var nodes: ImageVector? = null
|
||||
|
|
@ -1,160 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [settings](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:settings:FILL@0;wght@400;GRAD@0;opsz@24&icon.style=Rounded&icon.query=settings&icon.set=Material+Symbols&icon.size=24&icon.color=%23e3e3e3&icon.platform=android)
|
||||
*/
|
||||
val MeshtasticIcons.Settings: ImageVector
|
||||
get() {
|
||||
if (settings != null) {
|
||||
return settings!!
|
||||
}
|
||||
settings =
|
||||
ImageVector.Builder(
|
||||
name = "Settings",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color(0xFFE3E3E3))) {
|
||||
moveTo(433f, 880f)
|
||||
quadToRelative(-27f, 0f, -46.5f, -18f)
|
||||
reflectiveQuadTo(363f, 818f)
|
||||
lineToRelative(-9f, -66f)
|
||||
quadToRelative(-13f, -5f, -24.5f, -12f)
|
||||
reflectiveQuadTo(307f, 725f)
|
||||
lineToRelative(-62f, 26f)
|
||||
quadToRelative(-25f, 11f, -50f, 2f)
|
||||
reflectiveQuadToRelative(-39f, -32f)
|
||||
lineToRelative(-47f, -82f)
|
||||
quadToRelative(-14f, -23f, -8f, -49f)
|
||||
reflectiveQuadToRelative(27f, -43f)
|
||||
lineToRelative(53f, -40f)
|
||||
quadToRelative(-1f, -7f, -1f, -13.5f)
|
||||
verticalLineToRelative(-27f)
|
||||
quadToRelative(0f, -6.5f, 1f, -13.5f)
|
||||
lineToRelative(-53f, -40f)
|
||||
quadToRelative(-21f, -17f, -27f, -43f)
|
||||
reflectiveQuadToRelative(8f, -49f)
|
||||
lineToRelative(47f, -82f)
|
||||
quadToRelative(14f, -23f, 39f, -32f)
|
||||
reflectiveQuadToRelative(50f, 2f)
|
||||
lineToRelative(62f, 26f)
|
||||
quadToRelative(11f, -8f, 23f, -15f)
|
||||
reflectiveQuadToRelative(24f, -12f)
|
||||
lineToRelative(9f, -66f)
|
||||
quadToRelative(4f, -26f, 23.5f, -44f)
|
||||
reflectiveQuadToRelative(46.5f, -18f)
|
||||
horizontalLineToRelative(94f)
|
||||
quadToRelative(27f, 0f, 46.5f, 18f)
|
||||
reflectiveQuadToRelative(23.5f, 44f)
|
||||
lineToRelative(9f, 66f)
|
||||
quadToRelative(13f, 5f, 24.5f, 12f)
|
||||
reflectiveQuadToRelative(22.5f, 15f)
|
||||
lineToRelative(62f, -26f)
|
||||
quadToRelative(25f, -11f, 50f, -2f)
|
||||
reflectiveQuadToRelative(39f, 32f)
|
||||
lineToRelative(47f, 82f)
|
||||
quadToRelative(14f, 23f, 8f, 49f)
|
||||
reflectiveQuadToRelative(-27f, 43f)
|
||||
lineToRelative(-53f, 40f)
|
||||
quadToRelative(1f, 7f, 1f, 13.5f)
|
||||
verticalLineToRelative(27f)
|
||||
quadToRelative(0f, 6.5f, -2f, 13.5f)
|
||||
lineToRelative(53f, 40f)
|
||||
quadToRelative(21f, 17f, 27f, 43f)
|
||||
reflectiveQuadToRelative(-8f, 49f)
|
||||
lineToRelative(-48f, 82f)
|
||||
quadToRelative(-14f, 23f, -39f, 32f)
|
||||
reflectiveQuadToRelative(-50f, -2f)
|
||||
lineToRelative(-60f, -26f)
|
||||
quadToRelative(-11f, 8f, -23f, 15f)
|
||||
reflectiveQuadToRelative(-24f, 12f)
|
||||
lineToRelative(-9f, 66f)
|
||||
quadToRelative(-4f, 26f, -23.5f, 44f)
|
||||
reflectiveQuadTo(527f, 880f)
|
||||
horizontalLineToRelative(-94f)
|
||||
close()
|
||||
moveTo(440f, 800f)
|
||||
horizontalLineToRelative(79f)
|
||||
lineToRelative(14f, -106f)
|
||||
quadToRelative(31f, -8f, 57.5f, -23.5f)
|
||||
reflectiveQuadTo(639f, 633f)
|
||||
lineToRelative(99f, 41f)
|
||||
lineToRelative(39f, -68f)
|
||||
lineToRelative(-86f, -65f)
|
||||
quadToRelative(5f, -14f, 7f, -29.5f)
|
||||
reflectiveQuadToRelative(2f, -31.5f)
|
||||
quadToRelative(0f, -16f, -2f, -31.5f)
|
||||
reflectiveQuadToRelative(-7f, -29.5f)
|
||||
lineToRelative(86f, -65f)
|
||||
lineToRelative(-39f, -68f)
|
||||
lineToRelative(-99f, 42f)
|
||||
quadToRelative(-22f, -23f, -48.5f, -38.5f)
|
||||
reflectiveQuadTo(533f, 266f)
|
||||
lineToRelative(-13f, -106f)
|
||||
horizontalLineToRelative(-79f)
|
||||
lineToRelative(-14f, 106f)
|
||||
quadToRelative(-31f, 8f, -57.5f, 23.5f)
|
||||
reflectiveQuadTo(321f, 327f)
|
||||
lineToRelative(-99f, -41f)
|
||||
lineToRelative(-39f, 68f)
|
||||
lineToRelative(86f, 64f)
|
||||
quadToRelative(-5f, 15f, -7f, 30f)
|
||||
reflectiveQuadToRelative(-2f, 32f)
|
||||
quadToRelative(0f, 16f, 2f, 31f)
|
||||
reflectiveQuadToRelative(7f, 30f)
|
||||
lineToRelative(-86f, 65f)
|
||||
lineToRelative(39f, 68f)
|
||||
lineToRelative(99f, -42f)
|
||||
quadToRelative(22f, 23f, 48.5f, 38.5f)
|
||||
reflectiveQuadTo(427f, 694f)
|
||||
lineToRelative(13f, 106f)
|
||||
close()
|
||||
moveTo(482f, 620f)
|
||||
quadToRelative(58f, 0f, 99f, -41f)
|
||||
reflectiveQuadToRelative(41f, -99f)
|
||||
quadToRelative(0f, -58f, -41f, -99f)
|
||||
reflectiveQuadToRelative(-99f, -41f)
|
||||
quadToRelative(-59f, 0f, -99.5f, 41f)
|
||||
reflectiveQuadTo(342f, 480f)
|
||||
quadToRelative(0f, 58f, 40.5f, 99f)
|
||||
reflectiveQuadToRelative(99.5f, 41f)
|
||||
close()
|
||||
moveTo(480f, 480f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return settings!!
|
||||
}
|
||||
|
||||
private var settings: ImageVector? = null
|
||||
|
|
@ -1,236 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val primaryLight = Color(0xFF306A42)
|
||||
val onPrimaryLight = Color(0xFFFFFFFF)
|
||||
val primaryContainerLight = Color(0xFFB3F1BF)
|
||||
val onPrimaryContainerLight = Color(0xFF00210D)
|
||||
val secondaryLight = Color(0xFF506353)
|
||||
val onSecondaryLight = Color(0xFFFFFFFF)
|
||||
val secondaryContainerLight = Color(0xFFD2E8D3)
|
||||
val onSecondaryContainerLight = Color(0xFF0D1F12)
|
||||
val tertiaryLight = Color(0xFF3A656E)
|
||||
val onTertiaryLight = Color(0xFFFFFFFF)
|
||||
val tertiaryContainerLight = Color(0xFFBEEAF6)
|
||||
val onTertiaryContainerLight = Color(0xFF001F25)
|
||||
val errorLight = Color(0xFFBA1A1A)
|
||||
val onErrorLight = Color(0xFFFFFFFF)
|
||||
val errorContainerLight = Color(0xFFFFDAD6)
|
||||
val onErrorContainerLight = Color(0xFF410002)
|
||||
val backgroundLight = Color(0xFFF6FBF3)
|
||||
val onBackgroundLight = Color(0xFF181D18)
|
||||
val surfaceLight = Color(0xFFF6FBF3)
|
||||
val onSurfaceLight = Color(0xFF181D18)
|
||||
val surfaceVariantLight = Color(0xFFDDE5DA)
|
||||
val onSurfaceVariantLight = Color(0xFF414941)
|
||||
val outlineLight = Color(0xFF717971)
|
||||
val outlineVariantLight = Color(0xFFC1C9BF)
|
||||
val scrimLight = Color(0xFF000000)
|
||||
val inverseSurfaceLight = Color(0xFF2D322D)
|
||||
val inverseOnSurfaceLight = Color(0xFFEEF2EA)
|
||||
val inversePrimaryLight = Color(0xFF97D5A5)
|
||||
val surfaceDimLight = Color(0xFFD7DBD4)
|
||||
val surfaceBrightLight = Color(0xFFF6FBF3)
|
||||
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
|
||||
val surfaceContainerLowLight = Color(0xFFF0F5ED)
|
||||
val surfaceContainerLight = Color(0xFFEBEFE7)
|
||||
val surfaceContainerHighLight = Color(0xFFE5EAE2)
|
||||
val surfaceContainerHighestLight = Color(0xFFDFE4DC)
|
||||
|
||||
val primaryLightMediumContrast = Color(0xFF0F4D29)
|
||||
val onPrimaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val primaryContainerLightMediumContrast = Color(0xFF478157)
|
||||
val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val secondaryLightMediumContrast = Color(0xFF344738)
|
||||
val onSecondaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val secondaryContainerLightMediumContrast = Color(0xFF657A68)
|
||||
val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val tertiaryLightMediumContrast = Color(0xFF1C4952)
|
||||
val onTertiaryLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val tertiaryContainerLightMediumContrast = Color(0xFF517B85)
|
||||
val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val errorLightMediumContrast = Color(0xFF8C0009)
|
||||
val onErrorLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val errorContainerLightMediumContrast = Color(0xFFDA342E)
|
||||
val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val backgroundLightMediumContrast = Color(0xFFF6FBF3)
|
||||
val onBackgroundLightMediumContrast = Color(0xFF181D18)
|
||||
val surfaceLightMediumContrast = Color(0xFFF6FBF3)
|
||||
val onSurfaceLightMediumContrast = Color(0xFF181D18)
|
||||
val surfaceVariantLightMediumContrast = Color(0xFFDDE5DA)
|
||||
val onSurfaceVariantLightMediumContrast = Color(0xFF3D453D)
|
||||
val outlineLightMediumContrast = Color(0xFF596159)
|
||||
val outlineVariantLightMediumContrast = Color(0xFF757D74)
|
||||
val scrimLightMediumContrast = Color(0xFF000000)
|
||||
val inverseSurfaceLightMediumContrast = Color(0xFF2D322D)
|
||||
val inverseOnSurfaceLightMediumContrast = Color(0xFFEEF2EA)
|
||||
val inversePrimaryLightMediumContrast = Color(0xFF97D5A5)
|
||||
val surfaceDimLightMediumContrast = Color(0xFFD7DBD4)
|
||||
val surfaceBrightLightMediumContrast = Color(0xFFF6FBF3)
|
||||
val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF)
|
||||
val surfaceContainerLowLightMediumContrast = Color(0xFFF0F5ED)
|
||||
val surfaceContainerLightMediumContrast = Color(0xFFEBEFE7)
|
||||
val surfaceContainerHighLightMediumContrast = Color(0xFFE5EAE2)
|
||||
val surfaceContainerHighestLightMediumContrast = Color(0xFFDFE4DC)
|
||||
|
||||
val primaryLightHighContrast = Color(0xFF002911)
|
||||
val onPrimaryLightHighContrast = Color(0xFFFFFFFF)
|
||||
val primaryContainerLightHighContrast = Color(0xFF0F4D29)
|
||||
val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||
val secondaryLightHighContrast = Color(0xFF142619)
|
||||
val onSecondaryLightHighContrast = Color(0xFFFFFFFF)
|
||||
val secondaryContainerLightHighContrast = Color(0xFF344738)
|
||||
val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||
val tertiaryLightHighContrast = Color(0xFF00262E)
|
||||
val onTertiaryLightHighContrast = Color(0xFFFFFFFF)
|
||||
val tertiaryContainerLightHighContrast = Color(0xFF1C4952)
|
||||
val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||
val errorLightHighContrast = Color(0xFF4E0002)
|
||||
val onErrorLightHighContrast = Color(0xFFFFFFFF)
|
||||
val errorContainerLightHighContrast = Color(0xFF8C0009)
|
||||
val onErrorContainerLightHighContrast = Color(0xFFFFFFFF)
|
||||
val backgroundLightHighContrast = Color(0xFFF6FBF3)
|
||||
val onBackgroundLightHighContrast = Color(0xFF181D18)
|
||||
val surfaceLightHighContrast = Color(0xFFF6FBF3)
|
||||
val onSurfaceLightHighContrast = Color(0xFF000000)
|
||||
val surfaceVariantLightHighContrast = Color(0xFFDDE5DA)
|
||||
val onSurfaceVariantLightHighContrast = Color(0xFF1E261F)
|
||||
val outlineLightHighContrast = Color(0xFF3D453D)
|
||||
val outlineVariantLightHighContrast = Color(0xFF3D453D)
|
||||
val scrimLightHighContrast = Color(0xFF000000)
|
||||
val inverseSurfaceLightHighContrast = Color(0xFF2D322D)
|
||||
val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF)
|
||||
val inversePrimaryLightHighContrast = Color(0xFFBCFBC8)
|
||||
val surfaceDimLightHighContrast = Color(0xFFD7DBD4)
|
||||
val surfaceBrightLightHighContrast = Color(0xFFF6FBF3)
|
||||
val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF)
|
||||
val surfaceContainerLowLightHighContrast = Color(0xFFF0F5ED)
|
||||
val surfaceContainerLightHighContrast = Color(0xFFEBEFE7)
|
||||
val surfaceContainerHighLightHighContrast = Color(0xFFE5EAE2)
|
||||
val surfaceContainerHighestLightHighContrast = Color(0xFFDFE4DC)
|
||||
|
||||
val primaryDark = Color(0xFF97D5A5)
|
||||
val onPrimaryDark = Color(0xFF00391A)
|
||||
val primaryContainerDark = Color(0xFF15512C)
|
||||
val onPrimaryContainerDark = Color(0xFFB3F1BF)
|
||||
val secondaryDark = Color(0xFFB6CCB8)
|
||||
val onSecondaryDark = Color(0xFF223526)
|
||||
val secondaryContainerDark = Color(0xFF384B3C)
|
||||
val onSecondaryContainerDark = Color(0xFFD2E8D3)
|
||||
val tertiaryDark = Color(0xFFA2CED9)
|
||||
val onTertiaryDark = Color(0xFF01363F)
|
||||
val tertiaryContainerDark = Color(0xFF204D56)
|
||||
val onTertiaryContainerDark = Color(0xFFBEEAF6)
|
||||
val errorDark = Color(0xFFFFB4AB)
|
||||
val onErrorDark = Color(0xFF690005)
|
||||
val errorContainerDark = Color(0xFF93000A)
|
||||
val onErrorContainerDark = Color(0xFFFFDAD6)
|
||||
val backgroundDark = Color(0xFF101510)
|
||||
val onBackgroundDark = Color(0xFFDFE4DC)
|
||||
val surfaceDark = Color(0xFF101510)
|
||||
val onSurfaceDark = Color(0xFFDFE4DC)
|
||||
val surfaceVariantDark = Color(0xFF414941)
|
||||
val onSurfaceVariantDark = Color(0xFFC1C9BF)
|
||||
val outlineDark = Color(0xFF8B938A)
|
||||
val outlineVariantDark = Color(0xFF414941)
|
||||
val scrimDark = Color(0xFF000000)
|
||||
val inverseSurfaceDark = Color(0xFFDFE4DC)
|
||||
val inverseOnSurfaceDark = Color(0xFF2D322D)
|
||||
val inversePrimaryDark = Color(0xFF306A42)
|
||||
val surfaceDimDark = Color(0xFF101510)
|
||||
val surfaceBrightDark = Color(0xFF353A35)
|
||||
val surfaceContainerLowestDark = Color(0xFF0A0F0B)
|
||||
val surfaceContainerLowDark = Color(0xFF181D18)
|
||||
val surfaceContainerDark = Color(0xFF1C211C)
|
||||
val surfaceContainerHighDark = Color(0xFF262B26)
|
||||
val surfaceContainerHighestDark = Color(0xFF313631)
|
||||
|
||||
val primaryDarkMediumContrast = Color(0xFF9BD9A9)
|
||||
val onPrimaryDarkMediumContrast = Color(0xFF001B09)
|
||||
val primaryContainerDarkMediumContrast = Color(0xFF639D72)
|
||||
val onPrimaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||
val secondaryDarkMediumContrast = Color(0xFFBBD0BC)
|
||||
val onSecondaryDarkMediumContrast = Color(0xFF081A0D)
|
||||
val secondaryContainerDarkMediumContrast = Color(0xFF819683)
|
||||
val onSecondaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||
val tertiaryDarkMediumContrast = Color(0xFFA6D2DD)
|
||||
val onTertiaryDarkMediumContrast = Color(0xFF00191F)
|
||||
val tertiaryContainerDarkMediumContrast = Color(0xFF6D97A2)
|
||||
val onTertiaryContainerDarkMediumContrast = Color(0xFF000000)
|
||||
val errorDarkMediumContrast = Color(0xFFFFBAB1)
|
||||
val onErrorDarkMediumContrast = Color(0xFF370001)
|
||||
val errorContainerDarkMediumContrast = Color(0xFFFF5449)
|
||||
val onErrorContainerDarkMediumContrast = Color(0xFF000000)
|
||||
val backgroundDarkMediumContrast = Color(0xFF101510)
|
||||
val onBackgroundDarkMediumContrast = Color(0xFFDFE4DC)
|
||||
val surfaceDarkMediumContrast = Color(0xFF101510)
|
||||
val onSurfaceDarkMediumContrast = Color(0xFFF8FCF4)
|
||||
val surfaceVariantDarkMediumContrast = Color(0xFF414941)
|
||||
val onSurfaceVariantDarkMediumContrast = Color(0xFFC5CDC3)
|
||||
val outlineDarkMediumContrast = Color(0xFF9DA59C)
|
||||
val outlineVariantDarkMediumContrast = Color(0xFF7D857D)
|
||||
val scrimDarkMediumContrast = Color(0xFF000000)
|
||||
val inverseSurfaceDarkMediumContrast = Color(0xFFDFE4DC)
|
||||
val inverseOnSurfaceDarkMediumContrast = Color(0xFF262B26)
|
||||
val inversePrimaryDarkMediumContrast = Color(0xFF16522E)
|
||||
val surfaceDimDarkMediumContrast = Color(0xFF101510)
|
||||
val surfaceBrightDarkMediumContrast = Color(0xFF353A35)
|
||||
val surfaceContainerLowestDarkMediumContrast = Color(0xFF0A0F0B)
|
||||
val surfaceContainerLowDarkMediumContrast = Color(0xFF181D18)
|
||||
val surfaceContainerDarkMediumContrast = Color(0xFF1C211C)
|
||||
val surfaceContainerHighDarkMediumContrast = Color(0xFF262B26)
|
||||
val surfaceContainerHighestDarkMediumContrast = Color(0xFF313631)
|
||||
|
||||
val primaryDarkHighContrast = Color(0xFFEFFFEE)
|
||||
val onPrimaryDarkHighContrast = Color(0xFF000000)
|
||||
val primaryContainerDarkHighContrast = Color(0xFF9BD9A9)
|
||||
val onPrimaryContainerDarkHighContrast = Color(0xFF000000)
|
||||
val secondaryDarkHighContrast = Color(0xFFEFFFEE)
|
||||
val onSecondaryDarkHighContrast = Color(0xFF000000)
|
||||
val secondaryContainerDarkHighContrast = Color(0xFFBBD0BC)
|
||||
val onSecondaryContainerDarkHighContrast = Color(0xFF000000)
|
||||
val tertiaryDarkHighContrast = Color(0xFFF3FCFF)
|
||||
val onTertiaryDarkHighContrast = Color(0xFF000000)
|
||||
val tertiaryContainerDarkHighContrast = Color(0xFFA6D2DD)
|
||||
val onTertiaryContainerDarkHighContrast = Color(0xFF000000)
|
||||
val errorDarkHighContrast = Color(0xFFFFF9F9)
|
||||
val onErrorDarkHighContrast = Color(0xFF000000)
|
||||
val errorContainerDarkHighContrast = Color(0xFFFFBAB1)
|
||||
val onErrorContainerDarkHighContrast = Color(0xFF000000)
|
||||
val backgroundDarkHighContrast = Color(0xFF101510)
|
||||
val onBackgroundDarkHighContrast = Color(0xFFDFE4DC)
|
||||
val surfaceDarkHighContrast = Color(0xFF101510)
|
||||
val onSurfaceDarkHighContrast = Color(0xFFFFFFFF)
|
||||
val surfaceVariantDarkHighContrast = Color(0xFF414941)
|
||||
val onSurfaceVariantDarkHighContrast = Color(0xFFF5FDF2)
|
||||
val outlineDarkHighContrast = Color(0xFFC5CDC3)
|
||||
val outlineVariantDarkHighContrast = Color(0xFFC5CDC3)
|
||||
val scrimDarkHighContrast = Color(0xFF000000)
|
||||
val inverseSurfaceDarkHighContrast = Color(0xFFDFE4DC)
|
||||
val inverseOnSurfaceDarkHighContrast = Color(0xFF000000)
|
||||
val inversePrimaryDarkHighContrast = Color(0xFF003216)
|
||||
val surfaceDimDarkHighContrast = Color(0xFF101510)
|
||||
val surfaceBrightDarkHighContrast = Color(0xFF353A35)
|
||||
val surfaceContainerLowestDarkHighContrast = Color(0xFF0A0F0B)
|
||||
val surfaceContainerLowDarkHighContrast = Color(0xFF181D18)
|
||||
val surfaceContainerDarkHighContrast = Color(0xFF1C211C)
|
||||
val surfaceContainerHighDarkHighContrast = Color(0xFF262B26)
|
||||
val surfaceContainerHighestDarkHighContrast = Color(0xFF313631)
|
||||
|
|
@ -1,105 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val MeshtasticGreen = Color(0xFF67EA94)
|
||||
val MeshtasticAlt = Color(0xFF2C2D3C)
|
||||
val HyperlinkBlue = Color(0xFF43C3B0)
|
||||
val AnnotationColor = Color(0xFF039BE5)
|
||||
|
||||
object IAQColors {
|
||||
val IAQExcellent = Color(0xFF00E400)
|
||||
val IAQGood = Color(0xFF92D050)
|
||||
val IAQLightlyPolluted = Color(0xFFFFFF00)
|
||||
val IAQModeratelyPolluted = Color(0xFFFF7300)
|
||||
val IAQHeavilyPolluted = Color(0xFFFF0000)
|
||||
val IAQSeverelyPolluted = Color(0xFF99004C)
|
||||
val IAQExtremelyPolluted = Color(0xFF663300)
|
||||
val IAQDangerouslyPolluted = Color(0xFF663300)
|
||||
}
|
||||
|
||||
object GraphColors {
|
||||
val InfantryBlue = Color(red = 75, green = 119, blue = 190)
|
||||
val LightGreen = Color(0xFF4BF0BE)
|
||||
val Purple = Color(0xFF9C27B0)
|
||||
val Pink = Color(red = 255, green = 102, blue = 204)
|
||||
val Orange = Color(0xFFFF8800)
|
||||
|
||||
val Green = Color.Green
|
||||
val Red = Color.Red
|
||||
val Blue = Color.Blue
|
||||
val Yellow = Color.Yellow
|
||||
val Magenta = Color.Magenta
|
||||
val Cyan = Color.Cyan
|
||||
}
|
||||
|
||||
object StatusColors {
|
||||
val ColorScheme.StatusGreen: Color
|
||||
@Composable
|
||||
get() = // If it might change based on theme
|
||||
if (isSystemInDarkTheme()) {
|
||||
Color(0xFF28A03B) // Example dark green
|
||||
} else {
|
||||
Color(0xFF30C047)
|
||||
}
|
||||
|
||||
val ColorScheme.StatusYellow: Color
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
Color(0xFFFFC107)
|
||||
} else {
|
||||
Color(0xFFFFD54F)
|
||||
}
|
||||
|
||||
val ColorScheme.StatusOrange: Color
|
||||
@Composable
|
||||
get() =
|
||||
if (isSystemInDarkTheme()) {
|
||||
Color(0xFFE07000)
|
||||
} else {
|
||||
Color(0xFFFF8800)
|
||||
}
|
||||
|
||||
val ColorScheme.StatusRed: Color
|
||||
@Composable
|
||||
get() = // If it might change based on theme
|
||||
if (isSystemInDarkTheme()) {
|
||||
Color(0xFFB00020)
|
||||
} else {
|
||||
Color(0xFFF44336)
|
||||
}
|
||||
|
||||
val ColorScheme.StatusBlue: Color
|
||||
@Composable
|
||||
get() = // If it might change based on theme
|
||||
if (isSystemInDarkTheme()) {
|
||||
Color(0xFF2196F3)
|
||||
} else {
|
||||
Color(0xFF42A5F5)
|
||||
}
|
||||
}
|
||||
|
||||
object MessageItemColors {
|
||||
val Red = Color(0x4DFF0000)
|
||||
}
|
||||
|
|
@ -1,303 +0,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/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("UnusedPrivateProperty")
|
||||
|
||||
package com.geeksville.mesh.ui.common.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.MaterialExpressiveTheme
|
||||
import androidx.compose.material3.MotionScheme.Companion.expressive
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
private val lightScheme =
|
||||
lightColorScheme(
|
||||
primary = primaryLight,
|
||||
onPrimary = onPrimaryLight,
|
||||
primaryContainer = primaryContainerLight,
|
||||
onPrimaryContainer = onPrimaryContainerLight,
|
||||
secondary = secondaryLight,
|
||||
onSecondary = onSecondaryLight,
|
||||
secondaryContainer = secondaryContainerLight,
|
||||
onSecondaryContainer = onSecondaryContainerLight,
|
||||
tertiary = tertiaryLight,
|
||||
onTertiary = onTertiaryLight,
|
||||
tertiaryContainer = tertiaryContainerLight,
|
||||
onTertiaryContainer = onTertiaryContainerLight,
|
||||
error = errorLight,
|
||||
onError = onErrorLight,
|
||||
errorContainer = errorContainerLight,
|
||||
onErrorContainer = onErrorContainerLight,
|
||||
background = backgroundLight,
|
||||
onBackground = onBackgroundLight,
|
||||
surface = surfaceLight,
|
||||
onSurface = onSurfaceLight,
|
||||
surfaceVariant = surfaceVariantLight,
|
||||
onSurfaceVariant = onSurfaceVariantLight,
|
||||
outline = outlineLight,
|
||||
outlineVariant = outlineVariantLight,
|
||||
scrim = scrimLight,
|
||||
inverseSurface = inverseSurfaceLight,
|
||||
inverseOnSurface = inverseOnSurfaceLight,
|
||||
inversePrimary = inversePrimaryLight,
|
||||
surfaceDim = surfaceDimLight,
|
||||
surfaceBright = surfaceBrightLight,
|
||||
surfaceContainerLowest = surfaceContainerLowestLight,
|
||||
surfaceContainerLow = surfaceContainerLowLight,
|
||||
surfaceContainer = surfaceContainerLight,
|
||||
surfaceContainerHigh = surfaceContainerHighLight,
|
||||
surfaceContainerHighest = surfaceContainerHighestLight,
|
||||
)
|
||||
|
||||
private val darkScheme =
|
||||
darkColorScheme(
|
||||
primary = primaryDark,
|
||||
onPrimary = onPrimaryDark,
|
||||
primaryContainer = primaryContainerDark,
|
||||
onPrimaryContainer = onPrimaryContainerDark,
|
||||
secondary = secondaryDark,
|
||||
onSecondary = onSecondaryDark,
|
||||
secondaryContainer = secondaryContainerDark,
|
||||
onSecondaryContainer = onSecondaryContainerDark,
|
||||
tertiary = tertiaryDark,
|
||||
onTertiary = onTertiaryDark,
|
||||
tertiaryContainer = tertiaryContainerDark,
|
||||
onTertiaryContainer = onTertiaryContainerDark,
|
||||
error = errorDark,
|
||||
onError = onErrorDark,
|
||||
errorContainer = errorContainerDark,
|
||||
onErrorContainer = onErrorContainerDark,
|
||||
background = backgroundDark,
|
||||
onBackground = onBackgroundDark,
|
||||
surface = surfaceDark,
|
||||
onSurface = onSurfaceDark,
|
||||
surfaceVariant = surfaceVariantDark,
|
||||
onSurfaceVariant = onSurfaceVariantDark,
|
||||
outline = outlineDark,
|
||||
outlineVariant = outlineVariantDark,
|
||||
scrim = scrimDark,
|
||||
inverseSurface = inverseSurfaceDark,
|
||||
inverseOnSurface = inverseOnSurfaceDark,
|
||||
inversePrimary = inversePrimaryDark,
|
||||
surfaceDim = surfaceDimDark,
|
||||
surfaceBright = surfaceBrightDark,
|
||||
surfaceContainerLowest = surfaceContainerLowestDark,
|
||||
surfaceContainerLow = surfaceContainerLowDark,
|
||||
surfaceContainer = surfaceContainerDark,
|
||||
surfaceContainerHigh = surfaceContainerHighDark,
|
||||
surfaceContainerHighest = surfaceContainerHighestDark,
|
||||
)
|
||||
|
||||
private val mediumContrastLightColorScheme =
|
||||
lightColorScheme(
|
||||
primary = primaryLightMediumContrast,
|
||||
onPrimary = onPrimaryLightMediumContrast,
|
||||
primaryContainer = primaryContainerLightMediumContrast,
|
||||
onPrimaryContainer = onPrimaryContainerLightMediumContrast,
|
||||
secondary = secondaryLightMediumContrast,
|
||||
onSecondary = onSecondaryLightMediumContrast,
|
||||
secondaryContainer = secondaryContainerLightMediumContrast,
|
||||
onSecondaryContainer = onSecondaryContainerLightMediumContrast,
|
||||
tertiary = tertiaryLightMediumContrast,
|
||||
onTertiary = onTertiaryLightMediumContrast,
|
||||
tertiaryContainer = tertiaryContainerLightMediumContrast,
|
||||
onTertiaryContainer = onTertiaryContainerLightMediumContrast,
|
||||
error = errorLightMediumContrast,
|
||||
onError = onErrorLightMediumContrast,
|
||||
errorContainer = errorContainerLightMediumContrast,
|
||||
onErrorContainer = onErrorContainerLightMediumContrast,
|
||||
background = backgroundLightMediumContrast,
|
||||
onBackground = onBackgroundLightMediumContrast,
|
||||
surface = surfaceLightMediumContrast,
|
||||
onSurface = onSurfaceLightMediumContrast,
|
||||
surfaceVariant = surfaceVariantLightMediumContrast,
|
||||
onSurfaceVariant = onSurfaceVariantLightMediumContrast,
|
||||
outline = outlineLightMediumContrast,
|
||||
outlineVariant = outlineVariantLightMediumContrast,
|
||||
scrim = scrimLightMediumContrast,
|
||||
inverseSurface = inverseSurfaceLightMediumContrast,
|
||||
inverseOnSurface = inverseOnSurfaceLightMediumContrast,
|
||||
inversePrimary = inversePrimaryLightMediumContrast,
|
||||
surfaceDim = surfaceDimLightMediumContrast,
|
||||
surfaceBright = surfaceBrightLightMediumContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestLightMediumContrast,
|
||||
surfaceContainerLow = surfaceContainerLowLightMediumContrast,
|
||||
surfaceContainer = surfaceContainerLightMediumContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighLightMediumContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestLightMediumContrast,
|
||||
)
|
||||
|
||||
private val highContrastLightColorScheme =
|
||||
lightColorScheme(
|
||||
primary = primaryLightHighContrast,
|
||||
onPrimary = onPrimaryLightHighContrast,
|
||||
primaryContainer = primaryContainerLightHighContrast,
|
||||
onPrimaryContainer = onPrimaryContainerLightHighContrast,
|
||||
secondary = secondaryLightHighContrast,
|
||||
onSecondary = onSecondaryLightHighContrast,
|
||||
secondaryContainer = secondaryContainerLightHighContrast,
|
||||
onSecondaryContainer = onSecondaryContainerLightHighContrast,
|
||||
tertiary = tertiaryLightHighContrast,
|
||||
onTertiary = onTertiaryLightHighContrast,
|
||||
tertiaryContainer = tertiaryContainerLightHighContrast,
|
||||
onTertiaryContainer = onTertiaryContainerLightHighContrast,
|
||||
error = errorLightHighContrast,
|
||||
onError = onErrorLightHighContrast,
|
||||
errorContainer = errorContainerLightHighContrast,
|
||||
onErrorContainer = onErrorContainerLightHighContrast,
|
||||
background = backgroundLightHighContrast,
|
||||
onBackground = onBackgroundLightHighContrast,
|
||||
surface = surfaceLightHighContrast,
|
||||
onSurface = onSurfaceLightHighContrast,
|
||||
surfaceVariant = surfaceVariantLightHighContrast,
|
||||
onSurfaceVariant = onSurfaceVariantLightHighContrast,
|
||||
outline = outlineLightHighContrast,
|
||||
outlineVariant = outlineVariantLightHighContrast,
|
||||
scrim = scrimLightHighContrast,
|
||||
inverseSurface = inverseSurfaceLightHighContrast,
|
||||
inverseOnSurface = inverseOnSurfaceLightHighContrast,
|
||||
inversePrimary = inversePrimaryLightHighContrast,
|
||||
surfaceDim = surfaceDimLightHighContrast,
|
||||
surfaceBright = surfaceBrightLightHighContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestLightHighContrast,
|
||||
surfaceContainerLow = surfaceContainerLowLightHighContrast,
|
||||
surfaceContainer = surfaceContainerLightHighContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighLightHighContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestLightHighContrast,
|
||||
)
|
||||
|
||||
private val mediumContrastDarkColorScheme =
|
||||
darkColorScheme(
|
||||
primary = primaryDarkMediumContrast,
|
||||
onPrimary = onPrimaryDarkMediumContrast,
|
||||
primaryContainer = primaryContainerDarkMediumContrast,
|
||||
onPrimaryContainer = onPrimaryContainerDarkMediumContrast,
|
||||
secondary = secondaryDarkMediumContrast,
|
||||
onSecondary = onSecondaryDarkMediumContrast,
|
||||
secondaryContainer = secondaryContainerDarkMediumContrast,
|
||||
onSecondaryContainer = onSecondaryContainerDarkMediumContrast,
|
||||
tertiary = tertiaryDarkMediumContrast,
|
||||
onTertiary = onTertiaryDarkMediumContrast,
|
||||
tertiaryContainer = tertiaryContainerDarkMediumContrast,
|
||||
onTertiaryContainer = onTertiaryContainerDarkMediumContrast,
|
||||
error = errorDarkMediumContrast,
|
||||
onError = onErrorDarkMediumContrast,
|
||||
errorContainer = errorContainerDarkMediumContrast,
|
||||
onErrorContainer = onErrorContainerDarkMediumContrast,
|
||||
background = backgroundDarkMediumContrast,
|
||||
onBackground = onBackgroundDarkMediumContrast,
|
||||
surface = surfaceDarkMediumContrast,
|
||||
onSurface = onSurfaceDarkMediumContrast,
|
||||
surfaceVariant = surfaceVariantDarkMediumContrast,
|
||||
onSurfaceVariant = onSurfaceVariantDarkMediumContrast,
|
||||
outline = outlineDarkMediumContrast,
|
||||
outlineVariant = outlineVariantDarkMediumContrast,
|
||||
scrim = scrimDarkMediumContrast,
|
||||
inverseSurface = inverseSurfaceDarkMediumContrast,
|
||||
inverseOnSurface = inverseOnSurfaceDarkMediumContrast,
|
||||
inversePrimary = inversePrimaryDarkMediumContrast,
|
||||
surfaceDim = surfaceDimDarkMediumContrast,
|
||||
surfaceBright = surfaceBrightDarkMediumContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast,
|
||||
surfaceContainerLow = surfaceContainerLowDarkMediumContrast,
|
||||
surfaceContainer = surfaceContainerDarkMediumContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighDarkMediumContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast,
|
||||
)
|
||||
|
||||
private val highContrastDarkColorScheme =
|
||||
darkColorScheme(
|
||||
primary = primaryDarkHighContrast,
|
||||
onPrimary = onPrimaryDarkHighContrast,
|
||||
primaryContainer = primaryContainerDarkHighContrast,
|
||||
onPrimaryContainer = onPrimaryContainerDarkHighContrast,
|
||||
secondary = secondaryDarkHighContrast,
|
||||
onSecondary = onSecondaryDarkHighContrast,
|
||||
secondaryContainer = secondaryContainerDarkHighContrast,
|
||||
onSecondaryContainer = onSecondaryContainerDarkHighContrast,
|
||||
tertiary = tertiaryDarkHighContrast,
|
||||
onTertiary = onTertiaryDarkHighContrast,
|
||||
tertiaryContainer = tertiaryContainerDarkHighContrast,
|
||||
onTertiaryContainer = onTertiaryContainerDarkHighContrast,
|
||||
error = errorDarkHighContrast,
|
||||
onError = onErrorDarkHighContrast,
|
||||
errorContainer = errorContainerDarkHighContrast,
|
||||
onErrorContainer = onErrorContainerDarkHighContrast,
|
||||
background = backgroundDarkHighContrast,
|
||||
onBackground = onBackgroundDarkHighContrast,
|
||||
surface = surfaceDarkHighContrast,
|
||||
onSurface = onSurfaceDarkHighContrast,
|
||||
surfaceVariant = surfaceVariantDarkHighContrast,
|
||||
onSurfaceVariant = onSurfaceVariantDarkHighContrast,
|
||||
outline = outlineDarkHighContrast,
|
||||
outlineVariant = outlineVariantDarkHighContrast,
|
||||
scrim = scrimDarkHighContrast,
|
||||
inverseSurface = inverseSurfaceDarkHighContrast,
|
||||
inverseOnSurface = inverseOnSurfaceDarkHighContrast,
|
||||
inversePrimary = inversePrimaryDarkHighContrast,
|
||||
surfaceDim = surfaceDimDarkHighContrast,
|
||||
surfaceBright = surfaceBrightDarkHighContrast,
|
||||
surfaceContainerLowest = surfaceContainerLowestDarkHighContrast,
|
||||
surfaceContainerLow = surfaceContainerLowDarkHighContrast,
|
||||
surfaceContainer = surfaceContainerDarkHighContrast,
|
||||
surfaceContainerHigh = surfaceContainerHighDarkHighContrast,
|
||||
surfaceContainerHighest = surfaceContainerHighestDarkHighContrast,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class ColorFamily(val color: Color, val onColor: Color, val colorContainer: Color, val onColorContainer: Color)
|
||||
|
||||
val unspecified_scheme = ColorFamily(Color.Unspecified, Color.Unspecified, Color.Unspecified, Color.Unspecified)
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AppTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
content:
|
||||
@Composable()
|
||||
() -> Unit,
|
||||
) {
|
||||
val colorScheme =
|
||||
when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
MaterialExpressiveTheme(
|
||||
colorScheme = colorScheme,
|
||||
motionScheme = expressive(),
|
||||
typography = AppTypography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
const val MODE_DYNAMIC = 6969420
|
||||
|
|
@ -1,22 +0,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/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
|
||||
val AppTypography = Typography()
|
||||
|
|
@ -66,7 +66,6 @@ import com.geeksville.mesh.navigation.ConfigRoute
|
|||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.connections.components.BLEDevices
|
||||
import com.geeksville.mesh.ui.connections.components.ConnectionsSegmentedBar
|
||||
import com.geeksville.mesh.ui.connections.components.CurrentlyConnectedInfo
|
||||
|
|
@ -83,6 +82,7 @@ import org.meshtastic.core.database.model.Node
|
|||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
|
||||
fun String?.isIPAddress(): Boolean = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
@Suppress("DEPRECATION")
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.MultiplePermissionsState
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
|
||||
/**
|
||||
* Composable that displays a list of Bluetooth Low Energy (BLE) devices and allows scanning. It handles Bluetooth
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LambdaParameterEventTrailing")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ import androidx.compose.ui.unit.dp
|
|||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.PaxcountProtos
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.ui.common.components.MaterialBatteryInfo
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MaterialBatteryInfo
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
@Composable
|
||||
fun CurrentlyConnectedInfo(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun EmptyStateContent(imageVector: ImageVector? = null, text: String, actionButton: @Composable (() -> Unit)? = null) {
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ import com.geeksville.mesh.model.BTScanModel
|
|||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.connections.isIPAddress
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
|
|
|
|||
|
|
@ -38,14 +38,14 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.TopLevelDestination
|
||||
import com.geeksville.mesh.ui.common.icons.Device
|
||||
import com.geeksville.mesh.ui.common.icons.MeshtasticIcons
|
||||
import com.geeksville.mesh.ui.common.icons.NoDevice
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
import org.meshtastic.core.ui.icon.Device
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.NoDevice
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
@Composable
|
||||
fun TopLevelNavIcon(destination: TopLevelDestination, connectionState: ConnectionState, deviceType: DeviceType?) {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun UsbDevices(
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ import androidx.compose.ui.unit.dp
|
|||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.model.Contact
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ import androidx.compose.ui.text.font.FontStyle
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.size
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.datastore.core.IOException
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
|
|
@ -81,15 +80,15 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.android.BuildUtils.warn
|
||||
import com.geeksville.mesh.model.DebugViewModel
|
||||
import com.geeksville.mesh.model.DebugViewModel.UiMeshLog
|
||||
import com.geeksville.mesh.ui.common.components.CopyIconButton
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.theme.AnnotationColor
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.CopyIconButton
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.theme.AnnotationColor
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.io.OutputStreamWriter
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.text.SimpleDateFormat
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ import com.geeksville.mesh.model.DebugViewModel
|
|||
import com.geeksville.mesh.model.DebugViewModel.UiMeshLog
|
||||
import com.geeksville.mesh.model.LogSearchManager.SearchMatch
|
||||
import com.geeksville.mesh.model.LogSearchManager.SearchState
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
internal fun DebugSearchNavigation(
|
||||
|
|
|
|||
|
|
@ -98,7 +98,6 @@ import com.geeksville.mesh.AppOnlyProtos
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.model.getChannel
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.node.components.NodeKeyStatusIcon
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
|
|
@ -109,6 +108,7 @@ import org.meshtastic.core.database.model.Message
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
private const val MESSAGE_CHARACTER_LIMIT_BYTES = 200
|
||||
|
|
|
|||
|
|
@ -71,12 +71,12 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.common.components.dragContainer
|
||||
import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.common.components.rememberDragDropState
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.dragContainer
|
||||
import org.meshtastic.core.ui.component.dragDropItemsIndexed
|
||||
import org.meshtastic.core.ui.component.rememberDragDropState
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
internal fun QuickChatScreen(modifier: Modifier = Modifier, viewModel: UIViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -49,12 +49,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.components.MDText
|
||||
import com.geeksville.mesh.ui.common.components.Rssi
|
||||
import com.geeksville.mesh.ui.common.components.Snr
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.MessageItemColors
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
|
|
@ -62,6 +57,11 @@ import org.meshtastic.core.database.model.Message
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MDText
|
||||
import org.meshtastic.core.ui.component.Rssi
|
||||
import org.meshtastic.core.ui.component.Snr
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.MessageItemColors
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -52,9 +52,9 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.ui.common.components.BottomSheetDialog
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.database.entity.Reaction
|
||||
import org.meshtastic.core.ui.component.BottomSheetDialog
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
private fun ReactionItem(emoji: String, emojiCount: Int = 1, onClick: () -> Unit = {}, onLongClick: () -> Unit = {}) {
|
||||
|
|
|
|||
|
|
@ -63,14 +63,6 @@ import com.geeksville.mesh.TelemetryProtos
|
|||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.BatteryInfo
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Cyan
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Green
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Magenta
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Red
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MAX_PERCENT_VALUE
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
|
|
@ -78,6 +70,13 @@ import com.geeksville.mesh.util.GraphUtil
|
|||
import com.geeksville.mesh.util.GraphUtil.createPath
|
||||
import com.geeksville.mesh.util.GraphUtil.plotPoint
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.BatteryInfo
|
||||
import org.meshtastic.core.ui.component.OptionLabel
|
||||
import org.meshtastic.core.ui.component.SlidingSelector
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Cyan
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Green
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Magenta
|
||||
|
||||
private const val CHART_WEIGHT = 1f
|
||||
private const val Y_AXIS_WEIGHT = 0.1f
|
||||
|
|
|
|||
|
|
@ -53,14 +53,14 @@ import com.geeksville.mesh.TelemetryProtos.Telemetry
|
|||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.IaqDisplayMode
|
||||
import com.geeksville.mesh.ui.common.components.IndoorAirQuality
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.IaqDisplayMode
|
||||
import org.meshtastic.core.ui.component.IndoorAirQuality
|
||||
import org.meshtastic.core.ui.component.OptionLabel
|
||||
import org.meshtastic.core.ui.component.SlidingSelector
|
||||
|
||||
@Composable
|
||||
fun EnvironmentMetricsScreen(viewModel: MetricsViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.TelemetryProtos
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.text.DecimalFormat
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -57,11 +57,11 @@ import com.geeksville.mesh.PaxcountProtos
|
|||
import com.geeksville.mesh.Portnums.PortNum
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import org.meshtastic.core.database.entity.MeshLog
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.OptionLabel
|
||||
import org.meshtastic.core.ui.component.SlidingSelector
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.model.util.metersIn
|
||||
import org.meshtastic.core.model.util.toString
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.text.DateFormat
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
|
|
|
|||
|
|
@ -62,15 +62,15 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.InfantryBlue
|
||||
import com.geeksville.mesh.ui.common.theme.GraphColors.Red
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import com.geeksville.mesh.util.GraphUtil
|
||||
import com.geeksville.mesh.util.GraphUtil.createPath
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.OptionLabel
|
||||
import org.meshtastic.core.ui.component.SlidingSelector
|
||||
import org.meshtastic.core.ui.theme.GraphColors.InfantryBlue
|
||||
import org.meshtastic.core.ui.theme.GraphColors.Red
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
|
|
|
|||
|
|
@ -59,14 +59,14 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.TimeFrame
|
||||
import com.geeksville.mesh.ui.common.components.LoraSignalIndicator
|
||||
import com.geeksville.mesh.ui.common.components.OptionLabel
|
||||
import com.geeksville.mesh.ui.common.components.SlidingSelector
|
||||
import com.geeksville.mesh.ui.common.components.SnrAndRssi
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.DATE_TIME_FORMAT
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import com.geeksville.mesh.util.GraphUtil.plotPoint
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.LoraSignalIndicator
|
||||
import org.meshtastic.core.ui.component.OptionLabel
|
||||
import org.meshtastic.core.ui.component.SlidingSelector
|
||||
import org.meshtastic.core.ui.component.SnrAndRssi
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private enum class Metric(val color: Color, val min: Float, val max: Float) {
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.metrics.CommonCharts.MS_PER_SEC
|
||||
import org.meshtastic.core.model.fullRouteDiscovery
|
||||
import org.meshtastic.core.model.getTracerouteResponse
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import java.text.DateFormat
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -134,13 +134,7 @@ import com.geeksville.mesh.model.MetricsViewModel
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.ServiceAction
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusOrange
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import com.geeksville.mesh.ui.node.components.NodeActionDialogs
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.node.components.TracerouteButton
|
||||
|
|
@ -168,6 +162,12 @@ import org.meshtastic.core.navigation.NodeDetailRoutes
|
|||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
private data class VectorMetricInfo(
|
||||
@StringRes val label: Int,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.rememberTimeTickWithLifecycle
|
||||
import com.geeksville.mesh.ui.node.components.NodeFilterTextField
|
||||
import com.geeksville.mesh.ui.node.components.NodeItem
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
|
|
@ -60,6 +59,7 @@ import org.meshtastic.core.database.model.Node
|
|||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.rememberTimeTickWithLifecycle
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun LastHeardInfo(modifier: Modifier = Modifier, lastHeard: Int, currentTimeMillis: Long) {
|
||||
|
|
|
|||
|
|
@ -41,10 +41,10 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.core.net.toUri
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.HyperlinkBlue
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.util.GPSFormat
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.HyperlinkBlue
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ import androidx.compose.ui.text.input.ImeAction
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.preview.LargeFontPreview
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.database.model.NodeSortOption
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -51,14 +51,14 @@ import androidx.compose.ui.unit.dp
|
|||
import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.ui.common.components.BatteryInfo
|
||||
import com.geeksville.mesh.ui.common.components.SignalInfo
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.BatteryInfo
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -54,14 +54,14 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.geeksville.mesh.ui.common.components.CopyIconButton
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import com.google.protobuf.ByteString
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.CopyIconButton
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
@Composable
|
||||
private fun KeyStatusDialog(@StringRes title: Int, @StringRes text: Int, key: ByteString?, onDismiss: () -> Unit = {}) =
|
||||
|
|
|
|||
|
|
@ -38,10 +38,10 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.database.model.isUnmessageableRole
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusYellow
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
|
|||
|
|
@ -30,17 +30,14 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun SatelliteCountInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
satCount: Int,
|
||||
) {
|
||||
fun SatelliteCountInfo(modifier: Modifier = Modifier, satCount: Int) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(18.dp),
|
||||
|
|
@ -59,9 +56,5 @@ fun SatelliteCountInfo(
|
|||
@PreviewLightDark
|
||||
@Composable
|
||||
fun SatelliteCountInfoPreview() {
|
||||
AppTheme {
|
||||
SatelliteCountInfo(
|
||||
satCount = 5,
|
||||
)
|
||||
}
|
||||
AppTheme { SatelliteCountInfo(satCount = 5) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ import androidx.compose.ui.platform.LocalDensity
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
private const val COOL_DOWN_TIME_MS = 30000L
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,6 @@ import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
|||
import com.geeksville.mesh.android.gpsDisabled
|
||||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
|
||||
|
|
@ -78,6 +75,9 @@ import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
|||
import kotlinx.coroutines.delay
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
/** A clickable settings button item. */
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.navigation.ConfigRoute
|
||||
import com.geeksville.mesh.navigation.ModuleRoute
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.radio.components.WarningDialog
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -73,16 +73,16 @@ import androidx.navigation.NavController
|
|||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
import com.geeksville.mesh.ui.common.components.dragContainer
|
||||
import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.common.components.rememberDragDropState
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.PreferenceFooter
|
||||
import org.meshtastic.core.ui.component.dragContainer
|
||||
import org.meshtastic.core.ui.component.dragDropItemsIndexed
|
||||
import org.meshtastic.core.ui.component.rememberDragDropState
|
||||
|
||||
@Composable
|
||||
private fun ChannelItem(
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
private val DeviceConfig.Role.description: Int
|
||||
get() =
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ 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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ import com.geeksville.mesh.ChannelProtos
|
|||
import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.EditBase64Preference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PositionPrecisionPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.google.protobuf.Descriptors
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
private const val SUPPORTED_FIELDS = 7
|
||||
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.common.components.TextDividerPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
import org.meshtastic.core.ui.component.TextDividerPreference
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -33,15 +33,15 @@ 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.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.model.RegionInfo
|
||||
import org.meshtastic.core.model.numChannels
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SignedIntegerEditTextPreference
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
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.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditPasswordPreference
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue