From 24fcb3f58f3866cd259c48feae9eec09698d2063 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Mon, 28 Sep 2020 11:18:51 +0300 Subject: [PATCH] Differentiate wordings for direct rooms. --- .../src/main/res/values/strings.xml | 22 +++ .../home/room/detail/RoomDetailFragment.kt | 12 +- .../timeline/factory/EncryptionItemFactory.kt | 11 +- .../factory/MergedHeaderItemFactory.kt | 18 +- .../timeline/format/NoticeEventFormatter.kt | 183 ++++++++++++------ .../detail/timeline/item/BasedMergedItem.kt | 3 +- .../timeline/item/MergedRoomCreationItem.kt | 18 +- vector/src/main/res/values/strings.xml | 7 + 8 files changed, 206 insertions(+), 68 deletions(-) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index f64ec9926e..70a7461fe9 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -10,13 +10,19 @@ Your invitation %1$s created the room You created the room + %1$s joined + You joined %1$s invited %2$s You invited %1$s %1$s invited you %1$s joined the room You joined the room + %1$s joined + You joined %1$s left the room You left the room + %1$s left the room + You left the room %1$s rejected the invitation You rejected the invitation %1$s kicked %2$s @@ -53,6 +59,8 @@ You ended the call. %1$s made future room history visible to %2$s You made future room history visible to %1$s + %1$s made future messages visible to %2$s + You made future messages visible to %1$s all room members, from the point they are invited. all room members, from the point they joined. all room members. @@ -62,6 +70,8 @@ You turned on end-to-end encryption (%1$s) %s upgraded this room. You upgraded this room. + %s upgraded here. + You upgraded here. %1$s requested a VoIP conference You requested a VoIP conference @@ -83,8 +93,12 @@ You updated your profile %1$s %1$s sent an invitation to %2$s to join the room You sent an invitation to %1$s to join the room + %1$s invited %2$s + You invited %1$s %1$s revoked the invitation for %2$s to join the room You revoked the invitation for %1$s to join the room + %1$s revoked the invitation for %2$s + You revoked the invitation for %1$s %1$s accepted the invitation for %2$s You accepted the invitation for %1$s @@ -171,8 +185,12 @@ %1$s invited you. Reason: %2$s %1$s joined the room. Reason: %2$s You joined the room. Reason: %1$s + %1$s joined. Reason: %2$s + You joined. Reason: %1$s %1$s left the room. Reason: %2$s You left the room. Reason: %1$s + %1$s left. Reason: %2$s + You left. Reason: %1$s %1$s rejected the invitation. Reason: %2$s You rejected the invitation. Reason: %1$s %1$s kicked %2$s. Reason: %3$s @@ -220,8 +238,12 @@ "%1$s has allowed guests to join the room." "You have allowed guests to join the room." + "%1$s has allowed guests to join here." + "You have allowed guests to join here." "%1$s has prevented guests from joining the room." "You have prevented guests from joining the room." + "%1$s has prevented guests from joining the room." + "You have prevented guests from joining the room." %1$s turned on end-to-end encryption. You turned on end-to-end encryption. diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index e7140f06f4..b31af30609 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -704,7 +704,11 @@ class RoomDetailFragment @Inject constructor( // safeStartCall(it, isVideoCall) // } } else if (!state.isAllowedToStartWebRTCCall) { - showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call)) + if (session.getRoom(state.roomId)?.roomSummary()?.isDirect == true) { + showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call_in_direct_room)) + } else { + showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call)) + } } else { safeStartCall(isVideoCall) } @@ -714,7 +718,11 @@ class RoomDetailFragment @Inject constructor( // can you add widgets?? if (!state.isAllowedToManageWidgets) { // You do not have permission to start a conference call in this room - showDialogWithMessage(getString(R.string.no_permissions_to_start_conf_call)) + if (session.getRoom(state.roomId)?.roomSummary()?.isDirect == true) { + showDialogWithMessage(getString(R.string.no_permissions_to_start_conf_call_in_direct_room)) + } else { + showDialogWithMessage(getString(R.string.no_permissions_to_start_conf_call)) + } } else { if (state.activeRoomWidgets()?.filter { it.type == WidgetType.Jitsi }?.any() == true) { // A conference is already in progress! diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 5374b4792a..2ed88cbae9 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -25,6 +25,8 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_ +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM @@ -36,7 +38,8 @@ class EncryptionItemFactory @Inject constructor( private val messageColorProvider: MessageColorProvider, private val stringProvider: StringProvider, private val informationDataFactory: MessageInformationDataFactory, - private val avatarSizeProvider: AvatarSizeProvider) { + private val avatarSizeProvider: AvatarSizeProvider, + private val session: Session) { fun create(event: TimelineEvent, highlight: Boolean, @@ -51,7 +54,11 @@ class EncryptionItemFactory @Inject constructor( val shield: StatusTileTimelineItem.ShieldUIState if (isSafeAlgorithm) { title = stringProvider.getString(R.string.encryption_enabled) - description = stringProvider.getString(R.string.encryption_enabled_tile_description) + description = if (session.getRoomSummary(event.root.roomId ?: "")?.isDirect.orFalse()) { + stringProvider.getString(R.string.direct_room_encryption_enabled_tile_description) + } else { + stringProvider.getString(R.string.encryption_enabled_tile_description) + } shield = StatusTileTimelineItem.ShieldUIState.BLACK } else { title = stringProvider.getString(R.string.encryption_not_enabled) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 0ba3b4d47e..41a961dee0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -33,6 +33,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MergedRoomCreationI import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem import im.vector.app.features.home.room.detail.timeline.item.MergedUTDItem_ import im.vector.app.features.settings.VectorPreferences +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent @@ -78,6 +79,12 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde } } + private fun isDirectRoom(roomId: String?): Boolean { + return roomId?.let { + activeSessionHolder.getSafeActiveSession()?.getRoom(roomId)?.roomSummary()?.isDirect + }.orFalse() + } + private fun buildMembershipEventsMergedSummary(currentPosition: Int, items: List, event: TimelineEvent, @@ -100,7 +107,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde avatarUrl = mergedEvent.senderInfo.avatarUrl, memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, - eventId = mergedEvent.root.eventId ?: "" + eventId = mergedEvent.root.eventId ?: "", + isDirectRoom = isDirectRoom(mergedEvent.root.roomId) ) mergedData.add(data) } @@ -157,7 +165,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde items: List, event: TimelineEvent, eventIdToHighlight: String?, - // requestModelBuild: () -> Unit, + // requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedUTDItem_? { Timber.v("## MERGE: buildUTDMergedSummary from position $currentPosition") var prevEvent = items.prevOrNull(currentPosition) @@ -187,7 +195,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde avatarUrl = senderAvatar, memberName = senderName, localId = mergedEvent.localId, - eventId = mergedEvent.root.eventId ?: "" + eventId = mergedEvent.root.eventId ?: "", + isDirectRoom = isDirectRoom(mergedEvent.root.roomId) ) mergedData.add(data) } @@ -247,7 +256,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde avatarUrl = mergedEvent.senderInfo.avatarUrl, memberName = mergedEvent.senderInfo.disambiguatedDisplayName, localId = mergedEvent.localId, - eventId = mergedEvent.root.eventId ?: "" + eventId = mergedEvent.root.eventId ?: "", + isDirectRoom = isDirectRoom(mergedEvent.root.roomId) ) mergedData.add(data) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index 9a4729abee..64d0fdd127 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.detail.timeline.format +import androidx.annotation.StringRes import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.resources.StringProvider @@ -56,6 +57,27 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId + private fun isDirectRoom(roomId: String?): Boolean { + return roomId?.let { + activeSessionDataSource.currentValue?.orNull()?.getRoom(roomId)?.roomSummary()?.isDirect + }.orFalse() + } + + private fun chooseResourceByRoomType(event: Event, + @StringRes directRoomResId: Int, + @StringRes directRoomByUserResId: Int, + @StringRes publicRoomResId: Int, + @StringRes publicRoomByUserResId: Int, + userResArgs: Any? = null, + vararg thirdPartyResArgs: Any? + ): String { + return if (isDirectRoom(event.roomId)) { + if (event.isSentByCurrentUser()) sp.getString(directRoomByUserResId, userResArgs) else sp.getString(directRoomResId, *thirdPartyResArgs) + } else { + if (event.isSentByCurrentUser()) sp.getString(publicRoomByUserResId, userResArgs) else sp.getString(publicRoomResId, *thirdPartyResArgs) + } + } + fun format(timelineEvent: TimelineEvent): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) @@ -179,11 +201,14 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour return event.getClearContent().toModel() ?.takeIf { it.creator.isNullOrBlank().not() } ?.let { - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_created_by_you) - } else { - sp.getString(R.string.notice_room_created, it.creator) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_room_created_by_you, + directRoomByUserResId = R.string.notice_direct_room_created_by_you, + publicRoomResId = R.string.notice_room_created, + publicRoomByUserResId = R.string.notice_room_created_by_you, + thirdPartyResArgs = *arrayOf(it.creator) + ) } } @@ -205,11 +230,14 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } private fun formatRoomTombstoneEvent(event: Event, senderName: String?): CharSequence? { - return if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_update_by_you) - } else { - sp.getString(R.string.notice_room_update, senderName) - } + return chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_update, + directRoomByUserResId = R.string.notice_direct_room_update_by_you, + publicRoomResId = R.string.notice_room_update, + publicRoomByUserResId = R.string.notice_room_update_by_you, + thirdPartyResArgs = *arrayOf(senderName) + ) } private fun formatRoomTopicEvent(event: Event, senderName: String?): CharSequence? { @@ -250,11 +278,15 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour val historyVisibility = event.getClearContent().toModel()?.historyVisibility ?: return null val formattedVisibility = roomHistoryVisibilityFormatter.format(historyVisibility) - return if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_made_future_room_visibility_by_you, formattedVisibility) - } else { - sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) - } + return chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_made_future_direct_room_visibility, + directRoomByUserResId = R.string.notice_made_future_direct_room_visibility_by_you, + publicRoomResId = R.string.notice_made_future_room_visibility, + publicRoomByUserResId = R.string.notice_made_future_room_visibility_by_you, + userResArgs = formattedVisibility, + thirdPartyResArgs = *arrayOf(senderName, formattedVisibility) + ) } private fun formatRoomThirdPartyInvite(event: Event, senderName: String?): CharSequence? { @@ -264,19 +296,27 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour 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) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_third_party_revoked_invite, + directRoomByUserResId = R.string.notice_direct_room_third_party_revoked_invite_by_you, + publicRoomResId = R.string.notice_room_third_party_revoked_invite, + publicRoomByUserResId = R.string.notice_room_third_party_revoked_invite_by_you, + userResArgs = prevContent.displayName, + thirdPartyResArgs = *arrayOf(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) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_third_party_invite, + directRoomByUserResId = R.string.notice_direct_room_third_party_invite_by_you, + publicRoomResId = R.string.notice_room_third_party_invite, + publicRoomByUserResId = R.string.notice_room_third_party_invite_by_you, + userResArgs = content.displayName, + thirdPartyResArgs = *arrayOf(senderName, content.displayName) + ) } else -> null } @@ -391,17 +431,23 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour val eventContent: RoomGuestAccessContent? = event.getClearContent().toModel() return when (eventContent?.guestAccess) { GuestAccess.CanJoin -> - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_guest_access_can_join_by_you) - } else { - sp.getString(R.string.notice_room_guest_access_can_join, senderName) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_guest_access_can_join, + directRoomByUserResId = R.string.notice_direct_room_guest_access_can_join_by_you, + publicRoomResId = R.string.notice_room_guest_access_can_join, + publicRoomByUserResId = R.string.notice_room_guest_access_can_join_by_you, + thirdPartyResArgs = *arrayOf(senderName) + ) GuestAccess.Forbidden -> - if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_guest_access_forbidden_by_you) - } else { - sp.getString(R.string.notice_room_guest_access_forbidden, senderName) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_guest_access_forbidden, + directRoomByUserResId = R.string.notice_direct_room_guest_access_forbidden_by_you, + publicRoomResId = R.string.notice_room_guest_access_forbidden, + publicRoomByUserResId = R.string.notice_room_guest_access_forbidden_by_you, + thirdPartyResArgs = *arrayOf(senderName) + ) else -> null } } @@ -524,14 +570,25 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } Membership.JOIN -> - if (event.isSentByCurrentUser()) { - eventContent.safeReason?.let { reason -> - sp.getString(R.string.notice_room_join_with_reason_by_you, reason) - } ?: sp.getString(R.string.notice_room_join_by_you) - } else { - eventContent.safeReason?.let { reason -> - sp.getString(R.string.notice_room_join_with_reason, senderDisplayName, reason) - } ?: sp.getString(R.string.notice_room_join, senderDisplayName) + eventContent.safeReason?.let { reason -> + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_join_with_reason, + directRoomByUserResId = R.string.notice_direct_room_join_with_reason_by_you, + publicRoomResId = R.string.notice_room_join_with_reason, + publicRoomByUserResId = R.string.notice_room_join_with_reason_by_you, + userResArgs = reason, + thirdPartyResArgs = *arrayOf(senderDisplayName, reason) + ) + } ?: run { + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_join, + directRoomByUserResId = R.string.notice_direct_room_join_by_you, + publicRoomResId = R.string.notice_room_join, + publicRoomByUserResId = R.string.notice_room_join_by_you, + thirdPartyResArgs = *arrayOf(senderDisplayName) + ) } Membership.LEAVE -> // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked @@ -548,14 +605,25 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } ?: sp.getString(R.string.notice_room_reject, senderDisplayName) } else -> - if (event.isSentByCurrentUser()) { - eventContent.safeReason?.let { reason -> - sp.getString(R.string.notice_room_leave_with_reason_by_you, reason) - } ?: sp.getString(R.string.notice_room_leave_by_you) - } else { - eventContent.safeReason?.let { reason -> - sp.getString(R.string.notice_room_leave_with_reason, senderDisplayName, reason) - } ?: sp.getString(R.string.notice_room_leave, senderDisplayName) + eventContent.safeReason?.let { reason -> + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_leave_with_reason, + directRoomByUserResId = R.string.notice_direct_room_leave_with_reason_by_you, + publicRoomResId = R.string.notice_room_leave_with_reason, + publicRoomByUserResId = R.string.notice_room_leave_with_reason_by_you, + userResArgs = reason, + thirdPartyResArgs = *arrayOf(senderDisplayName, reason) + ) + } ?: run { + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.notice_direct_room_leave, + directRoomByUserResId = R.string.notice_direct_room_leave_by_you, + publicRoomResId = R.string.notice_room_leave, + publicRoomByUserResId = R.string.notice_room_leave_by_you, + thirdPartyResArgs = *arrayOf(senderDisplayName) + ) } } } else { @@ -622,11 +690,14 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour val content = event.getClearContent().toModel() ?: return null return when (content.joinRules) { RoomJoinRules.INVITE -> - if (event.isSentByCurrentUser()) { - sp.getString(R.string.room_join_rules_invite_by_you) - } else { - sp.getString(R.string.room_join_rules_invite, senderName) - } + chooseResourceByRoomType( + event = event, + directRoomResId = R.string.direct_room_join_rules_invite, + directRoomByUserResId = R.string.direct_room_join_rules_invite_by_you, + publicRoomResId = R.string.room_join_rules_invite, + publicRoomByUserResId = R.string.room_join_rules_invite_by_you, + thirdPartyResArgs = *arrayOf(senderName) + ) RoomJoinRules.PUBLIC -> if (event.isSentByCurrentUser()) { sp.getString(R.string.room_join_rules_public_by_you) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt index d9dcc98ed1..1f8ad3df1b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt @@ -62,7 +62,8 @@ abstract class BasedMergedItem : BaseEventItem() val eventId: String, val userId: String, val memberName: String, - val avatarUrl: String? + val avatarUrl: String?, + val isDirectRoom: Boolean ) fun Data.toMatrixItem() = MatrixItem.UserItem(userId, memberName, avatarUrl) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index dad5f99eeb..1896a812fc 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -48,9 +48,17 @@ abstract class MergedRoomCreationItem : BasedMergedItem"Due to missing permissions, this action is not possible. You need permission to invite to start a conference in this room You do not have permission to start a conference call in this room + You do not have permission to start a conference call You do not have permission to start a call in this room + You do not have permission to start a call A conference is already in progress! Start video meeting Start audio meeting @@ -1881,6 +1883,8 @@ You made the room public to whoever knows the link. %1$s made the room invite only. You made the room invite only. + %1$s made this invite only. + You made this invite only. Unread messages It\'s your conversation. Own it. @@ -2355,11 +2359,14 @@ Encryption enabled Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. + Messages in this room are end-to-end encrypted. Encryption not enabled The encryption used by this room is not supported %s created and configured the room. You created and configured the room. + %s joined. + You joined. Almost there! Is the other device showing the same shield? Almost there! Waiting for confirmation…