From dbb812ad846848777f50573cc4f666fbe5f62c7a Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 21 Jan 2019 18:16:15 +0100 Subject: [PATCH 1/7] Message type : add data classes for all the types --- .../detail/timeline/MessageItemFactory.kt | 45 ++++++++++++------- .../{MessageItem.kt => MessageTextItem.kt} | 2 +- .../session/room/model/message/AudioInfo.kt | 11 +++++ .../session/room/model/message/FileInfo.kt | 12 +++++ .../session/room/model/message/ImageInfo.kt | 16 +++++++ .../room/model/message/LocationInfo.kt | 10 +++++ .../room/model/message/MessageAudioContent.kt | 12 +++++ .../room/model/message/MessageContent.kt | 6 +++ .../model/message/MessageDefaultContent.kt | 10 +++++ .../room/model/message/MessageEmoteContent.kt | 12 +++++ .../room/model/message/MessageFileContent.kt | 13 ++++++ .../room/model/message/MessageImageContent.kt | 12 +++++ .../model/message/MessageLocationContent.kt | 12 +++++ .../model/message/MessageNoticeContent.kt | 12 +++++ .../MessageTextContent.kt} | 12 +++-- .../room/model/{ => message}/MessageType.kt | 2 +- .../room/model/message/MessageVideoContent.kt | 12 +++++ .../room/model/message/ThumbnailInfo.kt | 12 +++++ .../session/room/model/message/VideoInfo.kt | 15 +++++++ .../api/session/room/send/EventFactory.kt | 6 +-- .../android/internal/di/MoshiProvider.kt | 21 +++++++++ 21 files changed, 238 insertions(+), 27 deletions(-) rename app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/{MessageItem.kt => MessageTextItem.kt} (98%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/{MessageContent.kt => message/MessageTextContent.kt} (50%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/{ => message}/MessageType.kt (90%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 58a7bb17e2..e77b150c01 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -7,7 +7,8 @@ import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.api.session.room.model.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.riotredesign.core.extensions.localDateTime class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatter) { @@ -17,28 +18,43 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte fun create(event: TimelineEvent, nextEvent: TimelineEvent?, callback: TimelineEventController.Callback? - ): MessageItem? { + ): MessageTextItem? { - val messageContent: MessageContent? = event.root.content.toModel() val roomMember = event.roomMember - if (messageContent == null) { - return null - } val nextRoomMember = nextEvent?.roomMember val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) - ?: false + ?: false if (addDaySeparator - || nextRoomMember != roomMember - || nextEvent?.root?.type != EventType.MESSAGE - || isNextMessageReceivedMoreThanOneHourAgo) { + || nextRoomMember != roomMember + || nextEvent?.root?.type != EventType.MESSAGE + || isNextMessageReceivedMoreThanOneHourAgo) { messagesDisplayedWithInformation.add(event.root.eventId) } + val messageContent: MessageContent = event.root.content.toModel() ?: return null + val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) + val time = timelineDateFormatter.formatMessageHour(date) + val avatarUrl = roomMember?.avatarUrl + val memberName = roomMember?.displayName ?: event.root.sender + + return when (messageContent) { + is MessageTextContent -> buildTextMessageItem(messageContent, memberName, avatarUrl, time, showInformation, callback) + else -> null + } + } + + private fun buildTextMessageItem(messageContent: MessageTextContent, + memberName: String?, + avatarUrl: String?, + time: String, + showInformation: Boolean, + callback: TimelineEventController.Callback?): MessageTextItem? { + val message = messageContent.body?.let { val spannable = SpannableStringBuilder(it) MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback { @@ -49,13 +65,12 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte Linkify.addLinks(spannable, Linkify.ALL) spannable } - val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) - return MessageItem( + return MessageTextItem( message = message, - avatarUrl = roomMember?.avatarUrl, + avatarUrl = avatarUrl, showInformation = showInformation, - time = timelineDateFormatter.formatMessageHour(date), - memberName = roomMember?.displayName ?: event.root.sender + time = time, + memberName = memberName ) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt similarity index 98% rename from app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt rename to app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt index 4aae5dd2ed..e75a96afd9 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt @@ -8,7 +8,7 @@ import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.features.home.AvatarRenderer -class MessageItem( +class MessageTextItem( val message: CharSequence? = null, val time: CharSequence? = null, val avatarUrl: String?, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt new file mode 100644 index 0000000000..32670078af --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt @@ -0,0 +1,11 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class AudioInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "size") val size: Long, + @Json(name = "duration") val duration: Int +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt new file mode 100644 index 0000000000..75c263d9ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class FileInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "size") val size: Long, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, + @Json(name = "thumbnail_url") val thumbnailUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt new file mode 100644 index 0000000000..f636c82c36 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt @@ -0,0 +1,16 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ImageInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "w") val w: Int, + @Json(name = "h") val h: Int, + @Json(name = "size") val size: Long, + @Json(name = "rotation") val rotation: Int? = null, + @Json(name = "orientation") val orientation: Int? = null, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, + @Json(name = "thumbnail_url") val thumbnailUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt new file mode 100644 index 0000000000..ee1c98877c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt @@ -0,0 +1,10 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LocationInfo( + @Json(name = "thumbnail_url") val thumbnailUrl: String, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt new file mode 100644 index 0000000000..33e17d5cb3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageAudioContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: AudioInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt new file mode 100644 index 0000000000..93c89bc287 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt @@ -0,0 +1,6 @@ +package im.vector.matrix.android.api.session.room.model.message + +interface MessageContent { + val type: String + val body: String +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt new file mode 100644 index 0000000000..7f6dc34c35 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt @@ -0,0 +1,10 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageDefaultContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt new file mode 100644 index 0000000000..297a01624a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageEmoteContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt new file mode 100644 index 0000000000..9e542569e1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt @@ -0,0 +1,13 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageFileContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "filename") val filename: String, + @Json(name = "info") val info: FileInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt new file mode 100644 index 0000000000..34e4bc4cdb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageImageContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: ImageInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt new file mode 100644 index 0000000000..d45fd21b8e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageLocationContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "geo_uri") val geoUri: String, + @Json(name = "info") val info: LocationInfo +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt new file mode 100644 index 0000000000..eda19bcc67 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageNoticeContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt similarity index 50% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt index 49a3ad6a6a..e109250af7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt @@ -1,14 +1,12 @@ -package im.vector.matrix.android.api.session.room.model +package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class MessageContent( - - @Json(name = "msgtype") val type: String? = null, - @Json(name = "body") val body: String? = null, +data class MessageTextContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, @Json(name = "format") val format: String? = null, @Json(name = "formatted_body") val formattedBody: String? = null - -) \ No newline at end of file +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt similarity index 90% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt index 6ffebca66d..ab3e7f7544 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt @@ -1,4 +1,4 @@ -package im.vector.matrix.android.api.session.room.model +package im.vector.matrix.android.api.session.room.model.message object MessageType { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt new file mode 100644 index 0000000000..c817c13b13 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageVideoContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: VideoInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt new file mode 100644 index 0000000000..50084b5d78 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ThumbnailInfo( + @Json(name = "w") val width: Int, + @Json(name = "h") val height: Int, + @Json(name = "size") val size: Long, + @Json(name = "mimetype") val mimeType: String +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt new file mode 100644 index 0000000000..6ca32e9969 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt @@ -0,0 +1,15 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VideoInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "w") val w: Int, + @Json(name = "h") val h: Int, + @Json(name = "size") val size: Long, + @Json(name = "duration") val duration: Int, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, + @Json(name = "thumbnail_url") val thumbnailUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt index 2dafe5036b..1bdaf5d60e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt @@ -4,8 +4,8 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.model.MessageContent -import im.vector.matrix.android.api.session.room.model.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.internal.di.MoshiProvider internal class EventFactory(private val credentials: Credentials) { @@ -13,7 +13,7 @@ internal class EventFactory(private val credentials: Credentials) { private val moshi = MoshiProvider.providesMoshi() fun createTextEvent(roomId: String, text: String): Event { - val content = MessageContent(type = MessageType.MSGTYPE_TEXT, body = text) + val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text) return Event( roomId = roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index 1cabe215f0..984b90bddc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -1,6 +1,17 @@ package im.vector.matrix.android.internal.di import com.squareup.moshi.Moshi +import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent +import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent +import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageLocationContent +import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.UserAccountData @@ -14,6 +25,16 @@ object MoshiProvider { .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) ) + .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) + .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) + .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) + .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE) + .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO) + .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE) + .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) + .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) + .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) + ) .build() fun providesMoshi(): Moshi { From 1d400180bccfeec1798d8fb281e4bbc22779e553 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 22 Jan 2019 18:43:15 +0100 Subject: [PATCH 2/7] Timeline : start to handle media images/gif. Still a lot to do, but it's a first step. --- .../features/home/AvatarRenderer.kt | 5 +- .../room/detail/timeline/AbsMessageItem.kt | 33 ++++++++ .../room/detail/timeline/MessageImageItem.kt | 25 ++++++ .../detail/timeline/MessageInformationData.kt | 8 ++ .../detail/timeline/MessageItemFactory.kt | 25 +++--- .../room/detail/timeline/MessageTextItem.kt | 29 ++----- .../features/media/MessageImageRenderer.kt | 79 +++++++++++++++++++ .../item_timeline_event_image_message.xml | 64 +++++++++++++++ ...l => item_timeline_event_text_message.xml} | 0 .../api/session/content/ContentUrlResolver.kt | 31 ++++++++ .../session/room/model/message/ImageInfo.kt | 6 +- .../parsing/RuntimeJsonAdapterFactory.java | 10 +-- 12 files changed, 266 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt create mode 100644 app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt create mode 100644 app/src/main/res/layout/item_timeline_event_image_message.xml rename app/src/main/res/layout/{item_timeline_event_message.xml => item_timeline_event_text_message.xml} (100%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt index 1e6b27d36c..340912115e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt @@ -1,9 +1,10 @@ package im.vector.riotredesign.features.home -import androidx.core.content.ContextCompat import android.widget.ImageView +import androidx.core.content.ContextCompat import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.request.RequestOptions +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R @@ -27,7 +28,7 @@ object AvatarRenderer { if (name.isNullOrEmpty()) { return } - val resolvedUrl = avatarUrl?.replace(MXC_PREFIX, MEDIA_URL) + val resolvedUrl = ContentUrlResolver.resolve(avatarUrl) val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal) val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt new file mode 100644 index 0000000000..90a2670528 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt @@ -0,0 +1,33 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.LayoutRes +import im.vector.riotredesign.core.epoxy.KotlinModel +import im.vector.riotredesign.features.home.AvatarRenderer + +abstract class AbsMessageItem(private val informationData: MessageInformationData, + @LayoutRes layoutRes: Int +) : KotlinModel(layoutRes) { + + protected abstract val avatarImageView: ImageView + protected abstract val memberNameView: TextView + protected abstract val timeView: TextView + + override fun bind() { + if (informationData.showInformation) { + avatarImageView.visibility = View.VISIBLE + memberNameView.visibility = View.VISIBLE + timeView.visibility = View.VISIBLE + timeView.text = informationData.time + memberNameView.text = informationData.memberName + AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView) + } else { + avatarImageView.visibility = View.GONE + memberNameView.visibility = View.GONE + timeView.visibility = View.GONE + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt new file mode 100644 index 0000000000..bc7368f611 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt @@ -0,0 +1,25 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +import android.widget.ImageView +import android.widget.TextView +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.riotredesign.R +import im.vector.riotredesign.features.media.MessageImageRenderer + +class MessageImageItem( + private val messageContent: MessageImageContent, + informationData: MessageInformationData +) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) { + + override val avatarImageView by bind(R.id.messageAvatarImageView) + override val memberNameView by bind(R.id.messageMemberNameView) + override val timeView by bind(R.id.messageTimeView) + private val imageView by bind(R.id.messageImageView) + + override fun bind() { + super.bind() + MessageImageRenderer.render(messageContent, imageView) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt new file mode 100644 index 0000000000..09a1ede2cc --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt @@ -0,0 +1,8 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +data class MessageInformationData( + val time: CharSequence? = null, + val avatarUrl: String?, + val memberName: CharSequence? = null, + val showInformation: Boolean = true +) \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index e77b150c01..8168be1c28 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -8,6 +8,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.riotredesign.core.extensions.localDateTime @@ -18,7 +19,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte fun create(event: TimelineEvent, nextEvent: TimelineEvent?, callback: TimelineEventController.Callback? - ): MessageTextItem? { + ): AbsMessageItem? { val roomMember = event.roomMember val nextRoomMember = nextEvent?.roomMember @@ -41,21 +42,24 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte val time = timelineDateFormatter.formatMessageHour(date) val avatarUrl = roomMember?.avatarUrl val memberName = roomMember?.displayName ?: event.root.sender + val informationData = MessageInformationData(time, avatarUrl, memberName, showInformation) return when (messageContent) { - is MessageTextContent -> buildTextMessageItem(messageContent, memberName, avatarUrl, time, showInformation, callback) - else -> null + is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback) + is MessageImageContent -> buildImageMessageItem(messageContent, informationData) + else -> null } } + private fun buildImageMessageItem(messageContent: MessageImageContent, informationData: MessageInformationData): MessageImageItem? { + return MessageImageItem(messageContent, informationData) + } + private fun buildTextMessageItem(messageContent: MessageTextContent, - memberName: String?, - avatarUrl: String?, - time: String, - showInformation: Boolean, + informationData: MessageInformationData, callback: TimelineEventController.Callback?): MessageTextItem? { - val message = messageContent.body?.let { + val message = messageContent.body.let { val spannable = SpannableStringBuilder(it) MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback { override fun onUrlClicked(url: String) { @@ -67,10 +71,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte } return MessageTextItem( message = message, - avatarUrl = avatarUrl, - showInformation = showInformation, - time = time, - memberName = memberName + informationData = informationData ) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt index e75a96afd9..5cc0de5095 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt @@ -1,40 +1,23 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import android.view.View import android.widget.ImageView import android.widget.TextView import im.vector.matrix.android.api.permalinks.MatrixLinkify import im.vector.riotredesign.R -import im.vector.riotredesign.core.epoxy.KotlinModel -import im.vector.riotredesign.features.home.AvatarRenderer class MessageTextItem( val message: CharSequence? = null, - val time: CharSequence? = null, - val avatarUrl: String?, - val memberName: CharSequence? = null, - val showInformation: Boolean = true -) : KotlinModel(R.layout.item_timeline_event_message) { + informationData: MessageInformationData +) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_message) { - private val avatarImageView by bind(R.id.messageAvatarImageView) - private val memberNameView by bind(R.id.messageMemberNameView) - private val timeView by bind(R.id.messageTimeView) + override val avatarImageView by bind(R.id.messageAvatarImageView) + override val memberNameView by bind(R.id.messageMemberNameView) + override val timeView by bind(R.id.messageTimeView) private val messageView by bind(R.id.messageTextView) override fun bind() { + super.bind() messageView.text = message MatrixLinkify.addLinkMovementMethod(messageView) - if (showInformation) { - avatarImageView.visibility = View.VISIBLE - memberNameView.visibility = View.VISIBLE - timeView.visibility = View.VISIBLE - timeView.text = time - memberNameView.text = memberName - AvatarRenderer.render(avatarUrl, memberName?.toString(), avatarImageView) - } else { - avatarImageView.visibility = View.GONE - memberNameView.visibility = View.GONE - timeView.visibility = View.GONE - } } } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt new file mode 100644 index 0000000000..87eb6abc1c --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt @@ -0,0 +1,79 @@ +package im.vector.riotredesign.features.media + +import android.content.Context +import android.graphics.Point +import android.media.ExifInterface +import android.view.WindowManager +import android.widget.ImageView +import im.vector.matrix.android.api.session.content.ContentUrlResolver +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.riotredesign.core.glide.GlideApp + +object MessageImageRenderer { + + fun render(messageContent: MessageImageContent, imageView: ImageView) { + val (maxImageWidth, maxImageHeight) = computeMaxSize(imageView.context) + val imageInfo = messageContent.info + val rotationAngle = imageInfo.rotation ?: 0 + val orientation = imageInfo.orientation ?: ExifInterface.ORIENTATION_NORMAL + var width = imageInfo.width + var height = imageInfo.height + + var finalHeight = -1 + var finalWidth = -1 + + // if the image size is known + // compute the expected height + if (width > 0 && height > 0) { + // swap width and height if the image is side oriented + if (rotationAngle == 90 || rotationAngle == 270) { + val tmp = width + width = height + height = tmp + } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { + val tmp = width + width = height + height = tmp + } + finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight) + finalWidth = finalHeight * width / height + } + // ensure that some values are properly initialized + if (finalHeight < 0) { + finalHeight = maxImageHeight + } + if (finalWidth < 0) { + finalWidth = maxImageWidth + } + imageView.layoutParams.height = finalHeight + imageView.layoutParams.width = finalWidth + + val resolvedUrl = ContentUrlResolver.resolve(messageContent.url) ?: return + GlideApp + .with(imageView) + .load(resolvedUrl) + .override(finalWidth, finalHeight) + .thumbnail(0.3f) + .into(imageView) + } + + private fun computeMaxSize(context: Context): Pair { + val size = Point(0, 0) + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + wm.defaultDisplay.getSize(size) + val screenWidth = size.x + val screenHeight = size.y + val maxImageWidth: Int + val maxImageHeight: Int + // landscape / portrait + if (screenWidth < screenHeight) { + maxImageWidth = Math.round(screenWidth * 0.6f) + maxImageHeight = Math.round(screenHeight * 0.4f) + } else { + maxImageWidth = Math.round(screenWidth * 0.4f) + maxImageHeight = Math.round(screenHeight * 0.6f) + } + return Pair(maxImageWidth, maxImageHeight) + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_timeline_event_image_message.xml b/app/src/main/res/layout/item_timeline_event_image_message.xml new file mode 100644 index 0000000000..a8cca347f7 --- /dev/null +++ b/app/src/main/res/layout/item_timeline_event_image_message.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_timeline_event_message.xml b/app/src/main/res/layout/item_timeline_event_text_message.xml similarity index 100% rename from app/src/main/res/layout/item_timeline_event_message.xml rename to app/src/main/res/layout/item_timeline_event_text_message.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt new file mode 100644 index 0000000000..a8f116fbd0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt @@ -0,0 +1,31 @@ +package im.vector.matrix.android.api.session.content + +object ContentUrlResolver { + + private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" + private const val MEDIA_URL = "https://matrix.org/_matrix/media/v1/download/" + + /** + * Get the actual URL for accessing the full-size image of a Matrix media content URI. + * + * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). + * @return the URL to access the described resource, or null if the url is invalid. + */ + fun resolve(contentUrl: String?): String? { + if (contentUrl.isValidMatrixContentUrl()) { + return contentUrl?.replace(MATRIX_CONTENT_URI_SCHEME, MEDIA_URL) + } + return null + } + + /** + * Check whether an url is a valid matrix content url. + * + * @param contentUrl the content URL (in the form of "mxc://..."). + * @return true if contentUrl is valid. + */ + private fun String?.isValidMatrixContentUrl(): Boolean { + return !this.isNullOrEmpty() && startsWith(MATRIX_CONTENT_URI_SCHEME) + } + +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt index f636c82c36..8c58f6bbc5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt @@ -6,9 +6,9 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ImageInfo( @Json(name = "mimetype") val mimeType: String, - @Json(name = "w") val w: Int, - @Json(name = "h") val h: Int, - @Json(name = "size") val size: Long, + @Json(name = "w") val width: Int, + @Json(name = "h") val height: Int, + @Json(name = "size") val size: Int, @Json(name = "rotation") val rotation: Int? = null, @Json(name = "orientation") val orientation: Int? = null, @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java index 369efdde68..8ebad45328 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java @@ -129,16 +129,8 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { Object jsonValue = reader.readJsonValue(); Map jsonObject = (Map) jsonValue; Object label = jsonObject.get(labelKey); - if (label == null) { - throw new JsonDataException("Missing label for " + labelKey); - } if (!(label instanceof String)) { - throw new JsonDataException("Label for '" - + labelKey - + "' must be a string but was " - + label - + ", a " - + label.getClass()); + return null; } JsonAdapter adapter = labelToAdapter.get(label); if (adapter == null) { From cc4c1cf308f6db61d3ffe2d95aff8dd2afe95596 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Jan 2019 16:25:24 +0100 Subject: [PATCH 3/7] Content : make content url resolution aware of homeserver url --- .../features/home/AvatarRenderer.kt | 7 +- .../features/media/MessageImageRenderer.kt | 12 +++- .../im/vector/matrix/android/api/Matrix.kt | 2 +- .../matrix/android/api/session/Session.kt | 3 + .../api/session/content/ContentUrlResolver.kt | 50 +++++++++----- .../internal/session/DefaultSession.kt | 8 ++- .../android/internal/session/SessionModule.kt | 6 ++ .../content/DefaultContentUrlResolver.kt | 69 +++++++++++++++++++ 8 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt index 340912115e..68dc1cfb70 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt @@ -4,16 +4,13 @@ import android.widget.ImageView import androidx.core.content.ContextCompat import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.request.RequestOptions -import im.vector.matrix.android.api.session.content.ContentUrlResolver +import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.firstCharAsString import im.vector.riotredesign.core.glide.GlideApp -private const val MEDIA_URL = "https://matrix.org/_matrix/media/v1/download/" -private const val MXC_PREFIX = "mxc://" - object AvatarRenderer { fun render(roomMember: RoomMember, imageView: ImageView) { @@ -28,7 +25,7 @@ object AvatarRenderer { if (name.isNullOrEmpty()) { return } - val resolvedUrl = ContentUrlResolver.resolve(avatarUrl) + val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl) val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal) val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor) diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt index 87eb6abc1c..7b437ef922 100644 --- a/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt @@ -5,6 +5,7 @@ import android.graphics.Point import android.media.ExifInterface import android.view.WindowManager import android.widget.ImageView +import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.riotredesign.core.glide.GlideApp @@ -48,12 +49,17 @@ object MessageImageRenderer { imageView.layoutParams.height = finalHeight imageView.layoutParams.width = finalWidth - val resolvedUrl = ContentUrlResolver.resolve(messageContent.url) ?: return + val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver() + val resolvedUrl = contentUrlResolver.resolveThumbnail( + messageContent.url, + finalWidth, + finalHeight, + ContentUrlResolver.ThumbnailMethod.SCALE + ) ?: return + GlideApp .with(imageView) .load(resolvedUrl) - .override(finalWidth, finalHeight) - .thumbnail(0.3f) .into(imageView) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 5326a7960a..29d1292be5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -1,7 +1,7 @@ package im.vector.matrix.android.api -import androidx.lifecycle.ProcessLifecycleOwner import android.content.Context +import androidx.lifecycle.ProcessLifecycleOwner import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.session.Session diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 7157e764cc..00205e0099 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -2,6 +2,7 @@ package im.vector.matrix.android.api.session import androidx.annotation.MainThread import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.room.RoomService @@ -15,6 +16,8 @@ interface Session : RoomService, GroupService { @MainThread fun close() + fun contentUrlResolver(): ContentUrlResolver + fun addListener(listener: Listener) fun removeListener(listener: Listener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt index a8f116fbd0..3274d45f6a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt @@ -1,9 +1,32 @@ +/* + * + * * Copyright 2019 New Vector Ltd + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + package im.vector.matrix.android.api.session.content -object ContentUrlResolver { +/** + * This interface defines methods for accessing content from the current session. + */ +interface ContentUrlResolver { - private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" - private const val MEDIA_URL = "https://matrix.org/_matrix/media/v1/download/" + enum class ThumbnailMethod(val value: String) { + CROP("crop"), + SCALE("scale") + } /** * Get the actual URL for accessing the full-size image of a Matrix media content URI. @@ -11,21 +34,16 @@ object ContentUrlResolver { * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). * @return the URL to access the described resource, or null if the url is invalid. */ - fun resolve(contentUrl: String?): String? { - if (contentUrl.isValidMatrixContentUrl()) { - return contentUrl?.replace(MATRIX_CONTENT_URI_SCHEME, MEDIA_URL) - } - return null - } + fun resolveFullSize(contentUrl: String?): String? /** - * Check whether an url is a valid matrix content url. + * Get the actual URL for accessing the thumbnail image of a given Matrix media content URI. * - * @param contentUrl the content URL (in the form of "mxc://..."). - * @return true if contentUrl is valid. + * @param contentUrl the Matrix media content URI (in the form of "mxc://..."). + * @param width the desired width + * @param height the desired height + * @param method the desired method (METHOD_CROP or METHOD_SCALE) + * @return the URL to access the described resource, or null if the url is invalid. */ - private fun String?.isValidMatrixContentUrl(): Boolean { - return !this.isNullOrEmpty() && startsWith(MATRIX_CONTENT_URI_SCHEME) - } - + fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String? } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 5ba6e91f38..6ec8b232be 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -1,10 +1,11 @@ package im.vector.matrix.android.internal.session -import androidx.lifecycle.LiveData import android.os.Looper import androidx.annotation.MainThread +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.Group import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.model.GroupSummary @@ -35,6 +36,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi private val roomService by inject() private val groupService by inject() private val syncThread by inject() + private val contentUrlResolver by inject() private var isOpen = false @MainThread @@ -63,6 +65,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi isOpen = false } + override fun contentUrlResolver(): ContentUrlResolver { + return contentUrlResolver + } + override fun addListener(listener: Session.Listener) { sessionListeners.addListener(listener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index d36cbcbf0a..0307d4f275 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -3,9 +3,11 @@ package im.vector.matrix.android.internal.session import android.content.Context import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.DefaultRoomService @@ -78,6 +80,10 @@ internal class SessionModule(private val sessionParams: SessionParams) { SessionListeners() } + scope(DefaultSession.SCOPE) { + DefaultContentUrlResolver(sessionParams.homeServerConnectionConfig) as ContentUrlResolver + } + scope(DefaultSession.SCOPE) { val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) val groupSummaryUpdater = GroupSummaryUpdater(get()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt new file mode 100644 index 0000000000..182986e2c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2019 New Vector Ltd + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package im.vector.matrix.android.internal.session.content + +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig +import im.vector.matrix.android.api.session.content.ContentUrlResolver + + +private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" +private const val URI_PREFIX_CONTENT_API = "/_matrix/media/v1/" + +internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { + + override fun resolveFullSize(contentUrl: String?): String? { + if (contentUrl?.isValidMatrixContentUrl() == true) { + val baseUrl = homeServerConnectionConfig.homeServerUri.toString() + val prefix = URI_PREFIX_CONTENT_API + "download/" + return resolve(baseUrl, contentUrl, prefix) + } + return null + } + + override fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ContentUrlResolver.ThumbnailMethod): String? { + if (contentUrl?.isValidMatrixContentUrl() == true) { + val baseUrl = homeServerConnectionConfig.homeServerUri.toString() + val prefix = URI_PREFIX_CONTENT_API + "thumbnail/" + val params = "?width=$width&height=$height&method=${method.value}" + return resolve(baseUrl, contentUrl, prefix, params) + } + // do not allow non-mxc content URLs + return null + } + + private fun resolve(baseUrl: String, + contentUrl: String, + prefix: String, + params: String? = null): String? { + + var serverAndMediaId = contentUrl.removePrefix(MATRIX_CONTENT_URI_SCHEME) + val fragmentOffset = serverAndMediaId.indexOf("#") + var fragment = "" + if (fragmentOffset >= 0) { + fragment = serverAndMediaId.substring(fragmentOffset) + serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset) + } + return baseUrl + prefix + serverAndMediaId + (params ?: "") + fragment + } + + private fun String.isValidMatrixContentUrl(): Boolean { + return startsWith(MATRIX_CONTENT_URI_SCHEME) + } + +} \ No newline at end of file From f9ca8f35bc621774773fabdda7ec311ae5614c83 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Jan 2019 16:25:51 +0100 Subject: [PATCH 4/7] Fix MessageFileContent crash (no filename) --- .../api/session/room/model/message/MessageFileContent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt index 9e542569e1..45556b6813 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt @@ -7,7 +7,7 @@ import com.squareup.moshi.JsonClass data class MessageFileContent( @Json(name = "msgtype") override val type: String, @Json(name = "body") override val body: String, - @Json(name = "filename") val filename: String, + @Json(name = "filename") val filename: String? = null, @Json(name = "info") val info: FileInfo, @Json(name = "url") val url: String? = null ) : MessageContent \ No newline at end of file From 7c4ac7b53ac83e8e2a1c191c97c7df4ae9b30d75 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 23 Jan 2019 20:29:47 +0100 Subject: [PATCH 5/7] Media : start to refact renderer to be more generic --- .../room/detail/timeline/MessageImageItem.kt | 7 +- .../detail/timeline/MessageItemFactory.kt | 24 ++++-- .../features/media/MediaContentRenderer.kt | 85 +++++++++++++++++++ .../features/media/MessageImageRenderer.kt | 85 ------------------- .../session/room/model/message/ImageInfo.kt | 11 +-- 5 files changed, 112 insertions(+), 100 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt delete mode 100644 app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt index bc7368f611..b273cd4f4a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt @@ -2,12 +2,11 @@ package im.vector.riotredesign.features.home.room.detail.timeline import android.widget.ImageView import android.widget.TextView -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.riotredesign.R -import im.vector.riotredesign.features.media.MessageImageRenderer +import im.vector.riotredesign.features.media.MediaContentRenderer class MessageImageItem( - private val messageContent: MessageImageContent, + private val mediaData: MediaContentRenderer.Data, informationData: MessageInformationData ) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) { @@ -18,7 +17,7 @@ class MessageImageItem( override fun bind() { super.bind() - MessageImageRenderer.render(messageContent, imageView) + MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 8168be1c28..4264647801 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -11,6 +11,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.riotredesign.core.extensions.localDateTime +import im.vector.riotredesign.features.media.MediaContentRenderer class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatter) { @@ -28,12 +29,12 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte val nextDate = nextEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) - ?: false + ?: false if (addDaySeparator - || nextRoomMember != roomMember - || nextEvent?.root?.type != EventType.MESSAGE - || isNextMessageReceivedMoreThanOneHourAgo) { + || nextRoomMember != roomMember + || nextEvent?.root?.type != EventType.MESSAGE + || isNextMessageReceivedMoreThanOneHourAgo) { messagesDisplayedWithInformation.add(event.root.eventId) } @@ -51,8 +52,19 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte } } - private fun buildImageMessageItem(messageContent: MessageImageContent, informationData: MessageInformationData): MessageImageItem? { - return MessageImageItem(messageContent, informationData) + private fun buildImageMessageItem(messageContent: MessageImageContent, + informationData: MessageInformationData): MessageImageItem? { + // TODO : manage maxHeight/maxWidth + val data = MediaContentRenderer.Data( + url = messageContent.url, + height = messageContent.info.height, + maxHeight = 800, + width = messageContent.info.width, + maxWidth = 800, + rotation = messageContent.info.rotation, + orientation = messageContent.info.orientation + ) + return MessageImageItem(data, informationData) } private fun buildTextMessageItem(messageContent: MessageTextContent, diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt new file mode 100644 index 0000000000..e9deb18226 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt @@ -0,0 +1,85 @@ +package im.vector.riotredesign.features.media + +import android.media.ExifInterface +import android.widget.ImageView +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.content.ContentUrlResolver +import im.vector.riotredesign.core.glide.GlideApp + +object MediaContentRenderer { + + data class Data( + val url: String?, + val height: Int, + val maxHeight: Int, + val width: Int, + val maxWidth: Int = width, + val orientation: Int, + val rotation: Int + ) + + enum class Mode { + FULL_SIZE, + THUMBNAIL + } + + fun render(data: Data, mode: Mode, imageView: ImageView) { + val (width, height) = processSize(data, mode) + imageView.layoutParams.height = height + imageView.layoutParams.width = width + + val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver() + val resolvedUrl = when (mode) { + Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url) + Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) + } + ?: return + + GlideApp + .with(imageView) + .load(resolvedUrl) + .thumbnail(0.3f) + .into(imageView) + } + + private fun processSize(data: Data, mode: Mode): Pair { + val maxImageWidth = data.maxWidth + val maxImageHeight = data.maxHeight + val rotationAngle = data.rotation + val orientation = data.orientation + var width = data.width + var height = data.height + var finalHeight = -1 + var finalWidth = -1 + + // if the image size is known + // compute the expected height + if (width > 0 && height > 0) { + // swap width and height if the image is side oriented + if (rotationAngle == 90 || rotationAngle == 270) { + val tmp = width + width = height + height = tmp + } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { + val tmp = width + width = height + height = tmp + } + if (mode == Mode.FULL_SIZE) { + finalHeight = height + finalWidth = width + } else { + finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight) + finalWidth = finalHeight * width / height + } + } + // ensure that some values are properly initialized + if (finalHeight < 0) { + finalHeight = maxImageHeight + } + if (finalWidth < 0) { + finalWidth = maxImageWidth + } + return Pair(finalWidth, finalHeight) + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt deleted file mode 100644 index 7b437ef922..0000000000 --- a/app/src/main/java/im/vector/riotredesign/features/media/MessageImageRenderer.kt +++ /dev/null @@ -1,85 +0,0 @@ -package im.vector.riotredesign.features.media - -import android.content.Context -import android.graphics.Point -import android.media.ExifInterface -import android.view.WindowManager -import android.widget.ImageView -import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.session.content.ContentUrlResolver -import im.vector.matrix.android.api.session.room.model.message.MessageImageContent -import im.vector.riotredesign.core.glide.GlideApp - -object MessageImageRenderer { - - fun render(messageContent: MessageImageContent, imageView: ImageView) { - val (maxImageWidth, maxImageHeight) = computeMaxSize(imageView.context) - val imageInfo = messageContent.info - val rotationAngle = imageInfo.rotation ?: 0 - val orientation = imageInfo.orientation ?: ExifInterface.ORIENTATION_NORMAL - var width = imageInfo.width - var height = imageInfo.height - - var finalHeight = -1 - var finalWidth = -1 - - // if the image size is known - // compute the expected height - if (width > 0 && height > 0) { - // swap width and height if the image is side oriented - if (rotationAngle == 90 || rotationAngle == 270) { - val tmp = width - width = height - height = tmp - } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { - val tmp = width - width = height - height = tmp - } - finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight) - finalWidth = finalHeight * width / height - } - // ensure that some values are properly initialized - if (finalHeight < 0) { - finalHeight = maxImageHeight - } - if (finalWidth < 0) { - finalWidth = maxImageWidth - } - imageView.layoutParams.height = finalHeight - imageView.layoutParams.width = finalWidth - - val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver() - val resolvedUrl = contentUrlResolver.resolveThumbnail( - messageContent.url, - finalWidth, - finalHeight, - ContentUrlResolver.ThumbnailMethod.SCALE - ) ?: return - - GlideApp - .with(imageView) - .load(resolvedUrl) - .into(imageView) - } - - private fun computeMaxSize(context: Context): Pair { - val size = Point(0, 0) - val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - wm.defaultDisplay.getSize(size) - val screenWidth = size.x - val screenHeight = size.y - val maxImageWidth: Int - val maxImageHeight: Int - // landscape / portrait - if (screenWidth < screenHeight) { - maxImageWidth = Math.round(screenWidth * 0.6f) - maxImageHeight = Math.round(screenHeight * 0.4f) - } else { - maxImageWidth = Math.round(screenWidth * 0.4f) - maxImageHeight = Math.round(screenHeight * 0.6f) - } - return Pair(maxImageWidth, maxImageHeight) - } - -} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt index 8c58f6bbc5..f4c698c18b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt @@ -1,16 +1,17 @@ package im.vector.matrix.android.api.session.room.model.message +import android.media.ExifInterface import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ImageInfo( @Json(name = "mimetype") val mimeType: String, - @Json(name = "w") val width: Int, - @Json(name = "h") val height: Int, - @Json(name = "size") val size: Int, - @Json(name = "rotation") val rotation: Int? = null, - @Json(name = "orientation") val orientation: Int? = null, + @Json(name = "w") val width: Int = 0, + @Json(name = "h") val height: Int = 0, + @Json(name = "size") val size: Int = 0, + @Json(name = "rotation") val rotation: Int = 0, + @Json(name = "orientation") val orientation: Int = ExifInterface.ORIENTATION_NORMAL, @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, @Json(name = "thumbnail_url") val thumbnailUrl: String? = null ) \ No newline at end of file From 946ae32cd803b96518392f1eda13df5ac5662e03 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 24 Jan 2019 16:30:05 +0100 Subject: [PATCH 6/7] Let the app be compatible with android 16 --- app/build.gradle | 4 +++- app/src/main/java/im/vector/riotredesign/Riot.kt | 8 ++++++++ .../features/home/room/list/RoomCategoryItem.kt | 8 +++++--- matrix-sdk-android/build.gradle | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e9f9a3e985..8bfee140f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,9 @@ android { compileSdkVersion 28 defaultConfig { applicationId "im.vector.riotredesign" - minSdkVersion 21 + minSdkVersion 16 targetSdkVersion 28 + multiDexEnabled true versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -42,6 +43,7 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") + implementation 'com.android.support:multidex:1.0.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/app/src/main/java/im/vector/riotredesign/Riot.kt b/app/src/main/java/im/vector/riotredesign/Riot.kt index 052c717930..40f91e1cb8 100644 --- a/app/src/main/java/im/vector/riotredesign/Riot.kt +++ b/app/src/main/java/im/vector/riotredesign/Riot.kt @@ -1,6 +1,8 @@ package im.vector.riotredesign import android.app.Application +import android.content.Context +import androidx.multidex.MultiDex import com.jakewharton.threetenabp.AndroidThreeTen import im.vector.matrix.android.BuildConfig import im.vector.riotredesign.core.di.AppModule @@ -8,6 +10,7 @@ import org.koin.log.EmptyLogger import org.koin.standalone.StandAloneContext.startKoin import timber.log.Timber + class Riot : Application() { override fun onCreate() { @@ -19,4 +22,9 @@ class Riot : Application() { startKoin(listOf(AppModule(this).definition), logger = EmptyLogger()) } + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + MultiDex.install(this) + } + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt index ec17904654..929ba659b5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt @@ -1,8 +1,9 @@ package im.vector.riotredesign.features.home.room.list -import androidx.core.content.ContextCompat import android.view.ViewGroup import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel @@ -21,8 +22,9 @@ data class RoomCategoryItem( override fun bind() { val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white - val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes) - expandedArrowDrawable?.setTint(tintColor) + val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null) titleView.text = title rootView.setOnClickListener { listener?.invoke() } diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c7ca4b5d7a..47e0f16041 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -24,7 +24,7 @@ android { testOptions.unitTests.includeAndroidResources = true defaultConfig { - minSdkVersion 21 + minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" From 3cbdba2fe9163892471a5bfd68b3b1dc4ebbe18f Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 24 Jan 2019 18:04:55 +0100 Subject: [PATCH 7/7] Timeline media : compute max size from recyclerview size --- app/build.gradle | 2 ++ .../riotredesign/features/home/HomeModule.kt | 9 ++++-- .../detail/timeline/MessageItemFactory.kt | 19 +++++++----- .../timeline/TimelineEventController.kt | 9 +++++- .../helper/TimelineMediaSizeProvider.kt | 31 +++++++++++++++++++ 5 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt diff --git a/app/build.gradle b/app/build.gradle index 8bfee140f6..bc1957969b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,6 +49,8 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0-alpha01' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.core:core-ktx:1.0.1' + // Paging implementation 'androidx.paging:paging-runtime:2.0.0' diff --git a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 85aaa5e89b..f00e1d6548 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -10,6 +10,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFa import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import org.koin.dsl.module.module class HomeModule { @@ -21,7 +22,7 @@ class HomeModule { } single { - MessageItemFactory(get()) + MessageItemFactory(get(), get()) } single { @@ -49,7 +50,11 @@ class HomeModule { } factory { (roomId: String) -> - TimelineEventController(roomId, get(), get()) + TimelineEventController(roomId, get(), get(), get()) + } + + single { + TimelineMediaSizeProvider() } single { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 4264647801..afe4395207 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -11,9 +11,11 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageImageContent import im.vector.matrix.android.api.session.room.model.message.MessageTextContent import im.vector.riotredesign.core.extensions.localDateTime +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.media.MediaContentRenderer -class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatter) { +class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSizeProvider, + private val timelineDateFormatter: TimelineDateFormatter) { private val messagesDisplayedWithInformation = HashSet() @@ -29,12 +31,12 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte val nextDate = nextEvent?.root?.localDateTime() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) - ?: false + ?: false if (addDaySeparator - || nextRoomMember != roomMember - || nextEvent?.root?.type != EventType.MESSAGE - || isNextMessageReceivedMoreThanOneHourAgo) { + || nextRoomMember != roomMember + || nextEvent?.root?.type != EventType.MESSAGE + || isNextMessageReceivedMoreThanOneHourAgo) { messagesDisplayedWithInformation.add(event.root.eventId) } @@ -54,13 +56,14 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte private fun buildImageMessageItem(messageContent: MessageImageContent, informationData: MessageInformationData): MessageImageItem? { - // TODO : manage maxHeight/maxWidth + + val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() val data = MediaContentRenderer.Data( url = messageContent.url, height = messageContent.info.height, - maxHeight = 800, + maxHeight = maxHeight, width = messageContent.info.width, - maxWidth = 800, + maxWidth = maxWidth, rotation = messageContent.info.rotation, orientation = messageContent.info.orientation ) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index caede52053..1894731a22 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -1,5 +1,6 @@ package im.vector.riotredesign.features.home.room.detail.timeline +import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyModel import im.vector.matrix.android.api.session.events.model.EventType @@ -7,11 +8,13 @@ import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController class TimelineEventController(private val roomId: String, private val dateFormatter: TimelineDateFormatter, - private val timelineItemFactory: TimelineItemFactory + private val timelineItemFactory: TimelineItemFactory, + private val timelineMediaSizeProvider: TimelineMediaSizeProvider ) : PagedListEpoxyController( EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler() @@ -36,6 +39,10 @@ class TimelineEventController(private val roomId: String, } } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + timelineMediaSizeProvider.recyclerView = recyclerView + } override fun buildItemModels(currentPosition: Int, items: List): List> { if (items.isNullOrEmpty()) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt new file mode 100644 index 0000000000..a4251b6f29 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt @@ -0,0 +1,31 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.helper + +import androidx.recyclerview.widget.RecyclerView + +class TimelineMediaSizeProvider { + + lateinit var recyclerView: RecyclerView + private var cachedSize: Pair? = null + + fun getMaxSize(): Pair { + return cachedSize ?: computeMaxSize().also { cachedSize = it } + } + + private fun computeMaxSize(): Pair { + val width = recyclerView.width + val height = recyclerView.height + val maxImageWidth: Int + val maxImageHeight: Int + // landscape / portrait + if (width < height) { + maxImageWidth = Math.round(width * 0.7f) + maxImageHeight = Math.round(height * 0.5f) + } else { + maxImageWidth = Math.round(width * 0.5f) + maxImageHeight = Math.round(height * 0.7f) + } + return Pair(maxImageWidth, maxImageHeight) + } + + +} \ No newline at end of file