mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Merge branch 'main' into release/2.7.0
This commit is contained in:
commit
35b7845b7e
57 changed files with 777 additions and 299 deletions
|
|
@ -232,6 +232,7 @@ dependencies {
|
|||
implementation(libs.bundles.adaptive)
|
||||
implementation(libs.bundles.lifecycle)
|
||||
implementation(libs.bundles.navigation)
|
||||
implementation(libs.bundles.navigation3)
|
||||
implementation(libs.bundles.coroutines)
|
||||
implementation(libs.bundles.datastore)
|
||||
implementation(libs.bundles.room)
|
||||
|
|
|
|||
|
|
@ -193,12 +193,6 @@
|
|||
"title": "the original ZPS module from https://github.com/a-f-G-U-C/Meshtastic-ZPS",
|
||||
"page_url": "https://github.com/meshtastic/firmware/pull/7658",
|
||||
"zip_url": "https://github.com/meshtastic/firmware/actions/runs/17074730483"
|
||||
},
|
||||
{
|
||||
"id": "7583",
|
||||
"title": "chore(deps): update meshtastic/web to v2.6.6",
|
||||
"page_url": "https://github.com/meshtastic/firmware/pull/7583",
|
||||
"zip_url": "https://github.com/meshtastic/firmware/actions/runs/17070663764"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import android.os.Bundle
|
|||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
|
|
@ -47,7 +46,6 @@ import com.geeksville.mesh.model.BluetoothViewModel
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.DEEP_LINK_BASE_URI
|
||||
import com.geeksville.mesh.ui.MainScreen
|
||||
import com.geeksville.mesh.ui.common.components.MainMenuAction
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.intro.AppIntroductionScreen
|
||||
|
|
@ -117,11 +115,7 @@ class MainActivity :
|
|||
},
|
||||
)
|
||||
} else {
|
||||
MainScreen(
|
||||
uIViewModel = model,
|
||||
bluetoothViewModel = bluetoothViewModel,
|
||||
onAction = ::onMainMenuAction,
|
||||
)
|
||||
MainScreen(uIViewModel = model, bluetoothViewModel = bluetoothViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -204,30 +198,7 @@ class MainActivity :
|
|||
return resultPendingIntent!!
|
||||
}
|
||||
|
||||
private val createRangetestLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { file_uri -> model.saveRangetestCSV(file_uri) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSettingsPage() {
|
||||
createSettingsIntent().send()
|
||||
}
|
||||
|
||||
private fun onMainMenuAction(action: MainMenuAction) {
|
||||
when (action) {
|
||||
MainMenuAction.EXPORT_RANGETEST -> {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/csv"
|
||||
putExtra(Intent.EXTRA_TITLE, "rangetest.csv")
|
||||
}
|
||||
createRangetestLauncher.launch(intent)
|
||||
}
|
||||
|
||||
else -> warn("Unexpected action: $action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ import com.geeksville.mesh.repository.radio.MeshActivity
|
|||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.service.ServiceAction
|
||||
import com.geeksville.mesh.ui.common.components.MainMenuAction
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.util.getShortDate
|
||||
import com.geeksville.mesh.util.positionToMeter
|
||||
|
|
@ -848,7 +847,7 @@ constructor(
|
|||
|
||||
/** Write the persisted packet data out to a CSV file in the specified location. */
|
||||
@Suppress("detekt:CyclomaticComplexMethod", "detekt:LongMethod")
|
||||
fun saveRangetestCSV(uri: Uri) {
|
||||
fun saveRangeTestCsv(uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
// Extract distances to this device from position messages and put (node,SNR,distance)
|
||||
// in
|
||||
|
|
@ -1001,12 +1000,8 @@ constructor(
|
|||
private val _showAppIntro: MutableStateFlow<Boolean> = MutableStateFlow(!uiPrefs.appIntroCompleted)
|
||||
val showAppIntro: StateFlow<Boolean> = _showAppIntro.asStateFlow()
|
||||
|
||||
fun onMainMenuAction(action: MainMenuAction) {
|
||||
when (action) {
|
||||
MainMenuAction.SHOW_INTRO -> _showAppIntro.update { true }
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
fun showAppIntro() {
|
||||
_showAppIntro.update { true }
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -1177,7 +1177,9 @@ class MeshService :
|
|||
|
||||
// Generate our own hopsAway, comparing hopStart to hopLimit.
|
||||
it.hopsAway =
|
||||
if (packet.hopStart == 0 || packet.hopLimit > packet.hopStart) {
|
||||
if (packet.decoded.portnumValue == Portnums.PortNum.RANGE_TEST_APP_VALUE) {
|
||||
0 // These don't come with the .hop params, but do not propogate, so they must be 0
|
||||
} else if (packet.hopStart == 0 || packet.hopLimit > packet.hopStart) {
|
||||
-1
|
||||
} else {
|
||||
packet.hopStart - packet.hopLimit
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ 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.MainMenuAction
|
||||
import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
|
|
@ -146,7 +145,6 @@ fun MainScreen(
|
|||
uIViewModel: UIViewModel = hiltViewModel(),
|
||||
bluetoothViewModel: BluetoothViewModel = hiltViewModel(),
|
||||
scanModel: BTScanModel = hiltViewModel(),
|
||||
onAction: (MainMenuAction) -> Unit,
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val connectionState by uIViewModel.connectionState.collectAsStateWithLifecycle()
|
||||
|
|
@ -349,27 +347,19 @@ fun MainScreen(
|
|||
viewModel = uIViewModel,
|
||||
navController = navController,
|
||||
onAction = { action ->
|
||||
if (action is MainMenuAction) {
|
||||
when (action) {
|
||||
MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat)
|
||||
MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action)
|
||||
else -> onAction(action)
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> {
|
||||
navController.navigate(
|
||||
NodesRoutes.NodeDetailGraph(action.node.num),
|
||||
{
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
},
|
||||
)
|
||||
}
|
||||
} else if (action is NodeMenuAction) {
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> {
|
||||
navController.navigate(
|
||||
NodesRoutes.NodeDetailGraph(action.node.num),
|
||||
{
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
else -> {}
|
||||
}
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
else -> {}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,27 +17,21 @@
|
|||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -45,6 +39,7 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
|
|
@ -61,7 +56,7 @@ 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.settings.radio.RadioConfigMenuActions
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -69,7 +64,7 @@ fun MainAppBar(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: UIViewModel = hiltViewModel(),
|
||||
navController: NavHostController,
|
||||
onAction: (Any?) -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
) {
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentDestination = backStackEntry?.destination
|
||||
|
|
@ -117,13 +112,7 @@ fun MainAppBar(
|
|||
actions = {
|
||||
currentDestination?.let {
|
||||
when {
|
||||
it.isTopLevel() -> MainMenuActions(onAction)
|
||||
|
||||
currentDestination.hasRoute<SettingsRoutes.DebugPanel>() -> DebugMenuActions()
|
||||
|
||||
currentDestination.hasRoute<SettingsRoutes.Settings>() ->
|
||||
RadioConfigMenuActions(viewModel = viewModel)
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +133,7 @@ private fun MainAppBar(
|
|||
canNavigateUp: Boolean,
|
||||
onNavigateUp: () -> Unit,
|
||||
actions: @Composable () -> Unit,
|
||||
onAction: (Any?) -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
|
|
@ -195,44 +184,21 @@ private fun TopBarActions(
|
|||
isConnected: Boolean,
|
||||
showNodeChip: Boolean,
|
||||
actions: @Composable () -> Unit,
|
||||
onAction: (Any?) -> Unit,
|
||||
onAction: (NodeMenuAction) -> Unit,
|
||||
) {
|
||||
AnimatedVisibility(showNodeChip) {
|
||||
ourNode?.let { NodeChip(node = it, isThisNode = true, isConnected = isConnected, onAction = onAction) }
|
||||
}
|
||||
|
||||
actions()
|
||||
}
|
||||
|
||||
enum class MainMenuAction(@StringRes val stringRes: Int) {
|
||||
EXPORT_RANGETEST(R.string.save_rangetest),
|
||||
SHOW_INTRO(R.string.intro_show),
|
||||
QUICK_CHAT(R.string.quick_chat),
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MainMenuActions(onAction: (MainMenuAction) -> Unit) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { showMenu = true }) {
|
||||
Icon(imageVector = Icons.Default.MoreVert, contentDescription = stringResource(R.string.overflow_menu))
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false },
|
||||
modifier = Modifier.background(colorScheme.background.copy(alpha = 1f)),
|
||||
) {
|
||||
MainMenuAction.entries.forEach { action ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(id = action.stringRes)) },
|
||||
onClick = {
|
||||
onAction(action)
|
||||
showMenu = false
|
||||
},
|
||||
enabled = true,
|
||||
AnimatedVisibility(visible = showNodeChip, enter = fadeIn(), exit = fadeOut()) {
|
||||
ourNode?.let {
|
||||
NodeChip(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
node = it,
|
||||
isThisNode = true,
|
||||
isConnected = isConnected,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actions()
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
|
|
@ -247,7 +213,7 @@ private fun MainAppBarPreview(@PreviewParameter(BooleanProvider::class) canNavig
|
|||
showNodeChip = true,
|
||||
canNavigateUp = canNavigateUp,
|
||||
onNavigateUp = {},
|
||||
actions = { MainMenuActions(onAction = {}) },
|
||||
actions = {},
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -68,7 +67,7 @@ fun PositionPrecisionPreference(
|
|||
},
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
AnimatedVisibility(visible = value != POSITION_DISABLED) {
|
||||
if (value != POSITION_DISABLED) {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.precise_location),
|
||||
checked = value == POSITION_ENABLED,
|
||||
|
|
@ -80,7 +79,7 @@ fun PositionPrecisionPreference(
|
|||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = value in (POSITION_DISABLED + 1)..<POSITION_ENABLED) {
|
||||
if (value in (POSITION_DISABLED + 1) until POSITION_ENABLED) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Slider(
|
||||
value = value.toFloat(),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
|
|
@ -59,6 +61,8 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.AppOnlyProtos
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.getChannel
|
||||
|
|
@ -160,7 +164,7 @@ private fun SecurityIconDisplay(
|
|||
Icon(
|
||||
imageVector = badgeIcon,
|
||||
contentDescription = stringResource(R.string.security_icon_badge_warning_description),
|
||||
tint = badgeIconColor ?: MaterialTheme.colorScheme.onError, // Default for contrast
|
||||
tint = badgeIconColor ?: colorScheme.onError, // Default for contrast
|
||||
modifier = Modifier.size(16.dp), // Adjusted badge icon size
|
||||
)
|
||||
}
|
||||
|
|
@ -291,6 +295,29 @@ fun SecurityIcon(
|
|||
externalOnClick = externalOnClick,
|
||||
)
|
||||
|
||||
/**
|
||||
* Overload for [SecurityIcon] that enables recomposition when making changes to the [ChannelSettings].
|
||||
*
|
||||
* @param baseContentDescription The base content description for the icon.
|
||||
* @param externalOnClick Optional lambda for external actions, invoked when the icon is clicked.
|
||||
*/
|
||||
@Composable
|
||||
fun SecurityIcon(
|
||||
channelSettings: ChannelSettings,
|
||||
loraConfig: LoRaConfig,
|
||||
baseContentDescription: String = stringResource(id = R.string.security_icon_description),
|
||||
externalOnClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val channel = Channel(channelSettings, loraConfig)
|
||||
SecurityIcon(
|
||||
isLowEntropyKey = channel.isLowEntropyKey,
|
||||
isPreciseLocation = channel.isPreciseLocation,
|
||||
isMqttEnabled = channel.isMqttEnabled,
|
||||
baseContentDescription = baseContentDescription,
|
||||
externalOnClick = externalOnClick,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for [SecurityIcon] that takes an [AppOnlyProtos.ChannelSet] and a channel index. If the channel at the given
|
||||
* index is not found, nothing is rendered.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import android.net.InetAddresses
|
|||
import android.os.Build
|
||||
import android.util.Patterns
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
|
|
@ -77,7 +76,6 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.gpsDisabled
|
||||
|
|
@ -472,14 +470,12 @@ fun ConnectionsScreen(
|
|||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
|
||||
Row(
|
||||
Text(
|
||||
text = scanStatusText.orEmpty(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(text = BuildConfig.VERSION_NAME, fontSize = 10.sp, textAlign = TextAlign.Start)
|
||||
Text(text = scanStatusText.orEmpty(), fontSize = 10.sp, textAlign = TextAlign.End)
|
||||
}
|
||||
fontSize = 10.sp,
|
||||
textAlign = TextAlign.End,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,17 @@ import android.os.Build
|
|||
import android.provider.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import androidx.navigation3.runtime.entry
|
||||
import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Composable function for the main application introduction screen. This screen guides the user through initial setup
|
||||
|
|
@ -43,7 +46,6 @@ import com.google.accompanist.permissions.rememberPermissionState
|
|||
@Composable
|
||||
fun AppIntroductionScreen(onDone: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val navController = rememberNavController()
|
||||
|
||||
val notificationPermissionState: PermissionState? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
|
@ -56,56 +58,76 @@ fun AppIntroductionScreen(onDone: () -> Unit) {
|
|||
listOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||
val locationPermissionState = rememberMultiplePermissionsState(permissions = locationPermissions)
|
||||
|
||||
NavHost(navController = navController, startDestination = IntroRoute.Welcome.route) {
|
||||
composable(IntroRoute.Welcome.route) {
|
||||
WelcomeScreen(onGetStarted = { navController.navigate(IntroRoute.Notifications.route) })
|
||||
}
|
||||
composable(IntroRoute.Notifications.route) {
|
||||
val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true
|
||||
NotificationsScreen(
|
||||
showNextButton = notificationsAlreadyGranted,
|
||||
onSkip = { navController.navigate(IntroRoute.Location.route) },
|
||||
onConfigure = {
|
||||
if (notificationsAlreadyGranted) {
|
||||
navController.navigate(IntroRoute.CriticalAlerts.route)
|
||||
} else {
|
||||
// For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS
|
||||
// For lower versions, notificationPermissionState will be null, and this branch isn't taken.
|
||||
notificationPermissionState.launchPermissionRequest()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
composable(IntroRoute.CriticalAlerts.route) {
|
||||
CriticalAlertsScreen(
|
||||
onSkip = { navController.navigate(IntroRoute.Location.route) },
|
||||
onConfigure = {
|
||||
// Intent to open the specific notification channel settings for "my_alerts"
|
||||
// This allows the user to enable critical alerts if they were initially denied
|
||||
// or to adjust settings for notifications that can bypass Do Not Disturb.
|
||||
val intent =
|
||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts")
|
||||
val backStack = rememberNavBackStack(Welcome)
|
||||
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = { backStack.removeLastOrNull() },
|
||||
entryProvider =
|
||||
entryProvider {
|
||||
entry<Welcome> { WelcomeScreen(onGetStarted = { backStack.add(Notifications) }) }
|
||||
|
||||
entry<Notifications> {
|
||||
val notificationsAlreadyGranted = notificationPermissionState?.status?.isGranted ?: true
|
||||
NotificationsScreen(
|
||||
showNextButton = notificationsAlreadyGranted,
|
||||
onSkip = {
|
||||
// Skip this screen and the Critical Alerts screen. Proceed to Location screen.
|
||||
backStack.add(Location)
|
||||
},
|
||||
onConfigure = {
|
||||
if (notificationsAlreadyGranted) {
|
||||
backStack.add(CriticalAlerts)
|
||||
} else {
|
||||
// For Android Tiramisu (API 33) and above, this requests POST_NOTIFICATIONS
|
||||
// For lower versions, notificationPermissionState will be null, and this branch isn't
|
||||
// taken.
|
||||
notificationPermissionState?.launchPermissionRequest()
|
||||
}
|
||||
context.startActivity(intent)
|
||||
navController.navigate(IntroRoute.Location.route)
|
||||
},
|
||||
)
|
||||
}
|
||||
composable(IntroRoute.Location.route) {
|
||||
val locationAlreadyGranted = locationPermissionState.allPermissionsGranted
|
||||
LocationScreen(
|
||||
showNextButton = locationAlreadyGranted,
|
||||
onSkip = onDone, // Callback to signify completion of the intro flow
|
||||
onConfigure = {
|
||||
if (locationAlreadyGranted) {
|
||||
onDone() // Permissions already granted, proceed to finish
|
||||
} else {
|
||||
locationPermissionState.launchMultiplePermissionRequest()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
entry<CriticalAlerts> {
|
||||
CriticalAlertsScreen(
|
||||
onSkip = { backStack.add(Location) },
|
||||
onConfigure = {
|
||||
// Intent to open the specific notification channel settings for "my_alerts"
|
||||
// This allows the user to enable critical alerts if they were initially denied
|
||||
// or to adjust settings for notifications that can bypass Do Not Disturb.
|
||||
val intent =
|
||||
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
||||
putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts")
|
||||
}
|
||||
context.startActivity(intent)
|
||||
backStack.add(Location)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
entry<Location> {
|
||||
val locationAlreadyGranted = locationPermissionState.allPermissionsGranted
|
||||
LocationScreen(
|
||||
showNextButton = locationAlreadyGranted,
|
||||
onSkip = onDone, // Callback to signify completion of the intro flow
|
||||
onConfigure = {
|
||||
if (locationAlreadyGranted) {
|
||||
onDone() // Permissions already granted, proceed to finish
|
||||
} else {
|
||||
locationPermissionState.launchMultiplePermissionRequest()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable private data object Welcome : NavKey
|
||||
|
||||
@Serializable private data object Notifications : NavKey
|
||||
|
||||
@Serializable private data object CriticalAlerts : NavKey
|
||||
|
||||
@Serializable private data object Location : NavKey
|
||||
|
|
|
|||
|
|
@ -1,29 +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.intro
|
||||
|
||||
/** Sealed class defining type-safe navigation routes for the app introduction flow. */
|
||||
sealed class IntroRoute(val route: String) {
|
||||
object Welcome : IntroRoute("welcome")
|
||||
|
||||
object Notifications : IntroRoute("notifications")
|
||||
|
||||
object Location : IntroRoute("location")
|
||||
|
||||
object CriticalAlerts : IntroRoute("critical_alerts")
|
||||
}
|
||||
|
|
@ -256,7 +256,7 @@ private fun VoltageCurrentDisplay(envMetrics: TelemetryProtos.EnvironmentMetrics
|
|||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
text = "%s %.2f A".format(stringResource(R.string.current), current),
|
||||
text = "%s %.2f mA".format(stringResource(R.string.current), current),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = MaterialTheme.typography.labelLarge.fontSize,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ package com.geeksville.mesh.ui.settings
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -30,8 +32,13 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.rounded.FormatPaint
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.material.icons.rounded.Memory
|
||||
import androidx.compose.material.icons.rounded.Output
|
||||
import androidx.compose.material.icons.rounded.WavingHand
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -41,8 +48,8 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.DeviceUIProtos.Language
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
|
|
@ -51,12 +58,15 @@ import com.geeksville.mesh.navigation.getNavRouteFrom
|
|||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigItemList
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.util.LanguageUtils
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -165,7 +175,7 @@ fun SettingsScreen(
|
|||
onNavigate = onNavigate,
|
||||
)
|
||||
|
||||
TitledCard(title = stringResource(R.string.phone_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
TitledCard(title = stringResource(R.string.app_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (state.analyticsAvailable) {
|
||||
SettingsItemSwitch(
|
||||
text = stringResource(R.string.analytics_okay),
|
||||
|
|
@ -212,6 +222,74 @@ fun SettingsScreen(
|
|||
choices = themeMap.mapValues { (_, value) -> { uiViewModel.setTheme(value) } },
|
||||
)
|
||||
}
|
||||
|
||||
val exportRangeTestLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { uri -> uiViewModel.saveRangeTestCsv(uri) }
|
||||
}
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.save_rangetest),
|
||||
leadingIcon = Icons.Rounded.Output,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/csv"
|
||||
putExtra(Intent.EXTRA_TITLE, "rangetest.csv")
|
||||
}
|
||||
exportRangeTestLauncher.launch(intent)
|
||||
}
|
||||
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.intro_show),
|
||||
leadingIcon = Icons.Rounded.WavingHand,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
uiViewModel.showAppIntro()
|
||||
}
|
||||
|
||||
AppVersionButton(excludedModulesUnlocked) { uiViewModel.unlockExcludedModules() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val UNLOCK_CLICK_COUNT = 5 // Number of clicks required to unlock excluded modules.
|
||||
private const val UNLOCKED_CLICK_COUNT = 3 // Number of clicks before we toast that modules are already unlocked.
|
||||
private const val UNLOCK_TIMEOUT_SECONDS = 1 // Timeout in seconds to reset the click counter.
|
||||
|
||||
/** A button to display the app version. Clicking it 5 times will unlock the excluded modules. */
|
||||
@Composable
|
||||
private fun AppVersionButton(excludedModulesUnlocked: Boolean, onUnlockExcludedModules: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
var clickCount by remember { mutableIntStateOf(0) }
|
||||
|
||||
LaunchedEffect(clickCount) {
|
||||
if (clickCount in 1..<UNLOCK_CLICK_COUNT) {
|
||||
delay(UNLOCK_TIMEOUT_SECONDS.seconds)
|
||||
clickCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItemDetail(
|
||||
text = stringResource(R.string.app_version),
|
||||
icon = Icons.Rounded.Memory,
|
||||
trailingText = BuildConfig.VERSION_NAME,
|
||||
) {
|
||||
clickCount = clickCount.inc().coerceIn(0, UNLOCK_CLICK_COUNT)
|
||||
|
||||
when {
|
||||
clickCount == UNLOCKED_CLICK_COUNT && excludedModulesUnlocked -> {
|
||||
clickCount = 0
|
||||
Toast.makeText(context, context.getString(R.string.modules_already_unlocked), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
clickCount == UNLOCK_CLICK_COUNT -> {
|
||||
clickCount = 0
|
||||
onUnlockExcludedModules()
|
||||
Toast.makeText(context, context.getString(R.string.modules_unlocked), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
|
@ -25,24 +24,19 @@ import androidx.compose.material.icons.filled.Download
|
|||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material.icons.rounded.BugReport
|
||||
import androidx.compose.material.icons.rounded.CleaningServices
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.AdminRoute
|
||||
import com.geeksville.mesh.navigation.ConfigRoute
|
||||
import com.geeksville.mesh.navigation.ModuleRoute
|
||||
|
|
@ -53,8 +47,6 @@ 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 kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -168,32 +160,6 @@ fun RadioConfigItemList(
|
|||
}
|
||||
}
|
||||
|
||||
private const val UNLOCK_CLICK_COUNT = 5 // Number of clicks required to unlock excluded modules.
|
||||
private const val UNLOCK_TIMEOUT_SECONDS = 3 // Timeout in seconds to reset the click counter.
|
||||
|
||||
@Composable
|
||||
fun RadioConfigMenuActions(modifier: Modifier = Modifier, viewModel: UIViewModel = hiltViewModel()) {
|
||||
val context = LocalContext.current
|
||||
var counter by remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(counter) {
|
||||
if (counter > 0 && counter < UNLOCK_CLICK_COUNT) {
|
||||
delay(UNLOCK_TIMEOUT_SECONDS.seconds)
|
||||
counter = 0
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
enabled = counter < UNLOCK_CLICK_COUNT,
|
||||
onClick = {
|
||||
counter++
|
||||
if (counter == UNLOCK_CLICK_COUNT) {
|
||||
viewModel.unlockExcludedModules()
|
||||
Toast.makeText(context, context.getString(R.string.modules_unlocked), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
) {}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RadioSettingsScreenPreview() = AppTheme {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CloudDownload
|
||||
import androidx.compose.material.icons.filled.CloudUpload
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
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.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
|
||||
/**
|
||||
* At this firmware version periodic position sharing on a secondary channel was implemented. To enable this feature the
|
||||
* user must disable position on the primary channel and enable on a secondary channel. The lowest indexed secondary
|
||||
* channel with the position enabled will conduct the automatic position broadcasts.
|
||||
*/
|
||||
internal const val SECONDARY_CHANNEL_EPOCH = "2.6.10"
|
||||
|
||||
internal enum class ChannelIcons(
|
||||
val icon: ImageVector,
|
||||
@StringRes val descriptionResId: Int,
|
||||
@StringRes val additionalInfoResId: Int,
|
||||
) {
|
||||
LOCATION(
|
||||
icon = Icons.Filled.LocationOn,
|
||||
descriptionResId = R.string.location_sharing,
|
||||
additionalInfoResId = R.string.periodic_position_broadcast,
|
||||
),
|
||||
UPLINK(
|
||||
icon = Icons.Filled.CloudUpload,
|
||||
descriptionResId = R.string.uplink_enabled,
|
||||
additionalInfoResId = R.string.uplink_feature_description,
|
||||
),
|
||||
DOWNLINK(
|
||||
icon = Icons.Filled.CloudDownload,
|
||||
descriptionResId = R.string.downlink_enabled,
|
||||
additionalInfoResId = R.string.downlink_feature_description,
|
||||
),
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ChannelLegend(onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().clickable { onClick.invoke() },
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
) {
|
||||
Row {
|
||||
Icon(imageVector = Icons.Filled.Info, contentDescription = stringResource(R.string.info))
|
||||
Text(
|
||||
text = stringResource(R.string.primary),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.secondary),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ChannelLegendDialog(firmwareVersion: DeviceVersion, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.channel_features)) },
|
||||
text = {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.primary),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
Text(
|
||||
text = "- ${stringResource(R.string.primary_channel_feature)}",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.secondary),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
Text(
|
||||
text = "- ${stringResource(R.string.secondary_no_telemetry)}",
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Text(
|
||||
text =
|
||||
if (firmwareVersion >= DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) {
|
||||
/* 2.6.10+ */
|
||||
"- ${stringResource(R.string.secondary_channel_position_feature)}"
|
||||
} else {
|
||||
"- ${stringResource(R.string.manual_position_request)}"
|
||||
},
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
IconDefinitions()
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.security_icon_help_dismiss)) }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun IconDefinitions() {
|
||||
Text(text = stringResource(R.string.icon_meanings), style = MaterialTheme.typography.titleLarge)
|
||||
ChannelIcons.entries.forEach { icon ->
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = icon.icon, contentDescription = stringResource(icon.descriptionResId))
|
||||
Column(modifier = Modifier.padding(start = 16.dp)) {
|
||||
Text(text = stringResource(icon.descriptionResId), style = MaterialTheme.typography.titleMedium)
|
||||
Text(text = stringResource(icon.additionalInfoResId), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
if (icon != ChannelIcons.entries.lastOrNull()) {
|
||||
HorizontalDivider(modifier = Modifier.padding(top = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewChannelLegendDialog() {
|
||||
ChannelLegendDialog(firmwareVersion = DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) {}
|
||||
}
|
||||
|
|
@ -32,7 +32,6 @@ import androidx.compose.foundation.layout.RowScope
|
|||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
|
|
@ -59,6 +58,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -73,6 +73,7 @@ import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.channelSettings
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SecurityIcon
|
||||
|
|
@ -89,18 +90,20 @@ private fun ChannelItem(
|
|||
onClick: () -> Unit = {},
|
||||
content: @Composable RowScope.() -> Unit,
|
||||
) {
|
||||
val fontColor = if (index == 0) MaterialTheme.colorScheme.primary else Color.Unspecified
|
||||
Card(modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp).clickable(enabled = enabled) { onClick() }) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp),
|
||||
) {
|
||||
AssistChip(onClick = onClick, label = { Text(text = "$index") })
|
||||
AssistChip(onClick = onClick, label = { Text(text = "$index", color = fontColor) })
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.weight(1f),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = fontColor,
|
||||
)
|
||||
content()
|
||||
}
|
||||
|
|
@ -112,11 +115,34 @@ private fun ChannelCard(
|
|||
index: Int,
|
||||
title: String,
|
||||
enabled: Boolean,
|
||||
channelSettings: ChannelSettings,
|
||||
loraConfig: LoRaConfig,
|
||||
onEditClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit,
|
||||
channel: Channel,
|
||||
sharesLocation: Boolean,
|
||||
) = ChannelItem(index = index, title = title, enabled = enabled, onClick = onEditClick) {
|
||||
SecurityIcon(channel)
|
||||
if (sharesLocation) {
|
||||
Icon(
|
||||
imageVector = ChannelIcons.LOCATION.icon,
|
||||
contentDescription = stringResource(ChannelIcons.LOCATION.descriptionResId),
|
||||
modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp),
|
||||
)
|
||||
}
|
||||
if (channelSettings.uplinkEnabled) {
|
||||
Icon(
|
||||
imageVector = ChannelIcons.UPLINK.icon,
|
||||
contentDescription = stringResource(ChannelIcons.UPLINK.descriptionResId),
|
||||
modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp),
|
||||
)
|
||||
}
|
||||
if (channelSettings.downlinkEnabled) {
|
||||
Icon(
|
||||
imageVector = ChannelIcons.DOWNLINK.icon,
|
||||
contentDescription = stringResource(ChannelIcons.DOWNLINK.descriptionResId),
|
||||
modifier = Modifier.wrapContentSize().padding(horizontal = 5.dp),
|
||||
)
|
||||
}
|
||||
SecurityIcon(channelSettings, loraConfig)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
IconButton(onClick = { onDeleteClick() }) {
|
||||
Icon(
|
||||
|
|
@ -135,7 +161,7 @@ fun ChannelSelection(
|
|||
isSelected: Boolean,
|
||||
onSelected: (Boolean) -> Unit,
|
||||
channel: Channel,
|
||||
) = ChannelItem(index = index, title = title, enabled = enabled, onClick = {}) {
|
||||
) = ChannelItem(index = index, title = title, enabled = enabled) {
|
||||
SecurityIcon(channel)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Checkbox(enabled = enabled, checked = isSelected, onCheckedChange = onSelected)
|
||||
|
|
@ -152,25 +178,28 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
|||
ChannelSettingsItemList(
|
||||
settingsList = state.channelList,
|
||||
loraConfig = state.radioConfig.lora,
|
||||
enabled = state.connected,
|
||||
maxChannels = viewModel.maxChannels,
|
||||
firmwareVersion = state.metadata?.firmwareVersion ?: "0.0.0",
|
||||
enabled = state.connected,
|
||||
onPositiveClicked = { channelListInput -> viewModel.updateChannels(channelListInput, state.channelList) },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun ChannelSettingsItemList(
|
||||
private fun ChannelSettingsItemList(
|
||||
settingsList: List<ChannelSettings>,
|
||||
loraConfig: LoRaConfig,
|
||||
maxChannels: Int = 8,
|
||||
firmwareVersion: String,
|
||||
enabled: Boolean,
|
||||
onNegativeClicked: () -> Unit = {},
|
||||
onPositiveClicked: (List<ChannelSettings>) -> Unit,
|
||||
) {
|
||||
val primarySettings = settingsList.getOrNull(0) ?: return
|
||||
val modemPresetName by remember(loraConfig) { mutableStateOf(Channel(loraConfig = loraConfig).name) }
|
||||
val primaryChannel by remember(loraConfig) { mutableStateOf(Channel(primarySettings, loraConfig)) }
|
||||
val fwVersion by
|
||||
remember(firmwareVersion) { mutableStateOf(DeviceVersion(firmwareVersion.substringBeforeLast("."))) }
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
val settingsListInput =
|
||||
|
|
@ -180,7 +209,7 @@ fun ChannelSettingsItemList(
|
|||
|
||||
val listState = rememberLazyListState()
|
||||
val dragDropState =
|
||||
rememberDragDropState(listState, headerCount = 1) { fromIndex, toIndex ->
|
||||
rememberDragDropState(listState) { fromIndex, toIndex ->
|
||||
if (toIndex in settingsListInput.indices && fromIndex in settingsListInput.indices) {
|
||||
settingsListInput.apply { add(toIndex, removeAt(fromIndex)) }
|
||||
}
|
||||
|
|
@ -191,6 +220,7 @@ fun ChannelSettingsItemList(
|
|||
settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 }
|
||||
|
||||
var showEditChannelDialog: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
var showChannelLegendDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showEditChannelDialog != null) {
|
||||
val index = showEditChannelDialog ?: return
|
||||
|
|
@ -209,6 +239,10 @@ fun ChannelSettingsItemList(
|
|||
)
|
||||
}
|
||||
|
||||
if (showChannelLegendDialog) {
|
||||
ChannelLegendDialog(fwVersion) { showChannelLegendDialog = false }
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().clickable(onClick = {}, enabled = false)) {
|
||||
Column {
|
||||
ChannelsConfigHeader(
|
||||
|
|
@ -230,11 +264,11 @@ fun ChannelSettingsItemList(
|
|||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.primary),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
|
||||
ChannelLegend { showChannelLegendDialog = true }
|
||||
|
||||
val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList())
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current),
|
||||
state = listState,
|
||||
|
|
@ -245,38 +279,16 @@ fun ChannelSettingsItemList(
|
|||
channel,
|
||||
isDragging,
|
||||
->
|
||||
val channelObj = Channel(channel, loraConfig)
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
channelSettings = channel,
|
||||
loraConfig = loraConfig,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) },
|
||||
channel = channelObj,
|
||||
sharesLocation = locationChannel == index,
|
||||
)
|
||||
if (index == 0 && !isDragging) {
|
||||
Text(
|
||||
text = stringResource(R.string.primary_channel_feature),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(text = stringResource(R.string.secondary), color = MaterialTheme.colorScheme.onBackground)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.secondary_no_telemetry),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.manual_position_request),
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 10.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
|
|
@ -286,7 +298,6 @@ fun ChannelSettingsItemList(
|
|||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
onNegativeClicked()
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
|
|
@ -338,6 +349,33 @@ private fun ChannelsConfigHeader(frequency: Float, slot: Int) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what [Channel] if any is enabled to conduct automatic location sharing.
|
||||
*
|
||||
* @param firmwareVersion of the connected node.
|
||||
* @param settingsList Current list of channels on the node.
|
||||
* @return the index of the channel within `settingsList`.
|
||||
*/
|
||||
private fun determineLocationSharingChannel(firmwareVersion: DeviceVersion, settingsList: List<ChannelSettings>): Int {
|
||||
var output = -1
|
||||
if (firmwareVersion >= DeviceVersion(asString = SECONDARY_CHANNEL_EPOCH)) {
|
||||
/* Essentially the first index with the setting enabled */
|
||||
for ((i, settings) in settingsList.withIndex()) {
|
||||
if (settings.moduleSettings.positionPrecision > 0) {
|
||||
output = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Only the primary channel at index 0 can share locations automatically */
|
||||
val primary = settingsList[0]
|
||||
if (primary.moduleSettings.positionPrecision > 0) {
|
||||
output = 0
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ChannelSettingsPreview() {
|
||||
|
|
@ -351,6 +389,7 @@ private fun ChannelSettingsPreview() {
|
|||
channelSettings { name = stringResource(R.string.channel_name) },
|
||||
),
|
||||
loraConfig = Channel.default.loraConfig,
|
||||
firmwareVersion = "1.3.2",
|
||||
enabled = true,
|
||||
onPositiveClicked = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -782,4 +782,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -776,4 +776,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">Шаблон за URL</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Настройки на телефона</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Vorlage</string>
|
||||
<string name="track_point">Verlaufspunkt</string>
|
||||
<string name="phone_settings">Telefoneinstellungen</string>
|
||||
<string name="channel_features">Kanalfunktionen</string>
|
||||
<string name="location_sharing">Standortfreigabe</string>
|
||||
<string name="periodic_position_broadcast">Regelmäßige Standortübertragung</string>
|
||||
<string name="uplink_feature_description">Wenn aktiviert, werden Nachrichten aus dem Mesh über das konfigurierte Gateway eines beliebigen Knotens an das **öffentliche** Internet gesendet.</string>
|
||||
<string name="downlink_feature_description">Nachrichten von einem öffentlichen Internet Gateway werden an das lokale Mesh weitergeleitet. Aufgrund der Nullsprungrichtlinie wird der Datenverkehr vom MQTT Standardserver nicht über dieses Gerät hinaus weitergeleitet.</string>
|
||||
<string name="icon_meanings">Symbolbedeutung</string>
|
||||
<string name="secondary_channel_position_feature">Durch Deaktivieren des Standortes auf dem primären Kanal werden regelmäßige Standortübertragungen auf dem ersten sekundären Kanal mit aktiviertem Standort ermöglicht, andernfalls ist eine manuelle Standortanforderung erforderlich.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -775,4 +775,11 @@ Rango de Valores 0 - 500.</string>
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL mall</string>
|
||||
<string name="track_point">jälgimispunkt</string>
|
||||
<string name="phone_settings">Telefoni seaded</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL-mallipohja</string>
|
||||
<string name="track_point">seurantapiste</string>
|
||||
<string name="phone_settings">Puhelimen asetukset</string>
|
||||
<string name="channel_features">Kanavan ominaisuudet</string>
|
||||
<string name="location_sharing">Sijainnin jakaminen</string>
|
||||
<string name="periodic_position_broadcast">Sijainnin toistuva lähetys</string>
|
||||
<string name="uplink_feature_description">Verkosta tulevat viestit lähetetään julkiseen internetiin minkä tahansa laitteen määritetyn yhdyskäytävän kautta.</string>
|
||||
<string name="downlink_feature_description">Julkisesta internet-yhdyskäytävästä tulevat viestit välitetään paikalliseen mesh-verkkoon. Nollahyppysääntöjen vuoksi oletuksena MQTT-palvelimelta tuleva liikenne ei etene tätä laitetta pidemmälle.</string>
|
||||
<string name="icon_meanings">Kuvakkeiden merkitykset</string>
|
||||
<string name="secondary_channel_position_feature">Sijainnin poistaminen käytöstä ensisijaisella kanavalla mahdollistaa sijainnin jaksottaisen lähetyksen ensimmäisellä toissijaisella kanavalla, jossa sijainti on käytössä. Muussa tapauksessa vaaditaan manuaalinen sijaintipyyntö.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -771,4 +771,11 @@
|
|||
<string name="url_template">Modèle d\'URL</string>
|
||||
<string name="track_point">Point de suivi</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -780,4 +780,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -776,4 +776,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">Template dell\'URL</string>
|
||||
<string name="track_point">punto di interesse</string>
|
||||
<string name="phone_settings">Impostazioni telefono</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -773,4 +773,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -772,4 +772,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">Modelo de URL</string>
|
||||
<string name="track_point">ponto de rastreamento</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -776,4 +776,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -775,4 +775,11 @@
|
|||
<string name="url_template">Шаблон URL</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -776,4 +776,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -774,4 +774,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -778,4 +778,11 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -773,4 +773,11 @@
|
|||
<string name="url_template">URL 模板</string>
|
||||
<string name="track_point">轨迹点</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@
|
|||
<string name="udp_config">UDP設置</string>
|
||||
<string name="map_node_popup_details"><![CDATA[%1$s<br>最後接收: %2$s<br>最後位置: %3$s<br>電量: %4$s]]></string>
|
||||
<string name="toggle_my_position">切換我的位置</string>
|
||||
<string name="orient_north">Orient north</string>
|
||||
<string name="orient_north">以北為上</string>
|
||||
<string name="user">用戶</string>
|
||||
<string name="channels">頻道</string>
|
||||
<string name="device">裝置</string>
|
||||
|
|
@ -337,7 +337,7 @@
|
|||
<string name="detection_sensor">檢測傳感器</string>
|
||||
<string name="paxcounter">客流量計數</string>
|
||||
<string name="audio_config">音頻設置</string>
|
||||
<string name="codec_2_enabled">啟用 CODEC2</string>
|
||||
<string name="codec_2_enabled">CODEC 2 已啟用</string>
|
||||
<string name="ptt_pin">PTT針腳</string>
|
||||
<string name="codec2_sample_rate">CODEC2 取樣率</string>
|
||||
<string name="i2s_word_select">I2S WS 訊號選擇</string>
|
||||
|
|
@ -475,8 +475,8 @@
|
|||
<string name="subnet">子網</string>
|
||||
<string name="paxcounter_config">Paxcount設置</string>
|
||||
<string name="paxcounter_enabled">啟用Paxcount</string>
|
||||
<string name="wifi_rssi_threshold_defaults_to_80">WiFi RSSI 閾值(缺省-80)</string>
|
||||
<string name="ble_rssi_threshold_defaults_to_80">藍牙 RSSI 閾值(缺省-80)</string>
|
||||
<string name="wifi_rssi_threshold_defaults_to_80">WiFi RSSI 閾值(預設為-80)</string>
|
||||
<string name="ble_rssi_threshold_defaults_to_80">藍牙 RSSI 閾值(預設為-80)</string>
|
||||
<string name="position_config">位置設定</string>
|
||||
<string name="position_broadcast_interval_seconds">位置廣播間隔(秒)</string>
|
||||
<string name="smart_position_enabled">啟用智慧位置</string>
|
||||
|
|
@ -772,4 +772,11 @@
|
|||
<string name="url_template">URL 範本</string>
|
||||
<string name="track_point">軌跡點</string>
|
||||
<string name="phone_settings">手機設定</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -653,6 +653,7 @@
|
|||
<string name="export_keys">Export Keys</string>
|
||||
<string name="export_keys_confirmation">Exports public and private keys to a file. Please store somewhere securely.</string>
|
||||
<string name="modules_unlocked">Modules unlocked</string>
|
||||
<string name="modules_already_unlocked">Modules already unlocked</string>
|
||||
<string name="remote">Remote</string>
|
||||
<string name="node_count_template">(%1$d online / %2$d total)</string>
|
||||
<string name="react">React</string>
|
||||
|
|
@ -801,5 +802,13 @@
|
|||
<string name="url_template">URL Template</string>
|
||||
<string name="url_template_hint" translatable="false">https://a.tile.openstreetmap.org/{z}/{x}/{y}.png</string>
|
||||
<string name="track_point">track point</string>
|
||||
<string name="phone_settings">Phone Settings</string>
|
||||
<string name="app_settings">App</string>
|
||||
<string name="app_version">Version</string>
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -32,12 +32,13 @@ kotlinx-coroutines-android = "1.10.2"
|
|||
kotlinx-serialization-json = "1.9.0"
|
||||
lifecycle = "2.9.3"
|
||||
location-services = "21.3.0"
|
||||
maps-compose = "6.8.0"
|
||||
maps-compose = "6.9.0"
|
||||
markdownRenderer = "0.35.0"
|
||||
material = "1.13.0"
|
||||
material3 = "1.5.0-alpha03"
|
||||
mgrs = "2.1.3"
|
||||
navigation = "2.9.3"
|
||||
navigation3 = "1.0.0-alpha08"
|
||||
okhttp = "5.1.0"
|
||||
org-eclipse-paho-client-mqttv3 = "1.2.5"
|
||||
osmbonuspack = "6.9.0"
|
||||
|
|
@ -129,6 +130,8 @@ material = { group = "com.google.android.material", name = "material", version.r
|
|||
mgrs = { group = "mil.nga", name = "mgrs", version.ref = "mgrs" }
|
||||
navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
|
||||
navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" }
|
||||
navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" }
|
||||
navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" }
|
||||
okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||
okhttp3-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
|
||||
org-eclipse-paho-client-mqttv3 = { group = "org.eclipse.paho", name = "org.eclipse.paho.client.mqttv3", version.ref = "org-eclipse-paho-client-mqttv3" }
|
||||
|
|
@ -166,6 +169,9 @@ lifecycle = ["lifecycle-runtime-ktx", "lifecycle-livedata-ktx", "lifecycle-viewm
|
|||
# Navigation
|
||||
navigation = ["navigation-compose"]
|
||||
|
||||
# Navigation 3
|
||||
navigation3 = ["navigation3-runtime", "navigation3-ui"]
|
||||
|
||||
# Coroutines
|
||||
coroutines = ["kotlinx-coroutines-android", "kotlinx-coroutines-guava"]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue