@@ -34,6 +34,7 @@ import android.provider.MediaStore
3434import android.provider.Settings
3535import android.text.SpannableStringBuilder
3636import android.text.TextUtils
37+ import android.text.format.DateFormat
3738import android.util.Log
3839import android.view.Gravity
3940import android.view.Menu
@@ -59,11 +60,33 @@ import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
5960import androidx.appcompat.app.AlertDialog
6061import androidx.appcompat.view.ContextThemeWrapper
6162import androidx.cardview.widget.CardView
63+ import androidx.compose.foundation.background
64+ import androidx.compose.foundation.clickable
65+ import androidx.compose.foundation.layout.Arrangement
66+ import androidx.compose.foundation.layout.Box
67+ import androidx.compose.foundation.layout.Column
68+ import androidx.compose.foundation.layout.Row
69+ import androidx.compose.foundation.layout.Spacer
70+ import androidx.compose.foundation.layout.padding
71+ import androidx.compose.foundation.layout.size
72+ import androidx.compose.foundation.rememberScrollState
73+ import androidx.compose.foundation.shape.RoundedCornerShape
74+ import androidx.compose.foundation.verticalScroll
75+ import androidx.compose.material3.Icon
6276import androidx.compose.material3.MaterialTheme
77+ import androidx.compose.material3.Text
78+ import androidx.compose.runtime.Composable
6379import androidx.compose.runtime.getValue
6480import androidx.compose.runtime.mutableStateOf
81+ import androidx.compose.runtime.remember
6582import androidx.compose.runtime.setValue
83+ import androidx.compose.ui.Modifier
84+ import androidx.compose.ui.draw.shadow
85+ import androidx.compose.ui.graphics.Color
6686import androidx.compose.ui.platform.ComposeView
87+ import androidx.compose.ui.res.painterResource
88+ import androidx.compose.ui.res.stringResource
89+ import androidx.compose.ui.unit.dp
6790import androidx.coordinatorlayout.widget.CoordinatorLayout
6891import androidx.core.content.ContextCompat
6992import androidx.core.content.FileProvider
@@ -167,12 +190,14 @@ import com.nextcloud.talk.signaling.SignalingMessageReceiver
167190import com.nextcloud.talk.signaling.SignalingMessageSender
168191import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
169192import com.nextcloud.talk.translate.ui.TranslateActivity
193+ import com.nextcloud.talk.ui.ComposeChatAdapter
170194import com.nextcloud.talk.ui.PlaybackSpeed
171195import com.nextcloud.talk.ui.PlaybackSpeedControl
172196import com.nextcloud.talk.ui.StatusDrawable
173197import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
174198import com.nextcloud.talk.ui.dialog.DateTimeCompose
175199import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
200+ import com.nextcloud.talk.ui.dialog.GetPinnedOptionsDialog
176201import com.nextcloud.talk.ui.dialog.MessageActionsDialog
177202import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
178203import com.nextcloud.talk.ui.dialog.ShowReactionsDialog
@@ -250,7 +275,7 @@ import java.util.concurrent.ExecutionException
250275import javax.inject.Inject
251276import kotlin.math.roundToInt
252277
253- @Suppress(" TooManyFunctions" )
278+ @Suppress(" TooManyFunctions" , " LargeClass " , " LongMethod " )
254279@AutoInjector(NextcloudTalkApplication ::class )
255280class ChatActivity :
256281 BaseActivity (),
@@ -663,6 +688,27 @@ class ChatActivity :
663688 }
664689
665690 chatViewModel.getCapabilities(conversationUser!! , roomToken, currentConversation!! )
691+
692+ if (conversationModel.lastPinnedId != null &&
693+ conversationModel.lastPinnedId != 0L &&
694+ conversationModel.lastPinnedId != conversationModel.hiddenPinnedId
695+ ) {
696+ chatViewModel
697+ .getIndividualMessageFromServer(
698+ credentials!! ,
699+ conversationUser?.baseUrl!! ,
700+ roomToken,
701+ conversationModel.lastPinnedId.toString()
702+ )
703+ .collect { message ->
704+ binding.pinnedMessageContainer.visibility = View .VISIBLE
705+ binding.pinnedMessageComposeView.setContent {
706+ PinnedMessageView (message)
707+ }
708+ }
709+ } else {
710+ binding.pinnedMessageContainer.visibility = View .GONE
711+ }
666712 }.collect()
667713 }
668714
@@ -1130,6 +1176,10 @@ class ChatActivity :
11301176 val item = adapter?.items?.get(index)?.item
11311177 item?.let {
11321178 setMessageAsEdited(item as ChatMessage , newString)
1179+
1180+ if (item.jsonMessageId.toLong() == currentConversation?.lastPinnedId) {
1181+ chatViewModel.getRoom(roomToken)
1182+ }
11331183 }
11341184 }
11351185
@@ -1313,6 +1363,94 @@ class ChatActivity :
13131363 }
13141364 }
13151365
1366+ @Composable
1367+ private fun PinnedMessageView (message : ChatMessage ) {
1368+ message.incoming = true
1369+ val pinnedBy = stringResource(R .string.pinned_by)
1370+ message.actorDisplayName = " ${message.actorDisplayName} \n $pinnedBy ${message.pinnedActorDisplayName} "
1371+ val scrollState = rememberScrollState()
1372+
1373+ val outgoingBubbleColor = remember {
1374+ val colorInt = viewThemeUtils.talk
1375+ .getOutgoingMessageBubbleColor(context, message.isDeleted, false )
1376+
1377+ Color (colorInt)
1378+ }
1379+
1380+ val incomingBubbleColor = remember {
1381+ val colorInt = resources
1382+ .getColor(R .color.bg_message_list_incoming_bubble, null )
1383+
1384+ Color (colorInt)
1385+ }
1386+
1387+ val isAllowed = remember {
1388+ ConversationUtils .isParticipantOwnerOrModerator(currentConversation!! )
1389+ }
1390+
1391+ Column (
1392+ verticalArrangement = Arrangement .spacedBy((- 16 ).dp),
1393+ modifier = Modifier
1394+ ) {
1395+ Box (
1396+ modifier = Modifier
1397+ .shadow(4 .dp, shape = RoundedCornerShape (16 .dp))
1398+ .background(incomingBubbleColor, RoundedCornerShape (16 .dp))
1399+ .padding(16 .dp)
1400+ .verticalScroll(scrollState)
1401+ ) {
1402+ ComposeChatAdapter ().GetComposableForMessage (message)
1403+ }
1404+
1405+ Row (
1406+ modifier = Modifier
1407+ .padding(start = 16 .dp)
1408+ .background(outgoingBubbleColor, RoundedCornerShape (16 .dp))
1409+ .padding(16 .dp)
1410+ ) {
1411+ val hiddenEye = painterResource(R .drawable.ic_eye_off)
1412+ Icon (
1413+ hiddenEye,
1414+ " Hide pin" ,
1415+ modifier = Modifier
1416+ .size(16 .dp)
1417+ .clickable {
1418+ hidePinnedMessage(message)
1419+ }
1420+ )
1421+
1422+ if (isAllowed) {
1423+ Spacer (modifier = Modifier .size(16 .dp))
1424+ val read = painterResource(R .drawable.keep_off_24px)
1425+ Icon (
1426+ read,
1427+ " Unpin" ,
1428+ modifier = Modifier
1429+ .size(16 .dp)
1430+ .clickable {
1431+ unPinMessage(message)
1432+ }
1433+ )
1434+ }
1435+
1436+ val pinnedUntilStr = stringResource(R .string.pinned_until)
1437+ val pinnedIndefinitely = stringResource(R .string.pinned_indefinitely)
1438+ val pinnedText = message.pinnedUntil?.let {
1439+ val format = if (DateFormat .is24HourFormat(context)) " EEE, HH:mm" else " EEE, hh:mm a"
1440+ val localDateTime = Instant .ofEpochMilli(it)
1441+ .atZone(ZoneId .systemDefault())
1442+ .toLocalDateTime()
1443+
1444+ val timeString = localDateTime.format(DateTimeFormatter .ofPattern(format))
1445+
1446+ " $pinnedUntilStr $timeString "
1447+ } ? : pinnedIndefinitely
1448+
1449+ Text (pinnedText, modifier = Modifier .padding(start = 16 .dp))
1450+ }
1451+ }
1452+ }
1453+
13161454 private fun removeUnreadMessagesMarker () {
13171455 removeMessageById(UNREAD_MESSAGES_MARKER_ID .toString())
13181456 }
@@ -3915,6 +4053,32 @@ class ChatActivity :
39154053 }
39164054 }
39174055
4056+ fun hidePinnedMessage (message : ChatMessage ) {
4057+ val url = ApiUtils .getUrlForChatMessagePinning(chatApiVersion, conversationUser?.baseUrl, roomToken, message.id)
4058+ chatViewModel.hidePinnedMessage(credentials!! , url)
4059+ }
4060+
4061+ fun pinMessage (message : ChatMessage ) {
4062+ val url = ApiUtils .getUrlForChatMessagePinning(chatApiVersion, conversationUser?.baseUrl, roomToken, message.id)
4063+ binding.genericComposeView.apply {
4064+ val shouldDismiss = mutableStateOf(false )
4065+ setContent {
4066+ GetPinnedOptionsDialog (shouldDismiss, context, viewThemeUtils) { zonedDateTime ->
4067+ zonedDateTime?.let {
4068+ chatViewModel.pinMessage(credentials!! , url, pinUntil = zonedDateTime.toEpochSecond().toInt())
4069+ } ? : chatViewModel.pinMessage(credentials!! , url)
4070+
4071+ shouldDismiss.value = true
4072+ }
4073+ }
4074+ }
4075+ }
4076+
4077+ fun unPinMessage (message : ChatMessage ) {
4078+ val url = ApiUtils .getUrlForChatMessagePinning(chatApiVersion, conversationUser?.baseUrl, roomToken, message.id)
4079+ chatViewModel.unPinMessage(credentials!! , url)
4080+ }
4081+
39184082 fun markAsUnread (message : IMessage ? ) {
39194083 val chatMessage = message as ChatMessage ?
39204084 if (chatMessage!! .previousMessageId > NO_PREVIOUS_MESSAGE_ID ) {
0 commit comments