kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: add reaction dialog with grouped emojis and user list
rodzic
797fc67982
commit
dd3a77e2f7
|
@ -0,0 +1,52 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
|
||||
@Composable
|
||||
fun BottomSheetDialog(
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable ColumnScope.() -> Unit
|
||||
) = Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
.clickable(
|
||||
onClick = onDismiss,
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
color = MaterialTheme.colors.surface.copy(alpha = 1f),
|
||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
||||
)
|
||||
.padding(16.dp),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
|
@ -56,8 +56,21 @@ fun EmojiPicker(
|
|||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(fraction = 0.4f)
|
||||
.background(MaterialTheme.colors.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EmojiPickerDialog(
|
||||
onDismiss: () -> Unit = {},
|
||||
onConfirm: (String) -> Unit
|
||||
) = BottomSheetDialog(
|
||||
onDismiss = onDismiss,
|
||||
modifier = Modifier.fillMaxHeight(fraction = .4f),
|
||||
) {
|
||||
EmojiPicker(
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ import com.geeksville.mesh.MeshProtos.Waypoint
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.components.EmojiPicker
|
||||
import com.geeksville.mesh.ui.components.EmojiPickerDialog
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
import com.geeksville.mesh.waypoint
|
||||
|
||||
|
@ -179,7 +179,7 @@ internal fun EditWaypointDialog(
|
|||
}
|
||||
},
|
||||
) else {
|
||||
EmojiPicker(onDismiss = { showEmojiPickerView = false }) {
|
||||
EmojiPickerDialog(onDismiss = { showEmojiPickerView = false }) {
|
||||
showEmojiPickerView = false
|
||||
waypointInput = waypointInput.copy { icon = it.codePointAt(0) }
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import com.geeksville.mesh.model.Message
|
||||
import com.geeksville.mesh.ui.components.SimpleAlertDialog
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
|
@ -63,6 +64,12 @@ internal fun MessageList(
|
|||
SimpleAlertDialog(title = title, text = text) { showStatusDialog = null }
|
||||
}
|
||||
|
||||
var showReactionDialog by remember { mutableStateOf<List<Reaction>?>(null) }
|
||||
if (showReactionDialog != null) {
|
||||
val reactions = showReactionDialog ?: return
|
||||
ReactionDialog(reactions) { showReactionDialog = null }
|
||||
}
|
||||
|
||||
fun toggle(uuid: Long) = if (selectedIds.value.contains(uuid)) {
|
||||
selectedIds.value -= uuid
|
||||
} else {
|
||||
|
@ -79,7 +86,7 @@ internal fun MessageList(
|
|||
val fromLocal = msg.user.id == DataPacket.ID_LOCAL
|
||||
val selected by remember { derivedStateOf { selectedIds.value.contains(msg.uuid) } }
|
||||
|
||||
ReactionRow(fromLocal, msg.emojis) {} // TODO
|
||||
ReactionRow(fromLocal, msg.emojis) { showReactionDialog = msg.emojis }
|
||||
MessageItem(
|
||||
shortName = msg.user.shortName.takeIf { !fromLocal },
|
||||
messageText = msg.text,
|
||||
|
|
|
@ -23,14 +23,20 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Badge
|
||||
import androidx.compose.material.BadgedBox
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
|
@ -43,16 +49,18 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.database.entity.Reaction
|
||||
import com.geeksville.mesh.ui.components.EmojiPicker
|
||||
import com.geeksville.mesh.ui.components.BottomSheetDialog
|
||||
import com.geeksville.mesh.ui.components.EmojiPickerDialog
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
|
@ -159,17 +167,58 @@ fun ReactionRow(
|
|||
fun reduceEmojis(emojis: List<String>): Map<String, Int> = emojis.groupingBy { it }.eachCount()
|
||||
|
||||
@Composable
|
||||
fun EmojiPickerDialog(
|
||||
onConfirm: (String) -> Unit,
|
||||
onDismiss: () -> Unit = {},
|
||||
fun ReactionDialog(
|
||||
reactions: List<Reaction>,
|
||||
onDismiss: () -> Unit = {}
|
||||
) = BottomSheetDialog(
|
||||
onDismiss = onDismiss,
|
||||
modifier = Modifier.fillMaxHeight(fraction = .3f),
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
val groupedEmojis = reactions.groupBy { it.emoji }
|
||||
var selectedEmoji by remember { mutableStateOf<String?>(null) }
|
||||
val filteredReactions = selectedEmoji?.let { groupedEmojis[it] ?: emptyList() } ?: reactions
|
||||
|
||||
LazyRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
EmojiPicker(
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
items(groupedEmojis.entries.toList()) { (emoji, reactions) ->
|
||||
Text(
|
||||
text = "$emoji${reactions.size}",
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.background(if (selectedEmoji == emoji) Color.Gray else Color.Transparent)
|
||||
.padding(8.dp)
|
||||
.clickable {
|
||||
selectedEmoji = if (selectedEmoji == emoji) null else emoji
|
||||
},
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider(Modifier.padding(vertical = 8.dp))
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(filteredReactions) { reaction ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = reaction.user.longName,
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
Text(
|
||||
text = reaction.emoji,
|
||||
style = MaterialTheme.typography.h6
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue