mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(ui): add mesh activity modem lights (#2656)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
da42d67486
commit
ad59edd8d2
3 changed files with 206 additions and 84 deletions
|
|
@ -21,7 +21,11 @@ import android.Manifest
|
|||
import android.os.Build
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
|
|
@ -43,6 +47,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
|
|
@ -57,13 +62,16 @@ import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
|
|||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -98,12 +106,14 @@ import com.geeksville.mesh.navigation.NodesRoutes
|
|||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.showLongNameTitle
|
||||
import com.geeksville.mesh.repository.radio.MeshActivity
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
|
||||
import com.geeksville.mesh.ui.common.components.MultipleChoiceAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusOrange
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import com.geeksville.mesh.ui.debug.DebugMenuActions
|
||||
|
|
@ -114,6 +124,8 @@ import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
|||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, val route: Route) {
|
||||
Contacts(R.string.contacts, Icons.AutoMirrored.TwoTone.Chat, ContactsRoutes.ContactsGraph),
|
||||
|
|
@ -222,6 +234,39 @@ fun MainScreen(
|
|||
val navSuiteType = NavigationSuiteScaffoldDefaults.navigationSuiteType(currentWindowAdaptiveInfo())
|
||||
val currentDestination = navController.currentBackStackEntryAsState().value?.destination
|
||||
val topLevelDestination = TopLevelDestination.fromNavDestination(currentDestination)
|
||||
|
||||
// State for managing the glow animation around the Connections icon
|
||||
var currentGlowColor by remember { mutableStateOf(Color.Transparent) }
|
||||
val animatedGlowAlpha = remember { Animatable(0f) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val capturedColorScheme = colorScheme // Capture current colorScheme instance for LaunchedEffect
|
||||
|
||||
val sendColor = capturedColorScheme.StatusOrange
|
||||
val receiveColor = capturedColorScheme.StatusYellow
|
||||
LaunchedEffect(uIViewModel.meshActivity, capturedColorScheme) {
|
||||
uIViewModel.meshActivity.collectLatest { activity ->
|
||||
debug("MeshActivity Event: $activity, Current Alpha: ${animatedGlowAlpha.value}")
|
||||
|
||||
val newTargetColor =
|
||||
when (activity) {
|
||||
is MeshActivity.Send -> sendColor
|
||||
is MeshActivity.Receive -> receiveColor
|
||||
}
|
||||
|
||||
currentGlowColor = newTargetColor
|
||||
// Stop any existing animation and launch a new one.
|
||||
// Launching in a new coroutine ensures the collect block is not suspended.
|
||||
coroutineScope.launch {
|
||||
animatedGlowAlpha.stop() // Stop before snapping/animating
|
||||
animatedGlowAlpha.snapTo(1.0f) // Show glow instantly
|
||||
animatedGlowAlpha.animateTo(
|
||||
targetValue = 0.0f, // Fade out
|
||||
animationSpec = tween(durationMillis = 1000, easing = LinearEasing),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavigationSuiteScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
navigationSuiteItems = {
|
||||
|
|
@ -245,7 +290,39 @@ fun MainScreen(
|
|||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
TopLevelNavIcon(destination, connectionState)
|
||||
val iconModifier =
|
||||
if (isConnectionsRoute) {
|
||||
Modifier.drawWithCache {
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
if (animatedGlowAlpha.value > 0f) {
|
||||
val glowRadius = size.minDimension
|
||||
drawCircle(
|
||||
brush =
|
||||
Brush.radialGradient(
|
||||
colors =
|
||||
listOf(
|
||||
currentGlowColor.copy(
|
||||
alpha = 0.8f * animatedGlowAlpha.value,
|
||||
),
|
||||
currentGlowColor.copy(
|
||||
alpha = 0.4f * animatedGlowAlpha.value,
|
||||
),
|
||||
Color.Transparent,
|
||||
),
|
||||
center = center,
|
||||
radius = glowRadius,
|
||||
),
|
||||
radius = glowRadius,
|
||||
blendMode = BlendMode.Screen,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
Box(modifier = iconModifier) { TopLevelNavIcon(destination, connectionState) }
|
||||
}
|
||||
},
|
||||
selected = isSelected,
|
||||
|
|
@ -309,6 +386,25 @@ fun MainScreen(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopLevelNavIcon(destination: TopLevelDestination, connectionState: MeshService.ConnectionState) {
|
||||
val iconTint =
|
||||
when {
|
||||
destination == TopLevelDestination.Connections -> connectionState.getConnectionColor()
|
||||
else -> LocalContentColor.current
|
||||
}
|
||||
Icon(
|
||||
imageVector =
|
||||
if (destination == TopLevelDestination.Connections) {
|
||||
connectionState.getConnectionIcon()
|
||||
} else {
|
||||
destination.icon
|
||||
},
|
||||
contentDescription = stringResource(id = destination.label),
|
||||
tint = iconTint,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun VersionChecks(viewModel: UIViewModel) {
|
||||
|
|
@ -322,7 +418,8 @@ private fun VersionChecks(viewModel: UIViewModel) {
|
|||
|
||||
val currentDeviceHardware by viewModel.deviceHardware.collectAsStateWithLifecycle(null)
|
||||
|
||||
val latestStableFirmwareRelease by viewModel.latestStableFirmwareRelease.collectAsState(DeviceVersion("2.6.4"))
|
||||
val latestStableFirmwareRelease by
|
||||
viewModel.latestStableFirmwareRelease.collectAsStateWithLifecycle(DeviceVersion("2.6.4"))
|
||||
LaunchedEffect(connectionState, firmwareEdition) {
|
||||
if (connectionState == MeshService.ConnectionState.CONNECTED) {
|
||||
firmwareEdition?.let { edition ->
|
||||
|
|
@ -419,7 +516,7 @@ private fun MainAppBar(
|
|||
val totalNodeCount by viewModel.totalNodeCount.collectAsStateWithLifecycle(0)
|
||||
TopAppBar(
|
||||
title = {
|
||||
val title =
|
||||
val titleText =
|
||||
when {
|
||||
currentDestination == null || currentDestination.isTopLevel() ->
|
||||
stringResource(id = R.string.app_name)
|
||||
|
|
@ -435,7 +532,7 @@ private fun MainAppBar(
|
|||
else -> stringResource(id = R.string.app_name)
|
||||
}
|
||||
Text(
|
||||
text = title,
|
||||
text = titleText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
|
|
@ -514,7 +611,7 @@ private fun MainMenuActions(isManaged: Boolean, onAction: (MainMenuAction) -> Un
|
|||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { showMenu = false },
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.background.copy(alpha = 1f)),
|
||||
modifier = Modifier.background(colorScheme.background.copy(alpha = 1f)),
|
||||
) {
|
||||
MainMenuAction.entries.forEach { action ->
|
||||
DropdownMenuItem(
|
||||
|
|
@ -552,17 +649,3 @@ private fun MeshService.ConnectionState.getTooltipString(): String = when (this)
|
|||
MeshService.ConnectionState.DEVICE_SLEEP -> stringResource(R.string.device_sleeping)
|
||||
MeshService.ConnectionState.DISCONNECTED -> stringResource(R.string.disconnected)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopLevelNavIcon(dest: TopLevelDestination, connectionState: MeshService.ConnectionState) {
|
||||
when (dest) {
|
||||
TopLevelDestination.Connections ->
|
||||
Icon(
|
||||
imageVector = connectionState.getConnectionIcon(),
|
||||
contentDescription = stringResource(id = dest.label),
|
||||
tint = connectionState.getConnectionColor(),
|
||||
)
|
||||
|
||||
else -> Icon(imageVector = dest.icon, contentDescription = stringResource(id = dest.label))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue