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
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.common.components.precisionBitsToMeters
|
||||
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.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val POSITION_PRECISION_MIN = 12
|
||||
|
|
|
|||
|
|
@ -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 NeighborInfoConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -44,16 +44,16 @@ import com.geeksville.mesh.ConfigProtos.Config.NetworkConfig
|
|||
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.EditIPv4Preference
|
||||
import com.geeksville.mesh.ui.common.components.EditPasswordPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.EditIPv4Preference
|
||||
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.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
private fun ScanErrorDialog(onDismiss: () -> Unit = {}) =
|
||||
|
|
|
|||
|
|
@ -28,12 +28,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.SignedIntegerEditTextPreference
|
||||
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.SignedIntegerEditTextPreference
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun PaxcounterConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
|
|||
|
|
@ -43,17 +43,17 @@ import com.geeksville.mesh.ConfigProtos
|
|||
import com.geeksville.mesh.ConfigProtos.Config.PositionConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.BitwisePreference
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.common.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.BitwisePreference
|
||||
import org.meshtastic.core.ui.component.EditTextPreference
|
||||
import org.meshtastic.core.ui.component.PreferenceCategory
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
|
|
|
|||
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