From 70e90d854279d8f3be3ecaad29ed1693891f7c2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Jul 2020 17:10:12 +0200 Subject: [PATCH 01/30] Render third party invite event (#548) --- CHANGES.md | 1 + .../room/model/RoomThirdPartyInviteContent.kt | 67 +++++++++++++++++++ .../timeline/factory/TimelineItemFactory.kt | 4 +- .../timeline/format/NoticeEventFormatter.kt | 28 ++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt diff --git a/CHANGES.md b/CHANGES.md index 94af9346eb..32ab57fda8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ Improvements 🙌: - Setup server recovery banner (#1648) - Set up SSSS from security settings (#1567) - New lab setting to add 'unread notifications' tab to main screen + - Render third party invite event (#548) Bugfix 🐛: - Integration Manager: Wrong URL to review terms if URL in config contains path (#1606) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt new file mode 100644 index 0000000000..2b8daa0c5b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.room.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the EventType.STATE_ROOM_THIRD_PARTY_INVITE state event content + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#m-room-third-party-invite + */ +@JsonClass(generateAdapter = true) +data class RoomThirdPartyInviteContent( + /** + * Required. A user-readable string which represents the user who has been invited. + * This should not contain the user's third party ID, as otherwise when the invite + * is accepted it would leak the association between the matrix ID and the third party ID. + */ + @Json(name = "display_name") val displayName: String, + + /** + * Required. A URL which can be fetched, with querystring public_key=public_key, to validate + * whether the key has been revoked. The URL must return a JSON object containing a boolean property named 'valid'. + */ + @Json(name = "key_validity_url") val keyValidityUrl: String, + + /** + * Required. A base64-encoded ed25519 key with which token must be signed (though a signature from any entry in + * public_keys is also sufficient). This exists for backwards compatibility. + */ + @Json(name = "public_key") val publicKey: String, + + /** + * Keys with which the token may be signed. + */ + @Json(name = "public_keys") val publicKeys: List = emptyList() +) + +@JsonClass(generateAdapter = true) +data class PublicKeys( + /** + * An optional URL which can be fetched, with querystring public_key=public_key, to validate whether the key + * has been revoked. The URL must return a JSON object containing a boolean property named 'valid'. If this URL + * is absent, the key must be considered valid indefinitely. + */ + @Json(name = "key_validity_url") val keyValidityUrl: String? = null, + + /** + * Required. A base-64 encoded ed25519 key with which token may be signed. + */ + @Json(name = "public_key") val publicKey: String +) + diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 22fd4eb5ec..72da87415c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -50,6 +50,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_TOPIC, EventType.STATE_ROOM_AVATAR, EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, EventType.STATE_ROOM_ALIASES, EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_JOIN_RULES, @@ -96,8 +97,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me verificationConclusionItemFactory.create(event, highlight, callback) } - // Unhandled event types (yet) - EventType.STATE_ROOM_THIRD_PARTY_INVITE -> defaultItemFactory.create(event, highlight, callback) + // Unhandled event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON Timber.v("Type ${event.root.getClearType()} not handled") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c2f683d5a5..032ad4fb62 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.session.room.model.RoomJoinRules import im.vector.matrix.android.api.session.room.model.RoomJoinRulesContent import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.RoomNameContent +import im.vector.matrix.android.api.session.room.model.RoomThirdPartyInviteContent import im.vector.matrix.android.api.session.room.model.RoomTopicContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.api.session.room.model.create.RoomCreateContent @@ -63,6 +64,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) @@ -156,6 +158,7 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName) EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName) EventType.CALL_INVITE, EventType.CALL_HANGUP, @@ -254,6 +257,31 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } + private fun formatRoomThirdPartyInvite(event: Event, senderName: String?): CharSequence? { + val content = event.getClearContent().toModel() + val prevContent = event.resolvedPrevContent()?.toModel() + + return when { + prevContent != null -> { + // Revoke case + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_third_party_revoked_invite_by_you, prevContent.displayName) + } else { + sp.getString(R.string.notice_room_third_party_revoked_invite, senderName, prevContent.displayName) + } + } + content != null -> { + // Invitation case + if (event.isSentByCurrentUser()) { + sp.getString(R.string.notice_room_third_party_invite_by_you, content.displayName) + } else { + sp.getString(R.string.notice_room_third_party_invite, senderName, content.displayName) + } + } + else -> null + } + } + private fun formatCallEvent(type: String, event: Event, senderName: String?): CharSequence? { return when (type) { EventType.CALL_INVITE -> { From ab1d652f1725d6c5d116c51de48b13e9e008094e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 7 Jul 2020 19:52:04 +0200 Subject: [PATCH 02/30] Invite by email (msisdn not working), command line (#548) --- .../session/room/members/MembershipService.kt | 7 ++ .../identity/DefaultIdentityService.kt | 14 +--- .../session/identity/EnsureIdentityToken.kt | 59 +++++++++++++++++ .../session/identity/IdentityModule.kt | 3 + .../android/internal/session/room/RoomAPI.kt | 9 +++ .../internal/session/room/RoomModule.kt | 5 ++ .../membership/DefaultMembershipService.kt | 12 ++++ .../membership/threepid/InviteThreePidTask.kt | 65 +++++++++++++++++++ .../membership/threepid/ThreePidInviteBody.kt | 41 ++++++++++++ .../riotx/core/extensions/BasicExtensions.kt | 15 +++++ .../riotx/features/command/CommandParser.kt | 30 ++++++--- .../riotx/features/command/ParsedCommand.kt | 3 + .../home/room/detail/RoomDetailFragment.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 10 +++ 14 files changed, 253 insertions(+), 22 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt index f011d317cd..bb74b5afa5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/members/MembershipService.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.members import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.util.Cancelable @@ -63,6 +64,12 @@ interface MembershipService { reason: String? = null, callback: MatrixCallback): Cancelable + /** + * Invite a user with email or phone number in the room + */ + fun invite3pid(threePid: ThreePid, + callback: MatrixCallback): Cancelable + /** * Ban a user from the room */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt index 3f10bf791c..13c97599f7 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/DefaultIdentityService.kt @@ -62,6 +62,7 @@ import javax.net.ssl.HttpsURLConnection @SessionScope internal class DefaultIdentityService @Inject constructor( private val identityStore: IdentityStore, + private val ensureIdentityTokenTask: EnsureIdentityTokenTask, private val getOpenIdTokenTask: GetOpenIdTokenTask, private val identityBulkLookupTask: IdentityBulkLookupTask, private val identityRegisterTask: IdentityRegisterTask, @@ -278,7 +279,7 @@ internal class DefaultIdentityService @Inject constructor( } private suspend fun lookUpInternal(canRetry: Boolean, threePids: List): List { - ensureToken() + ensureIdentityTokenTask.execute(Unit) return try { identityBulkLookupTask.execute(IdentityBulkLookupTask.Params(threePids)) @@ -295,17 +296,6 @@ internal class DefaultIdentityService @Inject constructor( } } - private suspend fun ensureToken() { - val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured - val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured - - if (identityData.token == null) { - // Try to get a token - val token = getNewIdentityServerToken(url) - identityStore.setToken(token) - } - } - private suspend fun getNewIdentityServerToken(url: String): String { val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt new file mode 100644 index 0000000000..e727cd69bc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/EnsureIdentityToken.kt @@ -0,0 +1,59 @@ +/* + * 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 im.vector.matrix.android.internal.session.identity + +import dagger.Lazy +import im.vector.matrix.android.api.session.identity.IdentityServiceError +import im.vector.matrix.android.internal.di.UnauthenticatedWithCertificate +import im.vector.matrix.android.internal.network.RetrofitFactory +import im.vector.matrix.android.internal.session.identity.data.IdentityStore +import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask +import im.vector.matrix.android.internal.task.Task +import okhttp3.OkHttpClient +import javax.inject.Inject + +internal interface EnsureIdentityTokenTask : Task + +internal class DefaultEnsureIdentityTokenTask @Inject constructor( + private val identityStore: IdentityStore, + private val retrofitFactory: RetrofitFactory, + @UnauthenticatedWithCertificate + private val unauthenticatedOkHttpClient: Lazy, + private val getOpenIdTokenTask: GetOpenIdTokenTask, + private val identityRegisterTask: IdentityRegisterTask +) : EnsureIdentityTokenTask { + + override suspend fun execute(params: Unit) { + val identityData = identityStore.getIdentityData() ?: throw IdentityServiceError.NoIdentityServerConfigured + val url = identityData.identityServerUrl ?: throw IdentityServiceError.NoIdentityServerConfigured + + if (identityData.token == null) { + // Try to get a token + val token = getNewIdentityServerToken(url) + identityStore.setToken(token) + } + } + + private suspend fun getNewIdentityServerToken(url: String): String { + val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) + + val openIdToken = getOpenIdTokenTask.execute(Unit) + val token = identityRegisterTask.execute(IdentityRegisterTask.Params(api, openIdToken)) + + return token.token + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt index 9f902f79f1..79160b8c59 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/identity/IdentityModule.kt @@ -78,6 +78,9 @@ internal abstract class IdentityModule { @Binds abstract fun bindIdentityStore(store: RealmIdentityStore): IdentityStore + @Binds + abstract fun bindEnsureIdentityTokenTask(task: DefaultEnsureIdentityTokenTask): EnsureIdentityTokenTask + @Binds abstract fun bindIdentityPingTask(task: DefaultIdentityPingTask): IdentityPingTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index 59fc0efbc0..e00a94297a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody +import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody import im.vector.matrix.android.internal.session.room.relation.RelationsResponse import im.vector.matrix.android.internal.session.room.reporting.ReportContentBody import im.vector.matrix.android.internal.session.room.send.SendResponse @@ -170,6 +171,14 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") fun invite(@Path("roomId") roomId: String, @Body body: InviteBody): Call + /** + * Invite a user to a room, using a ThreePid + * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#id101 + * @param roomId Required. The room identifier (not alias) to which to invite the user. + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") + fun invite3pid(@Path("roomId") roomId: String, @Body body: ThreePidInviteBody): Call + /** * Send a generic state events * diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt index 7fa9c1526a..3eb5427b70 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt @@ -44,6 +44,8 @@ import im.vector.matrix.android.internal.session.room.membership.joining.InviteT import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.DefaultLeaveRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask +import im.vector.matrix.android.internal.session.room.membership.threepid.DefaultInviteThreePidTask +import im.vector.matrix.android.internal.session.room.membership.threepid.InviteThreePidTask import im.vector.matrix.android.internal.session.room.read.DefaultMarkAllRoomsReadTask import im.vector.matrix.android.internal.session.room.read.DefaultSetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.MarkAllRoomsReadTask @@ -139,6 +141,9 @@ internal abstract class RoomModule { @Binds abstract fun bindInviteTask(task: DefaultInviteTask): InviteTask + @Binds + abstract fun bindInviteThreePidTask(task: DefaultInviteThreePidTask): InviteThreePidTask + @Binds abstract fun bindJoinRoomTask(task: DefaultJoinRoomTask): JoinRoomTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt index 8467e8b46c..f413f5c9c0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/DefaultMembershipService.kt @@ -21,6 +21,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.room.members.MembershipService import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams import im.vector.matrix.android.api.session.room.model.Membership @@ -36,6 +37,7 @@ import im.vector.matrix.android.internal.session.room.membership.admin.Membershi import im.vector.matrix.android.internal.session.room.membership.joining.InviteTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.leaving.LeaveRoomTask +import im.vector.matrix.android.internal.session.room.membership.threepid.InviteThreePidTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.fetchCopied @@ -48,6 +50,7 @@ internal class DefaultMembershipService @AssistedInject constructor( private val taskExecutor: TaskExecutor, private val loadRoomMembersTask: LoadRoomMembersTask, private val inviteTask: InviteTask, + private val inviteThreePidTask: InviteThreePidTask, private val joinTask: JoinRoomTask, private val leaveRoomTask: LeaveRoomTask, private val membershipAdminTask: MembershipAdminTask, @@ -152,6 +155,15 @@ internal class DefaultMembershipService @AssistedInject constructor( .executeBy(taskExecutor) } + override fun invite3pid(threePid: ThreePid, callback: MatrixCallback): Cancelable { + val params = InviteThreePidTask.Params(roomId, threePid) + return inviteThreePidTask + .configureWith(params) { + this.callback = callback + } + .executeBy(taskExecutor) + } + override fun join(reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { val params = JoinRoomTask.Params(roomId, reason, viaServers) return joinTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt new file mode 100644 index 0000000000..25fe7b4888 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/InviteThreePidTask.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.membership.threepid + +import im.vector.matrix.android.api.session.identity.IdentityServiceError +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.matrix.android.api.session.identity.toMedium +import im.vector.matrix.android.internal.di.AuthenticatedIdentity +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.network.token.AccessTokenProvider +import im.vector.matrix.android.internal.session.identity.EnsureIdentityTokenTask +import im.vector.matrix.android.internal.session.identity.data.IdentityStore +import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol +import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + +internal interface InviteThreePidTask : Task { + data class Params( + val roomId: String, + val threePid: ThreePid + ) +} + +internal class DefaultInviteThreePidTask @Inject constructor( + private val roomAPI: RoomAPI, + private val eventBus: EventBus, + private val identityStore: IdentityStore, + private val ensureIdentityTokenTask: EnsureIdentityTokenTask, + @AuthenticatedIdentity + private val accessTokenProvider: AccessTokenProvider +) : InviteThreePidTask { + + override suspend fun execute(params: InviteThreePidTask.Params) { + ensureIdentityTokenTask.execute(Unit) + + val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured + val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured + + return executeRequest(eventBus) { + val body = ThreePidInviteBody( + id_server = identityServerUrlWithoutProtocol, + id_access_token = identityServerAccessToken, + medium = params.threePid.toMedium(), + address = params.threePid.value + ) + apiCall = roomAPI.invite3pid(params.roomId, body) + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt new file mode 100644 index 0000000000..23dd6bad77 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/threepid/ThreePidInviteBody.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.room.membership.threepid + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class ThreePidInviteBody( + /** + * Required. The hostname+port of the identity server which should be used for third party identifier lookups. + */ + @Json(name = "id_server") val id_server: String, + /** + * Required. An access token previously registered with the identity server. Servers can treat this as optional + * to distinguish between r0.5-compatible clients and this specification version. + */ + @Json(name = "id_access_token") val id_access_token: String, + /** + * Required. The kind of address being passed in the address field, for example email. + */ + @Json(name = "medium") val medium: String, + /** + * Required. The invitee's third party identifier. + */ + @Json(name = "address") val address: String +) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt index 5bd6852e8a..6afd4fb279 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt @@ -19,6 +19,8 @@ package im.vector.riotx.core.extensions import android.os.Bundle import android.util.Patterns import androidx.fragment.app.Fragment +import com.google.i18n.phonenumbers.NumberParseException +import com.google.i18n.phonenumbers.PhoneNumberUtil fun Boolean.toOnOff() = if (this) "ON" else "OFF" @@ -33,3 +35,16 @@ fun T.withArgs(block: Bundle.() -> Unit) = apply { arguments = Bu * Check if a CharSequence is an email */ fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches() + +/** + * Check if a CharSequence is a phone number + * FIXME It does not work? + */ +fun CharSequence.isMsisdn(): Boolean { + return try { + PhoneNumberUtil.getInstance().parse(this, null) + true + } catch (e: NumberParseException) { + false + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt index 7c32a34aff..2b38a1ac25 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/CommandParser.kt @@ -17,6 +17,9 @@ package im.vector.riotx.features.command import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.riotx.core.extensions.isEmail +import im.vector.riotx.core.extensions.isMsisdn import timber.log.Timber object CommandParser { @@ -139,15 +142,24 @@ object CommandParser { if (messageParts.size >= 2) { val userId = messageParts[1] - if (MatrixPatterns.isUserId(userId)) { - ParsedCommand.Invite( - userId, - textMessage.substring(Command.INVITE.length + userId.length) - .trim() - .takeIf { it.isNotBlank() } - ) - } else { - ParsedCommand.ErrorSyntax(Command.INVITE) + when { + MatrixPatterns.isUserId(userId) -> { + ParsedCommand.Invite( + userId, + textMessage.substring(Command.INVITE.length + userId.length) + .trim() + .takeIf { it.isNotBlank() } + ) + } + userId.isEmail() -> { + ParsedCommand.Invite3Pid(ThreePid.Email(userId)) + } + userId.isMsisdn() -> { + ParsedCommand.Invite3Pid(ThreePid.Msisdn(userId)) + } + else -> { + ParsedCommand.ErrorSyntax(Command.INVITE) + } } } else { ParsedCommand.ErrorSyntax(Command.INVITE) diff --git a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt index 44ad2265e1..041da3dcac 100644 --- a/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt +++ b/vector/src/main/java/im/vector/riotx/features/command/ParsedCommand.kt @@ -16,6 +16,8 @@ package im.vector.riotx.features.command +import im.vector.matrix.android.api.session.identity.ThreePid + /** * Represent a parsed command */ @@ -41,6 +43,7 @@ sealed class ParsedCommand { class UnbanUser(val userId: String, val reason: String?) : ParsedCommand() class SetUserPowerLevel(val userId: String, val powerLevel: Int?) : ParsedCommand() class Invite(val userId: String, val reason: String?) : ParsedCommand() + class Invite3Pid(val threePid: ThreePid) : ParsedCommand() class JoinRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class PartRoom(val roomAlias: String, val reason: String?) : ParsedCommand() class ChangeTopic(val topic: String) : ParsedCommand() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 62078c3053..3c65b6281f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -960,7 +960,7 @@ class RoomDetailFragment @Inject constructor( updateComposerText("") } is RoomDetailViewEvents.SlashCommandResultError -> { - displayCommandError(sendMessageResult.throwable.localizedMessage ?: getString(R.string.unexpected_error)) + displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } is RoomDetailViewEvents.SlashCommandNotImplemented -> { displayCommandError(getString(R.string.not_implemented)) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 982448d1c1..a396152f6b 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -457,6 +457,10 @@ class RoomDetailViewModel @AssistedInject constructor( handleInviteSlashCommand(slashCommandResult) popDraft() } + is ParsedCommand.Invite3Pid -> { + handleInvite3pidSlashCommand(slashCommandResult) + popDraft() + } is ParsedCommand.SetUserPowerLevel -> { handleSetUserPowerLevel(slashCommandResult) popDraft() @@ -678,6 +682,12 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) { + launchSlashCommandFlow { + room.invite3pid(invite.threePid, it) + } + } + private fun handleSetUserPowerLevel(setUserPowerLevel: ParsedCommand.SetUserPowerLevel) { val currentPowerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) ?.content From 3842ec6bb09e32a9a882c2aaa59fa1d7b25166d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Jul 2020 09:54:57 +0200 Subject: [PATCH 03/30] Invite by msisdn. Error 500 from matrix.org though (#548) --- .../matrix/android/api/extensions/Strings.kt | 24 +++++++++++++++++++ .../riotx/core/extensions/BasicExtensions.kt | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Strings.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Strings.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Strings.kt new file mode 100644 index 0000000000..202c15b5b0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Strings.kt @@ -0,0 +1,24 @@ +/* + * 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 im.vector.matrix.android.api.extensions + +fun CharSequence.ensurePrefix(prefix: CharSequence): CharSequence { + return when { + startsWith(prefix) -> this + else -> "$prefix$this" + } +} diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt index 6afd4fb279..99a5cb5a1a 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/BasicExtensions.kt @@ -21,6 +21,7 @@ import android.util.Patterns import androidx.fragment.app.Fragment import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import im.vector.matrix.android.api.extensions.ensurePrefix fun Boolean.toOnOff() = if (this) "ON" else "OFF" @@ -38,11 +39,10 @@ fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches() /** * Check if a CharSequence is a phone number - * FIXME It does not work? */ fun CharSequence.isMsisdn(): Boolean { return try { - PhoneNumberUtil.getInstance().parse(this, null) + PhoneNumberUtil.getInstance().parse(ensurePrefix("+"), null) true } catch (e: NumberParseException) { false From 1c733e666180b478bacb6cb133b30e5c6b9171e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Jul 2020 16:54:14 +0200 Subject: [PATCH 04/30] Display Contact list (#548) WIP (#548) WIP (#548) WIP (#548) WIP (#548) WIP (#548) --- .../riotx/core/contacts/ContactModel.kt | 58 ++++++ .../riotx/core/contacts/ContactsDataSource.kt | 131 ++++++++++++++ .../im/vector/riotx/core/di/FragmentModule.kt | 6 + .../createdirect/CreateDirectRoomActivity.kt | 12 +- .../riotx/features/home/AvatarRenderer.kt | 18 ++ .../invite/InviteUsersToRoomActivity.kt | 8 +- .../userdirectory/ContactDetailItem.kt | 47 +++++ .../features/userdirectory/ContactItem.kt | 46 +++++ .../userdirectory/KnownUsersFragment.kt | 8 + .../features/userdirectory/PhoneBookAction.kt | 23 +++ .../userdirectory/PhoneBookController.kt | 140 +++++++++++++++ .../userdirectory/PhoneBookFragment.kt | 99 ++++++++++ .../userdirectory/PhoneBookViewModel.kt | 169 ++++++++++++++++++ .../userdirectory/PhoneBookViewState.kt | 35 ++++ .../userdirectory/UserDirectoryAction.kt | 2 + .../UserDirectorySharedAction.kt | 1 + .../main/res/layout/fragment_known_users.xml | 19 +- .../main/res/layout/fragment_phonebook.xml | 109 +++++++++++ .../main/res/layout/item_contact_detail.xml | 46 +++++ .../src/main/res/layout/item_contact_main.xml | 38 ++++ vector/src/main/res/values/strings.xml | 4 + 21 files changed, 1013 insertions(+), 6 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt create mode 100644 vector/src/main/res/layout/fragment_phonebook.xml create mode 100644 vector/src/main/res/layout/item_contact_detail.xml create mode 100644 vector/src/main/res/layout/item_contact_main.xml diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt new file mode 100644 index 0000000000..589c3030c3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt @@ -0,0 +1,58 @@ +/* + * 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 im.vector.riotx.core.contacts + +import android.net.Uri + +/* TODO Rename to MxContact? */ + +class ContactModelBuilder( + val id: Long, + val displayName: String) { + + var photoURI: Uri? = null + val msisdns = mutableListOf() + val emails = mutableListOf() + + fun toContactModel(): ContactModel { + return ContactModel( + id = id, + displayName = displayName, + photoURI = photoURI, + msisdns = msisdns, + emails = emails + ) + } +} + +data class ContactModel( + val id: Long, + val displayName: String, + val photoURI: Uri? = null, + val msisdns: List = emptyList(), + val emails: List = emptyList() +) + +data class MappedEmail( + val email: String, + val matrixId: String? +) + +data class MappedMsisdn( + val phoneNumber: String, + val matrixId: String? +) diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt new file mode 100644 index 0000000000..2160216d5d --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -0,0 +1,131 @@ +/* + * 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 im.vector.riotx.core.contacts + +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.provider.ContactsContract +import androidx.annotation.WorkerThread +import javax.inject.Inject + +class ContactsDataSource @Inject constructor( + private val context: Context +) { + + @WorkerThread + fun getContacts(): List { + val result = mutableListOf() + val contentResolver = context.contentResolver + + contentResolver.query( + ContactsContract.Contacts.CONTENT_URI, + null, + /* TODO + arrayOf( + ContactsContract.Contacts._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI, + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.NUMBER, + ContactsContract.CommonDataKinds.Email.ADDRESS + ), + */ + null, + null, + // Sort by Display name + ContactsContract.Data.DISPLAY_NAME + ) + ?.use { cursor -> + if (cursor.count > 0) { + while (cursor.moveToNext()) { + val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue + val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue + + val currentContact = ContactModelBuilder( + id = id, + displayName = displayName + ) + + cursor.getString(ContactsContract.Data.PHOTO_URI) + ?.let { Uri.parse(it) } + ?.let { currentContact.photoURI = it } + + // Get the phone numbers + contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", + arrayOf(id.toString()), + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) + ?.let { + currentContact.msisdns.add( + MappedMsisdn( + phoneNumber = it, + matrixId = null + ) + ) + } + } + } + + // Get Emails + contentResolver.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + null, + ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", + arrayOf(id.toString()), + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + // This would allow you get several email addresses + // if the email addresses were stored in an array + innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) + ?.let { + currentContact.emails.add( + MappedEmail( + email = it, + matrixId = null + ) + ) + } + } + } + + result.add(currentContact.toContactModel()) + } + } + } + + return result + .filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() } + } + + private fun Cursor.getString(column: String): String? { + return getColumnIndex(column) + .takeIf { it != -1 } + ?.let { getString(it) } + } + + private fun Cursor.getLong(column: String): Long? { + return getColumnIndex(column) + .takeIf { it != -1 } + ?.let { getLong(it) } + } +} diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 21cff188d0..0201a44096 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -103,6 +103,7 @@ import im.vector.riotx.features.share.IncomingShareFragment import im.vector.riotx.features.signout.soft.SoftLogoutFragment import im.vector.riotx.features.terms.ReviewTermsFragment import im.vector.riotx.features.userdirectory.KnownUsersFragment +import im.vector.riotx.features.userdirectory.PhoneBookFragment import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.widgets.WidgetFragment @@ -528,4 +529,9 @@ interface FragmentModule { @IntoMap @FragmentKey(WidgetFragment::class) fun bindWidgetFragment(fragment: WidgetFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(PhoneBookFragment::class) + fun bindPhoneBookFragment(fragment: PhoneBookFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt index ef3e9bdeff..973a4b6f16 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt @@ -35,10 +35,12 @@ import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragmentToBackstack +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs +import im.vector.riotx.features.userdirectory.PhoneBookViewModel import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel @@ -53,6 +55,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory + @Inject lateinit var phoneBookViewModelFactory: PhoneBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter override fun injectWith(injector: ScreenComponent) { @@ -68,12 +71,13 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { .observe() .subscribe { sharedAction -> when (sharedAction) { - UserDirectorySharedAction.OpenUsersDirectory -> + UserDirectorySharedAction.OpenUsersDirectory -> addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java) - UserDirectorySharedAction.Close -> finish() - UserDirectorySharedAction.GoBack -> onBackPressed() + UserDirectorySharedAction.Close -> finish() + UserDirectorySharedAction.GoBack -> onBackPressed() is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - } + UserDirectorySharedAction.OpenPhoneBook -> TODO() + }.exhaustive } .disposeOnDestroy() if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt index f917b5a9f9..e0d41ca445 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt @@ -30,6 +30,7 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget import com.bumptech.glide.request.target.Target import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.util.MatrixItem +import im.vector.riotx.core.contacts.ContactModel import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideRequest @@ -63,6 +64,23 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active DrawableImageViewTarget(imageView)) } + @UiThread + fun render(contactModel: ContactModel, imageView: ImageView) { + // Create a Fake MatrixItem, for the placeholder + val matrixItem = MatrixItem.UserItem( + // Need an id starting with @ + id = "@${contactModel.displayName}", + displayName = contactModel.displayName + ) + + val placeholder = getPlaceholderDrawable(imageView.context, matrixItem) + GlideApp.with(imageView) + .load(contactModel.photoURI) + .apply(RequestOptions.circleCropTransform()) + .placeholder(placeholder) + .into(imageView) + } + @UiThread fun render(context: Context, glideRequests: GlideRequests, diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index 839a0767d8..af0e974c8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -30,11 +30,14 @@ import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.extensions.addFragmentToBackstack +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.utils.toast import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs +import im.vector.riotx.features.userdirectory.PhoneBookFragment +import im.vector.riotx.features.userdirectory.PhoneBookViewModel import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel @@ -53,6 +56,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory @Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory + @Inject lateinit var phoneBookViewModelFactory: PhoneBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter override fun injectWith(injector: ScreenComponent) { @@ -74,7 +78,9 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { UserDirectorySharedAction.Close -> finish() UserDirectorySharedAction.GoBack -> onBackPressed() is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - } + UserDirectorySharedAction.OpenPhoneBook -> + addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + }.exhaustive } .disposeOnDestroy() if (isFirstCreation()) { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt new file mode 100644 index 0000000000..df29545201 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt @@ -0,0 +1,47 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.ClickListener +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.core.epoxy.onClick +import im.vector.riotx.core.extensions.setTextOrHide + +@EpoxyModelClass(layout = R.layout.item_contact_detail) +abstract class ContactDetailItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var threePid: String + @EpoxyAttribute var matrixId: String? = null + @EpoxyAttribute var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(clickListener) + holder.nameView.text = threePid + holder.matrixIdView.setTextOrHide(matrixId) + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.contactDetailName) + val matrixIdView by bind(R.id.contactDetailMatrixId) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt new file mode 100644 index 0000000000..67d762b4b2 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt @@ -0,0 +1,46 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import android.widget.ImageView +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel +import im.vector.riotx.features.home.AvatarRenderer + +@EpoxyModelClass(layout = R.layout.item_contact_main) +abstract class ContactItem : VectorEpoxyModel() { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var contact: ContactModel + + override fun bind(holder: Holder) { + super.bind(holder) + // If name is empty, use userId as name and force it being centered + holder.nameView.text = contact.displayName + avatarRenderer.render(contact, holder.avatarImageView) + } + + class Holder : VectorEpoxyHolder() { + val nameView by bind(R.id.contactDisplayName) + val avatarImageView by bind(R.id.contactAvatar) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index 42dd46bd01..d681e5d92f 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -63,6 +63,7 @@ class KnownUsersFragment @Inject constructor( setupRecyclerView() setupFilterView() setupAddByMatrixIdView() + setupAddFromPhoneBookView() setupCloseView() viewModel.selectSubscribe(this, UserDirectoryViewState::selectedUsers) { renderSelectedUsers(it) @@ -96,6 +97,13 @@ class KnownUsersFragment @Inject constructor( } } + private fun setupAddFromPhoneBookView() { + addFromPhoneBook.debouncedClicks { + // TODO handle Permission first + sharedActionViewModel.post(UserDirectorySharedAction.OpenPhoneBook) + } + } + private fun setupRecyclerView() { knownUsersController.callback = this // Don't activate animation as we might have way to much item animation when filtering diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt new file mode 100644 index 0000000000..d3d534e694 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt @@ -0,0 +1,23 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import im.vector.riotx.core.platform.VectorViewModelAction + +sealed class PhoneBookAction : VectorViewModelAction { + data class FilterWith(val filter: String) : PhoneBookAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt new file mode 100644 index 0000000000..39f00d6557 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt @@ -0,0 +1,140 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import com.airbnb.epoxy.EpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.riotx.R +import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.epoxy.errorWithRetryItem +import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.epoxy.noResultItem +import im.vector.riotx.core.error.ErrorFormatter +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.home.AvatarRenderer +import javax.inject.Inject + +class PhoneBookController @Inject constructor( + private val stringProvider: StringProvider, + private val avatarRenderer: AvatarRenderer, + private val errorFormatter: ErrorFormatter) : EpoxyController() { + + private var state: PhoneBookViewState? = null + + var callback: Callback? = null + + init { + requestModelBuild() + } + + fun setData(state: PhoneBookViewState) { + this.state = state + requestModelBuild() + } + + override fun buildModels() { + val currentState = state ?: return + val hasSearch = currentState.searchTerm.isNotBlank() + when (val asyncMappedContacts = currentState.mappedContacts) { + is Uninitialized -> renderEmptyState(false) + is Loading -> renderLoading() + is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch) + is Fail -> renderFailure(asyncMappedContacts.error) + } + } + + private fun renderLoading() { + loadingItem { + id("loading") + } + } + + private fun renderFailure(failure: Throwable) { + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(failure)) + } + } + + private fun renderSuccess(mappedContacts: List, + hasSearch: Boolean) { + if (mappedContacts.isEmpty()) { + renderEmptyState(hasSearch) + } else { + renderContacts(mappedContacts) + } + } + + private fun renderContacts(mappedContacts: List) { + for (mappedContact in mappedContacts) { + contactItem { + id(mappedContact.id) + contact(mappedContact) + avatarRenderer(avatarRenderer) + } + mappedContact.emails.forEach { + contactDetailItem { + id("$mappedContact.id${it.email}") + threePid(it.email) + matrixId(it.matrixId) + clickListener { + if (it.matrixId != null) { + callback?.onMatrixIdClick(it.matrixId) + } else { + callback?.onThreePidClick(ThreePid.Email(it.email)) + } + } + } + } + mappedContact.msisdns.forEach { + contactDetailItem { + id("$mappedContact.id${it.phoneNumber}") + threePid(it.phoneNumber) + matrixId(it.matrixId) + clickListener { + if (it.matrixId != null) { + callback?.onMatrixIdClick(it.matrixId) + } else { + callback?.onThreePidClick(ThreePid.Msisdn(it.phoneNumber)) + } + } + } + } + } + } + + private fun renderEmptyState(hasSearch: Boolean) { + val noResultRes = if (hasSearch) { + R.string.no_result_placeholder + } else { + R.string.empty_phone_book + } + noResultItem { + id("noResult") + text(stringProvider.getString(noResultRes)) + } + } + + interface Callback { + fun onMatrixIdClick(matrixId: String) + fun onThreePidClick(threePid: ThreePid) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt new file mode 100644 index 0000000000..9f1f8268c3 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt @@ -0,0 +1,99 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.activityViewModel +import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.matrix.android.api.session.user.model.User +import im.vector.riotx.R +import im.vector.riotx.core.extensions.cleanup +import im.vector.riotx.core.extensions.configureWith +import im.vector.riotx.core.extensions.hideKeyboard +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_phonebook.* +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class PhoneBookFragment @Inject constructor( + val phoneBookViewModelFactory: PhoneBookViewModel.Factory, + private val phoneBookController: PhoneBookController +) : VectorBaseFragment(), PhoneBookController.Callback { + + override fun getLayoutResId() = R.layout.fragment_phonebook + private val viewModel: UserDirectoryViewModel by activityViewModel() + + // Use activityViewModel to avoid loading several times the data + private val phoneBookViewModel: PhoneBookViewModel by activityViewModel() + + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) + setupRecyclerView() + setupFilterView() + setupCloseView() + } + + private fun setupFilterView() { + phoneBookFilter + .textChanges() + .skipInitialValue() + .debounce(300, TimeUnit.MILLISECONDS) + .subscribe { + phoneBookViewModel.handle(PhoneBookAction.FilterWith(it.toString())) + } + .disposeOnDestroyView() + } + + override fun onDestroyView() { + phoneBookRecyclerView.cleanup() + phoneBookController.callback = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + phoneBookController.callback = this + phoneBookRecyclerView.configureWith(phoneBookController) + } + + private fun setupCloseView() { + phoneBookClose.debouncedClicks { + sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + } + } + + override fun invalidate() = withState(phoneBookViewModel) { + phoneBookController.setData(it) + } + + override fun onMatrixIdClick(matrixId: String) { + view?.hideKeyboard() + viewModel.handle(UserDirectoryAction.SelectUser(User(matrixId))) + sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + } + + override fun onThreePidClick(threePid: ThreePid) { + view?.hideKeyboard() + viewModel.handle(UserDirectoryAction.SelectThreePid(threePid)) + sharedActionViewModel.post(UserDirectorySharedAction.GoBack) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt new file mode 100644 index 0000000000..d894bbe908 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt @@ -0,0 +1,169 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.viewModelScope +import com.airbnb.mvrx.ActivityViewModelContext +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 +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.identity.FoundThreePid +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.contacts.ContactsDataSource +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.createdirect.CreateDirectRoomActivity +import im.vector.riotx.features.invite.InviteUsersToRoomActivity +import kotlinx.coroutines.launch + +private typealias PhoneBookSearch = String + +class PhoneBookViewModel @AssistedInject constructor(@Assisted + initialState: PhoneBookViewState, + private val contactsDataSource: ContactsDataSource, + private val session: Session) + : VectorViewModel(initialState) { + + @AssistedInject.Factory + interface Factory { + fun create(initialState: PhoneBookViewState): PhoneBookViewModel + } + + companion object : MvRxViewModelFactory { + + override fun create(viewModelContext: ViewModelContext, state: PhoneBookViewState): PhoneBookViewModel? { + return when (viewModelContext) { + is FragmentViewModelContext -> (viewModelContext.fragment() as PhoneBookFragment).phoneBookViewModelFactory.create(state) + is ActivityViewModelContext -> { + when (viewModelContext.activity()) { + is CreateDirectRoomActivity -> viewModelContext.activity().phoneBookViewModelFactory.create(state) + is InviteUsersToRoomActivity -> viewModelContext.activity().phoneBookViewModelFactory.create(state) + else -> error("Wrong activity or fragment") + } + } + else -> error("Wrong activity or fragment") + } + } + } + + private var allContacts: List = emptyList() + private var mappedContacts: List = emptyList() + private var foundThreePid: List = emptyList() + + init { + loadContacts() + + selectSubscribe(PhoneBookViewState::searchTerm) { + updateState() + } + } + + private fun loadContacts() { + setState { + copy( + mappedContacts = Loading() + ) + } + + viewModelScope.launch { + allContacts = contactsDataSource.getContacts() + mappedContacts = allContacts + + setState { + copy( + mappedContacts = Success(allContacts) + ) + } + + performLookup(allContacts) + updateState() + } + } + + private fun performLookup(data: List) { + viewModelScope.launch { + val threePids = data.flatMap { contact -> + contact.emails.map { ThreePid.Email(it.email) } + + contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) } + } + session.identityService().lookUp(threePids, object : MatrixCallback> { + override fun onFailure(failure: Throwable) { + // Ignore? + } + + override fun onSuccess(data: List) { + foundThreePid = data + + mappedContacts = allContacts.map { contactModel -> + contactModel.copy( + emails = contactModel.emails.map { email -> + email.copy( + matrixId = foundThreePid + .firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email } + ?.matrixId + ) + }, + msisdns = contactModel.msisdns.map { msisdn -> + msisdn.copy( + matrixId = foundThreePid + .firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber } + ?.matrixId + ) + } + ) + } + + updateState() + } + }) + } + } + + private fun updateState() = withState { state -> + val filteredMappedContacts = mappedContacts + .filter { it.displayName.contains(state.searchTerm, true) } + + setState { + copy( + filteredMappedContacts = filteredMappedContacts + ) + } + } + + override fun handle(action: PhoneBookAction) { + when (action) { + is PhoneBookAction.FilterWith -> handleFilterWith(action) + }.exhaustive + } + + private fun handleFilterWith(action: PhoneBookAction.FilterWith) { + setState { + copy( + searchTerm = action.filter + ) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt new file mode 100644 index 0000000000..81709f84b4 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt @@ -0,0 +1,35 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxState +import im.vector.riotx.core.contacts.ContactModel + +data class PhoneBookViewState( + val searchTerm: String = "", + val mappedContacts: Async> = Loading(), + val filteredMappedContacts: List = emptyList() + /* + val knownUsers: Async> = Uninitialized, + val directoryUsers: Async> = Uninitialized, + val selectedUsers: Set = emptySet(), + val createAndInviteState: Async = Uninitialized, + val filterKnownUsersValue: Option = Option.empty() + */ +) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt index 1df3c02736..3051e14bea 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.userdirectory +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorViewModelAction @@ -24,5 +25,6 @@ sealed class UserDirectoryAction : VectorViewModelAction { data class SearchDirectoryUsers(val value: String) : UserDirectoryAction() object ClearFilterKnownUsers : UserDirectoryAction() data class SelectUser(val user: User) : UserDirectoryAction() + data class SelectThreePid(val threePid: ThreePid) : UserDirectoryAction() data class RemoveSelectedUser(val user: User) : UserDirectoryAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt index 7d1987aa4b..b071b6f7a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt @@ -21,6 +21,7 @@ import im.vector.riotx.core.platform.VectorSharedAction sealed class UserDirectorySharedAction : VectorSharedAction { object OpenUsersDirectory : UserDirectorySharedAction() + object OpenPhoneBook : UserDirectorySharedAction() object Close : UserDirectorySharedAction() object GoBack : UserDirectorySharedAction() data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set) : UserDirectorySharedAction() diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml index c04cf027a6..16e6858e60 100644 --- a/vector/src/main/res/layout/fragment_known_users.xml +++ b/vector/src/main/res/layout/fragment_known_users.xml @@ -123,6 +123,23 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider" /> + + diff --git a/vector/src/main/res/layout/fragment_phonebook.xml b/vector/src/main/res/layout/fragment_phonebook.xml new file mode 100644 index 0000000000..297201e2b1 --- /dev/null +++ b/vector/src/main/res/layout/fragment_phonebook.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_contact_detail.xml b/vector/src/main/res/layout/item_contact_detail.xml new file mode 100644 index 0000000000..6e44797baa --- /dev/null +++ b/vector/src/main/res/layout/item_contact_detail.xml @@ -0,0 +1,46 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_contact_main.xml b/vector/src/main/res/layout/item_contact_main.xml new file mode 100644 index 0000000000..c4482241ae --- /dev/null +++ b/vector/src/main/res/layout/item_contact_main.xml @@ -0,0 +1,38 @@ + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 32de32e094..df596ae2d8 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2541,4 +2541,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Waiting for encryption history Save recovery key in + + Add from my phone book + Your phone book is empty + Phone book From 6ceac578a33752a4360e9c4ae2c253f6f2c45800 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Jul 2020 21:52:14 +0200 Subject: [PATCH 05/30] Add checkbox to filter contacts with MatrixId only --- .../features/userdirectory/PhoneBookAction.kt | 1 + .../userdirectory/PhoneBookController.kt | 63 ++++++++++--------- .../userdirectory/PhoneBookFragment.kt | 16 ++++- .../userdirectory/PhoneBookViewModel.kt | 45 +++++++++---- .../userdirectory/PhoneBookViewState.kt | 18 +++--- .../main/res/layout/fragment_phonebook.xml | 17 ++++- .../src/main/res/layout/item_contact_main.xml | 5 +- 7 files changed, 108 insertions(+), 57 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt index d3d534e694..a8c993e99d 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt @@ -20,4 +20,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction sealed class PhoneBookAction : VectorViewModelAction { data class FilterWith(val filter: String) : PhoneBookAction() + data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : PhoneBookAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt index 39f00d6557..6a79a4b15d 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt @@ -52,11 +52,11 @@ class PhoneBookController @Inject constructor( override fun buildModels() { val currentState = state ?: return - val hasSearch = currentState.searchTerm.isNotBlank() + val hasSearch = currentState.searchTerm.isNotEmpty() when (val asyncMappedContacts = currentState.mappedContacts) { is Uninitialized -> renderEmptyState(false) is Loading -> renderLoading() - is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch) + is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch, currentState.onlyBoundContacts) is Fail -> renderFailure(asyncMappedContacts.error) } } @@ -75,49 +75,54 @@ class PhoneBookController @Inject constructor( } private fun renderSuccess(mappedContacts: List, - hasSearch: Boolean) { + hasSearch: Boolean, + onlyBoundContacts: Boolean) { if (mappedContacts.isEmpty()) { renderEmptyState(hasSearch) } else { - renderContacts(mappedContacts) + renderContacts(mappedContacts, onlyBoundContacts) } } - private fun renderContacts(mappedContacts: List) { + private fun renderContacts(mappedContacts: List, onlyBoundContacts: Boolean) { for (mappedContact in mappedContacts) { contactItem { id(mappedContact.id) contact(mappedContact) avatarRenderer(avatarRenderer) } - mappedContact.emails.forEach { - contactDetailItem { - id("$mappedContact.id${it.email}") - threePid(it.email) - matrixId(it.matrixId) - clickListener { - if (it.matrixId != null) { - callback?.onMatrixIdClick(it.matrixId) - } else { - callback?.onThreePidClick(ThreePid.Email(it.email)) + mappedContact.emails + .filter { !onlyBoundContacts || it.matrixId != null } + .forEach { + contactDetailItem { + id("$mappedContact.id${it.email}") + threePid(it.email) + matrixId(it.matrixId) + clickListener { + if (it.matrixId != null) { + callback?.onMatrixIdClick(it.matrixId) + } else { + callback?.onThreePidClick(ThreePid.Email(it.email)) + } + } } } - } - } - mappedContact.msisdns.forEach { - contactDetailItem { - id("$mappedContact.id${it.phoneNumber}") - threePid(it.phoneNumber) - matrixId(it.matrixId) - clickListener { - if (it.matrixId != null) { - callback?.onMatrixIdClick(it.matrixId) - } else { - callback?.onThreePidClick(ThreePid.Msisdn(it.phoneNumber)) + mappedContact.msisdns + .filter { !onlyBoundContacts || it.matrixId != null } + .forEach { + contactDetailItem { + id("$mappedContact.id${it.phoneNumber}") + threePid(it.phoneNumber) + matrixId(it.matrixId) + clickListener { + if (it.matrixId != null) { + callback?.onMatrixIdClick(it.matrixId) + } else { + callback?.onThreePidClick(ThreePid.Msisdn(it.phoneNumber)) + } + } } } - } - } } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt index 9f1f8268c3..ac8d3290cb 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt @@ -18,8 +18,10 @@ package im.vector.riotx.features.userdirectory import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState +import com.jakewharton.rxbinding3.widget.checkedChanges import com.jakewharton.rxbinding3.widget.textChanges import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.user.model.User @@ -50,9 +52,18 @@ class PhoneBookFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) setupRecyclerView() setupFilterView() + setupOnlyBoundContactsView() setupCloseView() } + private fun setupOnlyBoundContactsView() { + phoneBookOnlyBoundContacts.checkedChanges() + .subscribe { + phoneBookViewModel.handle(PhoneBookAction.OnlyBoundContacts(it)) + } + .disposeOnDestroyView() + } + private fun setupFilterView() { phoneBookFilter .textChanges() @@ -81,8 +92,9 @@ class PhoneBookFragment @Inject constructor( } } - override fun invalidate() = withState(phoneBookViewModel) { - phoneBookController.setData(it) + override fun invalidate() = withState(phoneBookViewModel) { state -> + phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved + phoneBookController.setData(state) } override fun onMatrixIdClick(matrixId: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt index d894bbe908..d78932ccf2 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt @@ -37,7 +37,9 @@ import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.createdirect.CreateDirectRoomActivity import im.vector.riotx.features.invite.InviteUsersToRoomActivity +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import timber.log.Timber private typealias PhoneBookSearch = String @@ -71,13 +73,12 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted private var allContacts: List = emptyList() private var mappedContacts: List = emptyList() - private var foundThreePid: List = emptyList() init { loadContacts() - selectSubscribe(PhoneBookViewState::searchTerm) { - updateState() + selectSubscribe(PhoneBookViewState::searchTerm, PhoneBookViewState::onlyBoundContacts) { _, _ -> + updateFilteredMappedContacts() } } @@ -88,7 +89,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted ) } - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { allContacts = contactsDataSource.getContacts() mappedContacts = allContacts @@ -99,7 +100,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } performLookup(allContacts) - updateState() + updateFilteredMappedContacts() } } @@ -111,24 +112,23 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } session.identityService().lookUp(threePids, object : MatrixCallback> { override fun onFailure(failure: Throwable) { - // Ignore? + // Ignore + Timber.w(failure, "Unable to perform the lookup") } override fun onSuccess(data: List) { - foundThreePid = data - mappedContacts = allContacts.map { contactModel -> contactModel.copy( emails = contactModel.emails.map { email -> email.copy( - matrixId = foundThreePid + matrixId = data .firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email } ?.matrixId ) }, msisdns = contactModel.msisdns.map { msisdn -> msisdn.copy( - matrixId = foundThreePid + matrixId = data .firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber } ?.matrixId ) @@ -136,15 +136,25 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted ) } - updateState() + setState { + copy( + isBoundRetrieved = true + ) + } + + updateFilteredMappedContacts() } }) } } - private fun updateState() = withState { state -> + private fun updateFilteredMappedContacts() = withState { state -> val filteredMappedContacts = mappedContacts .filter { it.displayName.contains(state.searchTerm, true) } + .filter { contactModel -> + !state.onlyBoundContacts + || contactModel.emails.any { it.matrixId != null } || contactModel.msisdns.any { it.matrixId != null } + } setState { copy( @@ -155,10 +165,19 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted override fun handle(action: PhoneBookAction) { when (action) { - is PhoneBookAction.FilterWith -> handleFilterWith(action) + is PhoneBookAction.FilterWith -> handleFilterWith(action) + is PhoneBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action) }.exhaustive } + private fun handleOnlyBoundContacts(action: PhoneBookAction.OnlyBoundContacts) { + setState { + copy( + onlyBoundContacts = action.onlyBoundContacts + ) + } + } + private fun handleFilterWith(action: PhoneBookAction.FilterWith) { setState { copy( diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt index 81709f84b4..bfca2bc6b0 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt @@ -22,14 +22,14 @@ import com.airbnb.mvrx.MvRxState import im.vector.riotx.core.contacts.ContactModel data class PhoneBookViewState( - val searchTerm: String = "", + // All the contacts on the phone val mappedContacts: Async> = Loading(), - val filteredMappedContacts: List = emptyList() - /* - val knownUsers: Async> = Uninitialized, - val directoryUsers: Async> = Uninitialized, - val selectedUsers: Set = emptySet(), - val createAndInviteState: Async = Uninitialized, - val filterKnownUsersValue: Option = Option.empty() - */ + // Use to filter contacts by display name + val searchTerm: String = "", + // Tru to display only bound contacts with their bound 2pid + val onlyBoundContacts: Boolean = false, + // All contacts, filtered by searchTerm and onlyBoundContacts + val filteredMappedContacts: List = emptyList(), + // True when the identity service has return some data + val isBoundRetrieved: Boolean = false ) : MvRxState diff --git a/vector/src/main/res/layout/fragment_phonebook.xml b/vector/src/main/res/layout/fragment_phonebook.xml index 297201e2b1..14c44c11f0 100644 --- a/vector/src/main/res/layout/fragment_phonebook.xml +++ b/vector/src/main/res/layout/fragment_phonebook.xml @@ -79,6 +79,20 @@ + + + app:layout_constraintTop_toBottomOf="@+id/phoneBookOnlyBoundContacts" /> + android:paddingStart="8dp" + android:paddingTop="12dp" + android:paddingEnd="8dp"> Date: Wed, 8 Jul 2020 22:09:39 +0200 Subject: [PATCH 06/30] Fix a crash (#548) --- .../features/userdirectory/PhoneBookController.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt index 6a79a4b15d..e4266183df 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt @@ -92,10 +92,11 @@ class PhoneBookController @Inject constructor( avatarRenderer(avatarRenderer) } mappedContact.emails - .filter { !onlyBoundContacts || it.matrixId != null } - .forEach { + .forEachIndexed { index, it -> + if (onlyBoundContacts && it.matrixId == null) return@forEachIndexed + contactDetailItem { - id("$mappedContact.id${it.email}") + id("${mappedContact.id}-$index-${it.email}") threePid(it.email) matrixId(it.matrixId) clickListener { @@ -108,10 +109,11 @@ class PhoneBookController @Inject constructor( } } mappedContact.msisdns - .filter { !onlyBoundContacts || it.matrixId != null } - .forEach { + .forEachIndexed { index, it -> + if (onlyBoundContacts && it.matrixId == null) return@forEachIndexed + contactDetailItem { - id("$mappedContact.id${it.phoneNumber}") + id("${mappedContact.id}-$index-${it.phoneNumber}") threePid(it.phoneNumber) matrixId(it.matrixId) clickListener { From cc4603b61f1a316d4e9ffd5eb6790790787b3f68 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 09:55:33 +0200 Subject: [PATCH 07/30] Rename classes --- .../riotx/core/contacts/ContactsDataSource.kt | 8 ++++---- .../contacts/{ContactModel.kt => MappedContact.kt} | 14 ++++++-------- .../vector/riotx/features/home/AvatarRenderer.kt | 10 +++++----- .../riotx/features/userdirectory/ContactItem.kt | 8 ++++---- .../features/userdirectory/PhoneBookController.kt | 8 ++++---- .../features/userdirectory/PhoneBookViewModel.kt | 8 ++++---- .../features/userdirectory/PhoneBookViewState.kt | 6 +++--- 7 files changed, 30 insertions(+), 32 deletions(-) rename vector/src/main/java/im/vector/riotx/core/contacts/{ContactModel.kt => MappedContact.kt} (87%) diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt index 2160216d5d..b7762dd135 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -28,8 +28,8 @@ class ContactsDataSource @Inject constructor( ) { @WorkerThread - fun getContacts(): List { - val result = mutableListOf() + fun getContacts(): List { + val result = mutableListOf() val contentResolver = context.contentResolver contentResolver.query( @@ -56,7 +56,7 @@ class ContactsDataSource @Inject constructor( val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue - val currentContact = ContactModelBuilder( + val currentContact = MappedContactBuilder( id = id, displayName = displayName ) @@ -108,7 +108,7 @@ class ContactsDataSource @Inject constructor( } } - result.add(currentContact.toContactModel()) + result.add(currentContact.build()) } } } diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt b/vector/src/main/java/im/vector/riotx/core/contacts/MappedContact.kt similarity index 87% rename from vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt rename to vector/src/main/java/im/vector/riotx/core/contacts/MappedContact.kt index 589c3030c3..c89a3d4b01 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactModel.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/MappedContact.kt @@ -18,18 +18,16 @@ package im.vector.riotx.core.contacts import android.net.Uri -/* TODO Rename to MxContact? */ - -class ContactModelBuilder( +class MappedContactBuilder( val id: Long, - val displayName: String) { - + val displayName: String +) { var photoURI: Uri? = null val msisdns = mutableListOf() val emails = mutableListOf() - fun toContactModel(): ContactModel { - return ContactModel( + fun build(): MappedContact { + return MappedContact( id = id, displayName = displayName, photoURI = photoURI, @@ -39,7 +37,7 @@ class ContactModelBuilder( } } -data class ContactModel( +data class MappedContact( val id: Long, val displayName: String, val photoURI: Uri? = null, diff --git a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt index e0d41ca445..3bf2f13d48 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/AvatarRenderer.kt @@ -30,7 +30,7 @@ import com.bumptech.glide.request.target.DrawableImageViewTarget import com.bumptech.glide.request.target.Target import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.util.MatrixItem -import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.contacts.MappedContact import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.glide.GlideRequest @@ -65,17 +65,17 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active } @UiThread - fun render(contactModel: ContactModel, imageView: ImageView) { + fun render(mappedContact: MappedContact, imageView: ImageView) { // Create a Fake MatrixItem, for the placeholder val matrixItem = MatrixItem.UserItem( // Need an id starting with @ - id = "@${contactModel.displayName}", - displayName = contactModel.displayName + id = "@${mappedContact.displayName}", + displayName = mappedContact.displayName ) val placeholder = getPlaceholderDrawable(imageView.context, matrixItem) GlideApp.with(imageView) - .load(contactModel.photoURI) + .load(mappedContact.photoURI) .apply(RequestOptions.circleCropTransform()) .placeholder(placeholder) .into(imageView) diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt index 67d762b4b2..157ce9c7f6 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt @@ -21,7 +21,7 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.riotx.R -import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.contacts.MappedContact import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.features.home.AvatarRenderer @@ -30,13 +30,13 @@ import im.vector.riotx.features.home.AvatarRenderer abstract class ContactItem : VectorEpoxyModel() { @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer - @EpoxyAttribute lateinit var contact: ContactModel + @EpoxyAttribute lateinit var mappedContact: MappedContact override fun bind(holder: Holder) { super.bind(holder) // If name is empty, use userId as name and force it being centered - holder.nameView.text = contact.displayName - avatarRenderer.render(contact, holder.avatarImageView) + holder.nameView.text = mappedContact.displayName + avatarRenderer.render(mappedContact, holder.avatarImageView) } class Holder : VectorEpoxyHolder() { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt index e4266183df..4f33a4fa8f 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt @@ -23,7 +23,7 @@ import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.riotx.R -import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.contacts.MappedContact import im.vector.riotx.core.epoxy.errorWithRetryItem import im.vector.riotx.core.epoxy.loadingItem import im.vector.riotx.core.epoxy.noResultItem @@ -74,7 +74,7 @@ class PhoneBookController @Inject constructor( } } - private fun renderSuccess(mappedContacts: List, + private fun renderSuccess(mappedContacts: List, hasSearch: Boolean, onlyBoundContacts: Boolean) { if (mappedContacts.isEmpty()) { @@ -84,11 +84,11 @@ class PhoneBookController @Inject constructor( } } - private fun renderContacts(mappedContacts: List, onlyBoundContacts: Boolean) { + private fun renderContacts(mappedContacts: List, onlyBoundContacts: Boolean) { for (mappedContact in mappedContacts) { contactItem { id(mappedContact.id) - contact(mappedContact) + mappedContact(mappedContact) avatarRenderer(avatarRenderer) } mappedContact.emails diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt index d78932ccf2..d76c36847d 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt @@ -30,8 +30,8 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.identity.FoundThreePid import im.vector.matrix.android.api.session.identity.ThreePid -import im.vector.riotx.core.contacts.ContactModel import im.vector.riotx.core.contacts.ContactsDataSource +import im.vector.riotx.core.contacts.MappedContact import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel @@ -71,8 +71,8 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } } - private var allContacts: List = emptyList() - private var mappedContacts: List = emptyList() + private var allContacts: List = emptyList() + private var mappedContacts: List = emptyList() init { loadContacts() @@ -104,7 +104,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } } - private fun performLookup(data: List) { + private fun performLookup(data: List) { viewModelScope.launch { val threePids = data.flatMap { contact -> contact.emails.map { ThreePid.Email(it.email) } + diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt index bfca2bc6b0..60603b8785 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt @@ -19,17 +19,17 @@ package im.vector.riotx.features.userdirectory import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState -import im.vector.riotx.core.contacts.ContactModel +import im.vector.riotx.core.contacts.MappedContact data class PhoneBookViewState( // All the contacts on the phone - val mappedContacts: Async> = Loading(), + val mappedContacts: Async> = Loading(), // Use to filter contacts by display name val searchTerm: String = "", // Tru to display only bound contacts with their bound 2pid val onlyBoundContacts: Boolean = false, // All contacts, filtered by searchTerm and onlyBoundContacts - val filteredMappedContacts: List = emptyList(), + val filteredMappedContacts: List = emptyList(), // True when the identity service has return some data val isBoundRetrieved: Boolean = false ) : MvRxState From 327a596de5f254616420f3b7f9be6fa8d99b2491 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 10:02:04 +0200 Subject: [PATCH 08/30] Move classes --- .../src/main/java/im/vector/riotx/core/di/FragmentModule.kt | 2 +- .../riotx/features/createdirect/CreateDirectRoomActivity.kt | 2 +- .../riotx/features/invite/InviteUsersToRoomActivity.kt | 4 ++-- .../{userdirectory => phonebook}/ContactDetailItem.kt | 2 +- .../features/{userdirectory => phonebook}/ContactItem.kt | 2 +- .../{userdirectory => phonebook}/PhoneBookAction.kt | 2 +- .../{userdirectory => phonebook}/PhoneBookController.kt | 2 +- .../{userdirectory => phonebook}/PhoneBookFragment.kt | 6 +++++- .../{userdirectory => phonebook}/PhoneBookViewModel.kt | 2 +- .../{userdirectory => phonebook}/PhoneBookViewState.kt | 2 +- 10 files changed, 15 insertions(+), 11 deletions(-) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/ContactDetailItem.kt (97%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/ContactItem.kt (97%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/PhoneBookAction.kt (94%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/PhoneBookController.kt (99%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/PhoneBookFragment.kt (92%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/PhoneBookViewModel.kt (99%) rename vector/src/main/java/im/vector/riotx/features/{userdirectory => phonebook}/PhoneBookViewState.kt (96%) diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index 0201a44096..f5f236364f 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -103,7 +103,7 @@ import im.vector.riotx.features.share.IncomingShareFragment import im.vector.riotx.features.signout.soft.SoftLogoutFragment import im.vector.riotx.features.terms.ReviewTermsFragment import im.vector.riotx.features.userdirectory.KnownUsersFragment -import im.vector.riotx.features.userdirectory.PhoneBookFragment +import im.vector.riotx.features.phonebook.PhoneBookFragment import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.widgets.WidgetFragment diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt index 973a4b6f16..256d9902f6 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt @@ -40,7 +40,7 @@ import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs -import im.vector.riotx.features.userdirectory.PhoneBookViewModel +import im.vector.riotx.features.phonebook.PhoneBookViewModel import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index af0e974c8a..4d869aacee 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -36,8 +36,8 @@ import im.vector.riotx.core.platform.WaitingViewData import im.vector.riotx.core.utils.toast import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs -import im.vector.riotx.features.userdirectory.PhoneBookFragment -import im.vector.riotx.features.userdirectory.PhoneBookViewModel +import im.vector.riotx.features.phonebook.PhoneBookFragment +import im.vector.riotx.features.phonebook.PhoneBookViewModel import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt similarity index 97% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt index df29545201..3c739b7829 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactDetailItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt similarity index 97% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt index 157ce9c7f6..b962b0a40d 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/ContactItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import android.widget.ImageView import android.widget.TextView diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt similarity index 94% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt index a8c993e99d..3a5eb4d7ff 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import im.vector.riotx.core.platform.VectorViewModelAction diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt similarity index 99% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt index 4f33a4fa8f..0fe940b21e 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookController.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Fail diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt similarity index 92% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt index ac8d3290cb..0d268e4dc7 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import android.os.Bundle import android.view.View @@ -30,6 +30,10 @@ import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.userdirectory.UserDirectoryAction +import im.vector.riotx.features.userdirectory.UserDirectorySharedAction +import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel +import im.vector.riotx.features.userdirectory.UserDirectoryViewModel import kotlinx.android.synthetic.main.fragment_phonebook.* import java.util.concurrent.TimeUnit import javax.inject.Inject diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt similarity index 99% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt index d76c36847d..a609b63b67 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt similarity index 96% rename from vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt rename to vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt index 60603b8785..d4a578cd8a 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PhoneBookViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.userdirectory +package im.vector.riotx.features.phonebook import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading From f7145662008d3b66e4c3deade9c21171b752b605 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 10:43:08 +0200 Subject: [PATCH 09/30] use projection to gain 25% of time --- .../riotx/core/contacts/ContactsDataSource.kt | 144 +++++++++--------- 1 file changed, 73 insertions(+), 71 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt index b7762dd135..f694a9e3f0 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -21,7 +21,9 @@ import android.database.Cursor import android.net.Uri import android.provider.ContactsContract import androidx.annotation.WorkerThread +import timber.log.Timber import javax.inject.Inject +import kotlin.system.measureTimeMillis class ContactsDataSource @Inject constructor( private val context: Context @@ -32,86 +34,86 @@ class ContactsDataSource @Inject constructor( val result = mutableListOf() val contentResolver = context.contentResolver - contentResolver.query( - ContactsContract.Contacts.CONTENT_URI, - null, - /* TODO - arrayOf( - ContactsContract.Contacts._ID, - ContactsContract.Data.DISPLAY_NAME, - ContactsContract.Data.PHOTO_URI, - ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Phone.NUMBER, - ContactsContract.CommonDataKinds.Email.ADDRESS - ), - */ - null, - null, - // Sort by Display name - ContactsContract.Data.DISPLAY_NAME - ) - ?.use { cursor -> - if (cursor.count > 0) { - while (cursor.moveToNext()) { - val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue - val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue + measureTimeMillis { + contentResolver.query( + ContactsContract.Contacts.CONTENT_URI, + arrayOf( + ContactsContract.Contacts._ID, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.PHOTO_URI + ), + null, + null, + // Sort by Display name + ContactsContract.Data.DISPLAY_NAME + ) + ?.use { cursor -> + if (cursor.count > 0) { + while (cursor.moveToNext()) { + val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue + val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue - val currentContact = MappedContactBuilder( - id = id, - displayName = displayName - ) + val currentContact = MappedContactBuilder( + id = id, + displayName = displayName + ) - cursor.getString(ContactsContract.Data.PHOTO_URI) - ?.let { Uri.parse(it) } - ?.let { currentContact.photoURI = it } + cursor.getString(ContactsContract.Data.PHOTO_URI) + ?.let { Uri.parse(it) } + ?.let { currentContact.photoURI = it } - // Get the phone numbers - contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, - null, - ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", - arrayOf(id.toString()), - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) - ?.let { - currentContact.msisdns.add( - MappedMsisdn( - phoneNumber = it, - matrixId = null - ) - ) - } + // Get the phone numbers + contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Phone.NUMBER + ), + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", + arrayOf(id.toString()), + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) + ?.let { + currentContact.msisdns.add( + MappedMsisdn( + phoneNumber = it, + matrixId = null + ) + ) + } + } } - } - // Get Emails - contentResolver.query( - ContactsContract.CommonDataKinds.Email.CONTENT_URI, - null, - ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", - arrayOf(id.toString()), - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - // This would allow you get several email addresses - // if the email addresses were stored in an array - innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) - ?.let { - currentContact.emails.add( - MappedEmail( - email = it, - matrixId = null - ) - ) - } + // Get Emails + contentResolver.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Email.DATA + ), + ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", + arrayOf(id.toString()), + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + // This would allow you get several email addresses + // if the email addresses were stored in an array + innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) + ?.let { + currentContact.emails.add( + MappedEmail( + email = it, + matrixId = null + ) + ) + } + } } - } - result.add(currentContact.build()) + result.add(currentContact.build()) + } } } - } + }.also { Timber.d("Took ${it}ms to fetch ${result.size} contact(s)") } return result .filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() } From 6c0bb2a949a4709bbe0fcce8f68cbc9bab960dfe Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 11:27:45 +0200 Subject: [PATCH 10/30] Add 3Pid to the list. Not compiling, I have to modify CreateRoomParam --- .../session/room/model/create/Invite3Pid.kt | 8 ++++++ .../createdirect/CreateDirectRoomAction.kt | 4 +-- .../createdirect/CreateDirectRoomViewModel.kt | 4 +-- .../features/phonebook/PhoneBookFragment.kt | 5 ++-- .../userdirectory/DirectoryUsersController.kt | 2 +- .../userdirectory/KnownUsersController.kt | 2 +- .../userdirectory/KnownUsersFragment.kt | 13 ++++++---- .../features/userdirectory/PendingInvitee.kt | 25 +++++++++++++++++++ .../userdirectory/UserDirectoryAction.kt | 7 ++---- .../userdirectory/UserDirectoryFragment.kt | 5 ++-- .../UserDirectorySharedAction.kt | 3 +-- .../userdirectory/UserDirectoryViewModel.kt | 19 +++++++------- .../userdirectory/UserDirectoryViewState.kt | 12 ++++++++- 13 files changed, 76 insertions(+), 33 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt index 8e3386080f..66c8f1b2e8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt @@ -28,6 +28,14 @@ data class Invite3Pid( @Json(name = "id_server") val idServer: String, + /** + * Required. + * An access token previously registered with the identity server. Servers can treat this as optional to + * distinguish between r0.5-compatible clients and this specification version. + */ + @Json(name = "id_access_token") + val idAccessToken: String, + /** * Required. * The kind of address being passed in the address field, for example email. diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt index f995f82ff7..2af01b8964 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt @@ -16,9 +16,9 @@ package im.vector.riotx.features.createdirect -import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorViewModelAction +import im.vector.riotx.features.userdirectory.PendingInvitee sealed class CreateDirectRoomAction : VectorViewModelAction { - data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set) : CreateDirectRoomAction() + data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 1800759da6..90340c5cd8 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -23,9 +23,9 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx import im.vector.riotx.core.platform.VectorViewModel +import im.vector.riotx.features.userdirectory.PendingInvitee class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted initialState: CreateDirectRoomViewState, @@ -52,7 +52,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } } - private fun createRoomAndInviteSelectedUsers(selectedUsers: Set) { + private fun createRoomAndInviteSelectedUsers(selectedUsers: Set) { val roomParams = CreateRoomParams( invitedUserIds = selectedUsers.map { it.userId } ) diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt index 0d268e4dc7..1da9c6c306 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt @@ -30,6 +30,7 @@ import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.platform.VectorBaseFragment +import im.vector.riotx.features.userdirectory.PendingInvitee import im.vector.riotx.features.userdirectory.UserDirectoryAction import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel @@ -103,13 +104,13 @@ class PhoneBookFragment @Inject constructor( override fun onMatrixIdClick(matrixId: String) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectUser(User(matrixId))) + viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId)))) sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } override fun onThreePidClick(threePid: ThreePid) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectThreePid(threePid)) + viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid))) sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt index 9d11387fe8..d5fc34728a 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/DirectoryUsersController.kt @@ -60,7 +60,7 @@ class DirectoryUsersController @Inject constructor(private val session: Session, is Loading -> renderLoading() is Success -> renderSuccess( computeUsersList(asyncUsers(), currentState.directorySearchTerm), - currentState.selectedUsers.map { it.userId }, + currentState.getSelectedMatrixId(), hasSearch ) is Fail -> renderFailure(asyncUsers.error) diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt index 7a1ad49b8c..c78368f01b 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersController.kt @@ -51,7 +51,7 @@ class KnownUsersController @Inject constructor(private val session: Session, fun setData(state: UserDirectoryViewState) { this.isFiltering = !state.filterKnownUsersValue.isEmpty() - val newSelection = state.selectedUsers.map { it.userId } + val newSelection = state.getSelectedMatrixId() this.users = state.knownUsers if (newSelection != selectedUsers) { this.selectedUsers = newSelection diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index d681e5d92f..dc7ec5ee04 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -139,7 +139,7 @@ class KnownUsersFragment @Inject constructor( knownUsersController.setData(it) } - private fun renderSelectedUsers(selectedUsers: Set) { + private fun renderSelectedUsers(selectedUsers: Set) { invalidateOptionsMenu() val currentNumberOfChips = chipGroup.childCount @@ -156,22 +156,25 @@ class KnownUsersFragment @Inject constructor( } } - private fun addChipToGroup(user: User) { + private fun addChipToGroup(pendingInvitee: PendingInvitee) { val chip = Chip(requireContext()) chip.setChipBackgroundColorResource(android.R.color.transparent) chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat() - chip.text = user.getBestName() + chip.text = when (pendingInvitee) { + is PendingInvitee.UserPendingInvitee -> pendingInvitee.user.getBestName() + is PendingInvitee.ThreePidPendingInvitee -> pendingInvitee.threePid.value + } chip.isClickable = true chip.isCheckable = false chip.isCloseIconVisible = true chipGroup.addView(chip) chip.setOnCloseIconClickListener { - viewModel.handle(UserDirectoryAction.RemoveSelectedUser(user)) + viewModel.handle(UserDirectoryAction.RemovePendingInvitee(pendingInvitee)) } } override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectUser(user)) + viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt new file mode 100644 index 0000000000..b213061e4a --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt @@ -0,0 +1,25 @@ +/* + * 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 im.vector.riotx.features.userdirectory + +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.matrix.android.api.session.user.model.User + +sealed class PendingInvitee { + data class UserPendingInvitee(val user: User): PendingInvitee() + data class ThreePidPendingInvitee(val threePid: ThreePid): PendingInvitee() +} diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt index 3051e14bea..fde71cff5c 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryAction.kt @@ -16,15 +16,12 @@ package im.vector.riotx.features.userdirectory -import im.vector.matrix.android.api.session.identity.ThreePid -import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorViewModelAction sealed class UserDirectoryAction : VectorViewModelAction { data class FilterKnownUsers(val value: String) : UserDirectoryAction() data class SearchDirectoryUsers(val value: String) : UserDirectoryAction() object ClearFilterKnownUsers : UserDirectoryAction() - data class SelectUser(val user: User) : UserDirectoryAction() - data class SelectThreePid(val threePid: ThreePid) : UserDirectoryAction() - data class RemoveSelectedUser(val user: User) : UserDirectoryAction() + data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction() + data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt index 12de191b54..8dd4025350 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt @@ -21,7 +21,6 @@ import android.view.View import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.textChanges -import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -82,9 +81,9 @@ class UserDirectoryFragment @Inject constructor( directRoomController.setData(it) } - override fun onItemClick(user: User) { + override fun onItemClick(pendingInvitee: PendingInvitee) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectUser(user)) + viewModel.handle(UserDirectoryAction.SelectPendingInvitee(pendingInvitee)) sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt index b071b6f7a8..7506b97be3 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.userdirectory -import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorSharedAction sealed class UserDirectorySharedAction : VectorSharedAction { @@ -24,5 +23,5 @@ sealed class UserDirectorySharedAction : VectorSharedAction { object OpenPhoneBook : UserDirectorySharedAction() object Close : UserDirectorySharedAction() object GoBack : UserDirectorySharedAction() - data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set) : UserDirectorySharedAction() + data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set) : UserDirectorySharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt index 3111a86bf7..d7fb800aa4 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt @@ -28,6 +28,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.rx.rx +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.toggle import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.createdirect.CreateDirectRoomActivity @@ -59,9 +60,9 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state) is ActivityViewModelContext -> { when (viewModelContext.activity()) { - is CreateDirectRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) + is CreateDirectRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) is InviteUsersToRoomActivity -> viewModelContext.activity().userDirectoryViewModelFactory.create(state) - else -> error("Wrong activity or fragment") + else -> error("Wrong activity or fragment") } } else -> error("Wrong activity or fragment") @@ -79,20 +80,20 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted is UserDirectoryAction.FilterKnownUsers -> knownUsersFilter.accept(Option.just(action.value)) is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty()) is UserDirectoryAction.SearchDirectoryUsers -> directoryUsersSearch.accept(action.value) - is UserDirectoryAction.SelectUser -> handleSelectUser(action) - is UserDirectoryAction.RemoveSelectedUser -> handleRemoveSelectedUser(action) - } + is UserDirectoryAction.SelectPendingInvitee -> handleSelectUser(action) + is UserDirectoryAction.RemovePendingInvitee -> handleRemoveSelectedUser(action) + }.exhaustive } - private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemoveSelectedUser) = withState { state -> - val selectedUsers = state.selectedUsers.minus(action.user) + private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state -> + val selectedUsers = state.selectedUsers.minus(action.pendingInvitee) setState { copy(selectedUsers = selectedUsers) } } - private fun handleSelectUser(action: UserDirectoryAction.SelectUser) = withState { state -> + private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state -> // Reset the filter asap directoryUsersSearch.accept("") - val selectedUsers = state.selectedUsers.toggle(action.user) + val selectedUsers = state.selectedUsers.toggle(action.pendingInvitee) setState { copy(selectedUsers = selectedUsers) } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt index 52f92a9994..4dee0fe264 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt @@ -27,11 +27,21 @@ data class UserDirectoryViewState( val excludedUserIds: Set? = null, val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, - val selectedUsers: Set = emptySet(), + val selectedUsers: Set = emptySet(), val createAndInviteState: Async = Uninitialized, val directorySearchTerm: String = "", val filterKnownUsersValue: Option = Option.empty() ) : MvRxState { constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds) + + fun getSelectedMatrixId(): List { + return selectedUsers + .mapNotNull { + when (it) { + is PendingInvitee.UserPendingInvitee -> it.user.userId + is PendingInvitee.ThreePidPendingInvitee -> null + } + } + } } From 4b3a6a883d003bd20206429b08098487839ff25f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 14:07:00 +0200 Subject: [PATCH 11/30] CreateRoomParams has been replaced by CreateRoomParamsBuilder, to be able to invite 3pids --- CHANGES.md | 2 +- .../main/java/im/vector/matrix/rx/RxRoom.kt | 5 + .../java/im/vector/matrix/rx/RxSession.kt | 4 +- .../matrix/android/common/CryptoTestHelper.kt | 12 +- .../crypto/gossiping/KeyShareTests.kt | 9 +- .../android/api/session/room/RoomService.kt | 6 +- .../room/model/create/CreateRoomParams.kt | 268 ------------------ .../model/create/CreateRoomParamsBuilder.kt | 86 ++++++ .../session/room/model/create/Invite3Pid.kt | 50 ---- .../session/room/DefaultRoomService.kt | 4 +- .../android/internal/session/room/RoomAPI.kt | 6 +- .../session/room/create/CreateRoomParams.kt | 115 ++++++++ .../create/CreateRoomParamsInternalBuilder.kt | 147 ++++++++++ .../room}/create/CreateRoomResponse.kt | 4 +- .../session/room/create/CreateRoomTask.kt | 46 +-- .../room/membership/joining/JoinRoomTask.kt | 2 +- .../createdirect/CreateDirectRoomViewModel.kt | 19 +- .../VerificationBottomSheetViewModel.kt | 13 +- .../invite/InviteUsersToRoomAction.kt | 4 +- .../invite/InviteUsersToRoomViewModel.kt | 9 +- .../createroom/CreateRoomViewModel.kt | 24 +- .../userdirectory/KnownUsersFragment.kt | 5 +- .../features/userdirectory/PendingInvitee.kt | 11 +- .../userdirectory/UserDirectoryFragment.kt | 5 +- 24 files changed, 443 insertions(+), 413 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/{api/session/room/model => internal/session/room}/create/CreateRoomResponse.kt (89%) diff --git a/CHANGES.md b/CHANGES.md index 32ab57fda8..5daced2228 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,7 +28,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - CreateRoomParams has been replaced by CreateRoomParamsBuilder Build 🧱: - Upgrade some dependencies diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index b91949778d..2e96863d60 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -19,6 +19,7 @@ package im.vector.matrix.rx import android.net.Uri import im.vector.matrix.android.api.query.QueryStringValue import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary @@ -104,6 +105,10 @@ class RxRoom(private val room: Room) { room.invite(userId, reason, it) } + fun invite3pid(threePid: ThreePid): Completable = completableBuilder { + room.invite3pid(threePid, it) + } + fun updateTopic(topic: String): Completable = completableBuilder { room.updateTopic(topic, it) } diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index ca0bb46f4b..93e2dcae19 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -32,7 +32,7 @@ import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.members.ChangeMembershipState 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.CreateRoomParamsBuilder import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.widgets.model.Widget @@ -110,7 +110,7 @@ class RxSession(private val session: Session) { .startWithCallable { session.getThreePids() } } - fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { + fun createRoom(roomParams: CreateRoomParamsBuilder): Single = singleBuilder { session.createRoom(roomParams, it) } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 5425f97fc4..7e8410a440 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.Membership 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.CreateRoomParamsBuilder import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -65,7 +65,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = mTestHelper.doSync { - aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it) + aliceSession.createRoom(CreateRoomParamsBuilder().apply { name = "MyRoom" }, it) } if (encryptedRoom) { @@ -286,9 +286,11 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun createDM(alice: Session, bob: Session): String { val roomId = mTestHelper.doSync { alice.createRoom( - CreateRoomParams(invitedUserIds = listOf(bob.myUserId)) - .setDirectMessage() - .enableEncryptionIfInvitedUsersSupportIt(), + CreateRoomParamsBuilder().apply { + invitedUserIds.add(bob.myUserId) + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = true + }, it ) } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index e78ef04050..e90822a0c7 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationTran import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.SessionTestParams @@ -66,7 +66,10 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and add a message val roomId = mTestHelper.doSync { aliceSession.createRoom( - CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true), + CreateRoomParamsBuilder().apply { + visibility = RoomDirectoryVisibility.PRIVATE + enableEncryption() + }, it ) } @@ -285,7 +288,7 @@ class KeyShareTests : InstrumentedTest { mTestHelper.waitWithLatch(60_000) { latch -> val keysBackupService = aliceSession2.cryptoService().keysBackupService() mTestHelper.retryPeriodicallyWithLatch(latch) { - Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}") + Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}") keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 3319cecfef..788a074c65 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.room.members.ChangeMembershipState 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.CreateRoomParamsBuilder import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Optional @@ -32,7 +32,7 @@ interface RoomService { /** * Create a room asynchronously */ - fun createRoom(createRoomParams: CreateRoomParams, + fun createRoom(createRoomParams: CreateRoomParamsBuilder, callback: MatrixCallback): Cancelable /** @@ -113,5 +113,5 @@ interface RoomService { */ fun getChangeMembershipsLive(): LiveData> - fun getExistingDirectRoomWithUser(otherUserId: String) : Room? + fun getExistingDirectRoomWithUser(otherUserId: String): Room? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt deleted file mode 100644 index 1abbe9ef3a..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ /dev/null @@ -1,268 +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.android.api.session.room.model.create - -import android.util.Patterns -import androidx.annotation.CheckResult -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import im.vector.matrix.android.api.MatrixPatterns.isUserId -import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toContent -import im.vector.matrix.android.api.session.room.model.PowerLevelsContent -import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility -import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility -import im.vector.matrix.android.internal.auth.data.ThreePidMedium -import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import timber.log.Timber - -/** - * Parameter to create a room, with facilities functions to configure it - */ -@JsonClass(generateAdapter = true) -data class CreateRoomParams( - /** - * A public visibility indicates that the room will be shown in the published room list. - * A private visibility will hide the room from the published room list. - * Rooms default to private visibility if this key is not included. - * NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"] - */ - @Json(name = "visibility") - val visibility: RoomDirectoryVisibility? = null, - - /** - * The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room. - * The alias will belong on the same homeserver which created the room. - * For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com. - */ - @Json(name = "room_alias_name") - val roomAliasName: String? = null, - - /** - * If this is included, an m.room.name event will be sent into the room to indicate the name of the room. - * See Room Events for more information on m.room.name. - */ - @Json(name = "name") - val name: String? = null, - - /** - * If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room. - * See Room Events for more information on m.room.topic. - */ - @Json(name = "topic") - val topic: String? = null, - - /** - * A list of user IDs to invite to the room. - * This will tell the server to invite everyone in the list to the newly created room. - */ - @Json(name = "invite") - val invitedUserIds: List? = null, - - /** - * A list of objects representing third party IDs to invite into the room. - */ - @Json(name = "invite_3pid") - val invite3pids: List? = null, - - /** - * Extra keys to be added to the content of the m.room.create. - * The server will clobber the following keys: creator. - * Future versions of the specification may allow the server to clobber other keys. - */ - @Json(name = "creation_content") - val creationContent: Any? = null, - - /** - * A list of state events to set in the new room. - * This allows the user to override the default state events set in the new room. - * The expected format of the state events are an object with type, state_key and content keys set. - * Takes precedence over events set by presets, but gets overridden by name and topic keys. - */ - @Json(name = "initial_state") - val initialStates: List? = null, - - /** - * Convenience parameter for setting various default state events based on a preset. Must be either: - * private_chat => join_rules is set to invite. history_visibility is set to shared. - * trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the - * room creator. - * public_chat: => join_rules is set to public. history_visibility is set to shared. - */ - @Json(name = "preset") - val preset: CreateRoomPreset? = null, - - /** - * This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid. - * See Direct Messaging for more information. - */ - @Json(name = "is_direct") - val isDirect: Boolean? = null, - - /** - * The power level content to override in the default power level event - */ - @Json(name = "power_level_content_override") - val powerLevelContentOverride: PowerLevelsContent? = null -) { - @Transient - internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false - private set - - /** - * After calling this method, when the room will be created, if cross-signing is enabled and we can get keys for every invited users, - * the encryption will be enabled on the created room - * @param value true to activate this behavior. - * @return this, to allow chaining methods - */ - fun enableEncryptionIfInvitedUsersSupportIt(value: Boolean = true): CreateRoomParams { - enableEncryptionIfInvitedUsersSupportIt = value - return this - } - - /** - * Add the crypto algorithm to the room creation parameters. - * - * @param enable true to enable encryption. - * @param algorithm the algorithm, default to [MXCRYPTO_ALGORITHM_MEGOLM], which is actually the only supported algorithm for the moment - * @return a modified copy of the CreateRoomParams object, or this if there is no modification - */ - @CheckResult - fun enableEncryptionWithAlgorithm(enable: Boolean = true, - algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams { - // Remove the existing value if any. - val newInitialStates = initialStates - ?.filter { it.type != EventType.STATE_ROOM_ENCRYPTION } - - return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) { - if (enable) { - val contentMap = mapOf("algorithm" to algorithm) - - val algoEvent = Event( - type = EventType.STATE_ROOM_ENCRYPTION, - stateKey = "", - content = contentMap.toContent() - ) - - copy( - initialStates = newInitialStates.orEmpty() + algoEvent - ) - } else { - return copy( - initialStates = newInitialStates - ) - } - } else { - Timber.e("Unsupported algorithm: $algorithm") - this - } - } - - /** - * Force the history visibility in the room creation parameters. - * - * @param historyVisibility the expected history visibility, set null to remove any existing value. - * @return a modified copy of the CreateRoomParams object - */ - @CheckResult - fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams { - // Remove the existing value if any. - val newInitialStates = initialStates - ?.filter { it.type != EventType.STATE_ROOM_HISTORY_VISIBILITY } - - if (historyVisibility != null) { - val contentMap = mapOf("history_visibility" to historyVisibility) - - val historyVisibilityEvent = Event( - type = EventType.STATE_ROOM_HISTORY_VISIBILITY, - stateKey = "", - content = contentMap.toContent()) - - return copy( - initialStates = newInitialStates.orEmpty() + historyVisibilityEvent - ) - } else { - return copy( - initialStates = newInitialStates - ) - } - } - - /** - * Mark as a direct message room. - * @return a modified copy of the CreateRoomParams object - */ - @CheckResult - fun setDirectMessage(): CreateRoomParams { - return copy( - preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, - isDirect = true - ) - } - - /** - * Tells if the created room can be a direct chat one. - * - * @return true if it is a direct chat - */ - fun isDirect(): Boolean { - return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT - && isDirect == true - } - - /** - * @return the first invited user id - */ - fun getFirstInvitedUserId(): String? { - return invitedUserIds?.firstOrNull() ?: invite3pids?.firstOrNull()?.address - } - - /** - * Add some ids to the room creation - * ids might be a matrix id or an email address. - * - * @param ids the participant ids to add. - * @return a modified copy of the CreateRoomParams object - */ - @CheckResult - fun addParticipantIds(hsConfig: HomeServerConnectionConfig, - userId: String, - ids: List): CreateRoomParams { - return copy( - invite3pids = (invite3pids.orEmpty() + ids - .takeIf { hsConfig.identityServerUri != null } - ?.filter { id -> Patterns.EMAIL_ADDRESS.matcher(id).matches() } - ?.map { id -> - Invite3Pid( - idServer = hsConfig.identityServerUri!!.host!!, - medium = ThreePidMedium.EMAIL, - address = id - ) - } - .orEmpty()) - .distinct(), - invitedUserIds = (invitedUserIds.orEmpty() + ids - .filter { id -> isUserId(id) } - // do not invite oneself - .filter { id -> id != userId }) - .distinct() - ) - // TODO add phonenumbers when it will be available - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt new file mode 100644 index 0000000000..6637e3bcb2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt @@ -0,0 +1,86 @@ +/* + * 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 im.vector.matrix.android.api.session.room.model.create + +import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility +import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM + +class CreateRoomParamsBuilder { + var visibility: RoomDirectoryVisibility? = null + var roomAliasName: String? = null + var name: String? = null + var topic: String? = null + + /** + * UserIds to invite + */ + val invitedUserIds = mutableListOf() + + /** + * ThreePids to invite + */ + val invite3pids = mutableListOf() + + /** + * If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users, + * the encryption will be enabled on the created room + */ + var enableEncryptionIfInvitedUsersSupportIt: Boolean = false + + var preset: CreateRoomPreset? = null + + var isDirect: Boolean? = null + + /** + * Mark as a direct message room. + */ + fun setDirectMessage() { + preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + isDirect = true + } + + /** + * Supported value: MXCRYPTO_ALGORITHM_MEGOLM + */ + var algorithm: String? = null + private set + + var historyVisibility: RoomHistoryVisibility? = null + + fun enableEncryption() { + algorithm = MXCRYPTO_ALGORITHM_MEGOLM + } + + /** + * Tells if the created room can be a direct chat one. + * + * @return true if it is a direct chat + */ + fun isDirect(): Boolean { + return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + && isDirect == true + } + + /** + * @return the first invited user id + */ + fun getFirstInvitedUserId(): String? { + return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt deleted file mode 100644 index 66c8f1b2e8..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/Invite3Pid.kt +++ /dev/null @@ -1,50 +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.android.api.session.room.model.create - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class Invite3Pid( - /** - * Required. - * The hostname+port of the identity server which should be used for third party identifier lookups. - */ - @Json(name = "id_server") - val idServer: String, - - /** - * Required. - * An access token previously registered with the identity server. Servers can treat this as optional to - * distinguish between r0.5-compatible clients and this specification version. - */ - @Json(name = "id_access_token") - val idAccessToken: String, - - /** - * Required. - * The kind of address being passed in the address field, for example email. - */ - val medium: String, - - /** - * Required. - * The invitee's third party identifier. - */ - val address: String -) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index b8b4c968b1..7d5b8ac341 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.members.ChangeMembershipState 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.CreateRoomParamsBuilder import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask @@ -49,7 +49,7 @@ internal class DefaultRoomService @Inject constructor( private val taskExecutor: TaskExecutor ) : RoomService { - override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { + override fun createRoom(createRoomParams: CreateRoomParamsBuilder, callback: MatrixCallback): Cancelable { return createRoomTask .configureWith(createRoomParams) { this.callback = callback diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index e00a94297a..a82c96f93d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -18,9 +18,6 @@ package im.vector.matrix.android.internal.session.room import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse -import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol @@ -28,6 +25,9 @@ import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasBody import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription +import im.vector.matrix.android.internal.session.room.create.CreateRoomParams +import im.vector.matrix.android.internal.session.room.create.CreateRoomResponse +import im.vector.matrix.android.internal.session.room.create.JoinRoomResponse import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt new file mode 100644 index 0000000000..525a0501fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt @@ -0,0 +1,115 @@ +/* + * 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 im.vector.matrix.android.internal.session.room.create + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.model.PowerLevelsContent +import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility +import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset +import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody + +/** + * Parameter to create a room + */ +@JsonClass(generateAdapter = true) +internal data class CreateRoomParams( + /** + * A public visibility indicates that the room will be shown in the published room list. + * A private visibility will hide the room from the published room list. + * Rooms default to private visibility if this key is not included. + * NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"] + */ + @Json(name = "visibility") + val visibility: RoomDirectoryVisibility?, + + /** + * The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room. + * The alias will belong on the same homeserver which created the room. + * For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com. + */ + @Json(name = "room_alias_name") + val roomAliasName: String?, + + /** + * If this is included, an m.room.name event will be sent into the room to indicate the name of the room. + * See Room Events for more information on m.room.name. + */ + @Json(name = "name") + val name: String?, + + /** + * If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room. + * See Room Events for more information on m.room.topic. + */ + @Json(name = "topic") + val topic: String?, + + /** + * A list of user IDs to invite to the room. + * This will tell the server to invite everyone in the list to the newly created room. + */ + @Json(name = "invite") + val invitedUserIds: List?, + + /** + * A list of objects representing third party IDs to invite into the room. + */ + @Json(name = "invite_3pid") + val invite3pids: List?, + + /** + * Extra keys to be added to the content of the m.room.create. + * The server will clobber the following keys: creator. + * Future versions of the specification may allow the server to clobber other keys. + */ + @Json(name = "creation_content") + val creationContent: Any?, + + /** + * A list of state events to set in the new room. + * This allows the user to override the default state events set in the new room. + * The expected format of the state events are an object with type, state_key and content keys set. + * Takes precedence over events set by presets, but gets overridden by name and topic keys. + */ + @Json(name = "initial_state") + val initialStates: List?, + + /** + * Convenience parameter for setting various default state events based on a preset. Must be either: + * private_chat => join_rules is set to invite. history_visibility is set to shared. + * trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the + * room creator. + * public_chat: => join_rules is set to public. history_visibility is set to shared. + */ + @Json(name = "preset") + val preset: CreateRoomPreset?, + + /** + * This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid. + * See Direct Messaging for more information. + */ + @Json(name = "is_direct") + val isDirect: Boolean?, + + /** + * The power level content to override in the default power level event + */ + @Json(name = "power_level_content_override") + val powerLevelContentOverride: PowerLevelsContent? +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt new file mode 100644 index 0000000000..29cf0bbac6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt @@ -0,0 +1,147 @@ +/* + * 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 im.vector.matrix.android.internal.session.room.create + +import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.identity.IdentityServiceError +import im.vector.matrix.android.api.session.identity.toMedium +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.internal.crypto.DeviceListManager +import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import im.vector.matrix.android.internal.di.AuthenticatedIdentity +import im.vector.matrix.android.internal.network.token.AccessTokenProvider +import im.vector.matrix.android.internal.session.identity.EnsureIdentityTokenTask +import im.vector.matrix.android.internal.session.identity.data.IdentityStore +import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol +import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody +import java.security.InvalidParameterException +import javax.inject.Inject + +internal class CreateRoomParamsInternalBuilder @Inject constructor( + private val ensureIdentityTokenTask: EnsureIdentityTokenTask, + private val crossSigningService: CrossSigningService, + private val deviceListManager: DeviceListManager, + private val identityStore: IdentityStore, + @AuthenticatedIdentity + private val accessTokenProvider: AccessTokenProvider +) { + + suspend fun build(builder: CreateRoomParamsBuilder): CreateRoomParams { + val invite3pids = builder.invite3pids + .takeIf { it.isNotEmpty() } + .let { + // This can throw Exception if Identity server is not configured + ensureIdentityTokenTask.execute(Unit) + + val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() + ?: throw IdentityServiceError.NoIdentityServerConfigured + val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured + + builder.invite3pids.map { + ThreePidInviteBody( + id_server = identityServerUrlWithoutProtocol, + id_access_token = identityServerAccessToken, + medium = it.toMedium(), + address = it.value + ) + } + } + + val initialStates = listOfNotNull( + buildEncryptionWithAlgorithmEvent(builder), + buildHistoryVisibilityEvent(builder) + ) + .takeIf { it.isNotEmpty() } + + return CreateRoomParams( + visibility = builder.visibility, + roomAliasName = builder.roomAliasName, + name = builder.name, + topic = builder.topic, + invitedUserIds = builder.invitedUserIds, + invite3pids = invite3pids, + // TODO Support this + creationContent = null, + initialStates = initialStates, + preset = builder.preset, + isDirect = builder.isDirect, + // TODO Support this + powerLevelContentOverride = null + ) + } + + private fun buildHistoryVisibilityEvent(builder: CreateRoomParamsBuilder): Event? { + return builder.historyVisibility + ?.let { + val contentMap = mapOf("history_visibility" to it) + + Event( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + content = contentMap.toContent()) + } + } + + /** + * Add the crypto algorithm to the room creation parameters. + */ + private suspend fun buildEncryptionWithAlgorithmEvent(builder: CreateRoomParamsBuilder): Event? { + if (builder.algorithm == null + && canEnableEncryption(builder)) { + // Enable the encryption + builder.enableEncryption() + } + return builder.algorithm + ?.let { + if (it != MXCRYPTO_ALGORITHM_MEGOLM) { + throw InvalidParameterException("Unsupported algorithm: $it") + } + val contentMap = mapOf("algorithm" to it) + + Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = contentMap.toContent() + ) + } + } + + private suspend fun canEnableEncryption(builder: CreateRoomParamsBuilder): Boolean { + return (builder.enableEncryptionIfInvitedUsersSupportIt + && crossSigningService.isCrossSigningVerified() + && builder.invite3pids.isEmpty()) + && builder.invitedUserIds.isNotEmpty() + && builder.invitedUserIds.let { userIds -> + val keys = deviceListManager.downloadKeys(userIds, forceDownload = false) + + userIds.all { userId -> + keys.map[userId].let { deviceMap -> + if (deviceMap.isNullOrEmpty()) { + // A user has no device, so do not enable encryption + false + } else { + // Check that every user's device have at least one key + deviceMap.values.all { !it.keys.isNullOrEmpty() } + } + } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomResponse.kt similarity index 89% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomResponse.kt index da54b344a2..62208941cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomResponse.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 New Vector Ltd + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.matrix.android.api.session.room.model.create +package im.vector.matrix.android.internal.session.room.create import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index 2071b7736e..e32f8e39ab 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -17,11 +17,8 @@ package im.vector.matrix.android.internal.session.room.create import com.zhuinden.monarchy.Monarchy -import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams -import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse -import im.vector.matrix.android.internal.crypto.DeviceListManager +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder import im.vector.matrix.android.internal.database.awaitNotEmptyResult import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields @@ -41,7 +38,7 @@ import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject -internal interface CreateRoomTask : Task +internal interface CreateRoomTask : Task internal class DefaultCreateRoomTask @Inject constructor( private val roomAPI: RoomAPI, @@ -51,17 +48,12 @@ internal class DefaultCreateRoomTask @Inject constructor( private val readMarkersTask: SetReadMarkersTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, - private val crossSigningService: CrossSigningService, - private val deviceListManager: DeviceListManager, + private val createRoomParamsInternalBuilder: CreateRoomParamsInternalBuilder, private val eventBus: EventBus ) : CreateRoomTask { - override suspend fun execute(params: CreateRoomParams): String { - val createRoomParams = if (canEnableEncryption(params)) { - params.enableEncryptionWithAlgorithm() - } else { - params - } + override suspend fun execute(params: CreateRoomParamsBuilder): String { + val createRoomParams = createRoomParamsInternalBuilder.build(params) val createRoomResponse = executeRequest(eventBus) { apiCall = roomAPI.createRoom(createRoomParams) @@ -76,36 +68,14 @@ internal class DefaultCreateRoomTask @Inject constructor( } catch (exception: TimeoutCancellationException) { throw CreateRoomFailure.CreatedWithTimeout } - if (createRoomParams.isDirect()) { - handleDirectChatCreation(createRoomParams, roomId) + if (params.isDirect()) { + handleDirectChatCreation(params, roomId) } setReadMarkers(roomId) return roomId } - private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean { - return params.enableEncryptionIfInvitedUsersSupportIt - && crossSigningService.isCrossSigningVerified() - && params.invite3pids.isNullOrEmpty() - && params.invitedUserIds?.isNotEmpty() == true - && params.invitedUserIds.let { userIds -> - val keys = deviceListManager.downloadKeys(userIds, forceDownload = false) - - userIds.all { userId -> - keys.map[userId].let { deviceMap -> - if (deviceMap.isNullOrEmpty()) { - // A user has no device, so do not enable encryption - false - } else { - // Check that every user's device have at least one key - deviceMap.values.all { !it.keys.isNullOrEmpty() } - } - } - } - } - } - - private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String) { + private suspend fun handleDirectChatCreation(params: CreateRoomParamsBuilder, roomId: String) { val otherUserId = params.getFirstInvitedUserId() ?: throw IllegalStateException("You can't create a direct room without an invitedUser") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt index 7467a595bc..8fb9a1f065 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/membership/joining/JoinRoomTask.kt @@ -18,13 +18,13 @@ package im.vector.matrix.android.internal.session.room.membership.joining import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure import im.vector.matrix.android.api.session.room.members.ChangeMembershipState -import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse import im.vector.matrix.android.internal.database.awaitNotEmptyResult import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields import im.vector.matrix.android.internal.di.SessionDatabase import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI +import im.vector.matrix.android.internal.session.room.create.JoinRoomResponse import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.task.Task diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 90340c5cd8..44acdc0032 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -22,8 +22,9 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder import im.vector.matrix.rx.rx +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.userdirectory.PendingInvitee @@ -53,11 +54,17 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } private fun createRoomAndInviteSelectedUsers(selectedUsers: Set) { - val roomParams = CreateRoomParams( - invitedUserIds = selectedUsers.map { it.userId } - ) - .setDirectMessage() - .enableEncryptionIfInvitedUsersSupportIt() + val roomParams = CreateRoomParamsBuilder() + .apply { + selectedUsers.forEach { + when (it) { + is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId) + is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid) + }.exhaustive + } + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = true + } session.rx() .createRoom(roomParams) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 9b454436d9..1833688c35 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -43,7 +43,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationServ import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.LocalEcho -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 @@ -235,11 +235,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( pendingRequest = Loading() ) } - val roomParams = CreateRoomParams( - invitedUserIds = listOf(otherUserId) - ) - .setDirectMessage() - .enableEncryptionIfInvitedUsersSupportIt() + val roomParams = CreateRoomParamsBuilder() + .apply { + invitedUserIds.add(otherUserId) + setDirectMessage() + enableEncryptionIfInvitedUsersSupportIt = true + } session.createRoom(roomParams, object : MatrixCallback { override fun onSuccess(data: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt index 8a62935bdd..253b557cca 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt @@ -16,9 +16,9 @@ package im.vector.riotx.features.invite -import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.core.platform.VectorViewModelAction +import im.vector.riotx.features.userdirectory.PendingInvitee sealed class InviteUsersToRoomAction : VectorViewModelAction { - data class InviteSelectedUsers(val selectedUsers: Set) : InviteUsersToRoomAction() + data class InviteSelectedUsers(val selectedUsers: Set) : InviteUsersToRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index fc2f34b7a0..78a9961884 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -22,11 +22,11 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.rx.rx import im.vector.riotx.R import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.userdirectory.PendingInvitee import io.reactivex.Observable class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted @@ -57,11 +57,14 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted } } - private fun inviteUsersToRoom(selectedUsers: Set) { + private fun inviteUsersToRoom(selectedUsers: Set) { _viewEvents.post(InviteUsersToRoomViewEvents.Loading) Observable.fromIterable(selectedUsers).flatMapCompletable { user -> - room.rx().invite(user.userId, null) + when (user) { + is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null) + is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid) + } }.subscribe( { val successMessage = when (selectedUsers.size) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index cfe50bb2f7..5cb279c848 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -28,7 +28,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel @@ -84,15 +84,19 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr copy(asyncCreateRoomRequest = Loading()) } - val createRoomParams = CreateRoomParams( - name = state.roomName.takeIf { it.isNotBlank() }, - // Directory visibility - visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE, - // Public room - preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT - ) - // Encryption - .enableEncryptionWithAlgorithm(state.isEncrypted) + val createRoomParams = CreateRoomParamsBuilder() + .apply { + name = state.roomName.takeIf { it.isNotBlank() } + // Directory visibility + visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE + // Public room + preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + + // Encryption + if (state.isEncrypted) { + enableEncryption() + } + } session.createRoom(createRoomParams, object : MatrixCallback { override fun onSuccess(data: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index dc7ec5ee04..5367ec270c 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -160,10 +160,7 @@ class KnownUsersFragment @Inject constructor( val chip = Chip(requireContext()) chip.setChipBackgroundColorResource(android.R.color.transparent) chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat() - chip.text = when (pendingInvitee) { - is PendingInvitee.UserPendingInvitee -> pendingInvitee.user.getBestName() - is PendingInvitee.ThreePidPendingInvitee -> pendingInvitee.threePid.value - } + chip.text = pendingInvitee.getBestName() chip.isClickable = true chip.isCheckable = false chip.isCloseIconVisible = true diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt index b213061e4a..c9aad1cf65 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/PendingInvitee.kt @@ -20,6 +20,13 @@ import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.user.model.User sealed class PendingInvitee { - data class UserPendingInvitee(val user: User): PendingInvitee() - data class ThreePidPendingInvitee(val threePid: ThreePid): PendingInvitee() + data class UserPendingInvitee(val user: User) : PendingInvitee() + data class ThreePidPendingInvitee(val threePid: ThreePid) : PendingInvitee() + + fun getBestName(): String { + return when (this) { + is UserPendingInvitee -> user.getBestName() + is ThreePidPendingInvitee -> threePid.value + } + } } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt index 8dd4025350..a6d22dfbe3 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryFragment.kt @@ -21,6 +21,7 @@ import android.view.View import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState import com.jakewharton.rxbinding3.widget.textChanges +import im.vector.matrix.android.api.session.user.model.User import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup import im.vector.riotx.core.extensions.configureWith @@ -81,9 +82,9 @@ class UserDirectoryFragment @Inject constructor( directRoomController.setData(it) } - override fun onItemClick(pendingInvitee: PendingInvitee) { + override fun onItemClick(user: User) { view?.hideKeyboard() - viewModel.handle(UserDirectoryAction.SelectPendingInvitee(pendingInvitee)) + viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user))) sharedActionViewModel.post(UserDirectorySharedAction.GoBack) } From 25e7bbcd796263c5284ecfc778be2016896cac5a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 15:09:48 +0200 Subject: [PATCH 12/30] Handle contacts permission --- .../riotx/core/utils/PermissionsTools.kt | 1 + .../createdirect/CreateDirectRoomActivity.kt | 27 +++++++++++++++-- .../invite/InviteUsersToRoomActivity.kt | 29 ++++++++++++++++--- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt index 360a5efccc..6f081d52de 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt @@ -68,6 +68,7 @@ const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575 const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576 const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577 const val PERMISSION_REQUEST_CODE_PREVIEW_FRAGMENT = 578 +const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579 /** * Log the used permissions statuses. diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt index 256d9902f6..6acd0e099b 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt @@ -38,9 +38,14 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData +import im.vector.riotx.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH +import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS +import im.vector.riotx.core.utils.allGranted +import im.vector.riotx.core.utils.checkPermissions +import im.vector.riotx.features.phonebook.PhoneBookFragment +import im.vector.riotx.features.phonebook.PhoneBookViewModel import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs -import im.vector.riotx.features.phonebook.PhoneBookViewModel import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel @@ -76,7 +81,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { UserDirectorySharedAction.Close -> finish() UserDirectorySharedAction.GoBack -> onBackPressed() is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - UserDirectorySharedAction.OpenPhoneBook -> TODO() + UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() }.exhaustive } .disposeOnDestroy() @@ -95,6 +100,24 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { } } + private fun openPhoneBook() { + // Check permission first + if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, + this, + PERMISSION_REQUEST_CODE_READ_CONTACTS, + 0)) { + addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (allGranted(grantResults)) { + if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { + addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + } + } + } + private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selectedUsers)) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index 4d869aacee..29d230ba34 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -33,11 +33,15 @@ import im.vector.riotx.core.extensions.addFragmentToBackstack import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.SimpleFragmentActivity import im.vector.riotx.core.platform.WaitingViewData +import im.vector.riotx.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH +import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS +import im.vector.riotx.core.utils.allGranted +import im.vector.riotx.core.utils.checkPermissions import im.vector.riotx.core.utils.toast -import im.vector.riotx.features.userdirectory.KnownUsersFragment -import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs import im.vector.riotx.features.phonebook.PhoneBookFragment import im.vector.riotx.features.phonebook.PhoneBookViewModel +import im.vector.riotx.features.userdirectory.KnownUsersFragment +import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel @@ -78,8 +82,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { UserDirectorySharedAction.Close -> finish() UserDirectorySharedAction.GoBack -> onBackPressed() is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction) - UserDirectorySharedAction.OpenPhoneBook -> - addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + UserDirectorySharedAction.OpenPhoneBook -> openPhoneBook() }.exhaustive } .disposeOnDestroy() @@ -98,6 +101,24 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { viewModel.observeViewEvents { renderInviteEvents(it) } } + private fun openPhoneBook() { + // Check permission first + if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, + this, + PERMISSION_REQUEST_CODE_READ_CONTACTS, + 0)) { + addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + } + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (allGranted(grantResults)) { + if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { + addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + } + } + } + private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_invite_users_to_room_invite) { viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selectedUsers)) From 4c1d50d55433e30592692ac2476606e6f5085065 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 15:34:30 +0200 Subject: [PATCH 13/30] Renames package and some other things --- .../im/vector/riotx/core/di/FragmentModule.kt | 6 ++-- .../ContactDetailItem.kt | 2 +- .../ContactItem.kt | 2 +- .../ContactsBookAction.kt} | 8 ++--- .../ContactsBookController.kt} | 15 ++++---- .../ContactsBookFragment.kt} | 30 ++++++++-------- .../ContactsBookViewModel.kt} | 36 +++++++++---------- .../ContactsBookViewState.kt} | 4 +-- .../createdirect/CreateDirectRoomAction.kt | 2 +- .../createdirect/CreateDirectRoomActivity.kt | 12 +++---- .../createdirect/CreateDirectRoomViewModel.kt | 6 ++-- .../invite/InviteUsersToRoomAction.kt | 2 +- .../invite/InviteUsersToRoomActivity.kt | 12 +++---- .../invite/InviteUsersToRoomViewModel.kt | 20 +++++------ .../userdirectory/KnownUsersFragment.kt | 12 +++---- .../UserDirectorySharedAction.kt | 2 +- .../userdirectory/UserDirectoryViewModel.kt | 8 ++--- .../userdirectory/UserDirectoryViewState.kt | 4 +-- ...onebook.xml => fragment_contacts_book.xml} | 4 +-- .../main/res/layout/fragment_known_users.xml | 2 +- vector/src/main/res/values/strings.xml | 4 +++ 21 files changed, 99 insertions(+), 94 deletions(-) rename vector/src/main/java/im/vector/riotx/features/{phonebook => contactsbook}/ContactDetailItem.kt (97%) rename vector/src/main/java/im/vector/riotx/features/{phonebook => contactsbook}/ContactItem.kt (97%) rename vector/src/main/java/im/vector/riotx/features/{phonebook/PhoneBookAction.kt => contactsbook/ContactsBookAction.kt} (78%) rename vector/src/main/java/im/vector/riotx/features/{phonebook/PhoneBookController.kt => contactsbook/ContactsBookController.kt} (91%) rename vector/src/main/java/im/vector/riotx/features/{phonebook/PhoneBookFragment.kt => contactsbook/ContactsBookFragment.kt} (78%) rename vector/src/main/java/im/vector/riotx/features/{phonebook/PhoneBookViewModel.kt => contactsbook/ContactsBookViewModel.kt} (81%) rename vector/src/main/java/im/vector/riotx/features/{phonebook/PhoneBookViewState.kt => contactsbook/ContactsBookViewState.kt} (94%) rename vector/src/main/res/layout/{fragment_phonebook.xml => fragment_contacts_book.xml} (98%) diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt index f5f236364f..8e4f95ed54 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt @@ -23,6 +23,7 @@ import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment +import im.vector.riotx.features.contactsbook.ContactsBookFragment import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStorageKeyFragment import im.vector.riotx.features.crypto.quads.SharedSecuredStoragePassphraseFragment @@ -103,7 +104,6 @@ import im.vector.riotx.features.share.IncomingShareFragment import im.vector.riotx.features.signout.soft.SoftLogoutFragment import im.vector.riotx.features.terms.ReviewTermsFragment import im.vector.riotx.features.userdirectory.KnownUsersFragment -import im.vector.riotx.features.phonebook.PhoneBookFragment import im.vector.riotx.features.userdirectory.UserDirectoryFragment import im.vector.riotx.features.widgets.WidgetFragment @@ -532,6 +532,6 @@ interface FragmentModule { @Binds @IntoMap - @FragmentKey(PhoneBookFragment::class) - fun bindPhoneBookFragment(fragment: PhoneBookFragment): Fragment + @FragmentKey(ContactsBookFragment::class) + fun bindPhoneBookFragment(fragment: ContactsBookFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactDetailItem.kt similarity index 97% rename from vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactDetailItem.kt index 3c739b7829..8615838571 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/ContactDetailItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactDetailItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactItem.kt similarity index 97% rename from vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactItem.kt index b962b0a40d..9a6bf8f144 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/ContactItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactItem.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import android.widget.ImageView import android.widget.TextView diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookAction.kt similarity index 78% rename from vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookAction.kt index 3a5eb4d7ff..001630d398 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookAction.kt @@ -14,11 +14,11 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import im.vector.riotx.core.platform.VectorViewModelAction -sealed class PhoneBookAction : VectorViewModelAction { - data class FilterWith(val filter: String) : PhoneBookAction() - data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : PhoneBookAction() +sealed class ContactsBookAction : VectorViewModelAction { + data class FilterWith(val filter: String) : ContactsBookAction() + data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : ContactsBookAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookController.kt similarity index 91% rename from vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookController.kt index 0fe940b21e..796ed0d80c 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookController.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookController.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Fail @@ -32,12 +32,12 @@ import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.features.home.AvatarRenderer import javax.inject.Inject -class PhoneBookController @Inject constructor( +class ContactsBookController @Inject constructor( private val stringProvider: StringProvider, private val avatarRenderer: AvatarRenderer, private val errorFormatter: ErrorFormatter) : EpoxyController() { - private var state: PhoneBookViewState? = null + private var state: ContactsBookViewState? = null var callback: Callback? = null @@ -45,7 +45,7 @@ class PhoneBookController @Inject constructor( requestModelBuild() } - fun setData(state: PhoneBookViewState) { + fun setData(state: ContactsBookViewState) { this.state = state requestModelBuild() } @@ -64,6 +64,7 @@ class PhoneBookController @Inject constructor( private fun renderLoading() { loadingItem { id("loading") + loadingText(stringProvider.getString(R.string.loading_contact_book)) } } @@ -96,7 +97,7 @@ class PhoneBookController @Inject constructor( if (onlyBoundContacts && it.matrixId == null) return@forEachIndexed contactDetailItem { - id("${mappedContact.id}-$index-${it.email}") + id("${mappedContact.id}-e-$index-${it.email}") threePid(it.email) matrixId(it.matrixId) clickListener { @@ -113,7 +114,7 @@ class PhoneBookController @Inject constructor( if (onlyBoundContacts && it.matrixId == null) return@forEachIndexed contactDetailItem { - id("${mappedContact.id}-$index-${it.phoneNumber}") + id("${mappedContact.id}-m-$index-${it.phoneNumber}") threePid(it.phoneNumber) matrixId(it.matrixId) clickListener { @@ -132,7 +133,7 @@ class PhoneBookController @Inject constructor( val noResultRes = if (hasSearch) { R.string.no_result_placeholder } else { - R.string.empty_phone_book + R.string.empty_contact_book } noResultItem { id("noResult") diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookFragment.kt similarity index 78% rename from vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookFragment.kt index 1da9c6c306..2a2fd9fb5d 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookFragment.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import android.os.Bundle import android.view.View @@ -35,20 +35,20 @@ import im.vector.riotx.features.userdirectory.UserDirectoryAction import im.vector.riotx.features.userdirectory.UserDirectorySharedAction import im.vector.riotx.features.userdirectory.UserDirectorySharedActionViewModel import im.vector.riotx.features.userdirectory.UserDirectoryViewModel -import kotlinx.android.synthetic.main.fragment_phonebook.* +import kotlinx.android.synthetic.main.fragment_contacts_book.* import java.util.concurrent.TimeUnit import javax.inject.Inject -class PhoneBookFragment @Inject constructor( - val phoneBookViewModelFactory: PhoneBookViewModel.Factory, - private val phoneBookController: PhoneBookController -) : VectorBaseFragment(), PhoneBookController.Callback { +class ContactsBookFragment @Inject constructor( + val contactsBookViewModelFactory: ContactsBookViewModel.Factory, + private val contactsBookController: ContactsBookController +) : VectorBaseFragment(), ContactsBookController.Callback { - override fun getLayoutResId() = R.layout.fragment_phonebook + override fun getLayoutResId() = R.layout.fragment_contacts_book private val viewModel: UserDirectoryViewModel by activityViewModel() // Use activityViewModel to avoid loading several times the data - private val phoneBookViewModel: PhoneBookViewModel by activityViewModel() + private val contactsBookViewModel: ContactsBookViewModel by activityViewModel() private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel @@ -64,7 +64,7 @@ class PhoneBookFragment @Inject constructor( private fun setupOnlyBoundContactsView() { phoneBookOnlyBoundContacts.checkedChanges() .subscribe { - phoneBookViewModel.handle(PhoneBookAction.OnlyBoundContacts(it)) + contactsBookViewModel.handle(ContactsBookAction.OnlyBoundContacts(it)) } .disposeOnDestroyView() } @@ -75,20 +75,20 @@ class PhoneBookFragment @Inject constructor( .skipInitialValue() .debounce(300, TimeUnit.MILLISECONDS) .subscribe { - phoneBookViewModel.handle(PhoneBookAction.FilterWith(it.toString())) + contactsBookViewModel.handle(ContactsBookAction.FilterWith(it.toString())) } .disposeOnDestroyView() } override fun onDestroyView() { phoneBookRecyclerView.cleanup() - phoneBookController.callback = null + contactsBookController.callback = null super.onDestroyView() } private fun setupRecyclerView() { - phoneBookController.callback = this - phoneBookRecyclerView.configureWith(phoneBookController) + contactsBookController.callback = this + phoneBookRecyclerView.configureWith(contactsBookController) } private fun setupCloseView() { @@ -97,9 +97,9 @@ class PhoneBookFragment @Inject constructor( } } - override fun invalidate() = withState(phoneBookViewModel) { state -> + override fun invalidate() = withState(contactsBookViewModel) { state -> phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved - phoneBookController.setData(state) + contactsBookController.setData(state) } override fun onMatrixIdClick(matrixId: String) { diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt similarity index 81% rename from vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt index a609b63b67..c09eac2948 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope @@ -43,26 +43,26 @@ import timber.log.Timber private typealias PhoneBookSearch = String -class PhoneBookViewModel @AssistedInject constructor(@Assisted - initialState: PhoneBookViewState, - private val contactsDataSource: ContactsDataSource, - private val session: Session) - : VectorViewModel(initialState) { +class ContactsBookViewModel @AssistedInject constructor(@Assisted + initialState: ContactsBookViewState, + private val contactsDataSource: ContactsDataSource, + private val session: Session) + : VectorViewModel(initialState) { @AssistedInject.Factory interface Factory { - fun create(initialState: PhoneBookViewState): PhoneBookViewModel + fun create(initialState: ContactsBookViewState): ContactsBookViewModel } - companion object : MvRxViewModelFactory { + companion object : MvRxViewModelFactory { - override fun create(viewModelContext: ViewModelContext, state: PhoneBookViewState): PhoneBookViewModel? { + override fun create(viewModelContext: ViewModelContext, state: ContactsBookViewState): ContactsBookViewModel? { return when (viewModelContext) { - is FragmentViewModelContext -> (viewModelContext.fragment() as PhoneBookFragment).phoneBookViewModelFactory.create(state) + is FragmentViewModelContext -> (viewModelContext.fragment() as ContactsBookFragment).contactsBookViewModelFactory.create(state) is ActivityViewModelContext -> { when (viewModelContext.activity()) { - is CreateDirectRoomActivity -> viewModelContext.activity().phoneBookViewModelFactory.create(state) - is InviteUsersToRoomActivity -> viewModelContext.activity().phoneBookViewModelFactory.create(state) + is CreateDirectRoomActivity -> viewModelContext.activity().contactsBookViewModelFactory.create(state) + is InviteUsersToRoomActivity -> viewModelContext.activity().contactsBookViewModelFactory.create(state) else -> error("Wrong activity or fragment") } } @@ -77,7 +77,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted init { loadContacts() - selectSubscribe(PhoneBookViewState::searchTerm, PhoneBookViewState::onlyBoundContacts) { _, _ -> + selectSubscribe(ContactsBookViewState::searchTerm, ContactsBookViewState::onlyBoundContacts) { _, _ -> updateFilteredMappedContacts() } } @@ -163,14 +163,14 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } } - override fun handle(action: PhoneBookAction) { + override fun handle(action: ContactsBookAction) { when (action) { - is PhoneBookAction.FilterWith -> handleFilterWith(action) - is PhoneBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action) + is ContactsBookAction.FilterWith -> handleFilterWith(action) + is ContactsBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action) }.exhaustive } - private fun handleOnlyBoundContacts(action: PhoneBookAction.OnlyBoundContacts) { + private fun handleOnlyBoundContacts(action: ContactsBookAction.OnlyBoundContacts) { setState { copy( onlyBoundContacts = action.onlyBoundContacts @@ -178,7 +178,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted } } - private fun handleFilterWith(action: PhoneBookAction.FilterWith) { + private fun handleFilterWith(action: ContactsBookAction.FilterWith) { setState { copy( searchTerm = action.filter diff --git a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewState.kt similarity index 94% rename from vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt rename to vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewState.kt index d4a578cd8a..8f59403d6a 100644 --- a/vector/src/main/java/im/vector/riotx/features/phonebook/PhoneBookViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewState.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package im.vector.riotx.features.phonebook +package im.vector.riotx.features.contactsbook import com.airbnb.mvrx.Async import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxState import im.vector.riotx.core.contacts.MappedContact -data class PhoneBookViewState( +data class ContactsBookViewState( // All the contacts on the phone val mappedContacts: Async> = Loading(), // Use to filter contacts by display name diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt index 2af01b8964..fad36cc281 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomAction.kt @@ -20,5 +20,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.features.userdirectory.PendingInvitee sealed class CreateDirectRoomAction : VectorViewModelAction { - data class CreateRoomAndInviteSelectedUsers(val selectedUsers: Set) : CreateDirectRoomAction() + data class CreateRoomAndInviteSelectedUsers(val invitees: Set) : CreateDirectRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt index 6acd0e099b..72244d1c94 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomActivity.kt @@ -42,8 +42,8 @@ import im.vector.riotx.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.riotx.core.utils.allGranted import im.vector.riotx.core.utils.checkPermissions -import im.vector.riotx.features.phonebook.PhoneBookFragment -import im.vector.riotx.features.phonebook.PhoneBookViewModel +import im.vector.riotx.features.contactsbook.ContactsBookFragment +import im.vector.riotx.features.contactsbook.ContactsBookViewModel import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs import im.vector.riotx.features.userdirectory.UserDirectoryFragment @@ -60,7 +60,7 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory - @Inject lateinit var phoneBookViewModelFactory: PhoneBookViewModel.Factory + @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter override fun injectWith(injector: ScreenComponent) { @@ -106,21 +106,21 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() { this, PERMISSION_REQUEST_CODE_READ_CONTACTS, 0)) { - addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (allGranted(grantResults)) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { - addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } } } private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_create_direct_room) { - viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.selectedUsers)) + viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(action.invitees)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 44acdc0032..da81c13747 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -49,14 +49,14 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted override fun handle(action: CreateDirectRoomAction) { when (action) { - is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.selectedUsers) + is CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers -> createRoomAndInviteSelectedUsers(action.invitees) } } - private fun createRoomAndInviteSelectedUsers(selectedUsers: Set) { + private fun createRoomAndInviteSelectedUsers(invitees: Set) { val roomParams = CreateRoomParamsBuilder() .apply { - selectedUsers.forEach { + invitees.forEach { when (it) { is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId) is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid) diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt index 253b557cca..6c059c917f 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomAction.kt @@ -20,5 +20,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.features.userdirectory.PendingInvitee sealed class InviteUsersToRoomAction : VectorViewModelAction { - data class InviteSelectedUsers(val selectedUsers: Set) : InviteUsersToRoomAction() + data class InviteSelectedUsers(val invitees: Set) : InviteUsersToRoomAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt index 29d230ba34..af78457d96 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomActivity.kt @@ -38,8 +38,8 @@ import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS import im.vector.riotx.core.utils.allGranted import im.vector.riotx.core.utils.checkPermissions import im.vector.riotx.core.utils.toast -import im.vector.riotx.features.phonebook.PhoneBookFragment -import im.vector.riotx.features.phonebook.PhoneBookViewModel +import im.vector.riotx.features.contactsbook.ContactsBookFragment +import im.vector.riotx.features.contactsbook.ContactsBookViewModel import im.vector.riotx.features.userdirectory.KnownUsersFragment import im.vector.riotx.features.userdirectory.KnownUsersFragmentArgs import im.vector.riotx.features.userdirectory.UserDirectoryFragment @@ -60,7 +60,7 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory @Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory - @Inject lateinit var phoneBookViewModelFactory: PhoneBookViewModel.Factory + @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory @Inject lateinit var errorFormatter: ErrorFormatter override fun injectWith(injector: ScreenComponent) { @@ -107,21 +107,21 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() { this, PERMISSION_REQUEST_CODE_READ_CONTACTS, 0)) { - addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (allGranted(grantResults)) { if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) { - addFragmentToBackstack(R.id.container, PhoneBookFragment::class.java) + addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) } } } private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) { if (action.itemId == R.id.action_invite_users_to_room_invite) { - viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.selectedUsers)) + viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt index 78a9961884..2769dc56bb 100644 --- a/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/invite/InviteUsersToRoomViewModel.kt @@ -53,30 +53,30 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted override fun handle(action: InviteUsersToRoomAction) { when (action) { - is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.selectedUsers) + is InviteUsersToRoomAction.InviteSelectedUsers -> inviteUsersToRoom(action.invitees) } } - private fun inviteUsersToRoom(selectedUsers: Set) { + private fun inviteUsersToRoom(invitees: Set) { _viewEvents.post(InviteUsersToRoomViewEvents.Loading) - Observable.fromIterable(selectedUsers).flatMapCompletable { user -> + Observable.fromIterable(invitees).flatMapCompletable { user -> when (user) { is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null) is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid) } }.subscribe( { - val successMessage = when (selectedUsers.size) { + val successMessage = when (invitees.size) { 1 -> stringProvider.getString(R.string.invitation_sent_to_one_user, - selectedUsers.first().getBestName()) + invitees.first().getBestName()) 2 -> stringProvider.getString(R.string.invitations_sent_to_two_users, - selectedUsers.first().getBestName(), - selectedUsers.last().getBestName()) + invitees.first().getBestName(), + invitees.last().getBestName()) else -> stringProvider.getQuantityString(R.plurals.invitations_sent_to_one_and_more_users, - selectedUsers.size - 1, - selectedUsers.first().getBestName(), - selectedUsers.size - 1) + invitees.size - 1, + invitees.first().getBestName(), + invitees.size - 1) } _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage)) }, diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index 5367ec270c..671c0b0ee1 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -65,7 +65,7 @@ class KnownUsersFragment @Inject constructor( setupAddByMatrixIdView() setupAddFromPhoneBookView() setupCloseView() - viewModel.selectSubscribe(this, UserDirectoryViewState::selectedUsers) { + viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) { renderSelectedUsers(it) } } @@ -78,7 +78,7 @@ class KnownUsersFragment @Inject constructor( override fun onPrepareOptionsMenu(menu: Menu) { withState(viewModel) { - val showMenuItem = it.selectedUsers.isNotEmpty() + val showMenuItem = it.pendingInvitees.isNotEmpty() menu.forEach { menuItem -> menuItem.isVisible = showMenuItem } @@ -87,7 +87,7 @@ class KnownUsersFragment @Inject constructor( } override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) { - sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.selectedUsers)) + sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees)) return@withState true } @@ -139,14 +139,14 @@ class KnownUsersFragment @Inject constructor( knownUsersController.setData(it) } - private fun renderSelectedUsers(selectedUsers: Set) { + private fun renderSelectedUsers(invitees: Set) { invalidateOptionsMenu() val currentNumberOfChips = chipGroup.childCount - val newNumberOfChips = selectedUsers.size + val newNumberOfChips = invitees.size chipGroup.removeAllViews() - selectedUsers.forEach { addChipToGroup(it) } + invitees.forEach { addChipToGroup(it) } // Scroll to the bottom when adding chips. When removing chips, do not scroll if (newNumberOfChips >= currentNumberOfChips) { diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt index 7506b97be3..14270f31a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectorySharedAction.kt @@ -23,5 +23,5 @@ sealed class UserDirectorySharedAction : VectorSharedAction { object OpenPhoneBook : UserDirectorySharedAction() object Close : UserDirectorySharedAction() object GoBack : UserDirectorySharedAction() - data class OnMenuItemSelected(val itemId: Int, val selectedUsers: Set) : UserDirectorySharedAction() + data class OnMenuItemSelected(val itemId: Int, val invitees: Set) : UserDirectorySharedAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt index d7fb800aa4..57ebe408c7 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewModel.kt @@ -86,15 +86,15 @@ class UserDirectoryViewModel @AssistedInject constructor(@Assisted } private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state -> - val selectedUsers = state.selectedUsers.minus(action.pendingInvitee) - setState { copy(selectedUsers = selectedUsers) } + val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee) + setState { copy(pendingInvitees = selectedUsers) } } private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state -> // Reset the filter asap directoryUsersSearch.accept("") - val selectedUsers = state.selectedUsers.toggle(action.pendingInvitee) - setState { copy(selectedUsers = selectedUsers) } + val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee) + setState { copy(pendingInvitees = selectedUsers) } } private fun observeDirectoryUsers() = withState { state -> diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt index 4dee0fe264..4d99a75fde 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/UserDirectoryViewState.kt @@ -27,7 +27,7 @@ data class UserDirectoryViewState( val excludedUserIds: Set? = null, val knownUsers: Async> = Uninitialized, val directoryUsers: Async> = Uninitialized, - val selectedUsers: Set = emptySet(), + val pendingInvitees: Set = emptySet(), val createAndInviteState: Async = Uninitialized, val directorySearchTerm: String = "", val filterKnownUsersValue: Option = Option.empty() @@ -36,7 +36,7 @@ data class UserDirectoryViewState( constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds) fun getSelectedMatrixId(): List { - return selectedUsers + return pendingInvitees .mapNotNull { when (it) { is PendingInvitee.UserPendingInvitee -> it.user.userId diff --git a/vector/src/main/res/layout/fragment_phonebook.xml b/vector/src/main/res/layout/fragment_contacts_book.xml similarity index 98% rename from vector/src/main/res/layout/fragment_phonebook.xml rename to vector/src/main/res/layout/fragment_contacts_book.xml index 14c44c11f0..13a3142cec 100644 --- a/vector/src/main/res/layout/fragment_phonebook.xml +++ b/vector/src/main/res/layout/fragment_contacts_book.xml @@ -45,7 +45,7 @@ android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="1" - android:text="@string/phone_book_title" + android:text="@string/contacts_book_title" android:textColor="?riotx_text_primary" android:textSize="18sp" android:textStyle="bold" @@ -97,7 +97,7 @@ android:id="@+id/phoneBookFilterDivider" android:layout_width="0dp" android:layout_height="1dp" - android:layout_marginTop="16dp" + android:layout_marginTop="4dp" android:background="?attr/vctr_list_divider_color" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml index 16e6858e60..82ddea5323 100644 --- a/vector/src/main/res/layout/fragment_known_users.xml +++ b/vector/src/main/res/layout/fragment_known_users.xml @@ -132,7 +132,7 @@ android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:minHeight="@dimen/layout_touch_size" - android:text="@string/add_from_phone_book" + android:text="@string/search_in_my_contacts" android:visibility="visible" app:icon="@drawable/ic_plus_circle" app:iconPadding="13dp" diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index df596ae2d8..36ec74e028 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2545,4 +2545,8 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Add from my phone book Your phone book is empty Phone book + Search in my contacts + Retrieving your contacts… + Your contact book is empty + Contacts book From 3142442e5c689d30531649750208df4e5eaff43b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 15:49:10 +0200 Subject: [PATCH 14/30] Load contacts much faster --- .../riotx/core/contacts/ContactsDataSource.kt | 116 ++++++++++-------- 1 file changed, 64 insertions(+), 52 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt index f694a9e3f0..4307d106f9 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -31,7 +31,7 @@ class ContactsDataSource @Inject constructor( @WorkerThread fun getContacts(): List { - val result = mutableListOf() + val map = mutableMapOf() val contentResolver = context.contentResolver measureTimeMillis { @@ -53,70 +53,82 @@ class ContactsDataSource @Inject constructor( val id = cursor.getLong(ContactsContract.Contacts._ID) ?: continue val displayName = cursor.getString(ContactsContract.Contacts.DISPLAY_NAME) ?: continue - val currentContact = MappedContactBuilder( + val mappedContactBuilder = MappedContactBuilder( id = id, displayName = displayName ) cursor.getString(ContactsContract.Data.PHOTO_URI) ?.let { Uri.parse(it) } - ?.let { currentContact.photoURI = it } + ?.let { mappedContactBuilder.photoURI = it } - // Get the phone numbers - contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, - arrayOf( - ContactsContract.CommonDataKinds.Phone.NUMBER - ), - ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", - arrayOf(id.toString()), - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) - ?.let { - currentContact.msisdns.add( - MappedMsisdn( - phoneNumber = it, - matrixId = null - ) - ) - } - } - } - // Get Emails - contentResolver.query( - ContactsContract.CommonDataKinds.Email.CONTENT_URI, - arrayOf( - ContactsContract.CommonDataKinds.Email.DATA - ), - ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", - arrayOf(id.toString()), - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - // This would allow you get several email addresses - // if the email addresses were stored in an array - innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) - ?.let { - currentContact.emails.add( - MappedEmail( - email = it, - matrixId = null - ) - ) - } - } - } - - result.add(currentContact.build()) + map[id] = mappedContactBuilder } } } - }.also { Timber.d("Took ${it}ms to fetch ${result.size} contact(s)") } - return result + // Get the phone numbers + contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Phone.CONTACT_ID, + ContactsContract.CommonDataKinds.Phone.NUMBER + ), + null, + null, + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) + ?.let { map[it] } + ?: continue + innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) + ?.let { + mappedContactBuilder.msisdns.add( + MappedMsisdn( + phoneNumber = it, + matrixId = null + ) + ) + } + } + } + + // Get Emails + contentResolver.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Email.CONTACT_ID, + ContactsContract.CommonDataKinds.Email.DATA + ), + null, + null, + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + // This would allow you get several email addresses + // if the email addresses were stored in an array + val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Email.CONTACT_ID) + ?.let { map[it] } + ?: continue + innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) + ?.let { + mappedContactBuilder.emails.add( + MappedEmail( + email = it, + matrixId = null + ) + ) + } + } + } + + }.also { Timber.d("Took ${it}ms to fetch ${map.size} contact(s)") } + + return map + .values .filter { it.emails.isNotEmpty() || it.msisdns.isNotEmpty() } + .map { it.build() } } private fun Cursor.getString(column: String): String? { From 863c09142f213edde3ff942975c3df8b8734ffe3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 17:28:12 +0200 Subject: [PATCH 15/30] Display three pid invites in the room members list (#548) --- CHANGES.md | 1 + .../main/java/im/vector/matrix/rx/RxRoom.kt | 7 ++++ .../core/epoxy/profiles/ProfileMatrixItem.kt | 5 ++- .../members/RoomMemberListController.kt | 34 +++++++++++++++++++ .../members/RoomMemberListFragment.kt | 5 +++ .../members/RoomMemberListViewModel.kt | 8 +++++ .../members/RoomMemberListViewState.kt | 2 ++ vector/src/main/res/values/strings.xml | 1 + 8 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 5daced2228..3978038b8d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Improvements 🙌: - Set up SSSS from security settings (#1567) - New lab setting to add 'unread notifications' tab to main screen - Render third party invite event (#548) + - Display three pid invites in the room members list (#548) Bugfix 🐛: - Integration Manager: Wrong URL to review terms if URL in config contains path (#1606) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index 2e96863d60..e945a52650 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -72,6 +72,13 @@ class RxRoom(private val room: Room) { } } + fun liveStateEvents(eventTypes: Set): Observable> { + return room.getStateEventsLive(eventTypes).asObservable() + .startWithCallable { + room.getStateEvents(eventTypes) + } + } + fun liveReadMarker(): Observable> { return room.getReadMarkerLive().asObservable() } diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt index e9f4dba7a5..d6629c708e 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt @@ -42,7 +42,10 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() override fun bind(holder: Holder) { super.bind(holder) val bestName = matrixItem.getBestName() - val matrixId = matrixItem.id.takeIf { it != bestName } + val matrixId = matrixItem.id + .takeIf { it != bestName } + // Special case for ThreePid fake matrix item + .takeIf { it != "@" } holder.view.setOnClickListener(clickListener) holder.titleView.text = bestName holder.subtitleView.setTextOrHide(matrixId) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt index d0939e939e..6dcf5a0bd3 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -17,7 +17,11 @@ package im.vector.riotx.features.roomprofile.members import com.airbnb.epoxy.TypedEpoxyController +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMemberSummary +import im.vector.matrix.android.api.session.room.model.RoomThirdPartyInviteContent +import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.dividerItem @@ -37,6 +41,7 @@ class RoomMemberListController @Inject constructor( interface Callback { fun onRoomMemberClicked(roomMember: RoomMemberSummary) + fun onThreePidInvites(event: Event) } private val dividerColor = colorProvider.getColorFromAttribute(R.attr.vctr_list_divider_color) @@ -76,5 +81,34 @@ class RoomMemberListController @Inject constructor( } ) } + buildThreePidInvites(data) + } + + private fun buildThreePidInvites(data: RoomMemberListViewState) { + if (data.threePidInvites().isNullOrEmpty()) { + return + } + + buildProfileSection( + stringProvider.getString(R.string.room_member_power_level_three_pid_invites) + ) + + data.threePidInvites()?.forEachIndexed { idx, event -> + val content = event.content.toModel() ?: return@forEachIndexed + + profileMatrixItem { + id("3pid_$idx") + matrixItem(content.toMatrixItem()) + avatarRenderer(avatarRenderer) + clickListener { _ -> + callback?.onThreePidInvites(event) + } + } + + } + } + + private fun RoomThirdPartyInviteContent.toMatrixItem(): MatrixItem { + return MatrixItem.UserItem("@", displayName = displayName) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index 6bd2b5d0e3..72ec3e8462 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -23,6 +23,7 @@ import android.view.View import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R @@ -88,6 +89,10 @@ class RoomMemberListFragment @Inject constructor( navigator.openRoomMemberProfile(roomMember.userId, roomId = roomProfileArgs.roomId, context = requireActivity()) } + override fun onThreePidInvites(event: Event) { + // TODO Display a bottom sheet to revoke invite if power level is high enough + } + private fun renderRoomSummary(state: RoomMemberListViewState) { state.roomSummary()?.let { roomSettingsToolbarTitleView.text = it.displayName diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index f177d26725..e412e41b73 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -68,6 +68,7 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState init { observeRoomMemberSummaries() + observeThirdPartyInvites() observeRoomSummary() observePowerLevel() } @@ -140,6 +141,13 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } } + private fun observeThirdPartyInvites() { + room.rx().liveStateEvents(setOf(EventType.STATE_ROOM_THIRD_PARTY_INVITE)) + .execute { async -> + copy(threePidInvites = async) + } + } + private fun buildRoomMemberSummaries(powerLevelsContent: PowerLevelsContent, roomMembers: List): RoomMemberSummaries { val admins = ArrayList() val moderators = ArrayList() diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt index ece49a178c..6a8738b450 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt @@ -21,6 +21,7 @@ import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotx.R @@ -30,6 +31,7 @@ data class RoomMemberListViewState( val roomId: String, val roomSummary: Async = Uninitialized, val roomMemberSummaries: Async = Uninitialized, + val threePidInvites: Async> = Uninitialized, val trustLevelMap: Async> = Uninitialized, val actionsPermissions: ActionPermissions = ActionPermissions() ) : MvRxState { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 36ec74e028..62e27b51c2 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2148,6 +2148,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Custom Invites Users + Other invites Admin in %1$s Moderator in %1$s From c78bba803c0771ace846a59b72b5142198f229a0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Jul 2020 17:47:41 +0200 Subject: [PATCH 16/30] Revoke ThreePid invitation (#548) --- .../members/RoomMemberListAction.kt | 4 ++- .../members/RoomMemberListFragment.kt | 17 ++++++++++++- .../members/RoomMemberListViewModel.kt | 25 ++++++++++++++++++- .../members/RoomMemberListViewState.kt | 3 ++- vector/src/main/res/values/strings.xml | 3 +++ 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListAction.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListAction.kt index 01a35b84d3..d6a63197bd 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListAction.kt @@ -18,4 +18,6 @@ package im.vector.riotx.features.roomprofile.members import im.vector.riotx.core.platform.VectorViewModelAction -sealed class RoomMemberListAction : VectorViewModelAction +sealed class RoomMemberListAction : VectorViewModelAction { + data class RevokeThreePidInvite(val stateKey: String) : RoomMemberListAction() +} diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt index 72ec3e8462..6fe1f7ad18 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListFragment.kt @@ -20,11 +20,14 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.appcompat.app.AlertDialog import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMemberSummary +import im.vector.matrix.android.api.session.room.model.RoomThirdPartyInviteContent import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.extensions.cleanup @@ -90,7 +93,19 @@ class RoomMemberListFragment @Inject constructor( } override fun onThreePidInvites(event: Event) { - // TODO Display a bottom sheet to revoke invite if power level is high enough + // Display a dialog to revoke invite if power level is high enough + val content = event.content.toModel() ?: return + val stateKey = event.stateKey ?: return + if (withState(viewModel) { it.actionsPermissions.canRevokeThreePidInvite }) { + AlertDialog.Builder(requireActivity()) + .setTitle(R.string.three_pid_revoke_invite_dialog_title) + .setMessage(getString(R.string.three_pid_revoke_invite_dialog_content, content.displayName)) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.revoke) { _, _ -> + viewModel.handle(RoomMemberListAction.RevokeThreePidInvite(stateKey)) + } + .show() + } } private fun renderRoomSummary(state: RoomMemberListViewState) { diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt index e412e41b73..23d5e61399 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewModel.kt @@ -16,11 +16,13 @@ package im.vector.riotx.features.roomprofile.members +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import im.vector.matrix.android.api.NoOpMatrixCallback import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.query.QueryStringValue @@ -37,12 +39,14 @@ import im.vector.matrix.rx.asObservable import im.vector.matrix.rx.mapOptional import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.features.powerlevel.PowerLevelsObservableFactory import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.functions.BiFunction +import kotlinx.coroutines.launch import timber.log.Timber class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState: RoomMemberListViewState, @@ -125,7 +129,12 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState PowerLevelsObservableFactory(room).createObservable() .subscribe { val permissions = ActionPermissions( - canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId) + canInvite = PowerLevelsHelper(it).isUserAbleToInvite(session.myUserId), + canRevokeThreePidInvite = PowerLevelsHelper(it).isUserAllowedToSend( + userId = session.myUserId, + isState = true, + eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE + ) ) setState { copy(actionsPermissions = permissions) @@ -177,5 +186,19 @@ class RoomMemberListViewModel @AssistedInject constructor(@Assisted initialState } override fun handle(action: RoomMemberListAction) { + when (action) { + is RoomMemberListAction.RevokeThreePidInvite -> handleRevokeThreePidInvite(action) + }.exhaustive + } + + private fun handleRevokeThreePidInvite(action: RoomMemberListAction.RevokeThreePidInvite) { + viewModelScope.launch { + room.sendStateEvent( + eventType = EventType.STATE_ROOM_THIRD_PARTY_INVITE, + stateKey = action.stateKey, + body = emptyMap(), + callback = NoOpMatrixCallback() + ) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt index 6a8738b450..55fb950a8e 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListViewState.kt @@ -40,7 +40,8 @@ data class RoomMemberListViewState( } data class ActionPermissions( - val canInvite: Boolean = false + val canInvite: Boolean = false, + val canRevokeThreePidInvite: Boolean = false ) typealias RoomMemberSummaries = List>> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 62e27b51c2..15b658caeb 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2550,4 +2550,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Retrieving your contacts… Your contact book is empty Contacts book + + Revoke invite + Revoke invite to %1$s? From 3d68b15e60398bad316765a00efca466441621aa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 09:43:32 +0200 Subject: [PATCH 17/30] Disable fetching Msisdn, it does not work --- .../riotx/core/contacts/ContactsDataSource.kt | 113 ++++++++++-------- .../contactsbook/ContactsBookViewModel.kt | 6 +- 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt index 4307d106f9..1e487f5a23 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -29,8 +29,16 @@ class ContactsDataSource @Inject constructor( private val context: Context ) { + /** + * Will return a list of contact from the contacts book of the device, with at least one email or phone. + * If both param are false, you will get en empty list. + * Note: The return list does not contain any matrixId. + */ @WorkerThread - fun getContacts(): List { + fun getContacts( + withEmails: Boolean, + withMsisdn: Boolean + ): List { val map = mutableMapOf() val contentResolver = context.contentResolver @@ -69,60 +77,63 @@ class ContactsDataSource @Inject constructor( } // Get the phone numbers - contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, - arrayOf( - ContactsContract.CommonDataKinds.Phone.CONTACT_ID, - ContactsContract.CommonDataKinds.Phone.NUMBER - ), - null, - null, - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) - ?.let { map[it] } - ?: continue - innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) - ?.let { - mappedContactBuilder.msisdns.add( - MappedMsisdn( - phoneNumber = it, - matrixId = null - ) - ) - } + if (withMsisdn) { + contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Phone.CONTACT_ID, + ContactsContract.CommonDataKinds.Phone.NUMBER + ), + null, + null, + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Phone.CONTACT_ID) + ?.let { map[it] } + ?: continue + innerCursor.getString(ContactsContract.CommonDataKinds.Phone.NUMBER) + ?.let { + mappedContactBuilder.msisdns.add( + MappedMsisdn( + phoneNumber = it, + matrixId = null + ) + ) + } + } } - } + } // Get Emails - contentResolver.query( - ContactsContract.CommonDataKinds.Email.CONTENT_URI, - arrayOf( - ContactsContract.CommonDataKinds.Email.CONTACT_ID, - ContactsContract.CommonDataKinds.Email.DATA - ), - null, - null, - null) - ?.use { innerCursor -> - while (innerCursor.moveToNext()) { - // This would allow you get several email addresses - // if the email addresses were stored in an array - val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Email.CONTACT_ID) - ?.let { map[it] } - ?: continue - innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) - ?.let { - mappedContactBuilder.emails.add( - MappedEmail( - email = it, - matrixId = null - ) - ) - } + if (withEmails) { + contentResolver.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + arrayOf( + ContactsContract.CommonDataKinds.Email.CONTACT_ID, + ContactsContract.CommonDataKinds.Email.DATA + ), + null, + null, + null) + ?.use { innerCursor -> + while (innerCursor.moveToNext()) { + // This would allow you get several email addresses + // if the email addresses were stored in an array + val mappedContactBuilder = innerCursor.getLong(ContactsContract.CommonDataKinds.Email.CONTACT_ID) + ?.let { map[it] } + ?: continue + innerCursor.getString(ContactsContract.CommonDataKinds.Email.DATA) + ?.let { + mappedContactBuilder.emails.add( + MappedEmail( + email = it, + matrixId = null + ) + ) + } + } } - } - + } }.also { Timber.d("Took ${it}ms to fetch ${map.size} contact(s)") } return map diff --git a/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt index c09eac2948..3eb6b165b8 100644 --- a/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/contactsbook/ContactsBookViewModel.kt @@ -90,7 +90,11 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted } viewModelScope.launch(Dispatchers.IO) { - allContacts = contactsDataSource.getContacts() + allContacts = contactsDataSource.getContacts( + withEmails = true, + // Do not handle phone numbers for the moment + withMsisdn = false + ) mappedContacts = allContacts setState { From 4ba1a34f387bdd4d2eca54e47897245ebab5661e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 09:46:34 +0200 Subject: [PATCH 18/30] Hide right arrow if threepid invite can not be revoked --- .../vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt | 6 +++++- .../roomprofile/members/RoomMemberListController.kt | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt index d6629c708e..b89da07984 100644 --- a/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt +++ b/vector/src/main/java/im/vector/riotx/core/epoxy/profiles/ProfileMatrixItem.kt @@ -20,6 +20,7 @@ package im.vector.riotx.core.epoxy.profiles import android.view.View import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel @@ -36,6 +37,7 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var editable: Boolean = true @EpoxyAttribute var userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null @EpoxyAttribute var clickListener: View.OnClickListener? = null @@ -46,9 +48,10 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() .takeIf { it != bestName } // Special case for ThreePid fake matrix item .takeIf { it != "@" } - holder.view.setOnClickListener(clickListener) + holder.view.setOnClickListener(clickListener?.takeIf { editable }) holder.titleView.text = bestName holder.subtitleView.setTextOrHide(matrixId) + holder.editableView.isVisible = editable avatarRenderer.render(matrixItem, holder.avatarImageView) holder.avatarDecorationImageView.setImageResource(userEncryptionTrustLevel.toImageRes()) } @@ -58,5 +61,6 @@ abstract class ProfileMatrixItem : VectorEpoxyModel() val subtitleView by bind(R.id.matrixItemSubtitle) val avatarImageView by bind(R.id.matrixItemAvatar) val avatarDecorationImageView by bind(R.id.matrixItemAvatarDecoration) + val editableView by bind(R.id.matrixItemEditable) } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt index 6dcf5a0bd3..495d1164a5 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -100,6 +100,7 @@ class RoomMemberListController @Inject constructor( id("3pid_$idx") matrixItem(content.toMatrixItem()) avatarRenderer(avatarRenderer) + editable(data.actionsPermissions.canRevokeThreePidInvite) clickListener { _ -> callback?.onThreePidInvites(event) } From a58bb776f3db777f83c8e7840ee5ed6d4af8f11d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 10:14:02 +0200 Subject: [PATCH 19/30] Display threePid invite along with the other invite (code is a bit dirty) --- .../vector/riotx/core/extensions/Iterable.kt | 6 +- .../members/RoomMemberListController.kt | 83 +++++++++++++------ vector/src/main/res/values/strings.xml | 1 - 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Iterable.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Iterable.kt index 987194ea2f..b9907f8789 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Iterable.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Iterable.kt @@ -38,13 +38,13 @@ inline fun > Iterable.lastMinBy(selector: (T) -> R): T? /** * Call each for each item, and between between each items */ -inline fun Collection.join(each: (T) -> Unit, between: (T) -> Unit) { +inline fun Collection.join(each: (Int, T) -> Unit, between: (Int, T) -> Unit) { val lastIndex = size - 1 forEachIndexed { idx, t -> - each(t) + each(idx, t) if (idx != lastIndex) { - between(t) + between(idx, t) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt index 495d1164a5..8cf93e8589 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomprofile/members/RoomMemberListController.kt @@ -54,15 +54,29 @@ class RoomMemberListController @Inject constructor( override fun buildModels(data: RoomMemberListViewState?) { val roomMembersByPowerLevel = data?.roomMemberSummaries?.invoke() ?: return + val threePidInvites = data.threePidInvites().orEmpty() + var threePidInvitesDone = threePidInvites.isEmpty() + for ((powerLevelCategory, roomMemberList) in roomMembersByPowerLevel) { if (roomMemberList.isEmpty()) { continue } + + if (powerLevelCategory == RoomMemberListCategories.USER && !threePidInvitesDone) { + // If there is not regular invite, display threepid invite before the regular user + buildProfileSection( + stringProvider.getString(RoomMemberListCategories.INVITE.titleRes) + ) + + buildThreePidInvites(data) + threePidInvitesDone = true + } + buildProfileSection( stringProvider.getString(powerLevelCategory.titleRes) ) roomMemberList.join( - each = { roomMember -> + each = { _, roomMember -> profileMatrixItem { id(roomMember.userId) matrixItem(roomMember.toMatrixItem()) @@ -73,40 +87,59 @@ class RoomMemberListController @Inject constructor( } } }, - between = { roomMemberBefore -> + between = { _, roomMemberBefore -> dividerItem { id("divider_${roomMemberBefore.userId}") color(dividerColor) } } ) + if (powerLevelCategory == RoomMemberListCategories.INVITE) { + // Display the threepid invite after the regular invite + dividerItem { + id("divider_threepidinvites") + color(dividerColor) + } + buildThreePidInvites(data) + threePidInvitesDone = true + } + } + + if (!threePidInvitesDone) { + // If there is not regular invite and no regular user, finally display threepid invite here + buildProfileSection( + stringProvider.getString(RoomMemberListCategories.INVITE.titleRes) + ) + + buildThreePidInvites(data) } - buildThreePidInvites(data) } private fun buildThreePidInvites(data: RoomMemberListViewState) { - if (data.threePidInvites().isNullOrEmpty()) { - return - } - - buildProfileSection( - stringProvider.getString(R.string.room_member_power_level_three_pid_invites) - ) - - data.threePidInvites()?.forEachIndexed { idx, event -> - val content = event.content.toModel() ?: return@forEachIndexed - - profileMatrixItem { - id("3pid_$idx") - matrixItem(content.toMatrixItem()) - avatarRenderer(avatarRenderer) - editable(data.actionsPermissions.canRevokeThreePidInvite) - clickListener { _ -> - callback?.onThreePidInvites(event) - } - } - - } + data.threePidInvites() + ?.filter { it.content.toModel() != null } + ?.join( + each = { idx, event -> + event.content.toModel() + ?.let { content -> + profileMatrixItem { + id("3pid_$idx") + matrixItem(content.toMatrixItem()) + avatarRenderer(avatarRenderer) + editable(data.actionsPermissions.canRevokeThreePidInvite) + clickListener { _ -> + callback?.onThreePidInvites(event) + } + } + } + }, + between = { idx, _ -> + dividerItem { + id("divider3_$idx") + color(dividerColor) + } + } + ) } private fun RoomThirdPartyInviteContent.toMatrixItem(): MatrixItem { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 15b658caeb..be7602af51 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2148,7 +2148,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Custom Invites Users - Other invites Admin in %1$s Moderator in %1$s From e8f28d7ce43470f53cf962c0d6e6f14d307ca40e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 11:49:49 +0200 Subject: [PATCH 20/30] ktlint --- .../api/session/room/model/RoomThirdPartyInviteContent.kt | 1 - .../java/im/vector/riotx/core/contacts/ContactsDataSource.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt index 2b8daa0c5b..fa871d186e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomThirdPartyInviteContent.kt @@ -64,4 +64,3 @@ data class PublicKeys( */ @Json(name = "public_key") val publicKey: String ) - diff --git a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt index 1e487f5a23..fd23e495b9 100644 --- a/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/core/contacts/ContactsDataSource.kt @@ -70,7 +70,6 @@ class ContactsDataSource @Inject constructor( ?.let { Uri.parse(it) } ?.let { mappedContactBuilder.photoURI = it } - map[id] = mappedContactBuilder } } From d8a0a1d38e1737048aa63187595d705625f7f88f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 10 Jul 2020 11:55:11 +0200 Subject: [PATCH 21/30] Expose other objects in the builder to create a room --- .../session/room/model/create/CreateRoomParamsBuilder.kt | 5 +++++ .../session/room/create/CreateRoomParamsInternalBuilder.kt | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt index 6637e3bcb2..c6799a956f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.room.model.create import im.vector.matrix.android.api.session.identity.ThreePid +import im.vector.matrix.android.api.session.room.model.PowerLevelsContent import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -47,6 +48,10 @@ class CreateRoomParamsBuilder { var isDirect: Boolean? = null + var creationContent: Any? = null + + var powerLevelContentOverride: PowerLevelsContent? = null + /** * Mark as a direct message room. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt index 29cf0bbac6..765317005a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt @@ -77,13 +77,11 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( topic = builder.topic, invitedUserIds = builder.invitedUserIds, invite3pids = invite3pids, - // TODO Support this - creationContent = null, + creationContent = builder.creationContent, initialStates = initialStates, preset = builder.preset, isDirect = builder.isDirect, - // TODO Support this - powerLevelContentOverride = null + powerLevelContentOverride = builder.powerLevelContentOverride ) } From ded8acc83692f07deb67729b440b5c3fe9c8836d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:13:22 +0200 Subject: [PATCH 22/30] Rename internal class --- .../im/vector/matrix/android/internal/session/room/RoomAPI.kt | 4 ++-- .../room/create/{CreateRoomParams.kt => CreateRoomBody.kt} | 2 +- .../session/room/create/CreateRoomParamsInternalBuilder.kt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/{CreateRoomParams.kt => CreateRoomBody.kt} (99%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt index a82c96f93d..fd16b1891e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomAPI.kt @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.internal.network.NetworkConstants import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasBody import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription -import im.vector.matrix.android.internal.session.room.create.CreateRoomParams +import im.vector.matrix.android.internal.session.room.create.CreateRoomBody import im.vector.matrix.android.internal.session.room.create.CreateRoomResponse import im.vector.matrix.android.internal.session.room.create.JoinRoomResponse import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse @@ -80,7 +80,7 @@ internal interface RoomAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom") - fun createRoom(@Body param: CreateRoomParams): Call + fun createRoom(@Body param: CreateRoomBody): Call /** * Get a list of messages starting from a reference. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBody.kt similarity index 99% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBody.kt index 525a0501fc..7a27da3607 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBody.kt @@ -28,7 +28,7 @@ import im.vector.matrix.android.internal.session.room.membership.threepid.ThreeP * Parameter to create a room */ @JsonClass(generateAdapter = true) -internal data class CreateRoomParams( +internal data class CreateRoomBody( /** * A public visibility indicates that the room will be shown in the published room list. * A private visibility will hide the room from the published room list. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt index 765317005a..f7088841b8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt @@ -43,7 +43,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( private val accessTokenProvider: AccessTokenProvider ) { - suspend fun build(builder: CreateRoomParamsBuilder): CreateRoomParams { + suspend fun build(builder: CreateRoomParamsBuilder): CreateRoomBody { val invite3pids = builder.invite3pids .takeIf { it.isNotEmpty() } .let { @@ -70,7 +70,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( ) .takeIf { it.isNotEmpty() } - return CreateRoomParams( + return CreateRoomBody( visibility = builder.visibility, roomAliasName = builder.roomAliasName, name = builder.name, From e097bd8117fea84a3e8f47d8ed81d59222449dab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:15:26 +0200 Subject: [PATCH 23/30] Rename CreateRoomParamsBuilder to CreateRoomParams for clarity --- CHANGES.md | 2 +- .../src/main/java/im/vector/matrix/rx/RxSession.kt | 4 ++-- .../vector/matrix/android/common/CryptoTestHelper.kt | 6 +++--- .../android/internal/crypto/gossiping/KeyShareTests.kt | 4 ++-- .../matrix/android/api/session/room/RoomService.kt | 4 ++-- ...{CreateRoomParamsBuilder.kt => CreateRoomParams.kt} | 2 +- .../internal/session/room/DefaultRoomService.kt | 4 ++-- .../room/create/CreateRoomParamsInternalBuilder.kt | 10 +++++----- .../internal/session/room/create/CreateRoomTask.kt | 8 ++++---- .../features/createdirect/CreateDirectRoomViewModel.kt | 4 ++-- .../verification/VerificationBottomSheetViewModel.kt | 4 ++-- .../roomdirectory/createroom/CreateRoomViewModel.kt | 4 ++-- 12 files changed, 28 insertions(+), 28 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/{CreateRoomParamsBuilder.kt => CreateRoomParams.kt} (98%) diff --git a/CHANGES.md b/CHANGES.md index 3978038b8d..8f9d4f956b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,7 +29,7 @@ Translations 🗣: - SDK API changes ⚠️: - - CreateRoomParams has been replaced by CreateRoomParamsBuilder + - CreateRoomParams has been updated Build 🧱: - Upgrade some dependencies diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index 93e2dcae19..ca0bb46f4b 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -32,7 +32,7 @@ import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.widgets.model.Widget @@ -110,7 +110,7 @@ class RxSession(private val session: Session) { .startWithCallable { session.getThreePids() } } - fun createRoom(roomParams: CreateRoomParamsBuilder): Single = singleBuilder { + fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { session.createRoom(roomParams, it) } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 7e8410a440..26c425241d 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent @@ -65,7 +65,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams) val roomId = mTestHelper.doSync { - aliceSession.createRoom(CreateRoomParamsBuilder().apply { name = "MyRoom" }, it) + aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it) } if (encryptedRoom) { @@ -286,7 +286,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun createDM(alice: Session, bob: Session): String { val roomId = mTestHelper.doSync { alice.createRoom( - CreateRoomParamsBuilder().apply { + CreateRoomParams().apply { invitedUserIds.add(bob.myUserId) setDirectMessage() enableEncryptionIfInvitedUsersSupportIt = true diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index e90822a0c7..a5c0913909 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationTran import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.common.CommonTestHelper import im.vector.matrix.android.common.CryptoTestHelper import im.vector.matrix.android.common.SessionTestParams @@ -66,7 +66,7 @@ class KeyShareTests : InstrumentedTest { // Create an encrypted room and add a message val roomId = mTestHelper.doSync { aliceSession.createRoom( - CreateRoomParamsBuilder().apply { + CreateRoomParams().apply { visibility = RoomDirectoryVisibility.PRIVATE enableEncryption() }, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt index 788a074c65..4e7b973bba 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/RoomService.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +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.Optional @@ -32,7 +32,7 @@ interface RoomService { /** * Create a room asynchronously */ - fun createRoom(createRoomParams: CreateRoomParamsBuilder, + fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt similarity index 98% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index c6799a956f..8611395071 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParamsBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -class CreateRoomParamsBuilder { +class CreateRoomParams { var visibility: RoomDirectoryVisibility? = null var roomAliasName: String? = null var name: String? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt index 7d5b8ac341..b8b4c968b1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoomService.kt @@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.RoomService import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.members.ChangeMembershipState import im.vector.matrix.android.api.session.room.model.RoomSummary -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +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.Optional import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask @@ -49,7 +49,7 @@ internal class DefaultRoomService @Inject constructor( private val taskExecutor: TaskExecutor ) : RoomService { - override fun createRoom(createRoomParams: CreateRoomParamsBuilder, callback: MatrixCallback): Cancelable { + override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable { return createRoomTask .configureWith(createRoomParams) { this.callback = callback diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt index f7088841b8..afc5066ac9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.identity.IdentityServiceError import im.vector.matrix.android.api.session.identity.toMedium -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.di.AuthenticatedIdentity @@ -43,7 +43,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( private val accessTokenProvider: AccessTokenProvider ) { - suspend fun build(builder: CreateRoomParamsBuilder): CreateRoomBody { + suspend fun build(builder: CreateRoomParams): CreateRoomBody { val invite3pids = builder.invite3pids .takeIf { it.isNotEmpty() } .let { @@ -85,7 +85,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( ) } - private fun buildHistoryVisibilityEvent(builder: CreateRoomParamsBuilder): Event? { + private fun buildHistoryVisibilityEvent(builder: CreateRoomParams): Event? { return builder.historyVisibility ?.let { val contentMap = mapOf("history_visibility" to it) @@ -100,7 +100,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( /** * Add the crypto algorithm to the room creation parameters. */ - private suspend fun buildEncryptionWithAlgorithmEvent(builder: CreateRoomParamsBuilder): Event? { + private suspend fun buildEncryptionWithAlgorithmEvent(builder: CreateRoomParams): Event? { if (builder.algorithm == null && canEnableEncryption(builder)) { // Enable the encryption @@ -121,7 +121,7 @@ internal class CreateRoomParamsInternalBuilder @Inject constructor( } } - private suspend fun canEnableEncryption(builder: CreateRoomParamsBuilder): Boolean { + private suspend fun canEnableEncryption(builder: CreateRoomParams): Boolean { return (builder.enableEncryptionIfInvitedUsersSupportIt && crossSigningService.isCrossSigningVerified() && builder.invite3pids.isEmpty()) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index e32f8e39ab..d0b746af7d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.room.create import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.internal.database.awaitNotEmptyResult import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields @@ -38,7 +38,7 @@ import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit import javax.inject.Inject -internal interface CreateRoomTask : Task +internal interface CreateRoomTask : Task internal class DefaultCreateRoomTask @Inject constructor( private val roomAPI: RoomAPI, @@ -52,7 +52,7 @@ internal class DefaultCreateRoomTask @Inject constructor( private val eventBus: EventBus ) : CreateRoomTask { - override suspend fun execute(params: CreateRoomParamsBuilder): String { + override suspend fun execute(params: CreateRoomParams): String { val createRoomParams = createRoomParamsInternalBuilder.build(params) val createRoomResponse = executeRequest(eventBus) { @@ -75,7 +75,7 @@ internal class DefaultCreateRoomTask @Inject constructor( return roomId } - private suspend fun handleDirectChatCreation(params: CreateRoomParamsBuilder, roomId: String) { + private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String) { val otherUserId = params.getFirstInvitedUserId() ?: throw IllegalStateException("You can't create a direct room without an invitedUser") diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index da81c13747..319671b230 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -22,7 +22,7 @@ import com.airbnb.mvrx.ViewModelContext import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.rx.rx import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel @@ -54,7 +54,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted } private fun createRoomAndInviteSelectedUsers(invitees: Set) { - val roomParams = CreateRoomParamsBuilder() + val roomParams = CreateRoomParams() .apply { invitees.forEach { when (it) { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index 1833688c35..53c9deb296 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -43,7 +43,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationServ import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.LocalEcho -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 @@ -235,7 +235,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( pendingRequest = Loading() ) } - val roomParams = CreateRoomParamsBuilder() + val roomParams = CreateRoomParams() .apply { invitedUserIds.add(otherUserId) setDirectMessage() diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index 5cb279c848..b75e9444fe 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -28,7 +28,7 @@ import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility -import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel @@ -84,7 +84,7 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr copy(asyncCreateRoomRequest = Loading()) } - val createRoomParams = CreateRoomParamsBuilder() + val createRoomParams = CreateRoomParams() .apply { name = state.roomName.takeIf { it.isNotBlank() } // Directory visibility From a456f4c6a593fecb28dea1d25085f4b9a045b478 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:16:35 +0200 Subject: [PATCH 24/30] Rename CreateRoomParamsInternalBuilder to CreateRoomBodyBuilder for clarity --- ...eRoomParamsInternalBuilder.kt => CreateRoomBodyBuilder.kt} | 2 +- .../android/internal/session/room/create/CreateRoomTask.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/{CreateRoomParamsInternalBuilder.kt => CreateRoomBodyBuilder.kt} (98%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt similarity index 98% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt index afc5066ac9..442dde8ce2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomParamsInternalBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -34,7 +34,7 @@ import im.vector.matrix.android.internal.session.room.membership.threepid.ThreeP import java.security.InvalidParameterException import javax.inject.Inject -internal class CreateRoomParamsInternalBuilder @Inject constructor( +internal class CreateRoomBodyBuilder @Inject constructor( private val ensureIdentityTokenTask: EnsureIdentityTokenTask, private val crossSigningService: CrossSigningService, private val deviceListManager: DeviceListManager, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index d0b746af7d..40cd872287 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -48,12 +48,12 @@ internal class DefaultCreateRoomTask @Inject constructor( private val readMarkersTask: SetReadMarkersTask, @SessionDatabase private val realmConfiguration: RealmConfiguration, - private val createRoomParamsInternalBuilder: CreateRoomParamsInternalBuilder, + private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val eventBus: EventBus ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomParams = createRoomParamsInternalBuilder.build(params) + val createRoomParams = createRoomBodyBuilder.build(params) val createRoomResponse = executeRequest(eventBus) { apiCall = roomAPI.createRoom(createRoomParams) From 0f327fc75f8e4f80e02619496114b097c08f9827 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:17:55 +0200 Subject: [PATCH 25/30] Latest renaming --- .../room/create/CreateRoomBodyBuilder.kt | 52 +++++++++---------- .../session/room/create/CreateRoomTask.kt | 4 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt index 442dde8ce2..23eb88bea9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -43,8 +43,8 @@ internal class CreateRoomBodyBuilder @Inject constructor( private val accessTokenProvider: AccessTokenProvider ) { - suspend fun build(builder: CreateRoomParams): CreateRoomBody { - val invite3pids = builder.invite3pids + suspend fun build(params: CreateRoomParams): CreateRoomBody { + val invite3pids = params.invite3pids .takeIf { it.isNotEmpty() } .let { // This can throw Exception if Identity server is not configured @@ -54,7 +54,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( ?: throw IdentityServiceError.NoIdentityServerConfigured val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured - builder.invite3pids.map { + params.invite3pids.map { ThreePidInviteBody( id_server = identityServerUrlWithoutProtocol, id_access_token = identityServerAccessToken, @@ -65,28 +65,28 @@ internal class CreateRoomBodyBuilder @Inject constructor( } val initialStates = listOfNotNull( - buildEncryptionWithAlgorithmEvent(builder), - buildHistoryVisibilityEvent(builder) + buildEncryptionWithAlgorithmEvent(params), + buildHistoryVisibilityEvent(params) ) .takeIf { it.isNotEmpty() } return CreateRoomBody( - visibility = builder.visibility, - roomAliasName = builder.roomAliasName, - name = builder.name, - topic = builder.topic, - invitedUserIds = builder.invitedUserIds, + visibility = params.visibility, + roomAliasName = params.roomAliasName, + name = params.name, + topic = params.topic, + invitedUserIds = params.invitedUserIds, invite3pids = invite3pids, - creationContent = builder.creationContent, + creationContent = params.creationContent, initialStates = initialStates, - preset = builder.preset, - isDirect = builder.isDirect, - powerLevelContentOverride = builder.powerLevelContentOverride + preset = params.preset, + isDirect = params.isDirect, + powerLevelContentOverride = params.powerLevelContentOverride ) } - private fun buildHistoryVisibilityEvent(builder: CreateRoomParams): Event? { - return builder.historyVisibility + private fun buildHistoryVisibilityEvent(params: CreateRoomParams): Event? { + return params.historyVisibility ?.let { val contentMap = mapOf("history_visibility" to it) @@ -100,13 +100,13 @@ internal class CreateRoomBodyBuilder @Inject constructor( /** * Add the crypto algorithm to the room creation parameters. */ - private suspend fun buildEncryptionWithAlgorithmEvent(builder: CreateRoomParams): Event? { - if (builder.algorithm == null - && canEnableEncryption(builder)) { + private suspend fun buildEncryptionWithAlgorithmEvent(params: CreateRoomParams): Event? { + if (params.algorithm == null + && canEnableEncryption(params)) { // Enable the encryption - builder.enableEncryption() + params.enableEncryption() } - return builder.algorithm + return params.algorithm ?.let { if (it != MXCRYPTO_ALGORITHM_MEGOLM) { throw InvalidParameterException("Unsupported algorithm: $it") @@ -121,12 +121,12 @@ internal class CreateRoomBodyBuilder @Inject constructor( } } - private suspend fun canEnableEncryption(builder: CreateRoomParams): Boolean { - return (builder.enableEncryptionIfInvitedUsersSupportIt + private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean { + return (params.enableEncryptionIfInvitedUsersSupportIt && crossSigningService.isCrossSigningVerified() - && builder.invite3pids.isEmpty()) - && builder.invitedUserIds.isNotEmpty() - && builder.invitedUserIds.let { userIds -> + && params.invite3pids.isEmpty()) + && params.invitedUserIds.isNotEmpty() + && params.invitedUserIds.let { userIds -> val keys = deviceListManager.downloadKeys(userIds, forceDownload = false) userIds.all { userId -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index 40cd872287..e293f2068f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -53,10 +53,10 @@ internal class DefaultCreateRoomTask @Inject constructor( ) : CreateRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomParams = createRoomBodyBuilder.build(params) + val createRoomBody = createRoomBodyBuilder.build(params) val createRoomResponse = executeRequest(eventBus) { - apiCall = roomAPI.createRoom(createRoomParams) + apiCall = roomAPI.createRoom(createRoomBody) } val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) From 75ef491e3ec984e7d339c6c2432f694e1f010234 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:22:21 +0200 Subject: [PATCH 26/30] Move internal methods to internal task --- .../room/model/create/CreateRoomParams.kt | 17 ----------------- .../session/room/create/CreateRoomTask.kt | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 8611395071..40a9437f51 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -71,21 +71,4 @@ class CreateRoomParams { fun enableEncryption() { algorithm = MXCRYPTO_ALGORITHM_MEGOLM } - - /** - * Tells if the created room can be a direct chat one. - * - * @return true if it is a direct chat - */ - fun isDirect(): Boolean { - return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT - && isDirect == true - } - - /** - * @return the first invited user id - */ - fun getFirstInvitedUserId(): String? { - return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index e293f2068f..791091c549 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room.create import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset import im.vector.matrix.android.internal.database.awaitNotEmptyResult import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields @@ -93,4 +94,21 @@ internal class DefaultCreateRoomTask @Inject constructor( val setReadMarkerParams = SetReadMarkersTask.Params(roomId, forceReadReceipt = true, forceReadMarker = true) return readMarkersTask.execute(setReadMarkerParams) } + + /** + * Tells if the created room can be a direct chat one. + * + * @return true if it is a direct chat + */ + private fun CreateRoomParams.isDirect(): Boolean { + return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + && isDirect == true + } + + /** + * @return the first invited user id + */ + private fun CreateRoomParams.getFirstInvitedUserId(): String? { + return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value + } } From 602d67155fbe77320b5fd5f16b7f5dcfabb5040f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:25:21 +0200 Subject: [PATCH 27/30] Copy Javadoc to the API class --- .../room/model/create/CreateRoomParams.kt | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 40a9437f51..5c00e9eccc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -23,18 +23,41 @@ import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM class CreateRoomParams { + /** + * A public visibility indicates that the room will be shown in the published room list. + * A private visibility will hide the room from the published room list. + * Rooms default to private visibility if this key is not included. + * NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"] + */ var visibility: RoomDirectoryVisibility? = null + + /** + * The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room. + * The alias will belong on the same homeserver which created the room. + * For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com. + */ var roomAliasName: String? = null + + /** + * If this is not null, an m.room.name event will be sent into the room to indicate the name of the room. + * See Room Events for more information on m.room.name. + */ var name: String? = null + + /** + * If this is not null, an m.room.topic event will be sent into the room to indicate the topic for the room. + * See Room Events for more information on m.room.topic. + */ var topic: String? = null /** - * UserIds to invite + * A list of user IDs to invite to the room. + * This will tell the server to invite everyone in the list to the newly created room. */ val invitedUserIds = mutableListOf() /** - * ThreePids to invite + * A list of objects representing third party IDs to invite into the room. */ val invite3pids = mutableListOf() @@ -44,12 +67,31 @@ class CreateRoomParams { */ var enableEncryptionIfInvitedUsersSupportIt: Boolean = false + /** + * Convenience parameter for setting various default state events based on a preset. Must be either: + * private_chat => join_rules is set to invite. history_visibility is set to shared. + * trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the + * room creator. + * public_chat: => join_rules is set to public. history_visibility is set to shared. + */ var preset: CreateRoomPreset? = null + /** + * This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid. + * See Direct Messaging for more information. + */ var isDirect: Boolean? = null + /** + * Extra keys to be added to the content of the m.room.create. + * The server will clobber the following keys: creator. + * Future versions of the specification may allow the server to clobber other keys. + */ var creationContent: Any? = null + /** + * The power level content to override in the default power level event + */ var powerLevelContentOverride: PowerLevelsContent? = null /** From ece9fbd3bbab05ff91236e6272acfe652317ac9b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:26:21 +0200 Subject: [PATCH 28/30] Add TODO --- .../android/api/session/room/model/create/CreateRoomParams.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 5c00e9eccc..f89558801d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +// TODO Give a way to include other initial states class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. From 68d475dc55910c1897dd816c726591229569a457 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 11 Jul 2020 22:45:03 +0200 Subject: [PATCH 29/30] Fix crash after rebase --- vector/src/main/res/layout/fragment_contacts_book.xml | 2 +- vector/src/main/res/layout/item_contact_detail.xml | 4 ++-- vector/src/main/res/layout/item_contact_main.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/layout/fragment_contacts_book.xml b/vector/src/main/res/layout/fragment_contacts_book.xml index 13a3142cec..eb90da1bbe 100644 --- a/vector/src/main/res/layout/fragment_contacts_book.xml +++ b/vector/src/main/res/layout/fragment_contacts_book.xml @@ -37,7 +37,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - - Date: Sat, 11 Jul 2020 22:49:29 +0200 Subject: [PATCH 30/30] Fix test compilation issue --- .../java/im/vector/matrix/android/common/CryptoTestHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index 26c425241d..08c24227be 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -175,7 +175,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { } mTestHelper.doSync { - samSession.joinRoom(room.roomId, null, it) + samSession.joinRoom(room.roomId, null, emptyList(), it) } return samSession