diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift index d3b69beb7..ed515cd70 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift @@ -62,9 +62,9 @@ struct RoomEventStringBuilder { return stateEventStringBuilder .buildString(for: state, sender: sender, isOutgoing: isOutgoing) .map(AttributedString.init) - case .roomMembership(let userID, let displayName, let change, _): + case .roomMembership(let userID, let displayName, let change, let reason): return stateEventStringBuilder - .buildString(for: change, memberUserID: userID, memberDisplayName: displayName, sender: sender, isOutgoing: isOutgoing) + .buildString(for: change, reason: reason, memberUserID: userID, memberDisplayName: displayName, sender: sender, isOutgoing: isOutgoing) .map(AttributedString.init) case .profileChange(let displayName, let prevDisplayName, let avatarUrl, let prevAvatarUrl): return stateEventStringBuilder diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift index 13320fcc1..bb6d50793 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomStateEventStringBuilder.swift @@ -13,6 +13,7 @@ struct RoomStateEventStringBuilder { var shouldDisambiguateDisplayNames = true func buildString(for change: MembershipChange?, + reason: String?, memberUserID: String, memberDisplayName: String?, sender: TimelineItemSender, @@ -37,11 +38,21 @@ struct RoomStateEventStringBuilder { case .left: return memberIsYou ? L10n.stateEventRoomLeaveByYou : L10n.stateEventRoomLeave(member) case .banned, .kickedAndBanned: - return senderIsYou ? L10n.stateEventRoomBanByYou(member) : L10n.stateEventRoomBan(senderDisplayName, member) + return switch (senderIsYou, reason) { + case (true, .some(let reason)): L10n.stateEventRoomBanByYouWithReason(member, reason) + case (true, .none): L10n.stateEventRoomBanByYou(member) + case (false, .some(let reason)): L10n.stateEventRoomBanWithReason(senderDisplayName, member, reason) + case (false, .none): L10n.stateEventRoomBan(senderDisplayName, member) + } case .unbanned: return senderIsYou ? L10n.stateEventRoomUnbanByYou(member) : L10n.stateEventRoomUnban(senderDisplayName, member) case .kicked: - return senderIsYou ? L10n.stateEventRoomRemoveByYou(member) : L10n.stateEventRoomRemove(senderDisplayName, member) + return switch (senderIsYou, reason) { + case (true, .some(let reason)): L10n.stateEventRoomRemoveByYouWithReason(member, reason) + case (true, .none): L10n.stateEventRoomRemoveByYou(member) + case (false, .some(let reason)): L10n.stateEventRoomRemoveWithReason(senderDisplayName, member, reason) + case (false, .none): L10n.stateEventRoomRemove(senderDisplayName, member) + } case .invited: if senderIsYou { return L10n.stateEventRoomInviteByYou(member) diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index 080ac728d..5df5c0c46 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -50,11 +50,11 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { return nil } return buildStateTimelineItem(for: eventItemProxy, state: content, isOutgoing: isOutgoing) - case .roomMembership(userId: let userID, let displayName, change: let change, _): + case .roomMembership(userId: let userID, let displayName, change: let change, let reason): if isDM, change == .joined, userID == self.userID { return nil } - return buildStateMembershipChangeTimelineItem(for: eventItemProxy, memberUserID: userID, memberDisplayName: displayName, membershipChange: change, isOutgoing: isOutgoing) + return buildStateMembershipChangeTimelineItem(for: eventItemProxy, memberUserID: userID, memberDisplayName: displayName, membershipChange: change, reason: reason, isOutgoing: isOutgoing) case .profileChange(let displayName, let prevDisplayName, let avatarUrl, let prevAvatarUrl): return buildStateProfileChangeTimelineItem(for: eventItemProxy, displayName: displayName, @@ -728,8 +728,17 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { memberUserID: String, memberDisplayName: String?, membershipChange: MembershipChange?, + reason: String?, isOutgoing: Bool) -> RoomTimelineItemProtocol? { - guard let text = stateEventStringBuilder.buildString(for: membershipChange, memberUserID: memberUserID, memberDisplayName: memberDisplayName, sender: eventItemProxy.sender, isOutgoing: isOutgoing) else { return nil } + guard let text = stateEventStringBuilder.buildString(for: membershipChange, + reason: reason, + memberUserID: memberUserID, + memberDisplayName: memberDisplayName, + sender: eventItemProxy.sender, + isOutgoing: isOutgoing) else { + return nil + } + return buildStateTimelineItem(for: eventItemProxy, text: text, isOutgoing: isOutgoing) } diff --git a/UnitTests/Sources/RoomStateEventStringBuilderTests.swift b/UnitTests/Sources/RoomStateEventStringBuilderTests.swift index 0445bc89b..34ebd2eff 100644 --- a/UnitTests/Sources/RoomStateEventStringBuilderTests.swift +++ b/UnitTests/Sources/RoomStateEventStringBuilderTests.swift @@ -18,6 +18,8 @@ class RoomStateEventStringBuilderTests: XCTestCase { stringBuilder = RoomStateEventStringBuilder(userID: userID) } + // MARK: - User Profiles + func testDisplayNameChanges() { // Changes by you. validateDisplayNameChange(senderID: userID, oldName: "Alice", newName: "Bob", @@ -81,6 +83,8 @@ class RoomStateEventStringBuilderTests: XCTestCase { XCTAssertEqual(string, expectedString) } + // MARK: - Room Info + func testTopicChanges() { let you = TimelineItemSender(id: userID, displayName: "Alice") let other = TimelineItemSender(id: "@bob:matrix.org", displayName: "Bob") @@ -102,4 +106,78 @@ class RoomStateEventStringBuilderTests: XCTestCase { string = stringBuilder.buildString(for: .roomTopic(topic: nil), sender: other, isOutgoing: false) XCTAssertEqual(string, L10n.stateEventRoomTopicRemoved(other.displayName ?? "")) } + + // MARK: - Room Membership + + func testKickMember() { + let you = TimelineItemSender(id: userID, displayName: "Alice") + let other = TimelineItemSender(id: "@bob:matrix.org", displayName: "Bob") + let banned = TimelineItemSender(id: "@spam:matrix.org", displayName: "I like spam") + + let reason = "Spam" + var string = stringBuilder.buildString(for: .kicked, + reason: reason, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: you, + isOutgoing: true) + XCTAssertEqual(string, L10n.stateEventRoomRemoveByYouWithReason(banned.displayName ?? banned.id, reason)) + string = stringBuilder.buildString(for: .kicked, + reason: nil, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: you, + isOutgoing: true) + XCTAssertEqual(string, L10n.stateEventRoomRemoveByYou(banned.displayName ?? banned.id)) + string = stringBuilder.buildString(for: .kicked, + reason: reason, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: other, + isOutgoing: false) + XCTAssertEqual(string, L10n.stateEventRoomRemoveWithReason(other.displayName ?? other.id, banned.displayName ?? banned.id, reason)) + string = stringBuilder.buildString(for: .kicked, + reason: nil, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: other, + isOutgoing: false) + XCTAssertEqual(string, L10n.stateEventRoomRemove(other.displayName ?? other.id, banned.displayName ?? banned.id)) + } + + func testBanMember() { + let you = TimelineItemSender(id: userID, displayName: "Alice") + let other = TimelineItemSender(id: "@bob:matrix.org", displayName: "Bob") + let banned = TimelineItemSender(id: "@spam:matrix.org", displayName: "I like spam") + + let reason = "Spam" + var string = stringBuilder.buildString(for: .banned, + reason: reason, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: you, + isOutgoing: true) + XCTAssertEqual(string, L10n.stateEventRoomBanByYouWithReason(banned.displayName ?? banned.id, reason)) + string = stringBuilder.buildString(for: .banned, + reason: nil, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: you, + isOutgoing: true) + XCTAssertEqual(string, L10n.stateEventRoomBanByYou(banned.displayName ?? banned.id)) + string = stringBuilder.buildString(for: .banned, + reason: reason, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: other, + isOutgoing: false) + XCTAssertEqual(string, L10n.stateEventRoomBanWithReason(other.displayName ?? other.id, banned.displayName ?? banned.id, reason)) + string = stringBuilder.buildString(for: .banned, + reason: nil, + memberUserID: banned.id, + memberDisplayName: banned.displayName, + sender: other, + isOutgoing: false) + XCTAssertEqual(string, L10n.stateEventRoomBan(other.displayName ?? other.id, banned.displayName ?? banned.id)) + } }