mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(node): consolidate node chip and menu (#1941)
This commit is contained in:
parent
62e2368887
commit
6332b3bd42
18 changed files with 734 additions and 374 deletions
|
|
@ -18,6 +18,9 @@
|
|||
package com.geeksville.mesh.ui.message
|
||||
|
||||
import android.content.ClipData
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -86,6 +89,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
private const val MESSAGE_CHARACTER_LIMIT = 200
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
internal fun MessageScreen(
|
||||
|
|
@ -94,7 +98,9 @@ internal fun MessageScreen(
|
|||
viewModel: UIViewModel = hiltViewModel(),
|
||||
navigateToMessages: (String) -> Unit,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
onNavigateBack: () -> Unit
|
||||
onNavigateBack: () -> Unit,
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
animatedContentScope: AnimatedContentScope,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val clipboardManager = LocalClipboard.current
|
||||
|
|
@ -222,25 +228,21 @@ internal fun MessageScreen(
|
|||
viewModel = viewModel,
|
||||
contactKey = contactKey,
|
||||
onNodeMenuAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.Remove -> viewModel.removeNode(action.node.num)
|
||||
is NodeMenuAction.Ignore -> viewModel.ignoreNode(action.node)
|
||||
is NodeMenuAction.Favorite -> viewModel.favoriteNode(action.node)
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC =
|
||||
viewModel.ourNodeInfo.value?.hasPKC == true && action.node.hasPKC
|
||||
val channel =
|
||||
if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else action.node.channel
|
||||
navigateToMessages("$channel${action.node.user.id}")
|
||||
when (action) {
|
||||
is NodeMenuAction.DirectMessage -> {
|
||||
val hasPKC =
|
||||
viewModel.ourNodeInfo.value?.hasPKC == true && action.node.hasPKC
|
||||
val channel =
|
||||
if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else action.node.channel
|
||||
navigateToMessages("$channel${action.node.user.id}")
|
||||
}
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(action.node.num)
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
else -> viewModel.handleNodeMenuAction(action)
|
||||
}
|
||||
|
||||
is NodeMenuAction.RequestUserInfo -> viewModel.requestUserInfo(action.node.num)
|
||||
is NodeMenuAction.RequestPosition -> viewModel.requestPosition(action.node.num)
|
||||
is NodeMenuAction.TraceRoute -> viewModel.requestTraceroute(action.node.num)
|
||||
is NodeMenuAction.MoreDetails -> navigateToNodeDetails(action.node.num)
|
||||
is NodeMenuAction.Share -> sharedContact = action.node
|
||||
}
|
||||
}
|
||||
},
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedContentScope = animatedContentScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -393,7 +395,8 @@ private fun TextInput(
|
|||
message.value = it
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusEvent { isFocused = it.isFocused },
|
||||
enabled = enabled,
|
||||
placeholder = { Text(stringResource(id = R.string.send_text)) },
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
package com.geeksville.mesh.ui.message.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -52,13 +55,14 @@ import com.geeksville.mesh.DataPacket
|
|||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.NodeChip
|
||||
import com.geeksville.mesh.ui.components.AutoLinkText
|
||||
import com.geeksville.mesh.ui.components.UserAvatar
|
||||
import com.geeksville.mesh.ui.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.components.SharedTransitionPreview
|
||||
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
internal fun MessageItem(
|
||||
node: Node,
|
||||
|
|
@ -69,9 +73,12 @@ internal fun MessageItem(
|
|||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {},
|
||||
onChipClick: () -> Unit = {},
|
||||
onAction: (NodeMenuAction) -> Unit = {},
|
||||
onStatusClick: () -> Unit = {},
|
||||
onSendReaction: (String) -> Unit = {},
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
animatedContentScope: AnimatedContentScope,
|
||||
isConnected: Boolean,
|
||||
) = Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -91,12 +98,16 @@ internal fun MessageItem(
|
|||
Modifier.padding(start = 8.dp, top = 8.dp, end = 0.dp, bottom = 6.dp)
|
||||
}
|
||||
if (!fromLocal) {
|
||||
UserAvatar(
|
||||
NodeChip(
|
||||
node = node,
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, top = 8.dp)
|
||||
.align(Alignment.Top),
|
||||
) { onChipClick() }
|
||||
.padding(start = 8.dp, end = 4.dp),
|
||||
onAction = onAction,
|
||||
isConnected = isConnected,
|
||||
isThisNode = false,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedContentScope = animatedContentScope,
|
||||
)
|
||||
}
|
||||
Card(
|
||||
modifier = Modifier
|
||||
|
|
@ -166,16 +177,20 @@ internal fun MessageItem(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun MessageItemPreview() {
|
||||
AppTheme {
|
||||
SharedTransitionPreview { sharedTransitionScope, animatedContentScope ->
|
||||
MessageItem(
|
||||
node = NodePreviewParameterProvider().values.first(),
|
||||
messageText = stringResource(R.string.sample_message),
|
||||
messageTime = "10:00",
|
||||
messageStatus = MessageStatus.DELIVERED,
|
||||
selected = false,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedContentScope = animatedContentScope,
|
||||
isConnected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@
|
|||
package com.geeksville.mesh.ui.message.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.AnimatedContentScope
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -48,13 +51,13 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.components.NodeMenu
|
||||
import com.geeksville.mesh.ui.components.NodeMenuAction
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
|
@ -105,6 +108,7 @@ fun DeliveryInfo(
|
|||
containerColor = MaterialTheme.colorScheme.surface
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun MessageList(
|
||||
|
|
@ -113,9 +117,11 @@ internal fun MessageList(
|
|||
selectedIds: MutableState<Set<Long>>,
|
||||
onUnreadChanged: (Long) -> Unit,
|
||||
onSendReaction: (String, Int) -> Unit,
|
||||
onNodeMenuAction: (NodeMenuAction) -> Unit = {},
|
||||
onNodeMenuAction: (NodeMenuAction) -> Unit,
|
||||
viewModel: UIViewModel,
|
||||
contactKey: String
|
||||
contactKey: String,
|
||||
sharedTransitionScope: SharedTransitionScope,
|
||||
animatedContentScope: AnimatedContentScope,
|
||||
) {
|
||||
val haptics = LocalHapticFeedback.current
|
||||
val inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } }
|
||||
|
|
@ -155,6 +161,9 @@ internal fun MessageList(
|
|||
value += uuid
|
||||
}
|
||||
|
||||
val nodes by viewModel.nodeList.collectAsStateWithLifecycle()
|
||||
val isConnected by viewModel.isConnected.collectAsStateWithLifecycle(false)
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
|
|
@ -163,12 +172,16 @@ internal fun MessageList(
|
|||
items(messages, key = { it.uuid }) { msg ->
|
||||
val fromLocal = msg.node.user.id == DataPacket.ID_LOCAL
|
||||
val selected by remember { derivedStateOf { selectedIds.value.contains(msg.uuid) } }
|
||||
|
||||
var node by remember {
|
||||
mutableStateOf(nodes.find { it.num == msg.node.num } ?: msg.node)
|
||||
}
|
||||
LaunchedEffect(nodes) {
|
||||
node = nodes.find { it.num == msg.node.num } ?: msg.node
|
||||
}
|
||||
ReactionRow(fromLocal, msg.emojis) { showReactionDialog = msg.emojis }
|
||||
Box(Modifier.wrapContentSize(Alignment.TopStart)) {
|
||||
var expandedNodeMenu by remember { mutableStateOf(false) }
|
||||
MessageItem(
|
||||
node = msg.node,
|
||||
node = node,
|
||||
messageText = msg.text,
|
||||
messageTime = msg.time,
|
||||
messageStatus = msg.status,
|
||||
|
|
@ -178,20 +191,12 @@ internal fun MessageList(
|
|||
selectedIds.toggle(msg.uuid)
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
},
|
||||
onChipClick = {
|
||||
if (msg.node.num != 0) {
|
||||
expandedNodeMenu = true
|
||||
}
|
||||
},
|
||||
onAction = onNodeMenuAction,
|
||||
onStatusClick = { showStatusDialog = msg },
|
||||
onSendReaction = { onSendReaction(it, msg.packetId) },
|
||||
)
|
||||
NodeMenu(
|
||||
node = msg.node,
|
||||
showFullMenu = true,
|
||||
onDismissRequest = { expandedNodeMenu = false },
|
||||
expanded = expandedNodeMenu,
|
||||
onAction = onNodeMenuAction,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedContentScope = animatedContentScope,
|
||||
isConnected = isConnected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue