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 -> {