Merge branch 'feature/timeline_media' into develop

This commit is contained in:
ganfra
2019-01-25 12:36:01 +01:00
43 changed files with 698 additions and 93 deletions

View File

@ -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

View File

@ -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)

View File

@ -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?
}

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -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

View File

@ -0,0 +1,6 @@
package im.vector.matrix.android.api.session.room.model.message
interface MessageContent {
val type: String
val body: String
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)
) : MessageContent

View File

@ -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 {

View File

@ -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

View File

@ -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
)

View File

@ -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
)

View File

@ -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,

View File

@ -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 {

View File

@ -129,16 +129,8 @@ public final class RuntimeJsonAdapterFactory<T> implements JsonAdapter.Factory {
Object jsonValue = reader.readJsonValue();
Map<String, Object> jsonObject = (Map<String, Object>) 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<Object> adapter = labelToAdapter.get(label);
if (adapter == null) {

View File

@ -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<RoomService>()
private val groupService by inject<GroupService>()
private val syncThread by inject<SyncThread>()
private val contentUrlResolver by inject<ContentUrlResolver>()
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)
}

View File

@ -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())

View File

@ -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)
}
}