forked from GitHub-Mirror/riotX-android
Merge branch 'feature/timeline_media' into develop
This commit is contained in:
commit
4c7a6dfe33
@ -15,8 +15,9 @@ android {
|
|||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "im.vector.riotredesign"
|
applicationId "im.vector.riotredesign"
|
||||||
minSdkVersion 21
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
|
multiDexEnabled true
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@ -42,11 +43,14 @@ dependencies {
|
|||||||
|
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
implementation project(":matrix-sdk-android-rx")
|
implementation project(":matrix-sdk-android-rx")
|
||||||
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'androidx.core:core-ktx:1.0.1'
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
implementation 'androidx.paging:paging-runtime:2.0.0'
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package im.vector.riotredesign
|
package im.vector.riotredesign
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.multidex.MultiDex
|
||||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.riotredesign.core.di.AppModule
|
import im.vector.riotredesign.core.di.AppModule
|
||||||
@ -8,6 +10,7 @@ import org.koin.log.EmptyLogger
|
|||||||
import org.koin.standalone.StandAloneContext.startKoin
|
import org.koin.standalone.StandAloneContext.startKoin
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
class Riot : Application() {
|
class Riot : Application() {
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -19,4 +22,9 @@ class Riot : Application() {
|
|||||||
startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())
|
startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base)
|
||||||
|
MultiDex.install(this)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,18 +1,16 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
import com.bumptech.glide.request.RequestOptions
|
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.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.extensions.firstCharAsString
|
import im.vector.riotredesign.core.extensions.firstCharAsString
|
||||||
import im.vector.riotredesign.core.glide.GlideApp
|
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 {
|
object AvatarRenderer {
|
||||||
|
|
||||||
fun render(roomMember: RoomMember, imageView: ImageView) {
|
fun render(roomMember: RoomMember, imageView: ImageView) {
|
||||||
@ -27,7 +25,7 @@ object AvatarRenderer {
|
|||||||
if (name.isNullOrEmpty()) {
|
if (name.isNullOrEmpty()) {
|
||||||
return
|
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 avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
||||||
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
||||||
|
|
||||||
|
@ -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.TimelineDateFormatter
|
||||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
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.TimelineItemFactory
|
||||||
|
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||||
import org.koin.dsl.module.module
|
import org.koin.dsl.module.module
|
||||||
|
|
||||||
class HomeModule {
|
class HomeModule {
|
||||||
@ -21,7 +22,7 @@ class HomeModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
MessageItemFactory(get())
|
MessageItemFactory(get(), get())
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
@ -49,7 +50,11 @@ class HomeModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
factory { (roomId: String) ->
|
factory { (roomId: String) ->
|
||||||
TimelineEventController(roomId, get(), get())
|
TimelineEventController(roomId, get(), get(), get())
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
TimelineMediaSizeProvider()
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single {
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<ImageView>(R.id.messageAvatarImageView)
|
||||||
|
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
|
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
|
private val imageView by bind<ImageView>(R.id.messageImageView)
|
||||||
|
|
||||||
|
override fun bind() {
|
||||||
|
super.bind()
|
||||||
|
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
@ -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<ImageView>(R.id.messageAvatarImageView)
|
|
||||||
private val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
|
||||||
private val timeView by bind<TextView>(R.id.messageTimeView)
|
|
||||||
private val messageView by bind<TextView>(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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.EventType
|
||||||
import im.vector.matrix.android.api.session.events.model.TimelineEvent
|
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.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.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<String?>()
|
private val messagesDisplayedWithInformation = HashSet<String?>()
|
||||||
|
|
||||||
fun create(event: TimelineEvent,
|
fun create(event: TimelineEvent,
|
||||||
nextEvent: TimelineEvent?,
|
nextEvent: TimelineEvent?,
|
||||||
callback: TimelineEventController.Callback?
|
callback: TimelineEventController.Callback?
|
||||||
): MessageItem? {
|
): AbsMessageItem? {
|
||||||
|
|
||||||
val messageContent: MessageContent? = event.root.content.toModel()
|
|
||||||
val roomMember = event.roomMember
|
val roomMember = event.roomMember
|
||||||
if (messageContent == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val nextRoomMember = nextEvent?.roomMember
|
val nextRoomMember = nextEvent?.roomMember
|
||||||
|
|
||||||
val date = event.root.localDateTime()
|
val date = event.root.localDateTime()
|
||||||
val nextDate = nextEvent?.root?.localDateTime()
|
val nextDate = nextEvent?.root?.localDateTime()
|
||||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||||
?: false
|
?: false
|
||||||
|
|
||||||
if (addDaySeparator
|
if (addDaySeparator
|
||||||
|| nextRoomMember != roomMember
|
|| nextRoomMember != roomMember
|
||||||
|| nextEvent?.root?.type != EventType.MESSAGE
|
|| nextEvent?.root?.type != EventType.MESSAGE
|
||||||
|| isNextMessageReceivedMoreThanOneHourAgo) {
|
|| isNextMessageReceivedMoreThanOneHourAgo) {
|
||||||
messagesDisplayedWithInformation.add(event.root.eventId)
|
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)
|
val spannable = SpannableStringBuilder(it)
|
||||||
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
||||||
override fun onUrlClicked(url: String) {
|
override fun onUrlClicked(url: String) {
|
||||||
@ -49,13 +84,9 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte
|
|||||||
Linkify.addLinks(spannable, Linkify.ALL)
|
Linkify.addLinks(spannable, Linkify.ALL)
|
||||||
spannable
|
spannable
|
||||||
}
|
}
|
||||||
val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId)
|
return MessageTextItem(
|
||||||
return MessageItem(
|
|
||||||
message = message,
|
message = message,
|
||||||
avatarUrl = roomMember?.avatarUrl,
|
informationData = informationData
|
||||||
showInformation = showInformation,
|
|
||||||
time = timelineDateFormatter.formatMessageHour(date),
|
|
||||||
memberName = roomMember?.displayName ?: event.root.sender
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<ImageView>(R.id.messageAvatarImageView)
|
||||||
|
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||||
|
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||||
|
private val messageView by bind<TextView>(R.id.messageTextView)
|
||||||
|
|
||||||
|
override fun bind() {
|
||||||
|
super.bind()
|
||||||
|
messageView.text = message
|
||||||
|
MatrixLinkify.addLinkMovementMethod(messageView)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package im.vector.riotredesign.features.home.room.detail.timeline
|
package im.vector.riotredesign.features.home.room.detail.timeline
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.matrix.android.api.session.room.timeline.TimelineData
|
||||||
import im.vector.riotredesign.core.extensions.localDateTime
|
import im.vector.riotredesign.core.extensions.localDateTime
|
||||||
import im.vector.riotredesign.features.home.LoadingItemModel_
|
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
|
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
|
||||||
|
|
||||||
class TimelineEventController(private val roomId: String,
|
class TimelineEventController(private val roomId: String,
|
||||||
private val dateFormatter: TimelineDateFormatter,
|
private val dateFormatter: TimelineDateFormatter,
|
||||||
private val timelineItemFactory: TimelineItemFactory
|
private val timelineItemFactory: TimelineItemFactory,
|
||||||
|
private val timelineMediaSizeProvider: TimelineMediaSizeProvider
|
||||||
) : PagedListEpoxyController<TimelineEvent>(
|
) : PagedListEpoxyController<TimelineEvent>(
|
||||||
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
||||||
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<TimelineEvent?>): List<EpoxyModel<*>> {
|
override fun buildItemModels(currentPosition: Int, items: List<TimelineEvent?>): List<EpoxyModel<*>> {
|
||||||
if (items.isNullOrEmpty()) {
|
if (items.isNullOrEmpty()) {
|
||||||
|
@ -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<Int, Int>? = null
|
||||||
|
|
||||||
|
fun getMaxSize(): Pair<Int, Int> {
|
||||||
|
return cachedSize ?: computeMaxSize().also { cachedSize = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun computeMaxSize(): Pair<Int, Int> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
package im.vector.riotredesign.features.home.room.list
|
package im.vector.riotredesign.features.home.room.list
|
||||||
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.graphics.drawable.DrawableCompat
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
|
||||||
@ -21,8 +22,9 @@ data class RoomCategoryItem(
|
|||||||
|
|
||||||
override fun bind() {
|
override fun bind() {
|
||||||
val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
||||||
val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)
|
val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also {
|
||||||
expandedArrowDrawable?.setTint(tintColor)
|
DrawableCompat.setTint(it, tintColor)
|
||||||
|
}
|
||||||
titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
|
titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
|
||||||
titleView.text = title
|
titleView.text = title
|
||||||
rootView.setOnClickListener { listener?.invoke() }
|
rootView.setOnClickListener { listener?.invoke() }
|
||||||
|
@ -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<Int, Int> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingLeft="16dp"
|
||||||
|
android:paddingRight="16dp">
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageAvatarImageView"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@tools:sample/avatars" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageMemberNameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="64dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textSize="15sp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@tools:sample/full_names" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/messageTimeView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textColor="@color/brown_grey"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||||
|
tools:text="@tools:sample/date/hhmm" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/messageImageView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginStart="64dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -24,7 +24,7 @@ android {
|
|||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 16
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package im.vector.matrix.android.api
|
package im.vector.matrix.android.api
|
||||||
|
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
@ -2,6 +2,7 @@ package im.vector.matrix.android.api.session
|
|||||||
|
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ interface Session : RoomService, GroupService {
|
|||||||
@MainThread
|
@MainThread
|
||||||
fun close()
|
fun close()
|
||||||
|
|
||||||
|
fun contentUrlResolver(): ContentUrlResolver
|
||||||
|
|
||||||
fun addListener(listener: Listener)
|
fun addListener(listener: Listener)
|
||||||
|
|
||||||
fun removeListener(listener: Listener)
|
fun removeListener(listener: Listener)
|
||||||
|
@ -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?
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -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
|
@ -0,0 +1,6 @@
|
|||||||
|
package im.vector.matrix.android.api.session.room.model.message
|
||||||
|
|
||||||
|
interface MessageContent {
|
||||||
|
val type: String
|
||||||
|
val body: String
|
||||||
|
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessageContent(
|
data class MessageTextContent(
|
||||||
|
@Json(name = "msgtype") override val type: String,
|
||||||
@Json(name = "msgtype") val type: String? = null,
|
@Json(name = "body") override val body: String,
|
||||||
@Json(name = "body") val body: String? = null,
|
|
||||||
@Json(name = "format") val format: String? = null,
|
@Json(name = "format") val format: String? = null,
|
||||||
@Json(name = "formatted_body") val formattedBody: String? = null
|
@Json(name = "formatted_body") val formattedBody: String? = null
|
||||||
|
) : MessageContent
|
||||||
)
|
|
@ -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 {
|
object MessageType {
|
||||||
|
|
@ -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
|
@ -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
|
||||||
|
)
|
@ -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
|
||||||
|
)
|
@ -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.Content
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
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.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.model.MessageContent
|
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||||
import im.vector.matrix.android.api.session.room.model.MessageType
|
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
|
|
||||||
internal class EventFactory(private val credentials: Credentials) {
|
internal class EventFactory(private val credentials: Credentials) {
|
||||||
@ -13,7 +13,7 @@ internal class EventFactory(private val credentials: Credentials) {
|
|||||||
private val moshi = MoshiProvider.providesMoshi()
|
private val moshi = MoshiProvider.providesMoshi()
|
||||||
|
|
||||||
fun createTextEvent(roomId: String, text: String): Event {
|
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(
|
return Event(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
package im.vector.matrix.android.internal.di
|
package im.vector.matrix.android.internal.di
|
||||||
|
|
||||||
import com.squareup.moshi.Moshi
|
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.RuntimeJsonAdapterFactory
|
||||||
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
|
||||||
import im.vector.matrix.android.internal.session.sync.model.UserAccountData
|
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)
|
.add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
|
||||||
.registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
|
.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()
|
.build()
|
||||||
|
|
||||||
fun providesMoshi(): Moshi {
|
fun providesMoshi(): Moshi {
|
||||||
|
@ -129,16 +129,8 @@ public final class RuntimeJsonAdapterFactory<T> implements JsonAdapter.Factory {
|
|||||||
Object jsonValue = reader.readJsonValue();
|
Object jsonValue = reader.readJsonValue();
|
||||||
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue;
|
Map<String, Object> jsonObject = (Map<String, Object>) jsonValue;
|
||||||
Object label = jsonObject.get(labelKey);
|
Object label = jsonObject.get(labelKey);
|
||||||
if (label == null) {
|
|
||||||
throw new JsonDataException("Missing label for " + labelKey);
|
|
||||||
}
|
|
||||||
if (!(label instanceof String)) {
|
if (!(label instanceof String)) {
|
||||||
throw new JsonDataException("Label for '"
|
return null;
|
||||||
+ labelKey
|
|
||||||
+ "' must be a string but was "
|
|
||||||
+ label
|
|
||||||
+ ", a "
|
|
||||||
+ label.getClass());
|
|
||||||
}
|
}
|
||||||
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
JsonAdapter<Object> adapter = labelToAdapter.get(label);
|
||||||
if (adapter == null) {
|
if (adapter == null) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package im.vector.matrix.android.internal.session
|
package im.vector.matrix.android.internal.session
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.Group
|
||||||
import im.vector.matrix.android.api.session.group.GroupService
|
import im.vector.matrix.android.api.session.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
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<RoomService>()
|
private val roomService by inject<RoomService>()
|
||||||
private val groupService by inject<GroupService>()
|
private val groupService by inject<GroupService>()
|
||||||
private val syncThread by inject<SyncThread>()
|
private val syncThread by inject<SyncThread>()
|
||||||
|
private val contentUrlResolver by inject<ContentUrlResolver>()
|
||||||
private var isOpen = false
|
private var isOpen = false
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
@ -63,6 +65,10 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
|
|||||||
isOpen = false
|
isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun contentUrlResolver(): ContentUrlResolver {
|
||||||
|
return contentUrlResolver
|
||||||
|
}
|
||||||
|
|
||||||
override fun addListener(listener: Session.Listener) {
|
override fun addListener(listener: Session.Listener) {
|
||||||
sessionListeners.addListener(listener)
|
sessionListeners.addListener(listener)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ package im.vector.matrix.android.internal.session
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
import im.vector.matrix.android.api.session.room.RoomService
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
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.DefaultGroupService
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.room.DefaultRoomService
|
import im.vector.matrix.android.internal.session.room.DefaultRoomService
|
||||||
@ -78,6 +80,10 @@ internal class SessionModule(private val sessionParams: SessionParams) {
|
|||||||
SessionListeners()
|
SessionListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope(DefaultSession.SCOPE) {
|
||||||
|
DefaultContentUrlResolver(sessionParams.homeServerConnectionConfig) as ContentUrlResolver
|
||||||
|
}
|
||||||
|
|
||||||
scope(DefaultSession.SCOPE) {
|
scope(DefaultSession.SCOPE) {
|
||||||
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
|
val roomSummaryUpdater = RoomSummaryUpdater(get(), get(), get(), get(), sessionParams.credentials)
|
||||||
val groupSummaryUpdater = GroupSummaryUpdater(get())
|
val groupSummaryUpdater = GroupSummaryUpdater(get())
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user