From c5d3aaa2f43f2be33e45bf6865be4f82914acf75 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 10 Jul 2025 16:21:28 +0200 Subject: [PATCH] add chips like for conversation item Signed-off-by: Marcel Hibbe --- .../ThreadsOverviewActivity.kt | 107 +--------- .../threadsoverview/components/ThreadRow.kt | 195 ++++++++++++++++++ 2 files changed, 206 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/threadsoverview/components/ThreadRow.kt diff --git a/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt b/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt index fb370f0cc..b3f8d1d67 100644 --- a/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt @@ -12,20 +12,13 @@ import android.os.Bundle import android.text.format.DateUtils import androidx.activity.compose.setContent import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.CircularProgressIndicator @@ -39,14 +32,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModelProvider import autodagger.AutoInjector -import coil.compose.AsyncImage -import coil.request.ImageRequest import com.nextcloud.talk.R import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.api.NcApi @@ -56,6 +45,7 @@ import com.nextcloud.talk.components.ColoredStatusBar import com.nextcloud.talk.components.StandardAppBar import com.nextcloud.talk.contacts.loadImage import com.nextcloud.talk.models.json.threads.ThreadInfo +import com.nextcloud.talk.threadsoverview.components.ThreadRow import com.nextcloud.talk.threadsoverview.viewmodels.ThreadsOverviewViewModel import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils @@ -215,12 +205,21 @@ fun ThreadsList( val errorPlaceholderImage: Int = R.drawable.account_circle_96dp val imageRequest = loadImage(imageUri, context, errorPlaceholderImage) + + + val lastReadMessage = threadInfo.attendee?.lastReadMessage ?: 0 + val lastMentionMessage = threadInfo.attendee?.lastMentionMessage ?: 0 + val lastMentionDirect = threadInfo.attendee?.lastMentionDirect ?: 0 + + ThreadRow( threadId = threadInfo.thread!!.id, threadName = threadInfo.first?.actorDisplayName.orEmpty(), threadMessage = threadInfo.first?.message.toString(), numReplies = threadInfo.thread?.numReplies ?: 0, - lastActivityDate = getLastActivityDate(threadInfo), + unreadMention = lastMentionMessage > lastReadMessage, + unreadMentionDirect = lastMentionDirect > lastReadMessage, + lastActivityDate = getLastActivityDate(threadInfo), // TODO: replace with value from api when available imageRequest = imageRequest, roomToken = roomToken, onThreadClick = onThreadClick @@ -245,75 +244,6 @@ private fun getLastActivityDate(threadInfo: ThreadInfo): String { return lastActivityDate } -@Composable -fun ThreadRow( - threadId: Int, - threadName: String, - threadMessage: String, - lastActivityDate: String, - numReplies: Int, - imageRequest: ImageRequest?, - roomToken: String, - onThreadClick: ((String, Int) -> Unit?)? -) { - Row( - modifier = Modifier - .fillMaxWidth() - .clickable(enabled = onThreadClick != null) { - onThreadClick?.invoke(roomToken, threadId) - } - .padding(vertical = 8.dp, horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - AsyncImage( - model = imageRequest, - contentDescription = stringResource(R.string.user_avatar), - modifier = Modifier - .size(48.dp) - ) - - Spacer(modifier = Modifier.width(12.dp)) - - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = threadName, - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = threadMessage, - style = MaterialTheme.typography.bodyMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - - Spacer(modifier = Modifier.width(8.dp)) - - Column( - horizontalAlignment = Alignment.End - ) { - Text( - text = lastActivityDate, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = numReplies.toString(), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Spacer(modifier = Modifier.width(16.dp)) - } -} - @Composable fun LoadingIndicator() { Box( @@ -336,21 +266,6 @@ fun ErrorView(message: String) { } } -@Preview -@Composable -fun ThreadRowPreview() { - ThreadRow( - threadId = 123, - threadName = "actor name aka. thread name", - threadMessage = "The message of the first message of the thread...", - numReplies = 3, - lastActivityDate = "14 sec ago", - roomToken = "1234", - onThreadClick = null, - imageRequest = null - ) -} - // @Preview(showBackground = true) // @Composable // fun PreviewLoadingIndicator() { diff --git a/app/src/main/java/com/nextcloud/talk/threadsoverview/components/ThreadRow.kt b/app/src/main/java/com/nextcloud/talk/threadsoverview/components/ThreadRow.kt new file mode 100644 index 000000000..2d07c24d9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/threadsoverview/components/ThreadRow.kt @@ -0,0 +1,195 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Your Name + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.threadsoverview.components + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.nextcloud.talk.R + +@Composable +fun ThreadRow( + threadId: Int, + threadName: String, + threadMessage: String, + lastActivityDate: String, + numReplies: Int?, + unreadMention: Boolean, + unreadMentionDirect: Boolean, + imageRequest: ImageRequest?, + roomToken: String, + onThreadClick: ((String, Int) -> Unit?)? +) { + Row( + modifier = Modifier.Companion + .fillMaxWidth() + .clickable(enabled = onThreadClick != null) { + onThreadClick?.invoke(roomToken, threadId) + } + .padding(vertical = 8.dp, horizontal = 8.dp), + verticalAlignment = Alignment.Companion.CenterVertically + ) { + AsyncImage( + model = imageRequest, + contentDescription = stringResource(R.string.user_avatar), + modifier = Modifier.Companion.size(48.dp) + ) + + Spacer(modifier = Modifier.Companion.width(12.dp)) + + Column(modifier = Modifier.Companion.weight(1f)) { + Text( + text = threadName, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Companion.Ellipsis + ) + Spacer(modifier = Modifier.Companion.height(2.dp)) + Text( + text = threadMessage, + style = MaterialTheme.typography.bodyMedium, + maxLines = 2, + overflow = TextOverflow.Companion.Ellipsis + ) + } + + Spacer(modifier = Modifier.Companion.width(8.dp)) + + Column(horizontalAlignment = Alignment.Companion.End) { + Text( + text = lastActivityDate, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Spacer(modifier = Modifier.Companion.height(4.dp)) + + if ((numReplies ?: 0) > 0) { + val isOutlined = unreadMention + val chipColor = when { + unreadMentionDirect -> MaterialTheme.colorScheme.primary + unreadMention -> Color.Companion.Transparent + else -> MaterialTheme.colorScheme.surfaceVariant + } + val chipTextColor = when { + unreadMentionDirect -> MaterialTheme.colorScheme.onPrimary + unreadMention -> MaterialTheme.colorScheme.primary + else -> MaterialTheme.colorScheme.onSurfaceVariant + } + val border = if (isOutlined) + BorderStroke(1.dp, MaterialTheme.colorScheme.primary) + else + null + + Surface( + shape = RoundedCornerShape(12.dp), + color = chipColor, + border = border, + ) { + Text( + text = numReplies.toString(), + modifier = Modifier.Companion.padding(horizontal = 8.dp, vertical = 4.dp), + style = MaterialTheme.typography.bodySmall, + color = chipTextColor + ) + } + } + } + + Spacer(modifier = Modifier.Companion.width(16.dp)) + } +} + +@Preview +@Composable +fun ThreadRowPreview() { + ThreadRow( + threadId = 123, + threadName = "actor name aka. thread name", + threadMessage = "The message of the first message of the thread...", + numReplies = 0, + unreadMention = false, + unreadMentionDirect = false, + lastActivityDate = "14 sec ago", + roomToken = "1234", + onThreadClick = null, + imageRequest = null + ) +} + +@Preview +@Composable +fun ThreadRowUnreadMessagePreview() { + ThreadRow( + threadId = 123, + threadName = "actor name aka. thread name", + threadMessage = "The message of the first message of the thread...", + numReplies = 3, + unreadMention = false, + unreadMentionDirect = false, + lastActivityDate = "14 sec ago", + roomToken = "1234", + onThreadClick = null, + imageRequest = null + ) +} + +@Preview +@Composable +fun ThreadRowMentionPreview() { + ThreadRow( + threadId = 123, + threadName = "actor name aka. thread name", + threadMessage = "The message of the first message of the thread...", + numReplies = 3, + unreadMention = true, + unreadMentionDirect = false, + lastActivityDate = "14 sec ago", + roomToken = "1234", + onThreadClick = null, + imageRequest = null + ) +} + +@Preview +@Composable +fun ThreadRowDirectMentionPreview() { + ThreadRow( + threadId = 123, + threadName = "actor name aka. thread name", + threadMessage = "The message of the first message of the thread...", + numReplies = 3, + unreadMention = false, + unreadMentionDirect = true, + lastActivityDate = "14 sec ago", + roomToken = "1234", + onThreadClick = null, + imageRequest = null + ) +}