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
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
* Stefan Niedermann <info@niedermann.it>
* Stephan Ritscher <no3pam@gmail.com>
* Tarek Loubani <tarek@tarek.org>
* thirumani-vihaan <arjunthirumani02@gmail.com>
* Tilo Spannagel <development@tilosp.de>
* Tim KrΓΌger <t@timkrueger.me>
* Tobias Kaminsky <tobias@kaminsky.me>
Expand Down
13 changes: 9 additions & 4 deletions app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3154,10 +3154,15 @@ class CallActivity : CallBaseActivity() {
}

val isAllowedToStartOrStopRecording: Boolean
get() = (
isCallRecordingAvailable(conversationUser!!.capabilities!!.spreedCapability!!) &&
isModerator
)
get() {
val spreedCap = conversationUser.capabilities?.spreedCapability
val isClassified = currentConversation?.let {
com.nextcloud.talk.utils.ConversationUtils.isClassified(it, spreedCap)
} ?: false
return isCallRecordingAvailable(conversationUser.capabilities!!.spreedCapability!!) &&
isModerator &&
!isClassified
}
val isAllowedToRaiseHand: Boolean
get() = hasSpreedFeatureCapability(
conversationUser.capabilities!!.spreedCapability!!,
Expand Down
25 changes: 24 additions & 1 deletion app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfEventRooms
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfInstantMeetingRoom
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfClassifiedRoom
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfSIPRoom
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.ConversationUtils
Expand Down Expand Up @@ -1432,6 +1433,24 @@ class ChatActivity :
}
}

if (state.conversationModel.objectType == ConversationEnums.ObjectType.CLASSIFIED &&
hasSpreedFeatureCapability(
conversationUser?.capabilities!!.spreedCapability!!,
SpreedFeatures.UNBIND_CONVERSATION
)
) {
val retentionPeriod = retentionOfClassifiedRoom(spreedCapabilities)
val systemMessage = state.conversationModel.lastMessage?.systemMessageType
if (retentionPeriod != 0 &&
(
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED ||
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE
)
) {
showConversationDeletionWarning(retentionPeriod)
}
}

updateRoomTimerHandler(MILLIS_250)
}

Expand Down Expand Up @@ -1891,6 +1910,9 @@ class ChatActivity :
val isOneToOne = isOneToOneConversation()
val capabilitiesReady = ::spreedCapabilities.isInitialized

val isClassified = conversation != null &&
capabilitiesReady &&
ConversationUtils.isClassified(conversation, spreedCapabilities)
chatToolbarState = chatToolbarState.copy(
title = buildToolbarTitle(conversation),
subtitle = buildToolbarSubtitle(conversation),
Expand All @@ -1906,7 +1928,8 @@ class ChatActivity :
showEventMenu = conversation?.objectType == ConversationEnums.ObjectType.EVENT,
supportsSilentCall = capabilitiesReady &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL) &&
!isChatThread()
!isChatThread(),
isClassified = isClassified
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,6 @@ class MessageInputFragment : Fragment() {

override fun onSuccess(result: Drawable) {
binding.fragmentCallStarted.callAuthorChip.chipIcon = result

}
})
.build()
Expand Down
35 changes: 34 additions & 1 deletion app/src/main/java/com/nextcloud/talk/chat/ui/ChatToolbar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
Expand Down Expand Up @@ -74,6 +82,8 @@ import coil.request.ImageRequest
import com.nextcloud.talk.R
import com.nextcloud.talk.chat.MenuItemData

private const val BANNER_ANIMATION_DURATION_MS = 1000

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun ChatToolbar(state: ChatToolbarState, callbacks: ChatToolbarCallbacks, modifier: Modifier = Modifier) {
Expand Down Expand Up @@ -311,7 +321,30 @@ private fun ConversationHeader(state: ChatToolbarState, onClick: (() -> Unit)?)
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurface
)
if (state.subtitle.isNotEmpty()) {
if (state.isClassified) {
val infiniteTransition = rememberInfiniteTransition()
val alpha by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 0.3f,
animationSpec = infiniteRepeatable(
animation = tween(BANNER_ANIMATION_DURATION_MS, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
Surface(
modifier = Modifier.padding(top = 2.dp),
color = MaterialTheme.colorScheme.error.copy(alpha = alpha),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = "πŸ›‘οΈ Classified conversation",
color = MaterialTheme.colorScheme.onError,
fontSize = 10.sp,
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp)
)
}
} else if (state.subtitle.isNotEmpty()) {
Text(
text = state.subtitle,
fontSize = 12.sp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ data class ChatToolbarState(
/** Whether tapping the title area should open conversation info. */
val titleClickable: Boolean = false,
/** Whether the server capability SILENT_CALL is available (enables long-press on call buttons). */
val supportsSilentCall: Boolean = false
val supportsSilentCall: Boolean = false,
val isClassified: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ internal fun buildMessageActionsState(
val hasUserId = user?.userId?.isNotEmpty() == true && user.userId != "?"
val hasUserActorId = message.actorType == "users" && message.actorId != conversation?.actorId

val isClassifiedRoom = conversation != null && ConversationUtils.isClassified(conversation, spreedCapabilities)

return MessageActionsState(
showEmojiBar = showEmojiBar,
selfReactions = message.reactionsSelf?.toSet() ?: emptySet(),
Expand All @@ -236,11 +238,13 @@ internal fun buildMessageActionsState(
hasUserId &&
hasUserActorId &&
conversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
isOnline,
isOnline &&
!isClassifiedRoom,
showOpenThread = message.isThread && conversationThreadId == null,
showForward = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == messageType &&
!(message.isDeletedCommentMessage || message.isDeleted) &&
isOnline,
isOnline &&
!isClassifiedRoom,
showEdit = isMessageEditable,
showCopy = !message.isDeleted,
showCopyMessageLink = !message.isDeleted &&
Expand All @@ -259,10 +263,12 @@ internal fun buildMessageActionsState(
showTranslate = !message.isDeleted &&
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == messageType &&
CapabilitiesUtil.isTranslationsSupported(spreedCapabilities) &&
isOnline,
isOnline &&
!isClassifiedRoom,
showShareToNote = !message.isDeleted &&
!ConversationUtils.isNoteToSelfConversation(conversation) &&
isOnline,
isOnline &&
!isClassifiedRoom,
showShare = messageHasFileAttachment || (messageHasRegularText && isOnline),
showSave = messageHasFileAttachment,
showOpenInFiles = messageHasFileAttachment && isOnline,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ sealed interface MessageTypeContent {
val animateGif: Boolean = false,
val blurhash: String? = null,
val width: Int? = null,
val height: Int? = null
val height: Int? = null,
val isClassified: Boolean = false
) : MessageTypeContent

data class Geolocation(val id: String, val name: String, val lat: Double, val lon: Double) : MessageTypeContent
Expand Down Expand Up @@ -110,7 +111,8 @@ fun ChatMessage.toUiModel(
user: User,
chatMessage: ChatMessage,
lastCommonReadMessageId: Int,
parentMessage: ChatMessage?
parentMessage: ChatMessage?,
isClassified: Boolean = false
): ChatMessageUi =
ChatMessageUi(
id = jsonMessageId,
Expand All @@ -132,7 +134,7 @@ fun ChatMessage.toUiModel(
),
timestamp = timestamp,
date = dateKey(),
content = getMessageTypeContent(user, chatMessage),
content = getMessageTypeContent(user, chatMessage, isClassified),
roomToken = token,
activeUserId = user.userId,
activeUserBaseUrl = user.baseUrl,
Expand Down Expand Up @@ -243,14 +245,14 @@ fun resolveStatusIcon(
else -> MessageStatusIcon.SENT
}

fun getMessageTypeContent(user: User, message: ChatMessage): MessageTypeContent? =
fun getMessageTypeContent(user: User, message: ChatMessage, isClassified: Boolean = false): MessageTypeContent? =

if (message.isSystemMessage) {
MessageTypeContent.SystemMessage
} else if (message.isVoiceMessage) {
getVoiceContent(message)
} else if (message.hasFileAttachment) {
getMediaContent(user, message)
getMediaContent(user, message, isClassified)
} else if (message.hasGeoLocation) {
getGeolocationContent(message)
} else if (message.hasPoll) {
Expand All @@ -263,7 +265,7 @@ fun getMessageTypeContent(user: User, message: ChatMessage): MessageTypeContent?
?: MessageTypeContent.RegularText
}

fun getMediaContent(user: User, message: ChatMessage): MessageTypeContent.Media {
fun getMediaContent(user: User, message: ChatMessage, isClassified: Boolean = false): MessageTypeContent.Media {
val mimetype = message.fileParameters.mimetype
val drawableResourceId = DrawableUtils.getDrawableResourceIdForMimeType(mimetype)

Expand All @@ -285,7 +287,8 @@ fun getMediaContent(user: User, message: ChatMessage): MessageTypeContent.Media
animateGif = animateGif,
blurhash = message.fileParameters.blurhash,
width = message.fileParameters.width,
height = message.fileParameters.height
height = message.fileParameters.height,
isClassified = isClassified
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,9 @@ class ChatViewModel @AssistedInject constructor(
val lastCommonRead: Int,
val parentMap: Map<Long, ChatMessage>,
val conversationLastRead: Int,
val expandedParents: Set<Int> = emptySet()
val expandedParents: Set<Int> = emptySet(),
val conversation: ConversationModel? = null,
val capabilities: SpreedCapability? = null
)

data class CallStartedIndicatorData(
Expand Down Expand Up @@ -1044,13 +1046,31 @@ class ChatViewModel @AssistedInject constructor(
messagesFlow,
getLastCommonReadFlow.onStart { emit(0) },
parentMessagesFlow,
conversationFlow.map { it.lastReadMessage },
combine(conversationFlow, _spreedCapabilities) { conv, caps -> conv to caps },
expandedSystemMessageParents
) { messages, lastCommonRead, parentMap, conversationLastRead, expandedParents ->
CombinedInput(messages, lastCommonRead, parentMap, conversationLastRead, expandedParents)
) { messages, lastCommonRead, parentMap, convAndCaps, expandedParents ->
val (conversation, capabilities) = convAndCaps
CombinedInput(
messages,
lastCommonRead,
parentMap,
conversation.lastReadMessage,
expandedParents,
conversation,
capabilities
)
}
.debounce(MESSAGES_REBUILD_DEBOUNCE_MS)
.map { (messages, lastCommonRead, parentMap, conversationLastRead, expandedParents) ->
.map { input ->
val (
messages,
lastCommonRead,
parentMap,
conversationLastRead,
expandedParents,
conversation,
capabilities
) = input
val messageMap: Map<Long, ChatMessage> = messages.associateBy { it.jsonMessageId.toLong() }
val combinedMap: Map<Long, ChatMessage> = messageMap + parentMap

Expand All @@ -1059,6 +1079,7 @@ class ChatViewModel @AssistedInject constructor(
parentIds.filterNot { parentId -> combinedMap.containsKey(parentId) }
.distinct()

val isClassified = conversation != null && ConversationUtils.isClassified(conversation, capabilities)
val user = currentUserFlow.value
applyMessageGrouping(messages)
applySystemMessageGrouping(messages)
Expand All @@ -1068,7 +1089,8 @@ class ChatViewModel @AssistedInject constructor(
user = user ?: currentUser,
chatMessage = message,
lastCommonReadMessageId = lastCommonRead,
parentMessage = parent
parentMessage = parent,
isClassified = isClassified
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ data class ConversationInfoUiState(
val showImportantConversation: Boolean = false,
val sensitiveConversation: Boolean = false,
val showSensitiveConversation: Boolean = false,
val isClassified: Boolean = false,

val lobbyEnabled: Boolean = false,
val showWebinarSettings: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,12 +391,13 @@ private fun SettingsRow(
subtitle: String? = null,
@DrawableRes iconRes: Int? = null,
checked: Boolean? = null,
enabled: Boolean = true,
onClick: (() -> Unit)? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
.then(if (onClick != null && enabled) Modifier.clickable(onClick = onClick) else Modifier)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Expand All @@ -420,7 +421,7 @@ private fun SettingsRow(
}
}
if (checked != null) {
Switch(checked = checked, onCheckedChange = null)
Switch(checked = checked, onCheckedChange = null, enabled = enabled)
}
}
}
Expand Down Expand Up @@ -482,7 +483,8 @@ private fun NotificationSettingsSection(state: ConversationInfoUiState, callback
SettingsRow(
title = stringResource(R.string.nc_sensitive_conversation),
subtitle = stringResource(R.string.nc_sensitive_conversation_hint),
checked = state.sensitiveConversation,
checked = state.isClassified || state.sensitiveConversation,
enabled = !state.isClassified,
onClick = callbacks.onSensitiveConversationClick
)
}
Expand Down
Loading
Loading