add chips like for conversation item

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2025-07-10 16:21:28 +02:00
parent 4a05a082d2
commit c5d3aaa2f4
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
2 changed files with 206 additions and 96 deletions

View File

@ -12,20 +12,13 @@ import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -39,14 +32,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector import autodagger.AutoInjector
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi 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.components.StandardAppBar
import com.nextcloud.talk.contacts.loadImage import com.nextcloud.talk.contacts.loadImage
import com.nextcloud.talk.models.json.threads.ThreadInfo 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.threadsoverview.viewmodels.ThreadsOverviewViewModel
import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
@ -215,12 +205,21 @@ fun ThreadsList(
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
val imageRequest = loadImage(imageUri, context, errorPlaceholderImage) 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( ThreadRow(
threadId = threadInfo.thread!!.id, threadId = threadInfo.thread!!.id,
threadName = threadInfo.first?.actorDisplayName.orEmpty(), threadName = threadInfo.first?.actorDisplayName.orEmpty(),
threadMessage = threadInfo.first?.message.toString(), threadMessage = threadInfo.first?.message.toString(),
numReplies = threadInfo.thread?.numReplies ?: 0, 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, imageRequest = imageRequest,
roomToken = roomToken, roomToken = roomToken,
onThreadClick = onThreadClick onThreadClick = onThreadClick
@ -245,75 +244,6 @@ private fun getLastActivityDate(threadInfo: ThreadInfo): String {
return lastActivityDate 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 @Composable
fun LoadingIndicator() { fun LoadingIndicator() {
Box( 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) // @Preview(showBackground = true)
// @Composable // @Composable
// fun PreviewLoadingIndicator() { // fun PreviewLoadingIndicator() {

View File

@ -0,0 +1,195 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
* 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
)
}