diff --git a/CHANGES.md b/CHANGES.md index fd076e27ea..9348717fb5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Features ✨: - Improvements 🙌: + - Wording differentiation for direct rooms (#2176) - PIN code: request PIN code if phone has been locked - Small optimisation of scrolling experience in timeline (#2114) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index f64ec9926e..3f75b715f6 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 created the discussion + You created the discussion %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..3b4795b965 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,13 @@ class RoomDetailFragment @Inject constructor( // safeStartCall(it, isVideoCall) // } } else if (!state.isAllowedToStartWebRTCCall) { - showDialogWithMessage(getString(R.string.no_permissions_to_start_webrtc_call)) + showDialogWithMessage(getString( + if (state.isDm()) { + R.string.no_permissions_to_start_webrtc_call_in_direct_room + } else { + R.string.no_permissions_to_start_webrtc_call + }) + ) } else { safeStartCall(isVideoCall) } @@ -714,7 +720,13 @@ 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)) + showDialogWithMessage(getString( + if (state.isDm()) { + R.string.no_permissions_to_start_conf_call_in_direct_room + } else { + 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/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt index 16a7379b6a..36acc35148 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt @@ -77,4 +77,6 @@ data class RoomDetailViewState( // Also highlight the target event, if any highlightedEventId = args.eventId ) + + fun isDm() = asyncRoomSummary()?.isDirect == true } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index a49b74c243..8a25e5fc93 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -190,7 +190,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.CALL_CANDIDATES, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> { - noticeEventFormatter.format(timelineEvent) + noticeEventFormatter.format(timelineEvent, room?.roomSummary()) } else -> null } ?: "" 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..4f2c8ea63a 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,13 @@ 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 = stringProvider.getString( + if (session.getRoomSummary(event.root.roomId ?: "")?.isDirect.orFalse()) { + R.string.direct_room_encryption_enabled_tile_description + } else { + 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..08f93b23cd 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 @@ -22,6 +22,7 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration import im.vector.app.features.home.room.detail.timeline.helper.prevSameTypeEvents @@ -33,6 +34,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 @@ -45,6 +47,7 @@ import javax.inject.Inject class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider, + private val roomSummaryHolder: RoomSummaryHolder, private val vectorPreferences: VectorPreferences) { private val collapsedEventIds = linkedSetOf() @@ -78,6 +81,8 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde } } + private fun isDirectRoom() = roomSummaryHolder.roomSummary?.isDirect.orFalse() + private fun buildMembershipEventsMergedSummary(currentPosition: Int, items: List, event: TimelineEvent, @@ -100,7 +105,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() ) mergedData.add(data) } @@ -157,7 +163,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 +193,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() ) mergedData.add(data) } @@ -247,7 +254,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() ) mergedData.add(data) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 97bc693f26..1e809d9ab5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -38,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadSt import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageBlockCodeItem @@ -101,6 +102,7 @@ class MessageItemFactory @Inject constructor( private val messageItemAttributesFactory: MessageItemAttributesFactory, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, private val contentDownloadStateTrackerBinder: ContentDownloadStateTrackerBinder, + private val roomSummaryHolder: RoomSummaryHolder, private val defaultItemFactory: DefaultItemFactory, private val noticeItemFactory: NoticeItemFactory, private val avatarSizeProvider: AvatarSizeProvider, @@ -130,7 +132,7 @@ class MessageItemFactory @Inject constructor( || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should display it when debugging as a notice event - return noticeItemFactory.create(event, highlight, callback) + return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) } val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) @@ -146,7 +148,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback) + is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index cd8c682f39..ec065543f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvide import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.item.NoticeItem import im.vector.app.features.home.room.detail.timeline.item.NoticeItem_ +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject @@ -34,8 +35,9 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv fun create(event: TimelineEvent, highlight: Boolean, + roomSummary: RoomSummary?, callback: TimelineEventController.Callback?): NoticeItem? { - val formattedText = eventFormatter.format(event) ?: return null + val formattedText = eventFormatter.format(event, roomSummary) ?: return null val informationData = informationDataFactory.create(event, null) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 31adbdb8a6..25b5fd718b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -32,6 +33,7 @@ import javax.inject.Inject class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider, private val userPreferencesProvider: UserPreferencesProvider, private val session: Session, + private val roomSummaryHolder: RoomSummaryHolder, private val noticeItemFactory: NoticeItemFactory) { fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { @@ -52,7 +54,7 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { return if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, false, callback) + noticeItemFactory.create(event, false, roomSummaryHolder.roomSummary, callback) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 1f99efd08e..40286678c8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -20,6 +20,7 @@ import im.vector.app.core.epoxy.EmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -31,6 +32,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val defaultItemFactory: DefaultItemFactory, private val encryptionItemFactory: EncryptionItemFactory, private val roomCreateItemFactory: RoomCreateItemFactory, + private val roomSummaryHolder: RoomSummaryHolder, private val verificationConclusionItemFactory: VerificationItemFactory, private val userPreferencesProvider: UserPreferencesProvider) { @@ -63,10 +65,8 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_ANSWER, EventType.STATE_ROOM_POWER_LEVELS, EventType.REACTION, - EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) - EventType.STATE_ROOM_ENCRYPTION -> { - encryptionItemFactory.create(event, highlight, callback) - } + EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) // Crypto @@ -87,7 +87,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me // TODO These are not filtered out by timeline when encrypted // For now manually ignore if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, highlight, callback) + noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 0b623d78f1..59daf5a0a0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -24,6 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder 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.session.Session @@ -50,6 +51,7 @@ class VerificationItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val noticeItemFactory: NoticeItemFactory, private val userPreferencesProvider: UserPreferencesProvider, + private val roomSummaryHolder: RoomSummaryHolder, private val stringProvider: StringProvider, private val session: Session ) { @@ -151,7 +153,7 @@ class VerificationItemFactory @Inject constructor( highlight: Boolean, callback: TimelineEventController.Callback? ): VectorEpoxyModel<*>? { - if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback) + if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) return null } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index cb9f583a2a..78d9a49c9c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -23,6 +23,7 @@ import im.vector.app.core.resources.StringProvider import me.gujun.android.span.span 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.RoomSummary import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS @@ -40,7 +41,7 @@ class DisplayableEventFormatter @Inject constructor( private val noticeEventFormatter: NoticeEventFormatter ) { - fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean): CharSequence { + fun format(timelineEvent: TimelineEvent, appendAuthor: Boolean, roomSummary: RoomSummary?): CharSequence { if (timelineEvent.root.isRedacted()) { return noticeEventFormatter.formatRedactedEvent(timelineEvent.root) } @@ -130,7 +131,7 @@ class DisplayableEventFormatter @Inject constructor( } else -> { return span { - text = noticeEventFormatter.format(timelineEvent) ?: "" + text = noticeEventFormatter.format(timelineEvent, roomSummary) ?: "" textStyle = "italic" } } 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..e26472feb0 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 @@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -56,23 +57,26 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId - fun format(timelineEvent: TimelineEvent): CharSequence? { + private fun RoomSummary?.isDm() = this?.isDirect.orFalse() + + fun format(timelineEvent: TimelineEvent, rs: RoomSummary?): CharSequence? { return when (val type = timelineEvent.root.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) - EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) + EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, rs) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) 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_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) 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) - EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> + formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) + EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_WIDGET, EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, rs) EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.CALL_INVITE, EventType.CALL_CANDIDATES, @@ -151,19 +155,19 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - fun format(event: Event, senderName: String?): CharSequence? { + fun format(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { return when (val type = event.getClearType()) { - EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName) + EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, rs) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName) 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.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName, rs) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName, rs) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName, rs) EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName) - EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName) + EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, rs) else -> { Timber.v("Type $type not handled by this formatter") null @@ -175,14 +179,14 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour return "{ \"type\": ${event.getClearType()} }" } - private fun formatRoomCreateEvent(event: Event): CharSequence? { + private fun formatRoomCreateEvent(event: Event, rs: RoomSummary?): CharSequence? { return event.getClearContent().toModel() ?.takeIf { it.creator.isNullOrBlank().not() } ?.let { if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_created_by_you) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_created_by_you else R.string.notice_room_created_by_you) } else { - sp.getString(R.string.notice_room_created, it.creator) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_created else R.string.notice_room_created, it.creator) } } } @@ -204,11 +208,11 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - private fun formatRoomTombstoneEvent(event: Event, senderName: String?): CharSequence? { + private fun formatRoomTombstoneEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { return if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_update_by_you) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_update_by_you else R.string.notice_room_update_by_you) } else { - sp.getString(R.string.notice_room_update, senderName) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_update else R.string.notice_room_update, senderName) } } @@ -246,18 +250,20 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? { + private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { 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) + sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility_by_you else R.string.notice_made_future_room_visibility_by_you, + formattedVisibility) } else { - sp.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility) + sp.getString(if (rs.isDm()) R.string.notice_made_future_direct_room_visibility else R.string.notice_made_future_room_visibility, + senderName, formattedVisibility) } } - private fun formatRoomThirdPartyInvite(event: Event, senderName: String?): CharSequence? { + private fun formatRoomThirdPartyInvite(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { val content = event.getClearContent().toModel() val prevContent = event.resolvedPrevContent()?.toModel() @@ -265,17 +271,26 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour prevContent != null -> { // Revoke case if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_third_party_revoked_invite_by_you, prevContent.displayName) + sp.getString( + if (rs.isDm()) { + R.string.notice_direct_room_third_party_revoked_invite_by_you + } else { + 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) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_revoked_invite else 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) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_invite_by_you else R.string.notice_room_third_party_invite_by_you, + content.displayName) } else { - sp.getString(R.string.notice_room_third_party_invite, senderName, content.displayName) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_third_party_invite else R.string.notice_room_third_party_invite, + senderName, content.displayName) } } else -> null @@ -323,13 +338,13 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - private fun formatRoomMemberEvent(event: Event, senderName: String?): String? { + private fun formatRoomMemberEvent(event: Event, senderName: String?, rs: RoomSummary?): String? { val eventContent: RoomMemberContent? = event.getClearContent().toModel() val prevEventContent: RoomMemberContent? = event.resolvedPrevContent().toModel() val isMembershipEvent = prevEventContent?.membership != eventContent?.membership || eventContent?.membership == Membership.LEAVE return if (isMembershipEvent) { - buildMembershipNotice(event, senderName, eventContent, prevEventContent) + buildMembershipNotice(event, senderName, eventContent, prevEventContent, rs) } else { buildProfileNotice(event, senderName, eventContent, prevEventContent) } @@ -387,20 +402,26 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - private fun formatRoomGuestAccessEvent(event: Event, senderName: String?): String? { + private fun formatRoomGuestAccessEvent(event: Event, senderName: String?, rs: RoomSummary?): String? { 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) + sp.getString( + if (rs.isDm()) R.string.notice_direct_room_guest_access_can_join_by_you else R.string.notice_room_guest_access_can_join_by_you + ) } else { - sp.getString(R.string.notice_room_guest_access_can_join, senderName) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_guest_access_can_join else R.string.notice_room_guest_access_can_join, + senderName) } GuestAccess.Forbidden -> if (event.isSentByCurrentUser()) { - sp.getString(R.string.notice_room_guest_access_forbidden_by_you) + sp.getString( + if (rs.isDm()) R.string.notice_direct_room_guest_access_forbidden_by_you else R.string.notice_room_guest_access_forbidden_by_you + ) } else { - sp.getString(R.string.notice_room_guest_access_forbidden, senderName) + sp.getString(if (rs.isDm()) R.string.notice_direct_room_guest_access_forbidden else R.string.notice_room_guest_access_forbidden, + senderName) } else -> null } @@ -476,7 +497,11 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour return displayText.toString() } - private fun buildMembershipNotice(event: Event, senderName: String?, eventContent: RoomMemberContent?, prevEventContent: RoomMemberContent?): String? { + private fun buildMembershipNotice(event: Event, + senderName: String?, + eventContent: RoomMemberContent?, + prevEventContent: RoomMemberContent?, + rs: RoomSummary?): String? { val senderDisplayName = senderName ?: event.senderId ?: "" val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: event.stateKey ?: "" return when (eventContent?.membership) { @@ -524,14 +549,21 @@ 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 -> + if (event.isSentByCurrentUser()) { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_with_reason_by_you else R.string.notice_room_join_with_reason_by_you, + reason) + } else { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_with_reason else R.string.notice_room_join_with_reason, + senderDisplayName, reason) + } + } ?: run { + if (event.isSentByCurrentUser()) { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_join_by_you else R.string.notice_room_join_by_you) + } else { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_join else R.string.notice_room_join, + 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 +580,27 @@ 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 -> + if (event.isSentByCurrentUser()) { + sp.getString( + if (rs.isDm()) { + R.string.notice_direct_room_leave_with_reason_by_you + } else { + R.string.notice_room_leave_with_reason_by_you + }, + reason + ) + } else { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave_with_reason else R.string.notice_room_leave_with_reason, + senderDisplayName, reason) + } + } ?: run { + if (event.isSentByCurrentUser()) { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave_by_you else R.string.notice_room_leave_by_you) + } else { + sp.getString(if (rs.isDm()) R.string.notice_direct_room_leave else R.string.notice_room_leave, + senderDisplayName) + } } } } else { @@ -618,14 +663,15 @@ class NoticeEventFormatter @Inject constructor(private val activeSessionDataSour } } - private fun formatJoinRulesEvent(event: Event, senderName: String?): CharSequence? { + private fun formatJoinRulesEvent(event: Event, senderName: String?, rs: RoomSummary?): CharSequence? { 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) + sp.getString(if (rs.isDm()) R.string.direct_room_join_rules_invite_by_you else R.string.room_join_rules_invite_by_you) } else { - sp.getString(R.string.room_join_rules_invite, senderName) + sp.getString(if (rs.isDm()) R.string.direct_room_join_rules_invite else R.string.room_join_rules_invite, + senderName) } RoomJoinRules.PUBLIC -> if (event.isSentByCurrentUser()) { 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. @@ -2113,12 +2117,15 @@ Waiting for %s… For extra security, verify %s by checking a one-time code on both your devices.\n\nFor maximum security, do this in person. Messages in this room are not end-to-end encrypted. + Messages here are not end-to-end encrypted. Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them. + Messages here are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them. Security Learn more More Admin Actions Room settings + Settings Notifications "One person" @@ -2126,6 +2133,7 @@ Uploads Leave Room + Leave "Leaving the room…" Admins @@ -2355,11 +2363,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…