Show the kick/ban reason in the timeline. (#4071)

This commit is contained in:
Doug
2025-04-28 12:17:45 +01:00
committed by GitHub
parent dac1e62e14
commit b14729cdd2
4 changed files with 105 additions and 7 deletions
@@ -62,9 +62,9 @@ struct RoomEventStringBuilder {
return stateEventStringBuilder return stateEventStringBuilder
.buildString(for: state, sender: sender, isOutgoing: isOutgoing) .buildString(for: state, sender: sender, isOutgoing: isOutgoing)
.map(AttributedString.init) .map(AttributedString.init)
case .roomMembership(let userID, let displayName, let change, _): case .roomMembership(let userID, let displayName, let change, let reason):
return stateEventStringBuilder 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) .map(AttributedString.init)
case .profileChange(let displayName, let prevDisplayName, let avatarUrl, let prevAvatarUrl): case .profileChange(let displayName, let prevDisplayName, let avatarUrl, let prevAvatarUrl):
return stateEventStringBuilder return stateEventStringBuilder
@@ -13,6 +13,7 @@ struct RoomStateEventStringBuilder {
var shouldDisambiguateDisplayNames = true var shouldDisambiguateDisplayNames = true
func buildString(for change: MembershipChange?, func buildString(for change: MembershipChange?,
reason: String?,
memberUserID: String, memberUserID: String,
memberDisplayName: String?, memberDisplayName: String?,
sender: TimelineItemSender, sender: TimelineItemSender,
@@ -37,11 +38,21 @@ struct RoomStateEventStringBuilder {
case .left: case .left:
return memberIsYou ? L10n.stateEventRoomLeaveByYou : L10n.stateEventRoomLeave(member) return memberIsYou ? L10n.stateEventRoomLeaveByYou : L10n.stateEventRoomLeave(member)
case .banned, .kickedAndBanned: 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: case .unbanned:
return senderIsYou ? L10n.stateEventRoomUnbanByYou(member) : L10n.stateEventRoomUnban(senderDisplayName, member) return senderIsYou ? L10n.stateEventRoomUnbanByYou(member) : L10n.stateEventRoomUnban(senderDisplayName, member)
case .kicked: 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: case .invited:
if senderIsYou { if senderIsYou {
return L10n.stateEventRoomInviteByYou(member) return L10n.stateEventRoomInviteByYou(member)
@@ -50,11 +50,11 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return nil return nil
} }
return buildStateTimelineItem(for: eventItemProxy, state: content, isOutgoing: isOutgoing) 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 { if isDM, change == .joined, userID == self.userID {
return nil 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): case .profileChange(let displayName, let prevDisplayName, let avatarUrl, let prevAvatarUrl):
return buildStateProfileChangeTimelineItem(for: eventItemProxy, return buildStateProfileChangeTimelineItem(for: eventItemProxy,
displayName: displayName, displayName: displayName,
@@ -728,8 +728,17 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
memberUserID: String, memberUserID: String,
memberDisplayName: String?, memberDisplayName: String?,
membershipChange: MembershipChange?, membershipChange: MembershipChange?,
reason: String?,
isOutgoing: Bool) -> RoomTimelineItemProtocol? { 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) return buildStateTimelineItem(for: eventItemProxy, text: text, isOutgoing: isOutgoing)
} }
@@ -18,6 +18,8 @@ class RoomStateEventStringBuilderTests: XCTestCase {
stringBuilder = RoomStateEventStringBuilder(userID: userID) stringBuilder = RoomStateEventStringBuilder(userID: userID)
} }
// MARK: - User Profiles
func testDisplayNameChanges() { func testDisplayNameChanges() {
// Changes by you. // Changes by you.
validateDisplayNameChange(senderID: userID, oldName: "Alice", newName: "Bob", validateDisplayNameChange(senderID: userID, oldName: "Alice", newName: "Bob",
@@ -81,6 +83,8 @@ class RoomStateEventStringBuilderTests: XCTestCase {
XCTAssertEqual(string, expectedString) XCTAssertEqual(string, expectedString)
} }
// MARK: - Room Info
func testTopicChanges() { func testTopicChanges() {
let you = TimelineItemSender(id: userID, displayName: "Alice") let you = TimelineItemSender(id: userID, displayName: "Alice")
let other = TimelineItemSender(id: "@bob:matrix.org", displayName: "Bob") 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) string = stringBuilder.buildString(for: .roomTopic(topic: nil), sender: other, isOutgoing: false)
XCTAssertEqual(string, L10n.stateEventRoomTopicRemoved(other.displayName ?? "")) 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))
}
} }