element:// support + basic peeking + fix join via server

This commit is contained in:
Valere 2020-12-11 15:06:22 +01:00
parent 43ac66feb3
commit bd9da8eaa6
20 changed files with 488 additions and 55 deletions

View File

@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
class RxSession(private val session: Session) {
@ -139,7 +140,7 @@ class RxSession(private val session: Session) {
}
fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = singleBuilder {
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
}

View File

@ -25,6 +25,7 @@ interface PermalinkService {
companion object {
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
}
/**

View File

@ -18,12 +18,15 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
/**
* This interface defines methods to get rooms. It's implemented at the session level.
@ -120,7 +123,7 @@ interface RoomService {
*/
fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable
callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable
/**
* Delete a room alias
@ -163,4 +166,13 @@ interface RoomService {
* @return a LiveData of the optional found room member
*/
fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>>
fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>)
/**
* Use this if you want to get information from a room that you are not yet in (or invited)
* It might be possible to get some information on this room if it is public or if guest access is allowed
* This call will try to gather some information on this room, but it could fail and get nothing more
*/
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
}

View File

@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
@ -35,10 +36,14 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
@ -55,6 +60,8 @@ internal class DefaultRoomService @Inject constructor(
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
private val roomIdByAliasTask: GetRoomIdByAliasTask,
private val deleteRoomAliasTask: DeleteRoomAliasTask,
private val resolveRoomStateTask: ResolveRoomStateTask,
private val peekRoomTask: PeekRoomTask,
private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource,
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
@ -119,7 +126,7 @@ internal class DefaultRoomService @Inject constructor(
.executeBy(taskExecutor)
}
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<String>>): Cancelable {
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable {
return roomIdByAliasTask
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
this.callback = callback
@ -154,4 +161,20 @@ internal class DefaultRoomService @Inject constructor(
results.firstOrNull().toOptional()
}
}
override fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) {
resolveRoomStateTask
.configureWith(ResolveRoomStateTask.Params(roomId)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) {
peekRoomTask
.configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
}

View File

@ -354,4 +354,7 @@ internal interface RoomAPI {
fun deleteTag(@Path("userId") userId: String,
@Path("roomId") roomId: String,
@Path("tag") tag: String): Call<Unit>
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
fun getRoomState(@Path("roomId") roomId: String) : Call<List<Event>>
}

View File

@ -57,6 +57,10 @@ import org.matrix.android.sdk.internal.session.room.membership.leaving.DefaultLe
import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
import org.matrix.android.sdk.internal.session.room.membership.threepid.DefaultInviteThreePidTask
import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask
import org.matrix.android.sdk.internal.session.room.peeking.DefaultPeekRoomTask
import org.matrix.android.sdk.internal.session.room.peeking.DefaultResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.read.DefaultMarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.room.read.DefaultSetReadMarkersTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
@ -223,4 +227,10 @@ internal abstract class RoomModule {
@Binds
abstract fun bindDeleteTagFromRoomTask(task: DefaultDeleteTagFromRoomTask): DeleteTagFromRoomTask
@Binds
abstract fun bindResolveRoomStateTask(task: DefaultResolveRoomStateTask): ResolveRoomStateTask
@Binds
abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask
}

View File

@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<String>> {
internal interface GetRoomIdByAliasTask : Task<GetRoomIdByAliasTask.Params, Optional<RoomAliasDescription>> {
data class Params(
val roomAlias: String,
val searchOnServer: Boolean
@ -42,21 +42,21 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
private val eventBus: EventBus
) : GetRoomIdByAliasTask {
override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional<String> {
override suspend fun execute(params: GetRoomIdByAliasTask.Params): Optional<RoomAliasDescription> {
var roomId = Realm.getInstance(monarchy.realmConfiguration).use {
RoomSummaryEntity.findByAlias(it, params.roomAlias)?.roomId
}
return if (roomId != null) {
Optional.from(roomId)
Optional.from(RoomAliasDescription(roomId))
} else if (!params.searchOnServer) {
Optional.from<String>(null)
Optional.from<RoomAliasDescription>(null)
} else {
roomId = tryOrNull("## Failed to get roomId from alias") {
val description = tryOrNull("## Failed to get roomId from alias") {
executeRequest<RoomAliasDescription>(eventBus) {
apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias)
}
}?.roomId
Optional.from(roomId)
}
Optional.from(description)
}
}
}

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class RoomAliasDescription(
data class RoomAliasDescription(
/**
* The room ID for this alias.
*/

View File

@ -0,0 +1,173 @@
/*
* 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.room.peeking
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
sealed class PeekResult {
data class Success(
val roomId: String,
val alias: String?,
val name: String?,
val topic: String?,
val avatarUrl: String?,
val numJoinedMembers: Int?,
val viaServers: List<String>
) : PeekResult()
data class PeekingNotAllowed(
val roomId: String,
val alias: String?,
val viaServers: List<String>
) : PeekResult()
object UnknownAlias : PeekResult()
}
internal interface PeekRoomTask : Task<PeekRoomTask.Params, PeekResult> {
data class Params(
val roomIdOrAlias: String
)
}
internal class DefaultPeekRoomTask @Inject constructor(
private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
private val getPublicRoomTask: GetPublicRoomTask,
private val resolveRoomStateTask: ResolveRoomStateTask
) : PeekRoomTask {
override suspend fun execute(params: PeekRoomTask.Params): PeekResult {
val roomId: String?
val serverList: List<String>
val isAlias: Boolean
if (MatrixPatterns.isRoomAlias(params.roomIdOrAlias)) {
isAlias = true
// get alias description
val aliasDescription = getRoomIdByAliasTask
.execute(GetRoomIdByAliasTask.Params(params.roomIdOrAlias, true))
.getOrNull()
?: return PeekResult.UnknownAlias
roomId = aliasDescription.roomId
serverList = aliasDescription.servers
} else {
isAlias = false
roomId = params.roomIdOrAlias
serverList = emptyList()
}
// Is it a public room?
val publicRepoResult = when (getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))) {
RoomDirectoryVisibility.PRIVATE -> {
// We cannot resolve this room :/
null
}
RoomDirectoryVisibility.PUBLIC -> {
// Try to find it in directory
val filter = if (isAlias) PublicRoomsFilter(searchTerm = params.roomIdOrAlias.substring(1))
else null
getPublicRoomTask.execute(GetPublicRoomTask.Params(
server = serverList.firstOrNull(),
publicRoomsParams = PublicRoomsParams(
filter = filter,
limit = 20.takeIf { filter != null } ?: 100
)
)).chunk?.firstOrNull { it.roomId == roomId }
}
}
if (publicRepoResult != null) {
return PeekResult.Success(
roomId = roomId,
alias = publicRepoResult.getPrimaryAlias() ?: params.roomIdOrAlias.takeIf { isAlias },
avatarUrl = publicRepoResult.avatarUrl,
name = publicRepoResult.name,
topic = publicRepoResult.topic,
numJoinedMembers = publicRepoResult.numJoinedMembers,
viaServers = serverList
)
}
// mm... try to peek state ? maybe the room is not public but yet allow guest to get events?
// this could be slow
try {
val stateEvents = resolveRoomStateTask
.execute(ResolveRoomStateTask.Params(roomId))
val name = stateEvents.lastOrNull {
it.type == EventType.STATE_ROOM_NAME
&& it.stateKey == ""
}?.let { it.content?.toModel<RoomNameContent>()?.name }
val topic = stateEvents.lastOrNull {
it.type == EventType.STATE_ROOM_TOPIC
&& it.stateKey == ""
}?.let { it.content?.toModel<RoomTopicContent>()?.topic }
val avatarUrl = stateEvents.lastOrNull {
it.type == EventType.STATE_ROOM_AVATAR
}?.let { it.content?.toModel<RoomAvatarContent>()?.avatarUrl }
val alias = stateEvents.lastOrNull {
it.type == EventType.STATE_ROOM_CANONICAL_ALIAS
}?.let {
it.content?.toModel<RoomCanonicalAliasContent>()?.canonicalAlias
?: it.content?.toModel<RoomCanonicalAliasContent>()?.alternativeAliases?.firstOrNull()
}
// not sure if it's the right way to do that :/
val memberCount = stateEvents.filter {
it.type == EventType.STATE_ROOM_MEMBER
&& it.stateKey?.isNotEmpty() == true
}.distinctBy { it.stateKey }
.count()
return PeekResult.Success(
roomId = roomId,
alias = alias,
avatarUrl = avatarUrl,
name = name,
topic = topic,
numJoinedMembers = memberCount,
viaServers = serverList
)
} catch (failure: Throwable) {
// Would be M_FORBIDDEN if cannot peek :/
// User XXX not in room !XXX, and room previews are disabled
return PeekResult.PeekingNotAllowed(
roomId = roomId,
alias = params.roomIdOrAlias.takeIf { isAlias },
viaServers = serverList
)
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.room.peeking
import org.greenrobot.eventbus.EventBus
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.room.RoomAPI
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface ResolveRoomStateTask : Task<ResolveRoomStateTask.Params, List<Event>> {
data class Params(
val roomId: String
)
}
internal class DefaultResolveRoomStateTask @Inject constructor(
private val roomAPI: RoomAPI,
private val eventBus: EventBus
) : ResolveRoomStateTask {
override suspend fun execute(params: ResolveRoomStateTask.Params): List<Event> {
return executeRequest(eventBus) {
apiCall = roomAPI.getRoomState(params.roomId)
}
}
}

View File

@ -199,6 +199,11 @@
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="matrix.to" />
<data android:scheme="element"
android:host="user"/>
<data android:scheme="element"
android:host="room"/>
</intent-filter>
</activity>

View File

@ -126,9 +126,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
.observe()
.subscribe { sharedAction ->
when (sharedAction) {
is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START)
is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
is HomeActivitySharedAction.OpenGroup -> {
is HomeActivitySharedAction.OpenGroup -> {
drawerLayout.closeDrawer(GravityCompat.START)
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
}
@ -145,9 +145,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
homeActivityViewModel.observeViewEvents {
when (it) {
is HomeActivityViewEvents.AskPasswordToInitCrossSigning -> handleAskPasswordToInitCrossSigning(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
is HomeActivityViewEvents.OnNewSession -> handleOnNewSession(it)
HomeActivityViewEvents.PromptToEnableSessionPush -> handlePromptToEnablePush()
is HomeActivityViewEvents.OnCrossSignedInvalidated -> handleCrossSigningInvalidated(it)
}.exhaustive
}
homeActivityViewModel.subscribe(this) { renderState(it) }
@ -162,9 +162,27 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private fun handleIntent(intent: Intent?) {
intent?.dataString?.let { deepLink ->
if (!deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) return@let
val resolvedLink = if (deepLink.startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
deepLink
} else if (deepLink.startsWith(PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE)) {
// This is a bit hugly, but for now just convert to matrix.to link for compatibility
val service = activeSessionHolder.getSafeActiveSession()?.permalinkService()
val roomLinkPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}room/"
val userPrefix = "${PermalinkService.MATRIX_TO_CUSTOM_SCHEME_URL_BASE}user/"
when {
deepLink.startsWith(userPrefix) -> {
val userId = deepLink.substring(userPrefix.length)
service?.createPermalink(userId)
}
deepLink.startsWith(roomLinkPrefix) -> {
val param = deepLink.substring(roomLinkPrefix.length)
service?.createRoomPermalink(param)
}
else -> null
}
} else null
permalinkHandler.launch(this, deepLink,
permalinkHandler.launch(this, resolvedLink,
navigationInterceptor = this,
buildTask = true)
// .delay(500, TimeUnit.MILLISECONDS)
@ -180,7 +198,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> {
is InitialSyncProgressService.Status.Idle -> {
waiting_view.isVisible = false
}
is InitialSyncProgressService.Status.Progressing -> {

View File

@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkData
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.rx.rx
import javax.inject.Inject
@ -76,7 +77,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
buildTask: Boolean
): Single<Boolean> {
return when (permalinkData) {
is PermalinkData.RoomLink -> {
is PermalinkData.RoomLink -> {
permalinkData.getRoomId()
.observeOn(AndroidSchedulers.mainThread())
.map {
@ -92,11 +93,11 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
true
}
}
is PermalinkData.GroupLink -> {
is PermalinkData.GroupLink -> {
navigator.openGroupDetail(permalinkData.groupId, context, buildTask)
Single.just(true)
}
is PermalinkData.UserLink -> {
is PermalinkData.UserLink -> {
if (navigationInterceptor?.navToMemberProfile(permalinkData.userId, rawLink) != true) {
navigator.openRoomMemberProfile(userId = permalinkData.userId, roomId = null, context = context, buildTask = buildTask)
}
@ -111,7 +112,7 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
private fun PermalinkData.RoomLink.getRoomId(): Single<Optional<String>> {
val session = activeSessionHolder.getSafeActiveSession()
return if (isRoomAlias && session != null) {
session.rx().getRoomIdByAlias(roomIdOrAlias, true).subscribeOn(Schedulers.io())
session.rx().getRoomIdByAlias(roomIdOrAlias, true).map { it.getOrNull()?.roomId.toOptional() }.subscribeOn(Schedulers.io())
} else {
Single.just(Optional.from(roomIdOrAlias))
}
@ -149,16 +150,28 @@ class PermalinkHandler @Inject constructor(private val activeSessionHolder: Acti
navigator.openRoom(context, roomId, eventId, buildTask)
}
else -> {
val roomPreviewData = RoomPreviewData(
roomId = roomId,
eventId = eventId,
roomAlias = roomAlias ?: roomSummary?.canonicalAlias,
roomName = roomSummary?.displayName,
avatarUrl = roomSummary?.avatarUrl,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
if (roomSummary == null) {
// we don't know this room, try to peek
val roomPreviewData = RoomPreviewData(
roomId = roomId,
roomAlias = roomAlias,
peekFromServer = true,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
} else {
val roomPreviewData = RoomPreviewData(
roomId = roomId,
eventId = eventId,
roomAlias = roomAlias ?: roomSummary.canonicalAlias,
roomName = roomSummary.displayName,
avatarUrl = roomSummary.avatarUrl,
buildTask = buildTask,
homeServers = permalinkData.viaParameters
)
navigator.openRoomPreview(context, roomPreviewData)
}
}
}
}

View File

@ -130,7 +130,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy<Ava
private fun displayNextIfPossible() {
val currentActivity = weakCurrentActivity?.get()
if (Alerter.isShowing || currentActivity == null) {
if (Alerter.isShowing || currentActivity == null || currentActivity.isDestroyed) {
// will retry later
return
}

View File

@ -40,6 +40,7 @@ data class RoomPreviewData(
val worldReadable: Boolean = false,
val avatarUrl: String? = null,
val homeServers: List<String> = emptyList(),
val peekFromServer: Boolean = false,
val buildTask: Boolean = false
) : Parcelable {
val matrixItem: MatrixItem

View File

@ -20,6 +20,8 @@ import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.transition.TransitionManager
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@ -30,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.roomdirectory.JoinState
import kotlinx.android.synthetic.main.fragment_room_preview_no_preview.*
import org.matrix.android.sdk.api.util.MatrixItem
import javax.inject.Inject
/**
@ -48,22 +51,6 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupToolbar(roomPreviewNoPreviewToolbar)
val titleText = roomPreviewData.roomName ?: roomPreviewData.roomAlias ?: roomPreviewData.roomId
// Toolbar
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewToolbarAvatar)
roomPreviewNoPreviewToolbarTitle.text = titleText
// Screen
avatarRenderer.render(roomPreviewData.matrixItem, roomPreviewNoPreviewAvatar)
roomPreviewNoPreviewName.text = titleText
roomPreviewNoPreviewTopic.setTextOrHide(roomPreviewData.topic)
if (roomPreviewData.worldReadable) {
roomPreviewNoPreviewLabel.setText(R.string.room_preview_world_readable_room_not_supported_yet)
} else {
roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview)
}
roomPreviewNoPreviewJoin.callback = object : ButtonStateView.Callback {
override fun onButtonClicked() {
@ -100,7 +87,62 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
// Quit this screen
requireActivity().finish()
// Open room
navigator.openRoom(requireActivity(), roomPreviewData.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
navigator.openRoom(requireActivity(), state.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
}
val bestName = state.roomName ?: state.roomAlias ?: state.roomId
when (state.peekingState) {
is Loading -> {
roomPreviewPeekingProgress.isVisible = true
roomPreviewNoPreviewJoin.isVisible = false
}
is Success -> {
roomPreviewPeekingProgress.isVisible = false
when (state.peekingState.invoke()) {
PeekingState.FOUND -> {
// show join buttons
roomPreviewNoPreviewJoin.isVisible = true
renderState(bestName, state.matrixItem(), state.roomTopic)
}
PeekingState.NO_ACCESS -> {
roomPreviewNoPreviewJoin.isVisible = true
roomPreviewNoPreviewLabel.isVisible = true
roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join)
renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic)
}
else -> {
roomPreviewNoPreviewJoin.isVisible = false
roomPreviewNoPreviewLabel.isVisible = true
roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found)
renderState(bestName, null, state.roomTopic)
}
}
}
else -> {
// Render with initial state, no peeking
roomPreviewPeekingProgress.isVisible = false
roomPreviewNoPreviewJoin.isVisible = true
renderState(bestName, state.matrixItem(), state.roomTopic)
roomPreviewNoPreviewLabel.isVisible = false
}
}
}
private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) {
// Toolbar
if (matrixItem != null) {
roomPreviewNoPreviewToolbarAvatar.isVisible = true
roomPreviewNoPreviewAvatar.isVisible = true
avatarRenderer.render(matrixItem, roomPreviewNoPreviewToolbarAvatar)
avatarRenderer.render(matrixItem, roomPreviewNoPreviewAvatar)
} else {
roomPreviewNoPreviewToolbarAvatar.isVisible = false
roomPreviewNoPreviewAvatar.isVisible = false
}
roomPreviewNoPreviewToolbarTitle.text = roomName
// Screen
roomPreviewNoPreviewName.text = roomName
roomPreviewNoPreviewTopic.setTextOrHide(topic)
}
}

View File

@ -16,8 +16,11 @@
package im.vector.app.features.roomdirectory.roompreview
import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
@ -25,12 +28,17 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.EmptyViewEvents
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.roomdirectory.JoinState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.internal.session.room.peeking.PeekResult
import org.matrix.android.sdk.internal.util.awaitCallback
import org.matrix.android.sdk.rx.rx
import timber.log.Timber
@ -56,6 +64,52 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
// Observe joined room (from the sync)
observeRoomSummary()
observeMembershipChanges()
if (initialState.shouldPeekFromServer) {
setState {
copy(peekingState = Loading())
}
viewModelScope.launch(Dispatchers.IO) {
val peekResult = tryOrNull {
awaitCallback<PeekResult> {
session.peekRoom(initialState.roomAlias ?: initialState.roomId, it)
}
}
when (peekResult) {
is PeekResult.Success -> {
setState {
copy(
roomId = peekResult.roomId,
avatarUrl = peekResult.avatarUrl,
roomAlias = peekResult.alias ?: initialState.roomAlias,
roomTopic = peekResult.topic,
homeServers = peekResult.viaServers,
peekingState = Success(PeekingState.FOUND)
)
}
}
is PeekResult.PeekingNotAllowed -> {
setState {
copy(
roomId = peekResult.roomId,
roomAlias = peekResult.alias ?: initialState.roomAlias,
homeServers = peekResult.viaServers,
peekingState = Success(PeekingState.NO_ACCESS)
)
}
}
PeekResult.UnknownAlias,
null -> {
setState {
copy(
peekingState = Success(PeekingState.NOT_FOUND)
)
}
}
}
}
}
}
private fun observeRoomSummary() {
@ -82,7 +136,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
.subscribe {
val changeMembership = it[initialState.roomId] ?: ChangeMembershipState.Unknown
val joinState = when (changeMembership) {
is ChangeMembershipState.Joining -> JoinState.JOINING
is ChangeMembershipState.Joining -> JoinState.JOINING
is ChangeMembershipState.FailedJoining -> JoinState.JOINING_ERROR
// Other cases are handled by room summary
else -> null

View File

@ -16,13 +16,31 @@
package im.vector.app.features.roomdirectory.roompreview
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.features.roomdirectory.JoinState
import org.matrix.android.sdk.api.util.MatrixItem
enum class PeekingState {
UNKNOWN,
FOUND,
NOT_FOUND,
NO_ACCESS
}
data class RoomPreviewViewState(
val peekingState: Async<PeekingState> = Uninitialized,
// The room id
val roomId: String = "",
val roomAlias: String? = null,
val roomName: String? = null,
val roomTopic: String? = null,
val avatarUrl: String? = null,
val shouldPeekFromServer: Boolean = false,
/**
* Can be empty when the server is the current user's home server.
*/
@ -36,6 +54,14 @@ data class RoomPreviewViewState(
constructor(args: RoomPreviewData) : this(
roomId = args.roomId,
roomAlias = args.roomAlias,
homeServers = args.homeServers
homeServers = args.homeServers,
roomName = args.roomName,
roomTopic = args.topic,
avatarUrl = args.avatarUrl,
shouldPeekFromServer = args.peekFromServer
)
fun matrixItem() : MatrixItem {
return MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
}
}

View File

@ -54,6 +54,14 @@
</androidx.appcompat.widget.Toolbar>
<ProgressBar
android:id="@+id/roomPreviewPeekingProgress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="14dp"
android:layout_gravity="center"
android:background="?riotx_header_panel_background"
android:indeterminate="true" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
@ -71,7 +79,7 @@
android:id="@+id/roomPreviewNoPreviewAvatar"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_marginTop="123dp"
android:layout_marginTop="60dp"
tools:src="@tools:sample/avatars" />
<TextView

View File

@ -1711,7 +1711,8 @@
<string name="room_preview_no_preview">"This room can't be previewed"</string>
<string name="room_preview_world_readable_room_not_supported_yet">"The preview of world-readable room is not supported yet in Element"</string>
<string name="room_preview_not_found">This room is not accessible at this time.\nTry again later, or ask a room admin to check if you have access.</string>
<string name="room_preview_no_preview_join">"This room can't be previewed. Do you want to join it?"</string>
<string name="fab_menu_create_room">"Rooms"</string>
<string name="fab_menu_create_chat">"Direct Messages"</string>