Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
816 changes: 816 additions & 0 deletions app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/26.json

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ import com.nextcloud.talk.models.json.hovercard.HoverCardOverall
import com.nextcloud.talk.models.json.reactions.ReactionsOverall
import com.nextcloud.talk.models.json.status.StatusOverall
import com.nextcloud.talk.models.json.status.predefined.PredefinedStatusOverall
import com.nextcloud.talk.models.json.tags.AssignConversationTagsRequest
import com.nextcloud.talk.models.json.tags.ConversationTagOverall
import com.nextcloud.talk.models.json.tags.ConversationTagsOverall
import com.nextcloud.talk.models.json.tags.CreateConversationTagRequest
import com.nextcloud.talk.models.json.tags.ReorderConversationTagsRequest
import com.nextcloud.talk.models.json.tags.UpdateConversationTagRequest
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
import com.nextcloud.talk.models.json.threads.ThreadOverall
import com.nextcloud.talk.models.json.threads.ThreadsOverall
Expand Down Expand Up @@ -225,6 +231,43 @@ interface NcApiCoroutines {
@Url url: String
): GenericOverall

@GET
suspend fun getConversationTags(
@Header("Authorization") authorization: String,
@Url url: String
): ConversationTagsOverall

@POST
suspend fun createConversationTag(
@Header("Authorization") authorization: String,
@Url url: String,
@Body body: CreateConversationTagRequest
): ConversationTagOverall

@PUT
suspend fun updateConversationTag(
@Header("Authorization") authorization: String,
@Url url: String,
@Body body: UpdateConversationTagRequest
): ConversationTagOverall

@DELETE
suspend fun deleteConversationTag(@Header("Authorization") authorization: String, @Url url: String): GenericOverall

@PUT
suspend fun reorderConversationTags(
@Header("Authorization") authorization: String,
@Url url: String,
@Body body: ReorderConversationTagsRequest
): ConversationTagsOverall

@POST
suspend fun assignConversationTags(
@Header("Authorization") authorization: String,
@Url url: String,
@Body body: AssignConversationTagsRequest
): RoomOverall

@FormUrlEncoded
@POST
suspend fun notificationCalls(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import com.nextcloud.talk.conversationlist.ui.ConversationsListScreen
import com.nextcloud.talk.conversationlist.ui.ConversationsListScreenCallbacks
import com.nextcloud.talk.conversationlist.ui.ConversationsListScreenState
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
import com.nextcloud.talk.conversationtags.viewmodels.ConversationTagsViewModel
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.events.ConversationsListFetchDataEvent
Expand Down Expand Up @@ -145,6 +146,8 @@ class ConversationsListActivity : BaseActivity() {

lateinit var conversationsListViewModel: ConversationsListViewModel

lateinit var conversationTagsViewModel: ConversationTagsViewModel

private var currentUser: User? = null
private val snackbarHostState = SnackbarHostState()
private val isMaintenanceModeState = MutableStateFlow(false)
Expand Down Expand Up @@ -198,6 +201,7 @@ class ConversationsListActivity : BaseActivity() {
}

conversationsListViewModel = ViewModelProvider(this, viewModelFactory)[ConversationsListViewModel::class.java]
conversationTagsViewModel = ViewModelProvider(this, viewModelFactory)[ConversationTagsViewModel::class.java]

setSupportActionBar(null)
forwardMessageState.value = intent.getBooleanExtra(KEY_FORWARD_MSG_FLAG, false)
Expand All @@ -209,6 +213,7 @@ class ConversationsListActivity : BaseActivity() {
setContent {
ConversationsListScreen(
viewModel = conversationsListViewModel,
tagsViewModel = conversationTagsViewModel,
state = buildScreenState(),
callbacks = buildScreenCallbacks()
)
Expand Down Expand Up @@ -426,6 +431,7 @@ class ConversationsListActivity : BaseActivity() {

conversationsListViewModel.checkIfThreadsExist()
conversationsListViewModel.reloadFilterFromStorage(UserIdUtils.getIdForUser(currentUser))
conversationTagsViewModel.loadConversationTags()
}

override fun onPause() {
Expand Down Expand Up @@ -1140,6 +1146,9 @@ class ConversationsListActivity : BaseActivity() {
is ConversationOpsAction.AddToHomeScreen -> addConversationToHomeScreen(conversation)
is ConversationOpsAction.Leave -> leaveConversation(conversation)
is ConversationOpsAction.Delete -> showDeleteConversationDialog(conversation)
is ConversationOpsAction.ManageTags -> conversationTagsViewModel.setConversationForTagAssignment(
conversation
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.conversationlist.ui

import android.content.res.Configuration
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.nextcloud.talk.R
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.tags.ConversationTag

@Composable
fun AssignConversationTagsSheetContent(
conversation: ConversationModel,
tags: List<ConversationTag>,
onToggleTag: (String) -> Unit,
onManageTagsClick: () -> Unit
) {
val headerText = conversation.displayName.takeIf { it.isNotEmpty() } ?: conversation.name
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.navigationBarsPadding()
) {
Text(
text = headerText,
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = dimensionResource(R.dimen.standard_dialog_padding),
vertical = dimensionResource(R.dimen.standard_half_padding)
),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
)
if (tags.isEmpty()) {
Text(
text = stringResource(R.string.nc_conversation_tags_empty),
modifier = Modifier
.fillMaxWidth()
.padding(
horizontal = dimensionResource(R.dimen.standard_dialog_padding),
vertical = dimensionResource(R.dimen.standard_half_padding)
),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
} else {
tags.forEach { tag ->
AssignableTagRow(
tag = tag,
isAssigned = conversation.tagIds.contains(tag.id),
onToggleTag = onToggleTag
)
}
}
ManageTagsRow(onManageTagsClick)
}
}

@Composable
private fun AssignableTagRow(tag: ConversationTag, isAssigned: Boolean, onToggleTag: (String) -> Unit) {
TextButton(
onClick = { onToggleTag(tag.id) },
modifier = Modifier
.fillMaxWidth()
.height(dimensionResource(R.dimen.bottom_sheet_item_height)),
shape = RectangleShape,
contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.standard_dialog_padding)),
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface)
) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.Label,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.standard_dialog_padding)))
Text(
text = tag.name,
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Start
)
Checkbox(checked = isAssigned, onCheckedChange = { onToggleTag(tag.id) })
}
}

@Composable
private fun ManageTagsRow(onManageTagsClick: () -> Unit) {
TextButton(
onClick = onManageTagsClick,
modifier = Modifier
.fillMaxWidth()
.height(dimensionResource(R.dimen.bottom_sheet_item_height)),
shape = RectangleShape,
contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.standard_dialog_padding)),
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface)
) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.width(dimensionResource(R.dimen.standard_dialog_padding)))
Text(
text = stringResource(R.string.nc_conversation_tags_manage),
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Start
)
}
}

private fun previewConversation() =
ConversationModel(
internalId = "1@tok",
accountId = 1L,
token = "tok",
name = "alice",
displayName = "Alice",
description = "",
type = ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL,
participantType = Participant.ParticipantType.USER,
sessionId = "",
actorId = "user1",
actorType = "users",
objectType = ConversationEnums.ObjectType.DEFAULT,
notificationLevel = ConversationEnums.NotificationLevel.DEFAULT,
conversationReadOnlyState = ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_WRITE,
lobbyState = ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS,
lobbyTimer = 0L,
canLeaveConversation = true,
canDeleteConversation = true,
unreadMentionDirect = false,
notificationCalls = 0,
avatarVersion = "",
hasCustomAvatar = false,
callStartTime = 0L,
tagIds = listOf("2")
)

private fun previewTags() =
listOf(
ConversationTag(id = "1", name = "Work", sortOrder = 0),
ConversationTag(id = "2", name = "Family", sortOrder = 1)
)

@Preview(showBackground = true, name = "Light")
@Preview(showBackground = true, name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AssignConversationTagsSheetPreview() {
val colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
MaterialTheme(colorScheme = colorScheme) {
Surface(color = MaterialTheme.colorScheme.surfaceContainerLow) {
AssignConversationTagsSheetContent(
conversation = previewConversation(),
tags = previewTags(),
onToggleTag = {},
onManageTagsClick = {}
)
}
}
}
Loading
Loading