mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 13:38:49 +03:00
Create a PermalinkFactory to avoid injecting the service in SDK classes
This commit is contained in:
parent
2c90e33ceb
commit
fde2bdf304
3 changed files with 130 additions and 80 deletions
|
@ -19,44 +19,26 @@ package org.matrix.android.sdk.internal.session.permalinks
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
|
||||||
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
|
||||||
import java.net.URLEncoder
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Provider
|
|
||||||
|
|
||||||
internal class DefaultPermalinkService @Inject constructor(
|
internal class DefaultPermalinkService @Inject constructor(
|
||||||
@UserId
|
private val permalinkFactory: PermalinkFactory
|
||||||
private val userId: String,
|
|
||||||
// Use a provider to fix circular Dagger dependency
|
|
||||||
private val roomGetterProvider: Provider<RoomGetter>
|
|
||||||
) : PermalinkService {
|
) : PermalinkService {
|
||||||
|
|
||||||
override fun createPermalink(event: Event): String? {
|
override fun createPermalink(event: Event): String? {
|
||||||
if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) {
|
return permalinkFactory.createPermalink(event)
|
||||||
return null
|
|
||||||
}
|
|
||||||
return createPermalink(event.roomId, event.eventId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPermalink(id: String): String? {
|
override fun createPermalink(id: String): String? {
|
||||||
return if (id.isEmpty()) {
|
return permalinkFactory.createPermalink(id)
|
||||||
null
|
|
||||||
} else MATRIX_TO_URL_BASE + escape(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createRoomPermalink(roomId: String): String? {
|
override fun createRoomPermalink(roomId: String): String? {
|
||||||
return if (roomId.isEmpty()) {
|
return permalinkFactory.createRoomPermalink(roomId)
|
||||||
null
|
|
||||||
} else {
|
|
||||||
MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPermalink(roomId: String, eventId: String): String {
|
override fun createPermalink(roomId: String, eventId: String): String {
|
||||||
return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId)
|
return permalinkFactory.createPermalink(roomId, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLinkedId(url: String): String? {
|
override fun getLinkedId(url: String): String? {
|
||||||
|
@ -66,54 +48,4 @@ internal class DefaultPermalinkService @Inject constructor(
|
||||||
url.substring(MATRIX_TO_URL_BASE.length)
|
url.substring(MATRIX_TO_URL_BASE.length)
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute the via parameters.
|
|
||||||
* Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
|
|
||||||
* current user one.
|
|
||||||
*/
|
|
||||||
private fun computeViaParams(userId: String, roomId: String): String {
|
|
||||||
val userHomeserver = userId.substringAfter(":")
|
|
||||||
return getUserIdsOfJoinedMembers(roomId)
|
|
||||||
.map { it.substringAfter(":") }
|
|
||||||
.groupBy { it }
|
|
||||||
.mapValues { it.value.size }
|
|
||||||
.toMutableMap()
|
|
||||||
// Ensure the user homeserver will be included
|
|
||||||
.apply { this[userHomeserver] = Int.MAX_VALUE }
|
|
||||||
.let { map -> map.keys.sortedByDescending { map[it] } }
|
|
||||||
.take(3)
|
|
||||||
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escape '/' in id, because it is used as a separator
|
|
||||||
*
|
|
||||||
* @param id the id to escape
|
|
||||||
* @return the escaped id
|
|
||||||
*/
|
|
||||||
private fun escape(id: String): String {
|
|
||||||
return id.replace("/", "%2F")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unescape '/' in id
|
|
||||||
*
|
|
||||||
* @param id the id to escape
|
|
||||||
* @return the escaped id
|
|
||||||
*/
|
|
||||||
private fun unescape(id: String): String {
|
|
||||||
return id.replace("%2F", "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a set of userIds of joined members of a room
|
|
||||||
*/
|
|
||||||
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
|
|
||||||
return roomGetterProvider.get().getRoom(roomId)
|
|
||||||
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
|
|
||||||
?.map { it.userId }
|
|
||||||
.orEmpty()
|
|
||||||
.toSet()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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 org.matrix.android.sdk.internal.session.permalinks
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
|
||||||
|
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
|
import org.matrix.android.sdk.internal.session.room.RoomGetter
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
internal class PermalinkFactory @Inject constructor(
|
||||||
|
@UserId
|
||||||
|
private val userId: String,
|
||||||
|
// Use a provider to fix circular Dagger dependency
|
||||||
|
private val roomGetterProvider: Provider<RoomGetter>
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun createPermalink(event: Event): String? {
|
||||||
|
if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return createPermalink(event.roomId, event.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPermalink(id: String): String? {
|
||||||
|
return if (id.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else MATRIX_TO_URL_BASE + escape(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRoomPermalink(roomId: String): String? {
|
||||||
|
return if (roomId.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createPermalink(roomId: String, eventId: String): String {
|
||||||
|
return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLinkedId(url: String): String? {
|
||||||
|
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
|
||||||
|
|
||||||
|
return if (isSupported) {
|
||||||
|
url.substring(MATRIX_TO_URL_BASE.length)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the via parameters.
|
||||||
|
* Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
|
||||||
|
* current user one.
|
||||||
|
*/
|
||||||
|
private fun computeViaParams(userId: String, roomId: String): String {
|
||||||
|
val userHomeserver = userId.substringAfter(":")
|
||||||
|
return getUserIdsOfJoinedMembers(roomId)
|
||||||
|
.map { it.substringAfter(":") }
|
||||||
|
.groupBy { it }
|
||||||
|
.mapValues { it.value.size }
|
||||||
|
.toMutableMap()
|
||||||
|
// Ensure the user homeserver will be included
|
||||||
|
.apply { this[userHomeserver] = Int.MAX_VALUE }
|
||||||
|
.let { map -> map.keys.sortedByDescending { map[it] } }
|
||||||
|
.take(3)
|
||||||
|
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape '/' in id, because it is used as a separator
|
||||||
|
*
|
||||||
|
* @param id the id to escape
|
||||||
|
* @return the escaped id
|
||||||
|
*/
|
||||||
|
private fun escape(id: String): String {
|
||||||
|
return id.replace("/", "%2F")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescape '/' in id
|
||||||
|
*
|
||||||
|
* @param id the id to escape
|
||||||
|
* @return the escaped id
|
||||||
|
*/
|
||||||
|
private fun unescape(id: String): String {
|
||||||
|
return id.replace("%2F", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a set of userIds of joined members of a room
|
||||||
|
*/
|
||||||
|
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
|
||||||
|
return roomGetterProvider.get().getRoom(roomId)
|
||||||
|
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
|
||||||
|
?.map { it.userId }
|
||||||
|
.orEmpty()
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.AudioInfo
|
import org.matrix.android.sdk.api.session.room.model.message.AudioInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.FileInfo
|
import org.matrix.android.sdk.api.session.room.model.message.FileInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
|
import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
|
||||||
|
@ -59,6 +58,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.isReply
|
import org.matrix.android.sdk.api.session.room.timeline.isReply
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
|
import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
|
||||||
|
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
|
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
|
||||||
import org.matrix.android.sdk.internal.util.StringProvider
|
import org.matrix.android.sdk.internal.util.StringProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -79,7 +79,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
private val markdownParser: MarkdownParser,
|
private val markdownParser: MarkdownParser,
|
||||||
private val textPillsUtils: TextPillsUtils,
|
private val textPillsUtils: TextPillsUtils,
|
||||||
private val localEchoRepository: LocalEchoRepository,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val permalinkService: PermalinkService
|
private val permalinkFactory: PermalinkFactory
|
||||||
) {
|
) {
|
||||||
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
|
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
|
||||||
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
|
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
|
||||||
|
@ -168,8 +168,8 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
newBodyAutoMarkdown: Boolean,
|
newBodyAutoMarkdown: Boolean,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
compatibilityText: String): Event {
|
compatibilityText: String): Event {
|
||||||
val permalink = permalinkService.createPermalink(roomId, originalEvent.root.eventId ?: "")
|
val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "")
|
||||||
val userLink = originalEvent.root.senderId?.let { permalinkService.createPermalink(it) } ?: ""
|
val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it) } ?: ""
|
||||||
|
|
||||||
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply())
|
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply())
|
||||||
val replyFormatted = REPLY_PATTERN.format(
|
val replyFormatted = REPLY_PATTERN.format(
|
||||||
|
@ -205,7 +205,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
|
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
|
||||||
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
|
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
|
||||||
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
|
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
|
||||||
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
|
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,9 +365,9 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
autoMarkdown: Boolean): Event? {
|
autoMarkdown: Boolean): Event? {
|
||||||
// Fallbacks and event representation
|
// Fallbacks and event representation
|
||||||
// TODO Add error/warning logs when any of this is null
|
// TODO Add error/warning logs when any of this is null
|
||||||
val permalink = permalinkService.createPermalink(eventReplied.root) ?: return null
|
val permalink = permalinkFactory.createPermalink(eventReplied.root) ?: return null
|
||||||
val userId = eventReplied.root.senderId ?: return null
|
val userId = eventReplied.root.senderId ?: return null
|
||||||
val userLink = permalinkService.createPermalink(userId) ?: return null
|
val userLink = permalinkFactory.createPermalink(userId) ?: return null
|
||||||
|
|
||||||
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply())
|
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply())
|
||||||
val replyFormatted = REPLY_PATTERN.format(
|
val replyFormatted = REPLY_PATTERN.format(
|
||||||
|
|
Loading…
Reference in a new issue