Skip to content

Commit cc52fac

Browse files
authored
Merge pull request #5969 from anakin78z/autoplay-gifs
GIF animations autoplay in chat messages
2 parents 44e419e + bf9f374 commit cc52fac

File tree

6 files changed

+98
-9
lines changed

6 files changed

+98
-9
lines changed

app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import com.nextcloud.talk.utils.FileViewerUtils
4747
import com.nextcloud.talk.utils.FileViewerUtils.ProgressUi
4848
import com.nextcloud.talk.utils.message.MessageUtils
4949
import com.stfalcon.chatkit.messages.MessageHolders.IncomingImageMessageViewHolder
50+
import coil.load
51+
import com.nextcloud.talk.utils.ApiUtils
5052
import io.reactivex.Single
5153
import io.reactivex.SingleObserver
5254
import io.reactivex.disposables.Disposable
@@ -100,6 +102,22 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
100102
super.onBind(message)
101103
image.minimumHeight = DisplayUtils.convertDpToPixel(MIN_IMAGE_HEIGHT, context!!).toInt()
102104

105+
// Reset state for view recycling
106+
image.adjustViewBounds = false
107+
messageText.visibility = View.VISIBLE
108+
109+
// Check if image is GIF and load animated image
110+
if (message.imageUrl != null && message.shouldAutoplayGif()) {
111+
image.adjustViewBounds = true
112+
image.load(message.imageUrl) {
113+
size(coil.size.Size.ORIGINAL)
114+
addHeader(
115+
"Authorization",
116+
ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
117+
)
118+
}
119+
}
120+
103121
if (message.lastEditTimestamp != 0L && !message.isDeleted) {
104122
time.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
105123
messageEditIndicator.visibility = View.VISIBLE
@@ -110,14 +128,18 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
110128

111129
viewThemeUtils!!.platform.colorCircularProgressBar(progressBar!!, ColorRole.PRIMARY)
112130
clickView = image
113-
messageText.visibility = View.VISIBLE
114131
if (message.getCalculateMessageType() === ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
115132
val chatActivity = commonMessageInterface as ChatActivity
116133
fileViewerUtils = FileViewerUtils(chatActivity, message.activeUser!!)
117134
val fileName = message.selectedIndividualHashMap!![KEY_NAME]
118135

119136
messageText.text = fileName
120137

138+
// hide filename display for GIF images
139+
if (message.shouldAutoplayGif()) {
140+
messageText.visibility = View.INVISIBLE
141+
}
142+
121143
if (message.activeUser != null &&
122144
message.activeUser!!.username != null &&
123145
message.activeUser!!.baseUrl != null

app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.nextcloud.talk.models.json.chat.ReadStatus
2121
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
2222
import com.nextcloud.talk.utils.ApiUtils
2323
import com.nextcloud.talk.utils.CapabilitiesUtil
24+
import com.nextcloud.talk.utils.Mimetype
2425
import com.stfalcon.chatkit.commons.models.IUser
2526
import com.stfalcon.chatkit.commons.models.MessageContentType
2627
import java.security.MessageDigest
@@ -236,6 +237,22 @@ data class ChatMessage(
236237
return false
237238
}
238239

240+
/**
241+
* @return true if message is a GIF file, and size is below max-gif-size in the config
242+
*/
243+
fun shouldAutoplayGif(): Boolean {
244+
val mimetype = selectedIndividualHashMap?.get("mimetype")
245+
val capabilities = activeUser?.capabilities?.spreedCapability
246+
val fileSize = selectedIndividualHashMap?.get("size")?.toLongOrNull()
247+
248+
return if (mimetype != Mimetype.IMAGE_GIF || activeUser == null || capabilities == null) {
249+
false
250+
} else {
251+
val maxGifSize = CapabilitiesUtil.getMaxGifSize(capabilities)
252+
fileSize == null || fileSize in 1..maxGifSize
253+
}
254+
}
255+
239256
@Suppress("Detekt.NestedBlockDepth")
240257
override fun getImageUrl(): String? {
241258
if (messageParameters != null && messageParameters!!.size > 0) {
@@ -246,6 +263,16 @@ data class ChatMessage(
246263
selectedIndividualHashMap = individualHashMap
247264
if (!isVoiceMessage) {
248265
if (activeUser != null && activeUser!!.baseUrl != null) {
266+
val path = individualHashMap["path"]
267+
if (path != null && activeUser!!.username != null) {
268+
if (shouldAutoplayGif()) {
269+
return ApiUtils.getUrlForFileDownload(
270+
activeUser!!.baseUrl!!,
271+
activeUser!!.username!!,
272+
path
273+
)
274+
}
275+
}
249276
return ApiUtils.getUrlForFilePreviewWithFileId(
250277
activeUser!!.baseUrl!!,
251278
individualHashMap["id"]!!,

app/src/main/java/com/nextcloud/talk/contacts/ImageRequest.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Nextcloud Talk - Android Client
33
*
44
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@email.com>
5+
* SPDX-FileCopyrightText: 2026 Jens Zalzala <anakin78z@gmail.com>
56
* SPDX-License-Identifier: GPL-3.0-or-later
67
*/
78

@@ -26,13 +27,23 @@ fun loadImage(imageUri: String?, context: Context, errorPlaceholderImage: Int):
2627
}
2728

2829
@Composable
29-
fun load(imageUri: String?, context: Context, errorPlaceholderImage: Int): ImageRequest {
30-
val imageRequest = ImageRequest.Builder(context)
30+
fun load(
31+
imageUri: String?,
32+
context: Context,
33+
errorPlaceholderImage: Int,
34+
animated: Boolean = false,
35+
authHeader: String? = null
36+
): ImageRequest {
37+
val builder = ImageRequest.Builder(context)
3138
.data(imageUri)
3239
.size(Size.ORIGINAL)
33-
.transformations(RoundedCornersTransformation())
3440
.error(errorPlaceholderImage)
3541
.placeholder(errorPlaceholderImage)
36-
.build()
37-
return imageRequest
42+
if (!animated) {
43+
builder.transformations(RoundedCornersTransformation())
44+
}
45+
if (authHeader != null) {
46+
builder.addHeader("Authorization", authHeader)
47+
}
48+
return builder.build()
3849
}

app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ fun ImageView.loadThumbnail(url: String, user: User): io.reactivex.disposables.D
234234
requestBuilder.placeholder(LayerDrawable(layers))
235235

236236
if (url.startsWith(user.baseUrl!!) &&
237-
(url.contains("index.php/core/preview") || url.contains("/avatar/"))
237+
(url.contains("index.php/core/preview") || url.contains("/avatar/") || url.contains("remote.php/dav/"))
238238
) {
239239
requestBuilder.addHeader(
240240
"Authorization",
@@ -260,7 +260,7 @@ fun ImageView.loadImage(url: String, user: User, placeholder: Drawable? = null):
260260
.transformations(RoundedCornersTransformation(ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL, ROUNDING_PIXEL))
261261

262262
if (url.startsWith(user.baseUrl!!) &&
263-
(url.contains("index.php/core/preview") || url.contains("/avatar/"))
263+
(url.contains("index.php/core/preview") || url.contains("/avatar/") || url.contains("remote.php/dav/"))
264264
) {
265265
requestBuilder.addHeader(
266266
"Authorization",

app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ import com.nextcloud.talk.models.json.chat.ReadStatus
105105
import com.nextcloud.talk.models.json.opengraph.Reference
106106
import com.nextcloud.talk.ui.theme.ViewThemeUtils
107107
import com.nextcloud.talk.users.UserManager
108+
import com.nextcloud.talk.utils.ApiUtils
108109
import com.nextcloud.talk.utils.DateUtils
109110
import com.nextcloud.talk.utils.DisplayUtils
110111
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
@@ -814,7 +815,19 @@ class ComposeChatAdapter(
814815
val imageUri = message.imageUrl
815816
val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE]
816817
val drawableResourceId = getDrawableResourceIdForMimeType(mimetype)
817-
val loadedImage = load(imageUri, LocalContext.current, drawableResourceId)
818+
val isGif = message.shouldAutoplayGif()
819+
val authHeader = if (isGif) {
820+
ApiUtils.getCredentials(currentUser.username, currentUser.token)
821+
} else {
822+
null
823+
}
824+
val loadedImage = load(
825+
imageUri,
826+
LocalContext.current,
827+
drawableResourceId,
828+
animated = isGif,
829+
authHeader = authHeader
830+
)
818831

819832
AsyncImage(
820833
model = loadedImage,

app/src/main/java/com/nextcloud/talk/utils/CapabilitiesUtil.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,21 @@ object CapabilitiesUtil {
131131
return DEFAULT_CHAT_SIZE
132132
}
133133

134+
fun getMaxGifSize(spreedCapabilities: SpreedCapability): Long {
135+
if (spreedCapabilities.config?.containsKey("previews") == true) {
136+
val previewsConfigHashMap = spreedCapabilities.config!!["previews"]
137+
if (previewsConfigHashMap?.containsKey("max-gif-size") == true) {
138+
val maxGifSize = previewsConfigHashMap["max-gif-size"]!!.toString().toLong()
139+
return if (maxGifSize > 0) {
140+
maxGifSize
141+
} else {
142+
DEFAULT_MAX_GIF_SIZE
143+
}
144+
}
145+
}
146+
return DEFAULT_MAX_GIF_SIZE
147+
}
148+
134149
fun conversationDescriptionLength(spreedCapabilities: SpreedCapability): Int {
135150
if (spreedCapabilities.config?.containsKey("conversations") == true) {
136151
val map: Map<String, Any>? = spreedCapabilities.config!!["conversations"]
@@ -331,6 +346,7 @@ object CapabilitiesUtil {
331346

332347
private val TAG = CapabilitiesUtil::class.java.simpleName
333348
const val DEFAULT_CHAT_SIZE = 1000
349+
const val DEFAULT_MAX_GIF_SIZE = 3_145_728L
334350
const val RECORDING_CONSENT_NOT_REQUIRED = 0
335351
const val RECORDING_CONSENT_REQUIRED = 1
336352
const val RECORDING_CONSENT_DEPEND_ON_CONVERSATION = 2

0 commit comments

Comments
 (0)