mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge pull request #3739 from vector-im/feature/bca/accept_unbound_3pid_invite
support email invite
This commit is contained in:
commit
65c8ae3597
28 changed files with 512 additions and 33 deletions
1
changelog.d/3691.feature
Normal file
1
changelog.d/3691.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Support accept 3pid invite when email is not bound to account
|
1
changelog.d/3695.feature
Normal file
1
changelog.d/3695.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Update Email invite to be aware of spaces
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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
|
||||
|
||||
import org.junit.Assert
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
|
||||
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class PermalinkParserTest {
|
||||
|
||||
@Test
|
||||
fun testParseEmailInvite() {
|
||||
val rawInvite = """
|
||||
https://app.element.io/#/room/%21MRBNLPtFnMAazZVPMO%3Amatrix.org?email=bob%2Bspace%40example.com&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3DXmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe%26private_key%3DT2gq0c3kJB_8OroXVxl1pBnzHsN7V6Xn4bEBSeW1ep4&room_name=Team2&room_avatar_url=&inviter_name=hiphop5&guest_access_token=&guest_user_id=
|
||||
""".trimIndent()
|
||||
.replace("https://app.element.io/#/room/", "https://matrix.to/#/")
|
||||
|
||||
val parsedLink = PermalinkParser.parse(rawInvite)
|
||||
Assert.assertTrue("Should be parsed as email invite but was ${parsedLink::class.java}", parsedLink is PermalinkData.RoomEmailInviteLink)
|
||||
parsedLink as PermalinkData.RoomEmailInviteLink
|
||||
Assert.assertEquals("!MRBNLPtFnMAazZVPMO:matrix.org", parsedLink.roomId)
|
||||
Assert.assertEquals("XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", parsedLink.token)
|
||||
Assert.assertEquals("vector.im", parsedLink.identityServer)
|
||||
Assert.assertEquals("Team2", parsedLink.roomName)
|
||||
Assert.assertEquals("hiphop5", parsedLink.inviterName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseLinkWIthEvent() {
|
||||
val rawInvite = "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io"
|
||||
|
||||
val parsedLink = PermalinkParser.parse(rawInvite)
|
||||
Assert.assertTrue("Should be parsed as room link", parsedLink is PermalinkData.RoomLink)
|
||||
parsedLink as PermalinkData.RoomLink
|
||||
Assert.assertEquals("!OGEhHVWSdvArJzumhm:matrix.org", parsedLink.roomIdOrAlias)
|
||||
Assert.assertEquals("\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc", parsedLink.eventId)
|
||||
Assert.assertEquals(3, parsedLink.viaParameters.size)
|
||||
Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io"))
|
||||
Assert.assertTrue(parsedLink.viaParameters.contains("matrix.org"))
|
||||
Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io"))
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.identity
|
||||
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
|
||||
/**
|
||||
* Provides access to the identity server configuration and services identity server can provide
|
||||
*/
|
||||
|
@ -121,6 +123,18 @@ interface IdentityService {
|
|||
*/
|
||||
suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState>
|
||||
|
||||
/**
|
||||
* When one performs a 3pid invite and the third party identifier is unknown, the home server
|
||||
* will store the invitation in the Identity server and store some information in the room state membership event.
|
||||
* The email invite will contains the token and secret that can be used to claim the stored invitation
|
||||
*
|
||||
* To aid clients who may not be able to perform crypto themselves,
|
||||
* the identity server offers some crypto functionality to help in accepting invitations.
|
||||
* This is less secure than the client doing it itself, but may be useful where this isn't possible.
|
||||
*/
|
||||
suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String) : SignInvitationResult
|
||||
|
||||
fun addListener(listener: IdentityServiceListener)
|
||||
|
||||
fun removeListener(listener: IdentityServiceListener)
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.matrix.android.sdk.api.session.permalinks
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* This sealed class represents all the permalink cases.
|
||||
|
@ -31,6 +33,25 @@ sealed class PermalinkData {
|
|||
val viaParameters: List<String>
|
||||
) : PermalinkData()
|
||||
|
||||
/**
|
||||
* &room_name=Team2
|
||||
&room_avatar_url=mxc:
|
||||
&inviter_name=bob
|
||||
*/
|
||||
@Parcelize
|
||||
data class RoomEmailInviteLink(
|
||||
val roomId: String,
|
||||
val email: String,
|
||||
val signUrl: String,
|
||||
val roomName: String?,
|
||||
val roomAvatarUrl: String?,
|
||||
val inviterName: String?,
|
||||
val identityServer: String,
|
||||
val token: String,
|
||||
val privateKey: String,
|
||||
val roomType: String?
|
||||
) : PermalinkData(), Parcelable
|
||||
|
||||
data class UserLink(val userId: String) : PermalinkData()
|
||||
|
||||
data class GroupLink(val groupId: String) : PermalinkData()
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.api.session.permalinks
|
|||
import android.net.Uri
|
||||
import android.net.UrlQuerySanitizer
|
||||
import org.matrix.android.sdk.api.MatrixPatterns
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
||||
/**
|
||||
* This class turns an uri to a [PermalinkData]
|
||||
|
@ -35,12 +37,15 @@ object PermalinkParser {
|
|||
|
||||
/**
|
||||
* Turns an uri to a [PermalinkData]
|
||||
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
|
||||
*/
|
||||
fun parse(uri: Uri): PermalinkData {
|
||||
if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
val fragment = uri.fragment
|
||||
// We can't use uri.fragment as it is decoding to early and it will break the parsing
|
||||
// of parameters that represents url (like signurl)
|
||||
val fragment = uri.toString().substringAfter("#") // uri.fragment
|
||||
if (fragment.isNullOrEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
|
@ -51,6 +56,7 @@ object PermalinkParser {
|
|||
val params = safeFragment
|
||||
.split(MatrixPatterns.SEP_REGEX)
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { URLDecoder.decode(it, "UTF-8") }
|
||||
.take(2)
|
||||
|
||||
val identifier = params.getOrNull(0)
|
||||
|
@ -60,12 +66,7 @@ object PermalinkParser {
|
|||
MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier)
|
||||
MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier)
|
||||
MatrixPatterns.isRoomId(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = false,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
|
||||
viaParameters = viaQueryParameters
|
||||
)
|
||||
handleRoomIdCase(fragment, identifier, uri, extraParameter, viaQueryParameters)
|
||||
}
|
||||
MatrixPatterns.isRoomAlias(identifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
|
@ -79,13 +80,59 @@ object PermalinkParser {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData {
|
||||
// Can't rely on built in parsing because it's messing around the signurl
|
||||
val paramList = safeExtractParams(fragment)
|
||||
val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second
|
||||
val email = paramList.firstOrNull { it.first == "email" }?.second
|
||||
return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) {
|
||||
try {
|
||||
val signValidUri = Uri.parse(signUrl)
|
||||
val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException()
|
||||
val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException()
|
||||
val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException()
|
||||
PermalinkData.RoomEmailInviteLink(
|
||||
roomId = identifier,
|
||||
email = email!!,
|
||||
signUrl = signUrl!!,
|
||||
roomName = paramList.firstOrNull { it.first == "room_name" }?.second,
|
||||
inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second,
|
||||
roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second,
|
||||
roomType = paramList.firstOrNull { it.first == "room_type" }?.second,
|
||||
identityServer = identityServerHost,
|
||||
token = token,
|
||||
privateKey = privateKey
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.i("## Permalink: Failed to parse permalink $signUrl")
|
||||
PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
} else {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = false,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
|
||||
viaParameters = viaQueryParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun safeExtractParams(fragment: String) = fragment.substringAfter("?").split('&').mapNotNull {
|
||||
val splitNameValue = it.split("=")
|
||||
if (splitNameValue.size == 2) {
|
||||
Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8"))
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun String.getViaParameters(): List<String> {
|
||||
return UrlQuerySanitizer(this)
|
||||
.parameterList
|
||||
.filter {
|
||||
it.mParameter == "via"
|
||||
}.map {
|
||||
it.mValue
|
||||
it.mValue.let {
|
||||
URLDecoder.decode(it, "UTF-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
|||
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||
|
||||
/**
|
||||
|
@ -63,6 +64,18 @@ interface RoomService {
|
|||
reason: String? = null,
|
||||
viaServers: List<String> = emptyList())
|
||||
|
||||
/**
|
||||
* @param roomId the roomId of the room to join
|
||||
* @param reason optional reason for joining the room
|
||||
* @param thirdPartySigned A signature of an m.third_party_invite token to prove that this user owns a third party identity
|
||||
* which has been invited to the room.
|
||||
*/
|
||||
suspend fun joinRoom(
|
||||
roomId: String,
|
||||
reason: String? = null,
|
||||
thirdPartySigned: SignInvitationResult
|
||||
)
|
||||
|
||||
/**
|
||||
* Get a room from a roomId
|
||||
* @param roomId the roomId to look for.
|
||||
|
|
|
@ -30,6 +30,7 @@ internal object NetworkConstants {
|
|||
// Identity server
|
||||
const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2"
|
||||
const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/"
|
||||
const val URI_IDENTITY_PATH_V1 = "_matrix/identity/api/v1/"
|
||||
|
||||
// Push Gateway
|
||||
const val URI_PUSH_GATEWAY_PREFIX_PATH = "_matrix/push/v1/"
|
||||
|
|
|
@ -52,6 +52,7 @@ import kotlinx.coroutines.withContext
|
|||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
@ -79,6 +80,7 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
private val identityApiProvider: IdentityApiProvider,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||
private val sign3pidInvitationTask: DefaultSign3pidInvitationTask,
|
||||
private val sessionParams: SessionParams
|
||||
) : IdentityService, SessionLifecycleObserver {
|
||||
|
||||
|
@ -290,6 +292,14 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
return token.token
|
||||
}
|
||||
|
||||
override suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String): SignInvitationResult {
|
||||
return sign3pidInvitationTask.execute(Sign3pidInvitationTask.Params(
|
||||
url = identiyServer,
|
||||
token = token,
|
||||
privateKey = secret
|
||||
))
|
||||
}
|
||||
|
||||
override fun addListener(listener: IdentityServiceListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
|
|
@ -26,10 +26,12 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn
|
|||
import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody
|
||||
import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody
|
||||
import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* Ref: https://matrix.org/docs/spec/identity_service/latest
|
||||
|
@ -95,4 +97,16 @@ internal interface IdentityAPI {
|
|||
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken")
|
||||
suspend fun submitToken(@Path("medium") medium: String,
|
||||
@Body body: IdentityRequestOwnershipParams): SuccessResult
|
||||
|
||||
/**
|
||||
* https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519
|
||||
*
|
||||
* Have to rely on V1 for now
|
||||
*/
|
||||
@POST(NetworkConstants.URI_IDENTITY_PATH_V1 + "sign-ed25519")
|
||||
suspend fun signInvitationDetails(
|
||||
@Query("token") token: String,
|
||||
@Query("private_key") privateKey: String,
|
||||
@Query("mxid") mxid: String
|
||||
): SignInvitationResult
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.identity
|
||||
|
||||
import dagger.Lazy
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface Sign3pidInvitationTask : Task<Sign3pidInvitationTask.Params, SignInvitationResult> {
|
||||
data class Params(
|
||||
val token: String,
|
||||
val url: String,
|
||||
val privateKey: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultSign3pidInvitationTask @Inject constructor(
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
@UserId private val userId: String
|
||||
) : Sign3pidInvitationTask {
|
||||
|
||||
override suspend fun execute(params: Sign3pidInvitationTask.Params): SignInvitationResult {
|
||||
val identityAPI = retrofitFactory
|
||||
.create(okHttpClient, "https://${params.url}")
|
||||
.create(IdentityAPI::class.java)
|
||||
return identityAPI.signInvitationDetails(params.token, params.privateKey, userId)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.identity.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class SignInvitationBody(
|
||||
/**The Matrix user ID of the user accepting the invitation.*/
|
||||
val mxid: String,
|
||||
/**The token from the call to store- invite..*/
|
||||
val token: String,
|
||||
/** The private key, encoded as Unpadded base64. */
|
||||
val private_key: String
|
||||
)
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* 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.identity.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class SignInvitationResult(
|
||||
/** The Matrix user ID of the user accepting the invitation.*/
|
||||
val mxid: String,
|
||||
/** The Matrix user ID of the user who sent the invitation.*/
|
||||
val sender: String,
|
||||
/**The token from the call to store- invite..*/
|
||||
val signatures: Map<String, *>,
|
||||
/** The token for the invitation */
|
||||
val token: String
|
||||
)
|
|
@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.util.toOptional
|
|||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
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
|
||||
|
@ -122,6 +123,12 @@ internal class DefaultRoomService @Inject constructor(
|
|||
joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: String,
|
||||
reason: String?,
|
||||
thirdPartySigned: SignInvitationResult) {
|
||||
joinRoomTask.execute(JoinRoomTask.Params(roomId, reason, thirdPartySigned = thirdPartySigned))
|
||||
}
|
||||
|
||||
override suspend fun markAllAsRead(roomIds: List<String>) {
|
||||
markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
|
||||
}
|
||||
|
|
|
@ -254,7 +254,7 @@ internal interface RoomAPI {
|
|||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
|
||||
suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
|
||||
@Query("server_name") viaServers: List<String>,
|
||||
@Body params: Map<String, String?>): JoinRoomResponse
|
||||
@Body params: JsonDict): JoinRoomResponse
|
||||
|
||||
/**
|
||||
* Leave the given room.
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.space.SpaceService
|
|||
import org.matrix.android.sdk.internal.session.DefaultFileService
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.directory.DirectoryAPI
|
||||
import org.matrix.android.sdk.internal.session.identity.DefaultSign3pidInvitationTask
|
||||
import org.matrix.android.sdk.internal.session.identity.Sign3pidInvitationTask
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.DefaultUpdateRoomAccountDataTask
|
||||
import org.matrix.android.sdk.internal.session.room.accountdata.UpdateRoomAccountDataTask
|
||||
import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask
|
||||
|
@ -248,4 +250,7 @@ internal abstract class RoomModule {
|
|||
|
||||
@Binds
|
||||
abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room.membership.joining
|
|||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
|
@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.database.query.where
|
|||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||
import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
|
||||
|
@ -40,7 +42,8 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
|
|||
data class Params(
|
||||
val roomIdOrAlias: String,
|
||||
val reason: String?,
|
||||
val viaServers: List<String> = emptyList()
|
||||
val viaServers: List<String> = emptyList(),
|
||||
val thirdPartySigned : SignInvitationResult? = null
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -59,12 +62,16 @@ internal class DefaultJoinRoomTask @Inject constructor(
|
|||
return
|
||||
}
|
||||
roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining)
|
||||
val extraParams = mutableMapOf<String, Any>().apply {
|
||||
params.reason?.let { this["reason"] = it }
|
||||
params.thirdPartySigned?.let { this["third_party_signed"] = it.toContent() }
|
||||
}
|
||||
val joinRoomResponse = try {
|
||||
executeRequest(globalErrorReceiver) {
|
||||
roomAPI.join(
|
||||
roomIdOrAlias = params.roomIdOrAlias,
|
||||
viaServers = params.viaServers.take(3),
|
||||
params = mapOf("reason" to params.reason)
|
||||
params = extraParams
|
||||
)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
|
|
|
@ -183,6 +183,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor(
|
|||
// not yet supported
|
||||
_viewEvents.post(MatrixToViewEvents.Dismiss)
|
||||
}
|
||||
is PermalinkData.RoomEmailInviteLink,
|
||||
is PermalinkData.FallbackLink -> {
|
||||
_viewEvents.post(MatrixToViewEvents.Dismiss)
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ import im.vector.app.features.widgets.WidgetActivity
|
|||
import im.vector.app.features.widgets.WidgetArgsBuilder
|
||||
import im.vector.app.space
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
|
@ -249,7 +250,7 @@ class DefaultNavigator @Inject constructor(
|
|||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData) {
|
||||
override fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData, fromEmailInviteLink: PermalinkData.RoomEmailInviteLink?) {
|
||||
val intent = RoomPreviewActivity.newIntent(context, roomPreviewData)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.app.features.roomdirectory.RoomDirectoryData
|
|||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.share.SharedData
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
|
@ -65,7 +66,7 @@ interface Navigator {
|
|||
|
||||
fun openRoomPreview(context: Context, publicRoom: PublicRoom, roomDirectoryData: RoomDirectoryData)
|
||||
|
||||
fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData)
|
||||
fun openRoomPreview(context: Context, roomPreviewData: RoomPreviewData, fromEmailInviteLink: PermalinkData.RoomEmailInviteLink? = null)
|
||||
|
||||
fun openMatrixToBottomSheet(context: Context, link: String)
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import im.vector.app.R
|
|||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.utils.toast
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.app.features.roomdirectory.roompreview.RoomPreviewData
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
@ -77,7 +78,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 {
|
||||
|
@ -94,19 +95,30 @@ 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)
|
||||
}
|
||||
Single.just(true)
|
||||
}
|
||||
is PermalinkData.FallbackLink -> {
|
||||
is PermalinkData.FallbackLink -> {
|
||||
Single.just(false)
|
||||
}
|
||||
is PermalinkData.RoomEmailInviteLink -> {
|
||||
val data = RoomPreviewData(
|
||||
roomId = permalinkData.roomId,
|
||||
roomName = permalinkData.roomName,
|
||||
avatarUrl = permalinkData.roomAvatarUrl,
|
||||
fromEmailInvite = permalinkData,
|
||||
roomType = permalinkData.roomType
|
||||
)
|
||||
navigator.openRoomPreview(context, data)
|
||||
Single.just(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction
|
|||
|
||||
sealed class RoomPreviewAction : VectorViewModelAction {
|
||||
object Join : RoomPreviewAction()
|
||||
object JoinThirdParty : RoomPreviewAction()
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.app.core.platform.VectorBaseActivity
|
|||
import im.vector.app.databinding.ActivitySimpleBinding
|
||||
import im.vector.app.features.roomdirectory.RoomDirectoryData
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import timber.log.Timber
|
||||
|
@ -37,12 +38,14 @@ data class RoomPreviewData(
|
|||
val eventId: String? = null,
|
||||
val roomName: String? = null,
|
||||
val roomAlias: String? = null,
|
||||
val roomType: String? = null,
|
||||
val topic: String? = null,
|
||||
val worldReadable: Boolean = false,
|
||||
val avatarUrl: String? = null,
|
||||
val homeServers: List<String> = emptyList(),
|
||||
val peekFromServer: Boolean = false,
|
||||
val buildTask: Boolean = false
|
||||
val buildTask: Boolean = false,
|
||||
val fromEmailInvite: PermalinkData.RoomEmailInviteLink? = null
|
||||
) : Parcelable {
|
||||
val matrixItem: MatrixItem
|
||||
get() = MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
|
||||
|
|
|
@ -16,12 +16,15 @@
|
|||
|
||||
package im.vector.app.features.roomdirectory.roompreview
|
||||
|
||||
import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.toSpannable
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.transition.TransitionManager
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
|
@ -31,10 +34,16 @@ import im.vector.app.R
|
|||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.platform.ButtonStateView
|
||||
import im.vector.app.core.platform.VectorBaseFragment
|
||||
import im.vector.app.core.utils.styleMatchingText
|
||||
import im.vector.app.core.utils.tappableMatchingText
|
||||
import im.vector.app.databinding.FragmentRoomPreviewNoPreviewBinding
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.navigation.Navigator
|
||||
import im.vector.app.features.roomdirectory.JoinState
|
||||
|
||||
import im.vector.app.features.settings.VectorSettingsActivity
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import me.gujun.android.span.span
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -61,7 +70,6 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
|
|||
}
|
||||
|
||||
override fun invalidate() = withState(roomPreviewViewModel) { state ->
|
||||
TransitionManager.beginDelayedTransition(views.coordinatorLayout)
|
||||
|
||||
views.roomPreviewNoPreviewJoin.render(
|
||||
when (state.roomJoinState) {
|
||||
|
@ -83,7 +91,11 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
|
|||
// Quit this screen
|
||||
requireActivity().finish()
|
||||
// Open room
|
||||
navigator.openRoom(requireActivity(), state.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
|
||||
if (state.roomType == RoomType.SPACE) {
|
||||
navigator.switchToSpace(requireActivity(), state.roomId, Navigator.PostSwitchSpaceAction.None)
|
||||
} else {
|
||||
navigator.openRoom(requireActivity(), state.roomId, roomPreviewData.eventId, roomPreviewData.buildTask)
|
||||
}
|
||||
}
|
||||
|
||||
val bestName = state.roomName ?: state.roomAlias ?: state.roomId
|
||||
|
@ -98,19 +110,57 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
|
|||
PeekingState.FOUND -> {
|
||||
// show join buttons
|
||||
views.roomPreviewNoPreviewJoin.isVisible = true
|
||||
renderState(bestName, state.matrixItem(), state.roomTopic)
|
||||
renderState(bestName, state.matrixItem(), state.roomTopic
|
||||
/**, state.roomType*/)
|
||||
if (state.fromEmailInvite != null && !state.isEmailBoundToAccount) {
|
||||
views.roomPreviewNoPreviewLabel.text =
|
||||
span {
|
||||
span {
|
||||
textColor = ThemeUtils.getColor(requireContext(), R.attr.vctr_content_primary)
|
||||
text = if (state.roomType == RoomType.SPACE) {
|
||||
getString(R.string.this_invite_to_this_space_was_sent, state.fromEmailInvite.email)
|
||||
} else {
|
||||
getString(R.string.this_invite_to_this_room_was_sent, state.fromEmailInvite.email)
|
||||
}
|
||||
.toSpannable()
|
||||
.styleMatchingText(state.fromEmailInvite.email, Typeface.BOLD)
|
||||
}
|
||||
+"\n"
|
||||
span {
|
||||
text = getString(
|
||||
R.string.link_this_email_with_your_account,
|
||||
getString(R.string.link_this_email_settings_link)
|
||||
)
|
||||
.toSpannable()
|
||||
.tappableMatchingText(getString(R.string.link_this_email_settings_link), object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
navigator.openSettings(
|
||||
requireContext(),
|
||||
VectorSettingsActivity.EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
views.roomPreviewNoPreviewLabel.movementMethod = LinkMovementMethod.getInstance()
|
||||
views.roomPreviewNoPreviewJoin.commonClicked = {
|
||||
roomPreviewViewModel.handle(RoomPreviewAction.JoinThirdParty)
|
||||
}
|
||||
}
|
||||
}
|
||||
PeekingState.NO_ACCESS -> {
|
||||
views.roomPreviewNoPreviewJoin.isVisible = true
|
||||
views.roomPreviewNoPreviewLabel.isVisible = true
|
||||
views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_no_preview_join)
|
||||
renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic)
|
||||
renderState(bestName, state.matrixItem().takeIf { state.roomAlias != null }, state.roomTopic
|
||||
/**, state.roomType*/)
|
||||
}
|
||||
else -> {
|
||||
views.roomPreviewNoPreviewJoin.isVisible = false
|
||||
views.roomPreviewNoPreviewLabel.isVisible = true
|
||||
views.roomPreviewNoPreviewLabel.setText(R.string.room_preview_not_found)
|
||||
renderState(bestName, null, state.roomTopic)
|
||||
renderState(bestName, null, state.roomTopic
|
||||
/**, state.roomType*/)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,13 +168,16 @@ class RoomPreviewNoPreviewFragment @Inject constructor(
|
|||
// Render with initial state, no peeking
|
||||
views.roomPreviewPeekingProgress.isVisible = false
|
||||
views.roomPreviewNoPreviewJoin.isVisible = true
|
||||
renderState(bestName, state.matrixItem(), state.roomTopic)
|
||||
renderState(bestName, state.matrixItem(), state.roomTopic
|
||||
/**, state.roomType*/)
|
||||
views.roomPreviewNoPreviewLabel.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?) {
|
||||
private fun renderState(roomName: String, matrixItem: MatrixItem?, topic: String?
|
||||
/**, roomType: String?*/
|
||||
) {
|
||||
// Toolbar
|
||||
if (matrixItem != null) {
|
||||
views.roomPreviewNoPreviewToolbarAvatar.isVisible = true
|
||||
|
|
|
@ -23,8 +23,8 @@ import com.airbnb.mvrx.MvRxViewModelFactory
|
|||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.EmptyViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
|
@ -34,6 +34,8 @@ import kotlinx.coroutines.launch
|
|||
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.identity.SharedState
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
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.peeking.PeekResult
|
||||
|
@ -64,7 +66,39 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
observeRoomSummary()
|
||||
observeMembershipChanges()
|
||||
|
||||
if (initialState.shouldPeekFromServer) {
|
||||
if (initialState.fromEmailInvite != null) {
|
||||
setState {
|
||||
copy(peekingState = Loading())
|
||||
}
|
||||
viewModelScope.launch {
|
||||
// we might want to check if the mail is bound to this account?
|
||||
// if it is the invite
|
||||
val threePids = session
|
||||
.getThreePids()
|
||||
.filterIsInstance<ThreePid.Email>()
|
||||
|
||||
val status = if (threePids.indexOfFirst { it.email == initialState.fromEmailInvite.email } != -1) {
|
||||
try {
|
||||
session.identityService().getShareStatus(threePids)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.w(failure, "## Room Invite: Failed to get 3pids shared status")
|
||||
// If terms not signed, or no identity server setup, or different
|
||||
// id server from the one in the email invite, we consider the mails as not bound
|
||||
emptyMap()
|
||||
}.firstNotNullOfOrNull { if (it.key.value == initialState.fromEmailInvite.email) it.value else null }
|
||||
?: SharedState.NOT_SHARED
|
||||
} else {
|
||||
SharedState.NOT_SHARED
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
isEmailBoundToAccount = status == SharedState.SHARED,
|
||||
peekingState = Success(PeekingState.FOUND)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (initialState.shouldPeekFromServer) {
|
||||
peekRoomFromServer()
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +162,7 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
private fun observeRoomSummary() {
|
||||
val queryParams = roomSummaryQueryParams {
|
||||
roomId = QueryStringValue.Equals(initialState.roomId)
|
||||
excludeType = null
|
||||
}
|
||||
session
|
||||
.rx()
|
||||
|
@ -136,6 +171,11 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
val isRoomJoined = list.any {
|
||||
it.membership == Membership.JOIN
|
||||
}
|
||||
list.firstOrNull { it.roomId == initialState.roomId }?.roomType?.let {
|
||||
setState {
|
||||
copy(roomType = it)
|
||||
}
|
||||
}
|
||||
if (isRoomJoined) {
|
||||
setState { copy(roomJoinState = JoinState.JOINED) }
|
||||
}
|
||||
|
@ -163,10 +203,43 @@ class RoomPreviewViewModel @AssistedInject constructor(@Assisted private val ini
|
|||
|
||||
override fun handle(action: RoomPreviewAction) {
|
||||
when (action) {
|
||||
is RoomPreviewAction.Join -> handleJoinRoom()
|
||||
is RoomPreviewAction.Join -> handleJoinRoom()
|
||||
RoomPreviewAction.JoinThirdParty -> handleJoinRoomThirdParty()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleJoinRoomThirdParty() = withState { state ->
|
||||
if (state.roomJoinState == JoinState.JOINING) {
|
||||
// Request already sent, should not happen
|
||||
Timber.w("Try to join an already joining room. Should not happen")
|
||||
return@withState
|
||||
}
|
||||
// local echo
|
||||
setState {
|
||||
copy(roomJoinState = JoinState.JOINING)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// XXX this could be done locally, but the spec is incomplete and it's not clear
|
||||
// what needs to be signed with what?
|
||||
val thirdPartySigned = session.identityService().sign3pidInvitation(
|
||||
state.fromEmailInvite?.identityServer ?: "",
|
||||
state.fromEmailInvite?.token ?: "",
|
||||
state.fromEmailInvite?.privateKey ?: ""
|
||||
)
|
||||
|
||||
session.joinRoom(state.roomId, reason = null, thirdPartySigned)
|
||||
} catch (failure: Throwable) {
|
||||
setState {
|
||||
copy(
|
||||
roomJoinState = JoinState.JOINING_ERROR,
|
||||
lastError = failure
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleJoinRoom() = withState { state ->
|
||||
if (state.roomJoinState == JoinState.JOINING) {
|
||||
// Request already sent, should not happen
|
||||
|
|
|
@ -20,6 +20,8 @@ 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.session.permalinks.PermalinkData
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomType
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
data class RoomPreviewViewState(
|
||||
|
@ -27,6 +29,7 @@ data class RoomPreviewViewState(
|
|||
// The room id
|
||||
val roomId: String = "",
|
||||
val roomAlias: String? = null,
|
||||
val roomType: String? = null,
|
||||
|
||||
val roomName: String? = null,
|
||||
val roomTopic: String? = null,
|
||||
|
@ -40,7 +43,11 @@ data class RoomPreviewViewState(
|
|||
// Current state of the room in preview
|
||||
val roomJoinState: JoinState = JoinState.NOT_JOINED,
|
||||
// Last error of join room request
|
||||
val lastError: Throwable? = null
|
||||
val lastError: Throwable? = null,
|
||||
|
||||
val fromEmailInvite: PermalinkData.RoomEmailInviteLink? = null,
|
||||
// used only if it's an email invite
|
||||
val isEmailBoundToAccount: Boolean = false
|
||||
) : MvRxState {
|
||||
|
||||
constructor(args: RoomPreviewData) : this(
|
||||
|
@ -50,10 +57,13 @@ data class RoomPreviewViewState(
|
|||
roomName = args.roomName,
|
||||
roomTopic = args.topic,
|
||||
avatarUrl = args.avatarUrl,
|
||||
shouldPeekFromServer = args.peekFromServer
|
||||
shouldPeekFromServer = args.peekFromServer,
|
||||
fromEmailInvite = args.fromEmailInvite,
|
||||
roomType = args.roomType
|
||||
)
|
||||
|
||||
fun matrixItem() : MatrixItem {
|
||||
return MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
|
||||
return if (roomType == RoomType.SPACE) MatrixItem.SpaceItem(roomId, roomName ?: roomAlias, avatarUrl)
|
||||
else MatrixItem.RoomItem(roomId, roomName ?: roomAlias, avatarUrl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import im.vector.app.core.di.ScreenComponent
|
|||
import im.vector.app.core.extensions.replaceFragment
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
import im.vector.app.databinding.ActivityVectorSettingsBinding
|
||||
import im.vector.app.features.discovery.DiscoverySettingsFragment
|
||||
import im.vector.app.features.settings.devices.VectorSettingsDevicesFragment
|
||||
import im.vector.app.features.settings.notifications.VectorSettingsNotificationPreferenceFragment
|
||||
|
||||
|
@ -79,6 +80,9 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
|
|||
requestHighlightPreferenceKeyOnResume(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)
|
||||
replaceFragment(R.id.vector_settings_page, VectorSettingsNotificationPreferenceFragment::class.java, null, FRAGMENT_TAG)
|
||||
}
|
||||
EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS -> {
|
||||
replaceFragment(R.id.vector_settings_page, DiscoverySettingsFragment::class.java, null, FRAGMENT_TAG)
|
||||
}
|
||||
|
||||
else ->
|
||||
replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG)
|
||||
|
@ -160,6 +164,7 @@ class VectorSettingsActivity : VectorBaseActivity<ActivityVectorSettingsBinding>
|
|||
const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS = 3
|
||||
const val EXTRA_DIRECT_ACCESS_GENERAL = 4
|
||||
const val EXTRA_DIRECT_ACCESS_NOTIFICATIONS = 5
|
||||
const val EXTRA_DIRECT_ACCESS_DISCOVERY_SETTINGS = 6
|
||||
|
||||
private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment"
|
||||
}
|
||||
|
|
|
@ -3550,4 +3550,13 @@
|
|||
<string name="this_makes_it_easy_for_rooms_to_stay_private_to_a_space">This makes it easy for rooms to stay private to a space, while letting people in the space find and join them. All new rooms in a space will have this option available.</string>
|
||||
<string name="help_space_members">Help space members find private rooms</string>
|
||||
<string name="to_help_space_members_find_and_join">To help space members find and join a private room, go to that room’s settings by tapping on the avatar.</string>
|
||||
|
||||
<!-- %s will be replaced by an email at runtime -->
|
||||
<string name="this_invite_to_this_room_was_sent">This invite to this room was sent to %s which is not associated with your account</string>
|
||||
<!-- %s will be replaced by an email at runtime -->
|
||||
<string name="this_invite_to_this_space_was_sent">This invite to this space was sent to %s which is not associated with your account</string>
|
||||
|
||||
<string name="link_this_email_settings_link">Link this email with your account</string>
|
||||
<!-- %s will be replaced by the value of link_this_email_settings_link and styled as a link -->
|
||||
<string name="link_this_email_with_your_account">%s in Settings to receive invites directly in Element.</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue