diff --git a/app/build.gradle b/app/build.gradle index e9f9a3e985..bc1957969b 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,11 +43,14 @@ 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" 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/Riot.kt b/app/src/main/java/im/vector/riotredesign/Riot.kt index 052c717930..40f91e1cb8 100644 --- a/app/src/main/java/im/vector/riotredesign/Riot.kt +++ b/app/src/main/java/im/vector/riotredesign/Riot.kt @@ -1,6 +1,8 @@ package im.vector.riotredesign import android.app.Application +import android.content.Context +import androidx.multidex.MultiDex import com.jakewharton.threetenabp.AndroidThreeTen import im.vector.matrix.android.BuildConfig import im.vector.riotredesign.core.di.AppModule @@ -8,6 +10,7 @@ import org.koin.log.EmptyLogger import org.koin.standalone.StandAloneContext.startKoin import timber.log.Timber + class Riot : Application() { override fun onCreate() { @@ -19,4 +22,9 @@ class Riot : Application() { startKoin(listOf(AppModule(this).definition), logger = EmptyLogger()) } + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + MultiDex.install(this) + } + } \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt index 1e6b27d36c..68dc1cfb70 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt @@ -1,18 +1,16 @@ 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.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) { @@ -27,7 +25,7 @@ object AvatarRenderer { if (name.isNullOrEmpty()) { return } - val resolvedUrl = avatarUrl?.replace(MXC_PREFIX, MEDIA_URL) + 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/home/HomeModule.kt b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt index 85aaa5e89b..f00e1d6548 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt @@ -10,6 +10,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFa import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import org.koin.dsl.module.module class HomeModule { @@ -21,7 +22,7 @@ class HomeModule { } single { - MessageItemFactory(get()) + MessageItemFactory(get(), get()) } single { @@ -49,7 +50,11 @@ class HomeModule { } factory { (roomId: String) -> - TimelineEventController(roomId, get(), get()) + TimelineEventController(roomId, get(), get(), get()) + } + + single { + TimelineMediaSizeProvider() } single { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt new file mode 100644 index 0000000000..90a2670528 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/AbsMessageItem.kt @@ -0,0 +1,33 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.LayoutRes +import im.vector.riotredesign.core.epoxy.KotlinModel +import im.vector.riotredesign.features.home.AvatarRenderer + +abstract class AbsMessageItem(private val informationData: MessageInformationData, + @LayoutRes layoutRes: Int +) : KotlinModel(layoutRes) { + + protected abstract val avatarImageView: ImageView + protected abstract val memberNameView: TextView + protected abstract val timeView: TextView + + override fun bind() { + if (informationData.showInformation) { + avatarImageView.visibility = View.VISIBLE + memberNameView.visibility = View.VISIBLE + timeView.visibility = View.VISIBLE + timeView.text = informationData.time + memberNameView.text = informationData.memberName + AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView) + } else { + avatarImageView.visibility = View.GONE + memberNameView.visibility = View.GONE + timeView.visibility = View.GONE + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt new file mode 100644 index 0000000000..b273cd4f4a --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageImageItem.kt @@ -0,0 +1,24 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +import android.widget.ImageView +import android.widget.TextView +import im.vector.riotredesign.R +import im.vector.riotredesign.features.media.MediaContentRenderer + +class MessageImageItem( + private val mediaData: MediaContentRenderer.Data, + 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() + MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt new file mode 100644 index 0000000000..09a1ede2cc --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageInformationData.kt @@ -0,0 +1,8 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +data class MessageInformationData( + val time: CharSequence? = null, + val avatarUrl: String?, + val memberName: CharSequence? = null, + val showInformation: Boolean = true +) \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt deleted file mode 100644 index 4aae5dd2ed..0000000000 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ /dev/null @@ -1,40 +0,0 @@ -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 MessageItem( - 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) { - - private val avatarImageView by bind(R.id.messageAvatarImageView) - private val memberNameView by bind(R.id.messageMemberNameView) - private val timeView by bind(R.id.messageTimeView) - private val messageView by bind(R.id.messageTextView) - - override fun 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/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index 58a7bb17e2..afe4395207 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -7,39 +7,74 @@ 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.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() fun create(event: TimelineEvent, nextEvent: TimelineEvent?, callback: TimelineEventController.Callback? - ): MessageItem? { + ): AbsMessageItem? { - 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 message = messageContent.body?.let { + 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 + val informationData = MessageInformationData(time, avatarUrl, memberName, showInformation) + + return when (messageContent) { + is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback) + is MessageImageContent -> buildImageMessageItem(messageContent, informationData) + else -> null + } + } + + private fun buildImageMessageItem(messageContent: MessageImageContent, + informationData: MessageInformationData): MessageImageItem? { + + val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize() + val data = MediaContentRenderer.Data( + url = messageContent.url, + height = messageContent.info.height, + maxHeight = maxHeight, + width = messageContent.info.width, + maxWidth = maxWidth, + rotation = messageContent.info.rotation, + orientation = messageContent.info.orientation + ) + return MessageImageItem(data, informationData) + } + + private fun buildTextMessageItem(messageContent: MessageTextContent, + informationData: MessageInformationData, + callback: TimelineEventController.Callback?): MessageTextItem? { + + val message = messageContent.body.let { val spannable = SpannableStringBuilder(it) MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback { override fun onUrlClicked(url: String) { @@ -49,13 +84,9 @@ 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, - showInformation = showInformation, - time = timelineDateFormatter.formatMessageHour(date), - memberName = roomMember?.displayName ?: event.root.sender + 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 new file mode 100644 index 0000000000..5cc0de5095 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt @@ -0,0 +1,23 @@ +package im.vector.riotredesign.features.home.room.detail.timeline + +import android.widget.ImageView +import android.widget.TextView +import im.vector.matrix.android.api.permalinks.MatrixLinkify +import im.vector.riotredesign.R + +class MessageTextItem( + val message: CharSequence? = null, + informationData: MessageInformationData +) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_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 messageView by bind(R.id.messageTextView) + + override fun bind() { + super.bind() + messageView.text = message + MatrixLinkify.addLinkMovementMethod(messageView) + } +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index caede52053..1894731a22 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -1,5 +1,6 @@ package im.vector.riotredesign.features.home.room.detail.timeline +import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyModel import im.vector.matrix.android.api.session.events.model.EventType @@ -7,11 +8,13 @@ import im.vector.matrix.android.api.session.events.model.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineData import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.features.home.LoadingItemModel_ +import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController class TimelineEventController(private val roomId: String, private val dateFormatter: TimelineDateFormatter, - private val timelineItemFactory: TimelineItemFactory + private val timelineItemFactory: TimelineItemFactory, + private val timelineMediaSizeProvider: TimelineMediaSizeProvider ) : PagedListEpoxyController( EpoxyAsyncUtil.getAsyncBackgroundHandler(), EpoxyAsyncUtil.getAsyncBackgroundHandler() @@ -36,6 +39,10 @@ class TimelineEventController(private val roomId: String, } } + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + timelineMediaSizeProvider.recyclerView = recyclerView + } override fun buildItemModels(currentPosition: Int, items: List): List> { if (items.isNullOrEmpty()) { diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt new file mode 100644 index 0000000000..a4251b6f29 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineMediaSizeProvider.kt @@ -0,0 +1,31 @@ +package im.vector.riotredesign.features.home.room.detail.timeline.helper + +import androidx.recyclerview.widget.RecyclerView + +class TimelineMediaSizeProvider { + + lateinit var recyclerView: RecyclerView + private var cachedSize: Pair? = null + + fun getMaxSize(): Pair { + return cachedSize ?: computeMaxSize().also { cachedSize = it } + } + + private fun computeMaxSize(): Pair { + val width = recyclerView.width + val height = recyclerView.height + val maxImageWidth: Int + val maxImageHeight: Int + // landscape / portrait + if (width < height) { + maxImageWidth = Math.round(width * 0.7f) + maxImageHeight = Math.round(height * 0.5f) + } else { + maxImageWidth = Math.round(width * 0.5f) + maxImageHeight = Math.round(height * 0.7f) + } + return Pair(maxImageWidth, maxImageHeight) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt index ec17904654..929ba659b5 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/list/RoomCategoryItem.kt @@ -1,8 +1,9 @@ package im.vector.riotredesign.features.home.room.list -import androidx.core.content.ContextCompat import android.view.ViewGroup import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat import im.vector.riotredesign.R import im.vector.riotredesign.core.epoxy.KotlinModel @@ -21,8 +22,9 @@ data class RoomCategoryItem( override fun bind() { val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white - val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes) - expandedArrowDrawable?.setTint(tintColor) + val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null) titleView.text = title rootView.setOnClickListener { listener?.invoke() } diff --git a/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt new file mode 100644 index 0000000000..e9deb18226 --- /dev/null +++ b/app/src/main/java/im/vector/riotredesign/features/media/MediaContentRenderer.kt @@ -0,0 +1,85 @@ +package im.vector.riotredesign.features.media + +import android.media.ExifInterface +import android.widget.ImageView +import im.vector.matrix.android.api.Matrix +import im.vector.matrix.android.api.session.content.ContentUrlResolver +import im.vector.riotredesign.core.glide.GlideApp + +object MediaContentRenderer { + + data class Data( + val url: String?, + val height: Int, + val maxHeight: Int, + val width: Int, + val maxWidth: Int = width, + val orientation: Int, + val rotation: Int + ) + + enum class Mode { + FULL_SIZE, + THUMBNAIL + } + + fun render(data: Data, mode: Mode, imageView: ImageView) { + val (width, height) = processSize(data, mode) + imageView.layoutParams.height = height + imageView.layoutParams.width = width + + val contentUrlResolver = Matrix.getInstance().currentSession.contentUrlResolver() + val resolvedUrl = when (mode) { + Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url) + Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE) + } + ?: return + + GlideApp + .with(imageView) + .load(resolvedUrl) + .thumbnail(0.3f) + .into(imageView) + } + + private fun processSize(data: Data, mode: Mode): Pair { + val maxImageWidth = data.maxWidth + val maxImageHeight = data.maxHeight + val rotationAngle = data.rotation + val orientation = data.orientation + var width = data.width + var height = data.height + var finalHeight = -1 + var finalWidth = -1 + + // if the image size is known + // compute the expected height + if (width > 0 && height > 0) { + // swap width and height if the image is side oriented + if (rotationAngle == 90 || rotationAngle == 270) { + val tmp = width + width = height + height = tmp + } else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { + val tmp = width + width = height + height = tmp + } + if (mode == Mode.FULL_SIZE) { + finalHeight = height + finalWidth = width + } else { + finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight) + finalWidth = finalHeight * width / height + } + } + // ensure that some values are properly initialized + if (finalHeight < 0) { + finalHeight = maxImageHeight + } + if (finalWidth < 0) { + finalWidth = maxImageWidth + } + return Pair(finalWidth, finalHeight) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_timeline_event_image_message.xml b/app/src/main/res/layout/item_timeline_event_image_message.xml new file mode 100644 index 0000000000..a8cca347f7 --- /dev/null +++ b/app/src/main/res/layout/item_timeline_event_image_message.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_timeline_event_message.xml b/app/src/main/res/layout/item_timeline_event_text_message.xml similarity index 100% rename from app/src/main/res/layout/item_timeline_event_message.xml rename to app/src/main/res/layout/item_timeline_event_text_message.xml diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c7ca4b5d7a..47e0f16041 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -24,7 +24,7 @@ android { testOptions.unitTests.includeAndroidResources = true defaultConfig { - minSdkVersion 21 + minSdkVersion 16 targetSdkVersion 28 versionCode 1 versionName "1.0" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt index 5326a7960a..29d1292be5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/Matrix.kt @@ -1,7 +1,7 @@ package im.vector.matrix.android.api -import androidx.lifecycle.ProcessLifecycleOwner import android.content.Context +import androidx.lifecycle.ProcessLifecycleOwner import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.Authenticator import im.vector.matrix.android.api.session.Session diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 7157e764cc..00205e0099 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -2,6 +2,7 @@ package im.vector.matrix.android.api.session import androidx.annotation.MainThread import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.room.RoomService @@ -15,6 +16,8 @@ interface Session : RoomService, GroupService { @MainThread fun close() + fun contentUrlResolver(): ContentUrlResolver + fun addListener(listener: Listener) fun removeListener(listener: Listener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt new file mode 100644 index 0000000000..3274d45f6a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentUrlResolver.kt @@ -0,0 +1,49 @@ +/* + * + * * 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 + +/** + * This interface defines methods for accessing content from the current session. + */ +interface ContentUrlResolver { + + 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. + * + * @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 resolveFullSize(contentUrl: String?): String? + + /** + * Get the actual URL for accessing the thumbnail image of a given Matrix media content URI. + * + * @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. + */ + 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/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt new file mode 100644 index 0000000000..32670078af --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/AudioInfo.kt @@ -0,0 +1,11 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class AudioInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "size") val size: Long, + @Json(name = "duration") val duration: Int +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt new file mode 100644 index 0000000000..75c263d9ca --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/FileInfo.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class FileInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "size") val size: Long, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, + @Json(name = "thumbnail_url") val thumbnailUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt new file mode 100644 index 0000000000..f4c698c18b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ImageInfo.kt @@ -0,0 +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 = 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 diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt new file mode 100644 index 0000000000..ee1c98877c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/LocationInfo.kt @@ -0,0 +1,10 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class LocationInfo( + @Json(name = "thumbnail_url") val thumbnailUrl: String, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt new file mode 100644 index 0000000000..33e17d5cb3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageAudioContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageAudioContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: AudioInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt new file mode 100644 index 0000000000..93c89bc287 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageContent.kt @@ -0,0 +1,6 @@ +package im.vector.matrix.android.api.session.room.model.message + +interface MessageContent { + val type: String + val body: String +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt new file mode 100644 index 0000000000..7f6dc34c35 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageDefaultContent.kt @@ -0,0 +1,10 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageDefaultContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt new file mode 100644 index 0000000000..297a01624a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageEmoteContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageEmoteContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageFileContent.kt new file mode 100644 index 0000000000..45556b6813 --- /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? = null, + @Json(name = "info") val info: FileInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt new file mode 100644 index 0000000000..34e4bc4cdb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageImageContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageImageContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: ImageInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt new file mode 100644 index 0000000000..d45fd21b8e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageLocationContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageLocationContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "geo_uri") val geoUri: String, + @Json(name = "info") val info: LocationInfo +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt new file mode 100644 index 0000000000..eda19bcc67 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageNoticeContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageNoticeContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "format") val format: String? = null, + @Json(name = "formatted_body") val formattedBody: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt similarity index 50% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt index 49a3ad6a6a..e109250af7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageTextContent.kt @@ -1,14 +1,12 @@ -package im.vector.matrix.android.api.session.room.model +package im.vector.matrix.android.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class MessageContent( - - @Json(name = "msgtype") val type: String? = null, - @Json(name = "body") val body: String? = null, +data class MessageTextContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, @Json(name = "format") val format: String? = null, @Json(name = "formatted_body") val formattedBody: String? = null - -) \ No newline at end of file +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt similarity index 90% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt index 6ffebca66d..ab3e7f7544 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/MessageType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageType.kt @@ -1,4 +1,4 @@ -package im.vector.matrix.android.api.session.room.model +package im.vector.matrix.android.api.session.room.model.message object MessageType { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt new file mode 100644 index 0000000000..c817c13b13 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/MessageVideoContent.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class MessageVideoContent( + @Json(name = "msgtype") override val type: String, + @Json(name = "body") override val body: String, + @Json(name = "info") val info: VideoInfo, + @Json(name = "url") val url: String? = null +) : MessageContent \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt new file mode 100644 index 0000000000..50084b5d78 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/ThumbnailInfo.kt @@ -0,0 +1,12 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ThumbnailInfo( + @Json(name = "w") val width: Int, + @Json(name = "h") val height: Int, + @Json(name = "size") val size: Long, + @Json(name = "mimetype") val mimeType: String +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt new file mode 100644 index 0000000000..6ca32e9969 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/message/VideoInfo.kt @@ -0,0 +1,15 @@ +package im.vector.matrix.android.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VideoInfo( + @Json(name = "mimetype") val mimeType: String, + @Json(name = "w") val w: Int, + @Json(name = "h") val h: Int, + @Json(name = "size") val size: Long, + @Json(name = "duration") val duration: Int, + @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, + @Json(name = "thumbnail_url") val thumbnailUrl: String? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt index 2dafe5036b..1bdaf5d60e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/send/EventFactory.kt @@ -4,8 +4,8 @@ import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.room.model.MessageContent -import im.vector.matrix.android.api.session.room.model.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType import im.vector.matrix.android.internal.di.MoshiProvider internal class EventFactory(private val credentials: Credentials) { @@ -13,7 +13,7 @@ internal class EventFactory(private val credentials: Credentials) { private val moshi = MoshiProvider.providesMoshi() fun createTextEvent(roomId: String, text: String): Event { - val content = MessageContent(type = MessageType.MSGTYPE_TEXT, body = text) + val content = MessageTextContent(type = MessageType.MSGTYPE_TEXT, body = text) return Event( roomId = roomId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index 1cabe215f0..984b90bddc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -1,6 +1,17 @@ package im.vector.matrix.android.internal.di import com.squareup.moshi.Moshi +import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent +import im.vector.matrix.android.api.session.room.model.message.MessageContent +import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent +import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent +import im.vector.matrix.android.api.session.room.model.message.MessageFileContent +import im.vector.matrix.android.api.session.room.model.message.MessageImageContent +import im.vector.matrix.android.api.session.room.model.message.MessageLocationContent +import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent +import im.vector.matrix.android.api.session.room.model.message.MessageTextContent +import im.vector.matrix.android.api.session.room.model.message.MessageType +import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent import im.vector.matrix.android.internal.network.parsing.RuntimeJsonAdapterFactory import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.UserAccountData @@ -14,6 +25,16 @@ object MoshiProvider { .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java) .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES) ) + .add(RuntimeJsonAdapterFactory.of(MessageContent::class.java, "msgtype", MessageDefaultContent::class.java) + .registerSubtype(MessageTextContent::class.java, MessageType.MSGTYPE_TEXT) + .registerSubtype(MessageNoticeContent::class.java, MessageType.MSGTYPE_NOTICE) + .registerSubtype(MessageEmoteContent::class.java, MessageType.MSGTYPE_EMOTE) + .registerSubtype(MessageAudioContent::class.java, MessageType.MSGTYPE_AUDIO) + .registerSubtype(MessageImageContent::class.java, MessageType.MSGTYPE_IMAGE) + .registerSubtype(MessageVideoContent::class.java, MessageType.MSGTYPE_VIDEO) + .registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION) + .registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE) + ) .build() fun providesMoshi(): Moshi { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java index 369efdde68..8ebad45328 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/RuntimeJsonAdapterFactory.java @@ -129,16 +129,8 @@ public final class RuntimeJsonAdapterFactory implements JsonAdapter.Factory { Object jsonValue = reader.readJsonValue(); Map jsonObject = (Map) jsonValue; Object label = jsonObject.get(labelKey); - if (label == null) { - throw new JsonDataException("Missing label for " + labelKey); - } if (!(label instanceof String)) { - throw new JsonDataException("Label for '" - + labelKey - + "' must be a string but was " - + label - + ", a " - + label.getClass()); + return null; } JsonAdapter adapter = labelToAdapter.get(label); if (adapter == null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 5ba6e91f38..6ec8b232be 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -1,10 +1,11 @@ package im.vector.matrix.android.internal.session -import androidx.lifecycle.LiveData import android.os.Looper import androidx.annotation.MainThread +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.Group import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.group.model.GroupSummary @@ -35,6 +36,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi private val roomService by inject() private val groupService by inject() private val syncThread by inject() + private val contentUrlResolver by inject() private var isOpen = false @MainThread @@ -63,6 +65,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi isOpen = false } + override fun contentUrlResolver(): ContentUrlResolver { + return contentUrlResolver + } + override fun addListener(listener: Session.Listener) { sessionListeners.addListener(listener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index d36cbcbf0a..0307d4f275 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -3,9 +3,11 @@ package im.vector.matrix.android.internal.session import android.content.Context import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.SessionParams +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.group.GroupService import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.internal.database.LiveEntityObserver +import im.vector.matrix.android.internal.session.content.DefaultContentUrlResolver import im.vector.matrix.android.internal.session.group.DefaultGroupService import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater import im.vector.matrix.android.internal.session.room.DefaultRoomService @@ -78,6 +80,10 @@ internal class SessionModule(private val sessionParams: SessionParams) { SessionListeners() } + scope(DefaultSession.SCOPE) { + DefaultContentUrlResolver(sessionParams.homeServerConnectionConfig) as ContentUrlResolver + } + scope(DefaultSession.SCOPE) { val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials) val groupSummaryUpdater = GroupSummaryUpdater(get()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt new file mode 100644 index 0000000000..182986e2c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/DefaultContentUrlResolver.kt @@ -0,0 +1,69 @@ +/* + * + * * Copyright 2019 New Vector Ltd + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package im.vector.matrix.android.internal.session.content + +import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig +import im.vector.matrix.android.api.session.content.ContentUrlResolver + + +private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" +private const val URI_PREFIX_CONTENT_API = "/_matrix/media/v1/" + +internal class DefaultContentUrlResolver(private val homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { + + override fun resolveFullSize(contentUrl: String?): String? { + if (contentUrl?.isValidMatrixContentUrl() == true) { + val baseUrl = homeServerConnectionConfig.homeServerUri.toString() + val prefix = URI_PREFIX_CONTENT_API + "download/" + return resolve(baseUrl, contentUrl, prefix) + } + return null + } + + override fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ContentUrlResolver.ThumbnailMethod): String? { + if (contentUrl?.isValidMatrixContentUrl() == true) { + val baseUrl = homeServerConnectionConfig.homeServerUri.toString() + val prefix = URI_PREFIX_CONTENT_API + "thumbnail/" + val params = "?width=$width&height=$height&method=${method.value}" + return resolve(baseUrl, contentUrl, prefix, params) + } + // do not allow non-mxc content URLs + return null + } + + private fun resolve(baseUrl: String, + contentUrl: String, + prefix: String, + params: String? = null): String? { + + var serverAndMediaId = contentUrl.removePrefix(MATRIX_CONTENT_URI_SCHEME) + val fragmentOffset = serverAndMediaId.indexOf("#") + var fragment = "" + if (fragmentOffset >= 0) { + fragment = serverAndMediaId.substring(fragmentOffset) + serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset) + } + return baseUrl + prefix + serverAndMediaId + (params ?: "") + fragment + } + + private fun String.isValidMatrixContentUrl(): Boolean { + return startsWith(MATRIX_CONTENT_URI_SCHEME) + } + +} \ No newline at end of file