Room alias and matrix.to link: we can now open a room though roomAlias as long as it's a joined one

This commit is contained in:
ganfra 2019-11-25 17:02:04 +01:00 committed by Benoit Marty
parent 02febfb01b
commit abf0796794
20 changed files with 199 additions and 186 deletions

View File

@ -1,38 +0,0 @@
/*
* 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.rx
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import io.reactivex.CompletableEmitter
internal class MatrixCallbackCompletable<T>(private val completableEmitter: CompletableEmitter) : MatrixCallback<T> {
override fun onSuccess(data: T) {
completableEmitter.onComplete()
}
override fun onFailure(failure: Throwable) {
completableEmitter.tryOnError(failure)
}
}
fun Cancelable.toCompletable(completableEmitter: CompletableEmitter) {
completableEmitter.setCancellable {
this.cancel()
}
}

View File

@ -1,38 +0,0 @@
/*
* 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.rx
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import io.reactivex.SingleEmitter
internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<T>) : MatrixCallback<T> {
override fun onSuccess(data: T) {
singleEmitter.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
singleEmitter.tryOnError(failure)
}
}
fun <T> Cancelable.toSingle(singleEmitter: SingleEmitter<T>) {
singleEmitter.setCancellable {
this.cancel()
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.rx
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import io.reactivex.Completable
import io.reactivex.Single
fun <T> singleBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Single<T> = Single.create {
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
override fun onSuccess(data: T) {
it.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
it.tryOnError(failure)
}
}
val cancelable = builder(callback)
it.setCancellable {
cancelable.cancel()
}
}
fun <T> completableBuilder(builder: (callback: MatrixCallback<T>) -> Cancelable): Completable = Completable.create {
val callback: MatrixCallback<T> = object : MatrixCallback<T> {
override fun onSuccess(data: T) {
it.onComplete()
}
override fun onFailure(failure: Throwable) {
it.tryOnError(failure)
}
}
val cancelable = builder(callback)
it.setCancellable {
cancelable.cancel()
}
}

View File

@ -53,13 +53,13 @@ class RxRoom(private val room: Room) {
return room.getMyReadReceiptLive().asObservable() return room.getMyReadReceiptLive().asObservable()
} }
fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create { fun loadRoomMembersIfNeeded(): Single<Unit> = singleBuilder {
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) room.loadRoomMembersIfNeeded(it)
} }
fun joinRoom(reason: String? = null, fun joinRoom(reason: String? = null,
viaServers: List<String> = emptyList()): Single<Unit> = Single.create { viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
room.join(reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) room.join(reason, viaServers, it)
} }
fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> { fun liveEventReadReceipts(eventId: String): Observable<List<ReadReceipt>> {

View File

@ -66,20 +66,25 @@ class RxSession(private val session: Session) {
return session.livePagedUsers(filter).asObservable() return session.livePagedUsers(filter).asObservable()
} }
fun createRoom(roomParams: CreateRoomParams): Single<String> = Single.create { fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
session.createRoom(roomParams, MatrixCallbackSingle(it)).toSingle(it) session.createRoom(roomParams, it)
} }
fun searchUsersDirectory(search: String, fun searchUsersDirectory(search: String,
limit: Int, limit: Int,
excludedUserIds: Set<String>): Single<List<User>> = Single.create { excludedUserIds: Set<String>): Single<List<User>> = singleBuilder {
session.searchUsersDirectory(search, limit, excludedUserIds, MatrixCallbackSingle(it)).toSingle(it) session.searchUsersDirectory(search, limit, excludedUserIds, it)
} }
fun joinRoom(roomId: String, fun joinRoom(roomId: String,
reason: String? = null, reason: String? = null,
viaServers: List<String> = emptyList()): Single<Unit> = Single.create { viaServers: List<String> = emptyList()): Single<Unit> = singleBuilder {
session.joinRoom(roomId, reason, viaServers, MatrixCallbackSingle(it)).toSingle(it) session.joinRoom(roomId, reason, viaServers, it)
}
fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
} }
} }

View File

@ -24,9 +24,7 @@ import android.net.Uri
*/ */
sealed class PermalinkData { sealed class PermalinkData {
data class EventLink(val roomIdOrAlias: String, val eventId: String, val isRoomAlias: Boolean) : PermalinkData() data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?) : PermalinkData()
data class RoomLink(val roomIdOrAlias: String, val isRoomAlias: Boolean) : PermalinkData()
data class UserLink(val userId: String) : PermalinkData() data class UserLink(val userId: String) : PermalinkData()

View File

@ -18,8 +18,6 @@ package im.vector.matrix.android.api.permalinks
import android.net.Uri import android.net.Uri
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/** /**
* This class turns an uri to a [PermalinkData] * This class turns an uri to a [PermalinkData]
@ -65,18 +63,16 @@ object PermalinkParser {
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
MatrixPatterns.isRoomId(identifier) -> { MatrixPatterns.isRoomId(identifier) -> {
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) { val eventId = extraParameter.takeIf {
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter, isRoomAlias = false) !it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
} else {
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false)
} }
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = false, eventId = eventId)
} }
MatrixPatterns.isRoomAlias(identifier) -> { MatrixPatterns.isRoomAlias(identifier) -> {
if (!extraParameter.isNullOrEmpty() && MatrixPatterns.isEventId(extraParameter)) { val eventId = extraParameter.takeIf {
PermalinkData.EventLink(roomIdOrAlias = identifier, eventId = extraParameter, isRoomAlias = true) !it.isNullOrEmpty() && MatrixPatterns.isEventId(it)
} else {
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true)
} }
PermalinkData.RoomLink(roomIdOrAlias = identifier, isRoomAlias = true, eventId = eventId)
} }
else -> PermalinkData.FallbackLink(uri) else -> PermalinkData.FallbackLink(uri)
} }

View File

@ -18,10 +18,10 @@ package im.vector.matrix.android.api.session.room
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
/** /**
* This interface defines methods to get rooms. It's implemented at the session level. * This interface defines methods to get rooms. It's implemented at the session level.
@ -80,5 +80,6 @@ interface RoomService {
* Resolve a room alias to a room ID. * Resolve a room alias to a room ID.
*/ */
fun getRoomIdByAlias(roomAlias: String, fun getRoomIdByAlias(roomAlias: String,
callback: MatrixCallback<String?>): Cancelable searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable
} }

View File

@ -59,7 +59,6 @@ interface MembershipService {
/** /**
* Join the room, or accept an invitation. * Join the room, or accept an invitation.
*/ */
fun join(reason: String? = null, fun join(reason: String? = null,
viaServers: List<String> = emptyList(), viaServers: List<String> = emptyList(),
callback: MatrixCallback<Unit>): Cancelable callback: MatrixCallback<Unit>): Cancelable

View File

@ -19,7 +19,6 @@ package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.room.model.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import io.realm.RealmList
/** /**
* This class holds some data of a room. * This class holds some data of a room.

View File

@ -41,7 +41,8 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var userDrafts: UserDraftsEntity? = null, var userDrafts: UserDraftsEntity? = null,
var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS var breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS
var canonicalAlias: String? = null, var canonicalAlias: String? = null,
var aliases: RealmList<String> = RealmList() var aliases: RealmList<String> = RealmList(),
var flatAliases: String = ""
) : RealmObject() { ) : RealmObject() {
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name

View File

@ -40,7 +40,7 @@ internal fun RoomSummaryEntity.Companion.findByAlias(realm: Realm, roomAlias: St
return roomSummary return roomSummary
} }
return realm.where<RoomSummaryEntity>() return realm.where<RoomSummaryEntity>()
.`in`(RoomSummaryEntityFields.ALIASES.`$`, arrayOf(roomAlias)) .contains(RoomSummaryEntityFields.FLAT_ALIASES, "|$roomAlias")
.findFirst() .findFirst()
} }

View File

@ -21,11 +21,11 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.VersioningState import im.vector.matrix.android.api.session.room.model.VersioningState
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
@ -51,7 +51,6 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
private val roomFactory: RoomFactory, private val roomFactory: RoomFactory,
private val taskExecutor: TaskExecutor) : RoomService { private val taskExecutor: TaskExecutor) : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable { override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
return createRoomTask return createRoomTask
.configureWith(createRoomParams) { .configureWith(createRoomParams) {
@ -116,9 +115,9 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
.executeBy(taskExecutor) .executeBy(taskExecutor)
} }
override fun getRoomIdByAlias(roomAlias: String, callback: MatrixCallback<String?>): Cancelable { override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<String>>): Cancelable {
return roomIdByAliasTask return roomIdByAliasTask
.configureWith(GetRoomIdByAliasTask.Params(roomAlias)) { .configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
this.callback = callback this.callback = callback
} }
.executeBy(taskExecutor) .executeBy(taskExecutor)

View File

@ -110,7 +110,7 @@ internal class RoomSummaryUpdater @Inject constructor(@UserId private val userId
val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases ?: emptyList() val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases ?: emptyList()
roomSummaryEntity.aliases.clear() roomSummaryEntity.aliases.clear()
roomSummaryEntity.aliases.addAll(roomAliases) roomSummaryEntity.aliases.addAll(roomAliases)
roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|")
if (updateMembers) { if (updateMembers) {
val otherRoomMembers = RoomMembers(realm, roomId) val otherRoomMembers = RoomMembers(realm, roomId)

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room.alias package im.vector.matrix.android.internal.session.room.alias
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.findByAlias import im.vector.matrix.android.internal.database.query.findByAlias
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -25,24 +26,29 @@ import im.vector.matrix.android.internal.task.Task
import io.realm.Realm import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, String?> { internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
data class Params( data class Params(
val roomAlias: String val roomAlias: String,
val searchOnServer: Boolean
) )
} }
internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monarchy: Monarchy, internal class DefaultGetRoomIdByAliasTask @Inject constructor(private val monarchy: Monarchy,
private val roomAPI: RoomAPI) : GetRoomIdByAliasTask { private val roomAPI: RoomAPI) : GetRoomIdByAliasTask {
override suspend fun execute(params: GetRoomIdByAliasTask.Params): String? { override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional<String> {
val roomId = Realm.getInstance(monarchy.realmConfiguration).use { var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId
} }
if (roomId != null) { return if (roomId != null) {
return roomId Optional.from(roomId)
} else if (!params.searchOnServer) {
Optional.from<String>(null)
} else {
roomId = executeRequest<RoomAliasDescription> {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
}.roomId
Optional.from(roomId)
} }
return executeRequest<RoomAliasDescription> {
apiCall = roomAPI.getRoomIdByAlias(params.roomAlias)
}.roomId
} }
} }

View File

@ -113,6 +113,8 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.share.SharedData
import im.vector.riotx.features.themes.ThemeUtils import im.vector.riotx.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import kotlinx.android.synthetic.main.merge_composer_layout.view.* import kotlinx.android.synthetic.main.merge_composer_layout.view.*
@ -851,30 +853,33 @@ class RoomDetailFragment @Inject constructor(
// TimelineEventController.Callback ************************************************************ // TimelineEventController.Callback ************************************************************
override fun onUrlClicked(url: String): Boolean { override fun onUrlClicked(url: String): Boolean {
val managed = permalinkHandler.launch(requireActivity(), url, object : NavigateToRoomInterceptor { permalinkHandler
override fun navToRoom(roomId: String, eventId: String?): Boolean { .launch(requireActivity(), url, object : NavigateToRoomInterceptor {
// Same room? override fun navToRoom(roomId: String?, eventId: String?): Boolean {
if (roomId == roomDetailArgs.roomId) { // Same room?
// Navigation to same room if (roomId == roomDetailArgs.roomId) {
if (eventId == null) { // Navigation to same room
showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room)) if (eventId == null) {
} else { showSnackWithMessage(getString(R.string.navigate_to_room_when_already_in_the_room))
// Highlight and scroll to this event } else {
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true)) // Highlight and scroll to this event
roomDetailViewModel.handle(RoomDetailAction.NavigateToEvent(eventId, true))
}
return true
}
// Not handled
return false
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { managed ->
if (!managed) {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
} }
return true
} }
.disposeOnDestroyView()
// Not handled
return false
}
})
if (!managed) {
// Open in external browser, in a new Tab
openUrlInExternalBrowser(requireContext(), url)
}
// In fact it is always managed // In fact it is always managed
return true return true
} }
@ -1022,12 +1027,15 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onRoomCreateLinkClicked(url: String) { override fun onRoomCreateLinkClicked(url: String) {
permalinkHandler.launch(requireContext(), url, object : NavigateToRoomInterceptor { permalinkHandler
override fun navToRoom(roomId: String, eventId: String?): Boolean { .launch(requireContext(), url, object : NavigateToRoomInterceptor {
requireActivity().finish() override fun navToRoom(roomId: String?, eventId: String?): Boolean {
return false requireActivity().finish()
} return false
}) }
})
.subscribe()
.disposeOnDestroyView()
} }
override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) { override fun onReadReceiptsClicked(readReceipts: List<ReadReceiptData>) {

View File

@ -40,7 +40,6 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import androidx.core.app.TaskStackBuilder import androidx.core.app.TaskStackBuilder
@Singleton @Singleton
class DefaultNavigator @Inject constructor() : Navigator { class DefaultNavigator @Inject constructor() : Navigator {
@ -56,7 +55,7 @@ class DefaultNavigator @Inject constructor() : Navigator {
} }
} }
override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String, eventId: String?, buildTask: Boolean) { override fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String?, buildTask: Boolean) {
if (context is VectorBaseActivity) { if (context is VectorBaseActivity) {
context.notImplemented("Open not joined room") context.notImplemented("Open not joined room")
} else { } else {
@ -64,13 +63,20 @@ class DefaultNavigator @Inject constructor() : Navigator {
} }
} }
override fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean) { override fun openGroupDetail(groupId: String, context: Context, buildTask: Boolean) {
Timber.v("Open group detail $groupId") if (context is VectorBaseActivity) {
context.notImplemented("Open group detail")
} else {
context.toast(R.string.not_implemented)
}
} }
override fun openUserDetail(userId: String, context: Context, buildTask: Boolean) { override fun openUserDetail(userId: String, context: Context, buildTask: Boolean) {
Timber.v("Open user detail $userId") if (context is VectorBaseActivity) {
context.notImplemented("Open user detail")
} else {
context.toast(R.string.not_implemented)
}
} }
override fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData) { override fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData) {

View File

@ -27,7 +27,7 @@ interface Navigator {
fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData) fun openRoomForSharing(activity: Activity, roomId: String, sharedData: SharedData)
fun openNotJoinedRoom(context: Context, roomIdOrAlias: String, eventId: String? = null, buildTask: Boolean = false) fun openNotJoinedRoom(context: Context, roomIdOrAlias: String?, eventId: String? = null, buildTask: Boolean = false)
fun openRoomPreview(publicRoom: PublicRoom, context: Context) fun openRoomPreview(publicRoom: PublicRoom, context: Context)

View File

@ -21,7 +21,12 @@ import android.net.Uri
import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.rx.rx
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject import javax.inject.Inject
class PermalinkHandler @Inject constructor(private val session: Session, class PermalinkHandler @Inject constructor(private val session: Session,
@ -32,7 +37,7 @@ class PermalinkHandler @Inject constructor(private val session: Session,
deepLink: String?, deepLink: String?,
navigateToRoomInterceptor: NavigateToRoomInterceptor? = null, navigateToRoomInterceptor: NavigateToRoomInterceptor? = null,
buildTask: Boolean = false buildTask: Boolean = false
): Boolean { ): Single<Boolean> {
val uri = deepLink?.let { Uri.parse(it) } val uri = deepLink?.let { Uri.parse(it) }
return launch(context, uri, navigateToRoomInterceptor, buildTask) return launch(context, uri, navigateToRoomInterceptor, buildTask)
} }
@ -42,47 +47,53 @@ class PermalinkHandler @Inject constructor(private val session: Session,
deepLink: Uri?, deepLink: Uri?,
navigateToRoomInterceptor: NavigateToRoomInterceptor? = null, navigateToRoomInterceptor: NavigateToRoomInterceptor? = null,
buildTask: Boolean = false buildTask: Boolean = false
): Boolean { ): Single<Boolean> {
if (deepLink == null) { if (deepLink == null) {
return false return Single.just(false)
} }
return when (val permalinkData = PermalinkParser.parse(deepLink)) { return when (val permalinkData = PermalinkParser.parse(deepLink)) {
is PermalinkData.EventLink -> {
if (navigateToRoomInterceptor?.navToRoom(permalinkData.roomIdOrAlias, permalinkData.eventId) != true) {
openRoom(context, permalinkData.roomIdOrAlias, permalinkData.eventId, buildTask)
}
true
}
is PermalinkData.RoomLink -> { is PermalinkData.RoomLink -> {
if (navigateToRoomInterceptor?.navToRoom(permalinkData.roomIdOrAlias) != true) { permalinkData.getRoomId()
openRoom(context, permalinkData.roomIdOrAlias, null, buildTask) .observeOn(AndroidSchedulers.mainThread())
} .map {
val roomId = it.getOrNull()
true if (navigateToRoomInterceptor?.navToRoom(roomId) != true) {
openRoom(context, roomId, permalinkData.eventId, buildTask)
}
true
}
} }
is PermalinkData.GroupLink -> { is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId, context, buildTask) navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
false Single.just(true)
} }
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
navigator.openUserDetail(permalinkData.userId, context, buildTask) navigator.openUserDetail(permalinkData.userId, context, buildTask)
true Single.just(false)
} }
is PermalinkData.FallbackLink -> { is PermalinkData.FallbackLink -> {
false Single.just(false)
} }
} }
} }
private fun PermalinkData.RoomLink.getRoomId(): Single<Optional<String>> {
return if (isRoomAlias) {
// At the moment we are not fetching on the server as we don't handle not join room
session.rx().getRoomIdByAlias(roomIdOrAlias, false).subscribeOn(Schedulers.io())
} else {
Single.just(Optional.from(roomIdOrAlias))
}
}
/** /**
* Open room either joined, or not unknown * Open room either joined, or not unknown
*/ */
private fun openRoom(context: Context, roomIdOrAlias: String, eventId: String? = null, buildTask: Boolean) { private fun openRoom(context: Context, roomId: String?, eventId: String? = null, buildTask: Boolean) {
if (session.getRoom(roomIdOrAlias) != null) { return if (roomId != null && session.getRoom(roomId) != null) {
navigator.openRoom(context, roomIdOrAlias, eventId, buildTask) navigator.openRoom(context, roomId, eventId, buildTask)
} else { } else {
navigator.openNotJoinedRoom(context, roomIdOrAlias, eventId, buildTask) navigator.openNotJoinedRoom(context, roomId, eventId, buildTask)
} }
} }
} }
@ -92,5 +103,5 @@ interface NavigateToRoomInterceptor {
/** /**
* Return true if the navigation has been intercepted * Return true if the navigation has been intercepted
*/ */
fun navToRoom(roomId: String, eventId: String? = null): Boolean fun navToRoom(roomId: String?, eventId: String? = null): Boolean
} }

View File

@ -18,11 +18,14 @@ package im.vector.riotx.features.permalink
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.login.LoginActivity import im.vector.riotx.features.login.LoginActivity
import timber.log.Timber import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.synthetic.debug.activity_test_material_theme.*
import javax.inject.Inject import javax.inject.Inject
class PermalinkHandlerActivity : VectorBaseActivity() { class PermalinkHandlerActivity : VectorBaseActivity() {
@ -36,18 +39,23 @@ class PermalinkHandlerActivity : VectorBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// If we are not logged in, stop the sharing process and open login screen. setContentView(R.layout.activity)
// In the future, we might want to relaunch the sharing process after login. // If we are not logged in, open login screen.
// In the future, we might want to relaunch the process after login.
if (!sessionHolder.hasActiveSession()) { if (!sessionHolder.hasActiveSession()) {
startLoginActivity() startLoginActivity()
return return
} }
val uri = intent.dataString val uri = intent.dataString
val isHandled = permalinkHandler.launch(this, uri, buildTask = true) permalinkHandler.launch(this, uri, buildTask = true)
if (!isHandled) { .observeOn(AndroidSchedulers.mainThread())
Timber.v("Couldn't handle permalink") .subscribe { isHandled ->
} if (!isHandled) {
finish() toast("Your matrix.to link was malformed")
}
finish()
}
.disposeOnDestroy()
} }
private fun startLoginActivity() { private fun startLoginActivity() {
@ -56,6 +64,4 @@ class PermalinkHandlerActivity : VectorBaseActivity() {
startActivity(intent) startActivity(intent)
finish() finish()
} }
} }