From c7c3a57c9349c1f2bc5ee54e84f53e6bc0359670 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 18 Jan 2019 19:40:20 +0100 Subject: [PATCH 01/10] Timeline hot fix: when room member is null, use sender to render name. Still have to figure it out why RoomMember can be null. --- .../home/room/detail/timeline/MessageItemFactory.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 c1917b5a..58a7bb17 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 @@ -21,7 +21,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte val messageContent: MessageContent? = event.root.content.toModel() val roomMember = event.roomMember - if (messageContent == null || roomMember == null) { + if (messageContent == null) { return null } val nextRoomMember = nextEvent?.roomMember @@ -34,7 +34,7 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte if (addDaySeparator || nextRoomMember != roomMember - || nextEvent.root.type != EventType.MESSAGE + || nextEvent?.root?.type != EventType.MESSAGE || isNextMessageReceivedMoreThanOneHourAgo) { messagesDisplayedWithInformation.add(event.root.eventId) } @@ -52,10 +52,10 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) return MessageItem( message = message, - avatarUrl = roomMember.avatarUrl, + avatarUrl = roomMember?.avatarUrl, showInformation = showInformation, time = timelineDateFormatter.formatMessageHour(date), - memberName = roomMember.displayName ?: event.root.sender + memberName = roomMember?.displayName ?: event.root.sender ) } From dbb812ad846848777f50573cc4f666fbe5f62c7a Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 21 Jan 2019 18:16:15 +0100 Subject: [PATCH 02/10] 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 58a7bb17..e77b150c 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 4aae5dd2..e75a96af 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 00000000..32670078 --- /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 00000000..75c263d9 --- /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 00000000..f636c82c --- /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 00000000..ee1c9887 --- /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 00000000..33e17d5c --- /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 00000000..93c89bc2 --- /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 00000000..7f6dc34c --- /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 00000000..297a0162 --- /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 00000000..9e542569 --- /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 00000000..34e4bc4c --- /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 00000000..d45fd21b --- /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 00000000..eda19bcc --- /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 49a3ad6a..e109250a 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 6ffebca6..ab3e7f75 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 00000000..c817c13b --- /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 00000000..50084b5d --- /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 00000000..6ca32e99 --- /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 2dafe503..1bdaf5d6 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 1cabe215..984b90bd 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 32b29c47e7ebc031aa9cfb29f47ec76276baef40 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 21 Jan 2019 18:31:19 +0100 Subject: [PATCH 03/10] Hot fix : makes room topic optional as it may be null. --- .../home/room/detail/timeline/RoomTopicItemFactory.kt | 7 +++---- .../android/api/session/room/model/RoomTopicContent.kt | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/RoomTopicItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/RoomTopicItemFactory.kt index a8f0647c..3d67eb59 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/RoomTopicItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/RoomTopicItemFactory.kt @@ -1,6 +1,5 @@ package im.vector.riotredesign.features.home.room.detail.timeline -import android.text.TextUtils 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.RoomTopicContent @@ -16,10 +15,10 @@ class RoomTopicItemFactory(private val stringProvider: StringProvider) { if (content == null || roomMember == null) { return null } - val text = if (!TextUtils.isEmpty(content.topic)) { - stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic) - } else { + val text = if (content.topic.isNullOrEmpty()) { stringProvider.getString(R.string.notice_room_topic_removed, roomMember.displayName) + } else { + stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic) } return NoticeItem(text, roomMember.avatarUrl, roomMember.displayName) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomTopicContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomTopicContent.kt index 5967e934..16b18de9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomTopicContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomTopicContent.kt @@ -5,5 +5,5 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomTopicContent( - @Json(name = "topic") val topic: String + @Json(name = "topic") val topic: String? = null ) \ No newline at end of file From 85608b04d19a9b7526230c88bd3afd605b80ea56 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 21 Jan 2019 19:39:45 +0100 Subject: [PATCH 04/10] Room list : quickly branch filter room name field --- .../riotredesign/core/extensions/EditText.kt | 44 ++++++++++++++++++ .../riotredesign/core/extensions/View.kt | 10 ++++ .../home/room/list/RoomListActions.kt | 2 + .../home/room/list/RoomListFragment.kt | 21 +++++++++ .../home/room/list/RoomListViewModel.kt | 35 +++++++++++--- .../main/res/drawable-hdpi/ic_clear_white.png | Bin 0 -> 276 bytes .../main/res/drawable-mdpi/ic_clear_white.png | Bin 0 -> 209 bytes .../res/drawable-xhdpi/ic_clear_white.png | Bin 0 -> 329 bytes .../res/drawable-xxhdpi/ic_clear_white.png | Bin 0 -> 462 bytes .../res/drawable-xxxhdpi/ic_clear_white.png | Bin 0 -> 601 bytes .../main/res/layout/fragment_room_list.xml | 7 +-- 11 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/im/vector/riotredesign/core/extensions/EditText.kt create mode 100644 app/src/main/java/im/vector/riotredesign/core/extensions/View.kt create mode 100644 app/src/main/res/drawable-hdpi/ic_clear_white.png create mode 100644 app/src/main/res/drawable-mdpi/ic_clear_white.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_clear_white.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_clear_white.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_clear_white.png diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/EditText.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/EditText.kt new file mode 100644 index 00000000..bace1321 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/EditText.kt @@ -0,0 +1,44 @@ +package im.vector.riotredesign.core.extensions + +import android.text.Editable +import android.text.InputType +import android.text.TextWatcher +import android.view.MotionEvent +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import im.vector.riotredesign.R + +fun EditText.setupAsSearch() { + addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(editable: Editable?) { + val clearIcon = if (editable?.isNotEmpty() == true) R.drawable.ic_clear_white else 0 + setCompoundDrawablesWithIntrinsicBounds(0, 0, clearIcon, 0) + } + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit + }) + + maxLines = 1 + inputType = InputType.TYPE_CLASS_TEXT + imeOptions = EditorInfo.IME_ACTION_SEARCH + setOnEditorActionListener { _, actionId, event -> + var consumed = false + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + hideKeyboard() + consumed = true + } + consumed + } + + setOnTouchListener(View.OnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_UP) { + if (event.rawX >= (this.right - this.compoundPaddingRight)) { + text = null + return@OnTouchListener true + } + } + return@OnTouchListener false + }) +} diff --git a/app/src/main/java/im/vector/riotredesign/core/extensions/View.kt b/app/src/main/java/im/vector/riotredesign/core/extensions/View.kt new file mode 100644 index 00000000..80217290 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/core/extensions/View.kt @@ -0,0 +1,10 @@ +package im.vector.riotredesign.core.extensions + +import android.content.Context +import android.view.View +import android.view.inputmethod.InputMethodManager + +fun View.hideKeyboard() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.hideSoftInputFromWindow(windowToken, 0) +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt index 17b8ec9b..bfed4431 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListActions.kt @@ -8,4 +8,6 @@ sealed class RoomListActions { object RoomDisplayed : RoomListActions() + data class FilterRooms(val roomName: CharSequence? = null) : RoomListActions() + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt index f66ee630..43a2ef97 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt @@ -1,9 +1,12 @@ package im.vector.riotredesign.features.home.room.list import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Incomplete import com.airbnb.mvrx.Success @@ -11,6 +14,8 @@ import com.airbnb.mvrx.activityViewModel import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R +import im.vector.riotredesign.core.extensions.hideKeyboard +import im.vector.riotredesign.core.extensions.setupAsSearch import im.vector.riotredesign.core.platform.RiotFragment import im.vector.riotredesign.core.platform.StateView import im.vector.riotredesign.features.home.HomeNavigator @@ -38,6 +43,7 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { roomController = RoomSummaryController(this) stateView.contentView = epoxyRecyclerView epoxyRecyclerView.setController(roomController) + setupFilterView() homeViewModel.subscribe { renderState(it) } } @@ -70,6 +76,21 @@ class RoomListFragment : RiotFragment(), RoomSummaryController.Callback { stateView.state = StateView.State.Error(message) } + private fun setupFilterView() { + filterRoomView.setupAsSearch() + filterRoomView.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable?) = Unit + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + homeViewModel.accept(RoomListActions.FilterRooms(s)) + } + }) + } + + // RoomSummaryController.Callback ************************************************************** + override fun onRoomSelected(room: RoomSummary) { homeViewModel.accept(RoomListActions.SelectRoom(room)) homeNavigator.openRoomDetail(room.roomId, null) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt index c39317aa..cbf2a489 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListViewModel.kt @@ -3,6 +3,7 @@ package im.vector.riotredesign.features.home.room.list import arrow.core.Option import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import com.jakewharton.rxrelay2.BehaviorRelay import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.group.model.GroupSummary @@ -12,10 +13,12 @@ import im.vector.riotredesign.core.platform.RiotViewModel import im.vector.riotredesign.features.home.group.SelectedGroupHolder import im.vector.riotredesign.features.home.room.VisibleRoomHolder import io.reactivex.Observable -import io.reactivex.functions.BiFunction +import io.reactivex.functions.Function3 import io.reactivex.rxkotlin.subscribeBy import org.koin.android.ext.android.get +typealias RoomListFilterName = CharSequence + class RoomListViewModel(initialState: RoomListViewState, private val session: Session, private val selectedGroupHolder: SelectedGroupHolder, @@ -35,6 +38,9 @@ class RoomListViewModel(initialState: RoomListViewState, } } + + private val roomListFilter = BehaviorRelay.createDefault>(Option.empty()) + init { observeRoomSummaries() observeVisibleRoom() @@ -42,7 +48,8 @@ class RoomListViewModel(initialState: RoomListViewState, fun accept(action: RoomListActions) { when (action) { - is RoomListActions.SelectRoom -> handleSelectRoom(action) + is RoomListActions.SelectRoom -> handleSelectRoom(action) + is RoomListActions.FilterRooms -> handleFilterRooms(action) } } @@ -54,6 +61,11 @@ class RoomListViewModel(initialState: RoomListViewState, } } + private fun handleFilterRooms(action: RoomListActions.FilterRooms) { + val optionalFilter = Option.fromNullable(action.roomName) + roomListFilter.accept(optionalFilter) + } + private fun observeVisibleRoom() { visibleRoomHolder.visibleRoom() .subscribeBy { @@ -63,13 +75,22 @@ class RoomListViewModel(initialState: RoomListViewState, } private fun observeRoomSummaries() { - Observable.combineLatest, Option, RoomSummaries>( + Observable.combineLatest, Option, Option, RoomSummaries>( session.rx().liveRoomSummaries(), selectedGroupHolder.selectedGroup(), - BiFunction { rooms, selectedGroupOption -> - val selectedGroup = selectedGroupOption.orNull() + roomListFilter, + Function3 { rooms, selectedGroupOption, filterRoomOption -> + val filterRoom = filterRoomOption.orNull() + val filteredRooms = rooms.filter { + if (filterRoom.isNullOrBlank()) { + true + } else { + it.displayName.contains(other = filterRoom, ignoreCase = true) + } + } - val filteredDirectRooms = rooms + val selectedGroup = selectedGroupOption.orNull() + val filteredDirectRooms = filteredRooms .filter { it.isDirect } .filter { if (selectedGroup == null) { @@ -81,7 +102,7 @@ class RoomListViewModel(initialState: RoomListViewState, } } - val filteredGroupRooms = rooms + val filteredGroupRooms = filteredRooms .filter { !it.isDirect } .filter { selectedGroup?.roomIds?.contains(it.roomId) ?: true diff --git a/app/src/main/res/drawable-hdpi/ic_clear_white.png b/app/src/main/res/drawable-hdpi/ic_clear_white.png new file mode 100644 index 0000000000000000000000000000000000000000..fc5a294ed5ff9e16a4cda3e88eb1beb1a078de96 GIT binary patch literal 276 zcmV+v0qg#WP)lUcPU>TM!j6?-=93f~FWKwOUldq_*wN)Ly`le0JY$7NT;DXh4!tA}9z`v#CR% zB}=dr62O?+V=qBb7^Dt`z-yLpjo)buS{O?x8pBlTU$a4D*sG(2vP@MU2GrG(l}?jA zV^jY-+RQZrfh2W?MzzEs*9-(gDlm*X~)of^&6pdjFSw?%K! z@qhbE1hZamlr#`9+%3%F#P(hP{KY*!T1%Jg>zUP*$ZCBw$b`XA#641#Dei!O#RP*t zC*DVOI;2bP0 Hl+XkK)aOdC literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_clear_white.png b/app/src/main/res/drawable-xhdpi/ic_clear_white.png new file mode 100644 index 0000000000000000000000000000000000000000..0b00a33a728a56902219f949ade2a25821dc9276 GIT binary patch literal 329 zcmV-P0k-~$P)-Hp^2CxGti$Q;Z^2qD!Xpyz3;wxcLE3?fB*sr@K1nC z*e>jz5XOc*Z)Z>d@xeF$SSuJ|w`az+!X}>Cv)%zD zqB#87TPTGXYKJemMu!3fEPQDlID>$OM-;LYVyqD!Rf)$p5R~x9R^kXj3yvk`%46&{AjIcz3X)jh<-8f+~U23w9b*s3vFoS36UO{WDAKmY** b5CFaJELQUamZcBV00000NkvXXu0mjf$2@;u literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_clear_white.png b/app/src/main/res/drawable-xxhdpi/ic_clear_white.png new file mode 100644 index 0000000000000000000000000000000000000000..717c7b59190385c7c55c515beb1f45439eee6d19 GIT binary patch literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2V4Ujd;uunK>+P+Bd4~*mjurad zJya|Ev~7y-*f|&&Sp*!w1Pg@9W~-Y2 z$l$2LoeQ@`j%#ka*^oA;`|mn^!Am{Ham70-6e}Ok;K=Mu=3X*a>V=q}$tB%6s+Zz8 zza;iwFHc_LZc+bw0gKc+4z49ab6%~#p&ZB>#L@R$Q>;^@?Qt#tEMcp^lYgu<`WP@r z%>3mvkB1g(y?Vtwgyu}!IzxRD&+^iZuNqa3cCL8V-o)EiZl-a(Gpl@0(-bE4P4ZuO zf~4lWXp7Vgl9F+C-}vOUNAu#Wo%^~}GLL9RFHSC#DZRW;#IucAz~1oR63b`TOU_Kc zHzCwEPxFHD)58-uHKqO9GCef{d>_ta($w~QqTs;v4uu9V!2zMFy5$om-}hO)V5KH7o)|n`{an^LB{Ts5sZG74 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_clear_white.png b/app/src/main/res/drawable-xxxhdpi/ic_clear_white.png new file mode 100644 index 0000000000000000000000000000000000000000..9ef2d8f9fbe5914285f0776e412c4d1be25d17aa GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U{dgOaSW-r_4ZC+UbBIS%f%dp zw?z$e53|kETYi>JV@2nbevLzo>E~~VXZ|}qXB+3Y!^v<76Oj13%SGvlPQ2{H$e#~6cAgG*tq85ta5{3<=kvDL;Vg;XKfh-eF}%ooYNfDb ziu|W=>C$9Aj-#jh|A{T>Q3$N-d(Pm>GV@chEvL4k$JF`7x*DPj^B4ZP(_L8W$a3`yKlehY9-ozqI*i9SVs4$z`47%+TcfQ!9FsyHdo|^;^_y zSejygX3hRoEueEcd`?#d`@wBBS2s?X;d`RHW&PO55cgztX3> zd(`%ICm3=(GtsJ8yS9db;X%BB{2_)9rc>Ar8OqjX?doy7rE_fQy0<0gINm+A=3_aX zcj8U#)h#Y7mexeK%sRE*WyRW>rI8_-4KlYo_A)!YUC(+_dCP~W2_{b3?^8}HZTaFC z_C#luNyDW(=N7bGxWZl7#j$p(e*1-_pFtdJr|CBf6b3K8sTX=m|I7=94RSAw3~wvl zY1%MvUTN7+o{H876$!x}KQG>Inx|+X7bzA746p{#gMTmVyD(YUa@(d)$=aN_;*enh fFd`M6eq#S;eQNDygJ~+j2xjnf^>bP0l+XkKX6gf2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_room_list.xml b/app/src/main/res/layout/fragment_room_list.xml index 96c1eead..89af7058 100644 --- a/app/src/main/res/layout/fragment_room_list.xml +++ b/app/src/main/res/layout/fragment_room_list.xml @@ -11,15 +11,16 @@ android:id="@+id/filterRoomView" android:layout_width="0dp" android:layout_height="32dp" - android:layout_marginBottom="8dp" - android:layout_marginEnd="16dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="8dp" android:background="@drawable/bg_search_edit_text" android:drawableLeft="@drawable/ic_search_white" android:drawablePadding="8dp" android:drawableTint="#9fa9ba" android:hint="Filter by name..." + android:lines="1" android:paddingLeft="8dp" app:layout_constraintBottom_toTopOf="@+id/stateView" app:layout_constraintEnd_toEndOf="parent" @@ -30,8 +31,8 @@ android:id="@+id/stateView" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginBottom="0dp" android:layout_marginEnd="0dp" + android:layout_marginBottom="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" From 1d400180bccfeec1798d8fb281e4bbc22779e553 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 22 Jan 2019 18:43:15 +0100 Subject: [PATCH 05/10] 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 1e6b27d3..34091211 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 00000000..90a26705 --- /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 00000000..bc7368f6 --- /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 00000000..09a1ede2 --- /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 e77b150c..8168be1c 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 e75a96af..5cc0de50 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 00000000..87eb6abc --- /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 00000000..a8cca347 --- /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 00000000..a8f116fb --- /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 f636c82c..8c58f6bb 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 369efdde..8ebad453 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 06/10] 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 34091211..68dc1cfb 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 87eb6abc..7b437ef9 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 5326a796..29d1292b 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 7157e764..00205e00 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 a8f116fb..3274d45f 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 5ba6e91f..6ec8b232 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 d36cbcbf..0307d4f2 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 00000000..182986e2 --- /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 07/10] 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 9e542569..45556b68 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 08/10] 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 bc7368f6..b273cd4f 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 8168be1c..42646478 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 00000000..e9deb182 --- /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 7b437ef9..00000000 --- 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 8c58f6bb..f4c698c1 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 09/10] 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 e9f9a3e9..8bfee140 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 052c7179..40f91e1c 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 ec179046..929ba659 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 c7ca4b5d..47e0f160 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 10/10] 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 8bfee140..bc195796 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 85aaa5e8..f00e1d65 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 42646478..afe43952 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 caede520..1894731a 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 00000000..a4251b6f --- /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