Skip to content
Merged
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
46 changes: 38 additions & 8 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import com.nextcloud.talk.chat.data.model.FileParameters
import com.nextcloud.talk.chat.ui.ChatEmptyState
import com.nextcloud.talk.chat.ui.ChatEmptyStateType
import com.nextcloud.talk.chat.ui.ChatToolbar
import com.nextcloud.talk.chat.ui.ChatToolbarAvatarType
import com.nextcloud.talk.chat.ui.ChatToolbarCallbacks
import com.nextcloud.talk.chat.ui.ChatToolbarState
import com.nextcloud.talk.chat.ui.MessageActionsBottomSheet
Expand Down Expand Up @@ -1821,7 +1822,6 @@ class ChatActivity :
onSilentVoiceCall = { startACall(true, true) },
onVideoCall = { startACall(false, false) },
onSilentVideoCall = { startACall(false, true) },
onSearchOpen = { startMessageSearch() },
onSearchClose = {
chatViewModel.exitSearchMode()
chatToolbarState = chatToolbarState.copy(isSearchMode = false, searchQuery = "")
Expand Down Expand Up @@ -1894,12 +1894,12 @@ class ChatActivity :
chatToolbarState = chatToolbarState.copy(
title = buildToolbarTitle(conversation),
subtitle = buildToolbarSubtitle(conversation),
avatarUrl = buildAvatarUrl(user, conversation, isOneToOne),
avatarType = buildAvatarType(conversation),
avatarUrl = buildAvatarUrl(user, conversation),
credentials = user?.let { ApiUtils.getCredentials(it.username, it.token) },
userStatus = if (isOneToOne) conversation?.status else null,
showVoiceCall = isCallsEnabled(capabilitiesReady, conversation),
showVideoCall = isCallsEnabled(capabilitiesReady, conversation),
showSearch = isSearchAvailable(capabilitiesReady, conversation),
titleClickable = user?.userId != "?" && !chatToolbarState.isSearchMode,
overflowItems = buildOverflowItems(),
threadNotificationIcon = buildThreadNotificationIcon(capabilitiesReady),
Expand All @@ -1924,13 +1924,37 @@ class ChatActivity :
else -> ""
}

private fun buildAvatarUrl(user: User?, conversation: ConversationModel?, isOneToOne: Boolean): String? =
if (user != null && conversation != null && isOneToOne) {
ApiUtils.getUrlForAvatar(user.baseUrl!!, conversation.name, true, DisplayUtils.isDarkModeOn(this))
} else {
null
private fun buildAvatarType(conversation: ConversationModel?): ChatToolbarAvatarType =
when (conversation?.type) {
ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL,
ConversationEnums.ConversationType.ROOM_GROUP_CALL,
ConversationEnums.ConversationType.ROOM_PUBLIC_CALL -> ChatToolbarAvatarType.URL
ConversationEnums.ConversationType.ROOM_SYSTEM -> ChatToolbarAvatarType.SYSTEM
ConversationEnums.ConversationType.NOTE_TO_SELF -> ChatToolbarAvatarType.NOTE_TO_SELF
else -> ChatToolbarAvatarType.NONE
}

private fun buildAvatarUrl(user: User?, conversation: ConversationModel?): String? {
if (user == null || conversation == null) {
return null
}
val isDark = DisplayUtils.isDarkModeOn(this)
return when (conversation.type) {
ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ->
ApiUtils.getUrlForAvatar(user.baseUrl!!, conversation.name, true, isDark)
ConversationEnums.ConversationType.ROOM_GROUP_CALL,
ConversationEnums.ConversationType.ROOM_PUBLIC_CALL ->
ApiUtils.getUrlForConversationAvatarWithVersion(
1,
user.baseUrl,
conversation.token,
isDark,
conversation.avatarVersion
)
else -> null
}
}

private fun isCallsEnabled(capabilitiesReady: Boolean, conversation: ConversationModel?): Boolean =
capabilitiesReady &&
CapabilitiesUtil.isAbleToCall(spreedCapabilities) &&
Expand Down Expand Up @@ -1982,6 +2006,12 @@ class ChatActivity :
val isThread = isChatThread()
val capabilitiesReady = ::spreedCapabilities.isInitialized

if (isSearchAvailable(capabilitiesReady, currentConversation)) {
items += MenuItemData(
title = getString(R.string.nc_search),
onClick = { startMessageSearch() }
)
}
if (conversationUser?.userId != "?" && !isThread) {
items += MenuItemData(
title = getString(R.string.nc_conversation_menu_conversation_info),
Expand Down
84 changes: 62 additions & 22 deletions app/src/main/java/com/nextcloud/talk/chat/ui/ChatToolbar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.nextcloud.talk.chat.ui

import android.content.res.Configuration
import android.widget.ImageView
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
Expand Down Expand Up @@ -57,6 +58,7 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
Expand All @@ -69,10 +71,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.nextcloud.talk.R
import com.nextcloud.talk.chat.MenuItemData
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
Expand Down Expand Up @@ -209,11 +214,6 @@ private fun ToolbarActions(state: ChatToolbarState, callbacks: ChatToolbarCallba
onLongClick = callbacks.onSilentVideoCall.takeIf { state.supportsSilentCall }
)
}
if (state.showSearch) {
IconButton(onClick = callbacks.onSearchOpen) {
Icon(painterResource(R.drawable.ic_search_white_24dp), stringResource(R.string.nc_search))
}
}
if (state.overflowItems.isNotEmpty()) {
OverflowMenuButton(items = state.overflowItems)
}
Expand Down Expand Up @@ -294,8 +294,9 @@ private fun ConversationHeader(state: ChatToolbarState, onClick: (() -> Unit)?)
modifier = rowModifier,
verticalAlignment = Alignment.CenterVertically
) {
if (state.avatarUrl != null) {
if (state.avatarType != ChatToolbarAvatarType.NONE) {
ConversationAvatar(
avatarType = state.avatarType,
avatarUrl = state.avatarUrl,
credentials = state.credentials,
userStatus = state.userStatus,
Expand Down Expand Up @@ -326,27 +327,66 @@ private fun ConversationHeader(state: ChatToolbarState, onClick: (() -> Unit)?)

@Composable
private fun ConversationAvatar(
avatarUrl: String,
avatarType: ChatToolbarAvatarType,
avatarUrl: String?,
credentials: String?,
userStatus: String?,
modifier: Modifier = Modifier
) {
Box(modifier = modifier) {
val context = LocalContext.current
val request = ImageRequest.Builder(context)
.data(avatarUrl)
.apply { credentials?.let { addHeader("Authorization", it) } }
.crossfade(true)
.build()
when (avatarType) {
ChatToolbarAvatarType.URL -> {
val context = LocalContext.current
val request = ImageRequest.Builder(context)
.data(avatarUrl)
.apply { credentials?.let { addHeader("Authorization", it) } }
.crossfade(true)
.build()

AsyncImage(
model = request,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
)
AsyncImage(
model = request,
contentDescription = null,
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.account_circle_96dp),
error = painterResource(R.drawable.account_circle_96dp),
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
)
}

ChatToolbarAvatarType.SYSTEM -> {
if (LocalInspectionMode.current) {
Icon(
painter = painterResource(R.drawable.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
} else {
AndroidView(
factory = { ctx -> ImageView(ctx).apply { loadSystemAvatar() } },
modifier = Modifier.fillMaxSize()
)
}
}

ChatToolbarAvatarType.NOTE_TO_SELF -> {
if (LocalInspectionMode.current) {
Icon(
painter = painterResource(R.drawable.ic_note_to_self),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
} else {
AndroidView(
factory = { ctx -> ImageView(ctx).apply { loadNoteToSelfAvatar() } },
modifier = Modifier.fillMaxSize()
)
}
}

ChatToolbarAvatarType.NONE -> Unit
}

if (userStatus != null) {
UserStatusBadge(
Expand Down Expand Up @@ -430,8 +470,8 @@ private fun NormalModePreview() {
userStatus = "online",
showVoiceCall = true,
showVideoCall = true,
showSearch = true,
overflowItems = listOf(
MenuItemData(title = "Search", onClick = {}),
MenuItemData(title = "Conversation info", onClick = {}),
MenuItemData(title = "Shared items", onClick = {})
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ data class ChatToolbarCallbacks(
val onSilentVoiceCall: () -> Unit = {},
val onVideoCall: () -> Unit = {},
val onSilentVideoCall: () -> Unit = {},
val onSearchOpen: () -> Unit = {},
val onSearchClose: () -> Unit = {},
val onSearchQueryChange: (String) -> Unit = {},
val onSearchSubmit: () -> Unit = {},
Expand Down
13 changes: 10 additions & 3 deletions app/src/main/java/com/nextcloud/talk/chat/ui/ChatToolbarState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,19 @@ package com.nextcloud.talk.chat.ui

import com.nextcloud.talk.chat.MenuItemData

enum class ChatToolbarAvatarType {
NONE,
URL,
SYSTEM,
NOTE_TO_SELF
}

data class ChatToolbarState(
val title: String = "",
val subtitle: String = "",
/** Non-null only for 1-to-1 conversations; drives avatar display. */
/** Which kind of avatar (if any) to render in the toolbar. */
val avatarType: ChatToolbarAvatarType = ChatToolbarAvatarType.NONE,
/** Set only when [avatarType] is [ChatToolbarAvatarType.URL]. */
val avatarUrl: String? = null,
/** HTTP Basic / Bearer credential string for the avatar request. */
val credentials: String? = null,
Expand All @@ -22,8 +31,6 @@ data class ChatToolbarState(
val isLoading: Boolean = false,
val showVoiceCall: Boolean = false,
val showVideoCall: Boolean = false,
/** Whether the server supports message search for this conversation. */
val showSearch: Boolean = false,
val searchQuery: String = "",
val overflowItems: List<MenuItemData> = emptyList(),
/** Non-null in thread view; the drawable resource for the current notification level. */
Expand Down
Loading