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
72 changes: 66 additions & 6 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,16 @@ import androidx.cardview.widget.CardView
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.coordinatorlayout.widget.CoordinatorLayout
Expand Down Expand Up @@ -117,6 +122,8 @@ import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.data.model.FileParameters
import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet
import com.nextcloud.talk.chat.ui.model.MessageTypeContent
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
Expand Down Expand Up @@ -162,7 +169,6 @@ import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
import com.nextcloud.talk.ui.dialog.GetPinnedOptionsDialog
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
import com.nextcloud.talk.chat.ui.ShowReactionsModalBottomSheet
import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog
import com.nextcloud.talk.ui.theme.LocalMessageUtils
import com.nextcloud.talk.ui.theme.LocalOpenGraphFetcher
Expand Down Expand Up @@ -603,6 +609,7 @@ class ChatActivity :
binding.messagesListViewCompose.visibility = View.VISIBLE

val listState = rememberLazyListState()

SideEffect { chatListState = listState }

CompositionLocalProvider(
Expand All @@ -613,21 +620,34 @@ class ChatActivity :
val isOneToOneConversation = uiState.isOneToOneConversation
Log.d(TAG, "isOneToOneConversation=" + isOneToOneConversation)

// list of the file ids of messages being downloaded
val downloadingFileState = remember { mutableStateOf(listOf<String>()) }

// openWhenDownloaded is a derived boolean state of the visible chat message list on the condition
// that if any of the messages that are present contain a fileId that is within downloadingFileState
val openWhenDownloadState = remember { mutableStateOf(false) }

val visibleIds = listState.visibleItemsWithThreshold()
LaunchedEffect(visibleIds, downloadingFileState.value) {
openWhenDownloadState.value = (downloadingFileState.value.intersect(visibleIds).isNotEmpty())
}

ChatView(
state = ChatViewState(
chatItems = uiState.items,
isOneToOneConversation = isOneToOneConversation,
conversationThreadId = conversationThreadId,
hasChatPermission = this::participantPermissions.isInitialized &&
participantPermissions.hasChatPermission()
participantPermissions.hasChatPermission(),
downloadingFileState = downloadingFileState.value
),
callbacks = ChatViewCallbacks(
onLoadMore = { loadMoreMessagesCompose() },
advanceLocalLastReadMessageIfNeeded = { advanceLocalLastReadMessageIfNeeded(it) },
updateRemoteLastReadMessageIfNeeded = { updateRemoteLastReadMessageIfNeeded() },
onLongClick = { openMessageActionsDialog(it) },
onSwipeReply = { handleSwipeToReply(it) },
onFileClick = { downloadAndOpenFile(it) },
onFileClick = { downloadAndOpenFile(it, openWhenDownloadState, downloadingFileState) },
onPollClick = { pollId, pollName -> openPollDialog(pollId, pollName) },
onVoicePlayPauseClick = { onVoicePlayPauseClickCompose(it) },
onVoiceSeek = { _, progress -> chatViewModel.seekToMediaPlayer(progress) },
Expand Down Expand Up @@ -662,6 +682,39 @@ class ChatActivity :
}
}

@Composable
private fun LazyListState.visibleItemsWithThreshold(): List<String> =
remember(this) {
derivedStateOf {
val visibleItemsInfo = layoutInfo.visibleItemsInfo
if (layoutInfo.totalItemsCount == 0) {
emptyList()
} else {
visibleItemsInfo.toMutableList().map { it.key as String }
}
}
}.value.mapNotNull { key ->
val messageItem = chatViewModel.uiState.value.items.firstOrNull { it.stableKey() == key }
val message = messageItem?.messageOrNull()
var result: String? = null
message?.let {
if (message.messageParameters.isNotEmpty()) {
runCatching {
message.messageParameters as HashMap<String?, HashMap<String?, String?>>?
val fileParameters = FileParameters(message.messageParameters)
result = fileParameters.id
}.onFailure { e ->
when (e) {
is ClassCastException -> {} // weird
else -> Log.e(TAG, "Error in LazyListState.visibleItemsWithThreshold $e")
}
}
}
}

result
}

private fun onLoadQuotedMessage(messageId: Int) {
// Loading and displaying surrounding messages for quotes is pending; replace flow from latestChatBlock with
// other flow
Expand Down Expand Up @@ -730,10 +783,18 @@ class ChatActivity :
chatViewModel.setVoiceMessageSpeed(messageId, nextSpeed)
}

fun downloadAndOpenFile(messageId: Int) {
fun downloadAndOpenFile(
messageId: Int,
openWhenDownloadState: MutableState<Boolean>,
downloadState: MutableState<List<String>>
) {
lifecycleScope.launch {
val chatMessage = chatViewModel.getMessageById(messageId.toLong()).first()
FileViewerUtils(this@ChatActivity, conversationUser).openFile(chatMessage)
FileViewerUtils(this@ChatActivity, conversationUser).openFile(
chatMessage,
openWhenDownloadState,
downloadState
)
}
}

Expand Down Expand Up @@ -3558,7 +3619,6 @@ class ChatActivity :

if (noteToSelfConversation != null) {
var shareUri: Uri? = null
val data: HashMap<String, String>?
var metaData = ""
var objectId = ""
if (message.hasFileAttachment) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.compose.runtime.mutableStateOf
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.nextcloud.talk.data.user.model.User
Expand Down Expand Up @@ -58,6 +59,7 @@ abstract class SharedItemsViewHolder(
This should be done after a refactoring of FileViewerUtils.
*/
val fileViewerUtils = FileViewerUtils(image.context, user)
val trueState = mutableStateOf(true)

clickTarget.setOnClickListener {
fileViewerUtils.openFile(
Expand All @@ -74,15 +76,15 @@ abstract class SharedItemsViewHolder(
// null,
// image
// ),
true
trueState
)
}

fileViewerUtils.resumeToUpdateViewsByProgress(
item.name,
item.id,
item.mimeType,
true,
trueState,
FileViewerUtils.ProgressUi(progressBar, null, image)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ private const val QUOTE_HIGHLIGHT_FADE_OUT_MILLIS = 1500
data class ChatMessageContext(
val isOneToOneConversation: Boolean = false,
val conversationThreadId: Long? = null,
val hasChatPermission: Boolean = true
val hasChatPermission: Boolean = true,
val downloadingFileState: List<String> = listOf()
)

class ChatMessageCallbacks(
data class ChatMessageCallbacks(
val onLongClick: ((Int) -> Unit?)? = null,
val onSwipeReply: ((Int) -> Unit)? = null,
val onFileClick: (Int) -> Unit = {},
Expand All @@ -68,6 +69,7 @@ class ChatMessageCallbacks(
val onQuotedMessageClick: (Int) -> Unit = {}
)

@Suppress("TooLongMethod")
@Composable
fun ChatMessageView(
message: ChatMessageUi,
Expand Down Expand Up @@ -130,6 +132,7 @@ fun ChatMessageView(
message = message,
isOneToOneConversation = context.isOneToOneConversation,
conversationThreadId = context.conversationThreadId,
chatViewDownloadingFileState = context.downloadingFileState,
onImageClick = callbacks.onFileClick
)
}
Expand Down
14 changes: 8 additions & 6 deletions app/src/main/java/com/nextcloud/talk/ui/chat/ChatView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
Expand All @@ -25,8 +25,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
Expand Down Expand Up @@ -57,6 +57,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nextcloud.talk.R
import com.nextcloud.talk.chat.ui.model.ChatMessageUi
import com.nextcloud.talk.chat.ui.model.MessageStatusIcon
import com.nextcloud.talk.chat.ui.model.MessageTypeContent
Expand All @@ -72,7 +73,6 @@ import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import com.nextcloud.talk.R

private const val LONG_1000 = 1000L
private const val LOAD_MORE_BUFFER_ITEMS = 5
Expand All @@ -88,10 +88,11 @@ data class ChatViewState(
val conversationThreadId: Long? = null,
val hasChatPermission: Boolean = true,
val initialUnreadCount: Int = 0,
val initialShowUnreadPopup: Boolean = false
val initialShowUnreadPopup: Boolean = false,
val downloadingFileState: List<String> = listOf()
)

class ChatViewCallbacks(
data class ChatViewCallbacks(
val onLoadMore: (() -> Unit?)? = null,
val advanceLocalLastReadMessageIfNeeded: ((Int) -> Unit?)? = null,
val updateRemoteLastReadMessageIfNeeded: (() -> Unit?)? = null,
Expand Down Expand Up @@ -311,7 +312,8 @@ fun ChatView(
context = ChatMessageContext(
isOneToOneConversation = state.isOneToOneConversation,
conversationThreadId = state.conversationThreadId,
hasChatPermission = state.hasChatPermission
hasChatPermission = state.hasChatPermission,
downloadingFileState = state.downloadingFileState
),
callbacks = ChatMessageCallbacks(
onLongClick = callbacks.onLongClick,
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/java/com/nextcloud/talk/ui/chat/MediaMessage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
Expand All @@ -28,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.nextcloud.talk.R
import com.nextcloud.talk.chat.data.model.FileParameters
import com.nextcloud.talk.chat.ui.model.ChatMessageUi
import com.nextcloud.talk.chat.ui.model.MessageTypeContent
import com.nextcloud.talk.contacts.load
Expand All @@ -37,15 +39,19 @@ private const val FILE_PLACEHOLDER_MESSAGE = "{file}"
private val mediaRadiusBig = 8.dp
private val mediaRadiusSmall = 2.dp

@Suppress("Detekt.LongMethod")
@Suppress("Detekt.LongMethod", "LongParameterList")
@Composable
fun MediaMessage(
typeContent: MessageTypeContent.Media,
message: ChatMessageUi,
isOneToOneConversation: Boolean = false,
conversationThreadId: Long? = null,
chatViewDownloadingFileState: List<String>,
onImageClick: (Int) -> Unit
) {
val fileParameters =
remember { FileParameters(message.messageParameters as HashMap<String?, HashMap<String?, String?>>?) }

val captionText = message.message.takeUnless { it == FILE_PLACEHOLDER_MESSAGE }
val hasCaption = captionText != null
val mediaInset = 4.dp
Expand Down Expand Up @@ -111,6 +117,15 @@ fun MediaMessage(
tint = Color.White
)
}

if (chatViewDownloadingFileState.contains(fileParameters.id)) {
CircularProgressIndicator(
modifier = Modifier
.size(48.dp)
.align(Alignment.Center),
strokeWidth = 2.dp
)
}
}
}
}
Expand Down
Loading
Loading