Show user statuses in mention suggestions.

This commit is contained in:
Doug
2026-07-10 13:16:03 +01:00
committed by Doug
parent 6199ab596f
commit e80717f63d
4 changed files with 91 additions and 18 deletions
@@ -87,7 +87,8 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol {
Self.shouldIncludeMember(userID: member.userID, displayName: member.displayName, searchText: suggestionTrigger.text) else { Self.shouldIncludeMember(userID: member.userID, displayName: member.displayName, searchText: suggestionTrigger.text) else {
return nil return nil
} }
return .init(suggestionType: .user(.init(id: member.userID, displayName: member.displayName, avatarURL: member.avatarURL)), range: suggestionTrigger.range, rawSuggestionText: suggestionTrigger.text) return .init(suggestionType: .user(.init(id: member.userID, displayName: member.displayName, avatarURL: member.avatarURL, status: member.status)),
range: suggestionTrigger.range, rawSuggestionText: suggestionTrigger.text)
} }
if canMentionAllUsers, if canMentionAllUsers,
@@ -32,6 +32,7 @@ struct SuggestionItem: Identifiable, Equatable {
let id: String let id: String
let displayName: String? let displayName: String?
let avatarURL: URL? let avatarURL: URL?
var status = UserStatus()
} }
struct Room: Equatable { struct Room: Equatable {
@@ -59,22 +60,31 @@ struct SuggestionItem: Identifiable, Equatable {
var displayName: String { var displayName: String {
switch suggestionType { switch suggestionType {
case .allUsers: case .allUsers:
return PillUtilities.everyone PillUtilities.everyone
case .user(let user): case .user(let user):
return user.displayName ?? user.id user.displayName ?? user.id
case .room(let room): case .room(let room):
return room.name room.name
}
}
var statusEmoji: Character? {
switch suggestionType {
case .user(let user):
user.status.displayed?.emoji
case .allUsers, .room:
nil
} }
} }
var subtitle: String? { var subtitle: String? {
switch suggestionType { switch suggestionType {
case .allUsers: case .allUsers:
return PillUtilities.atRoom PillUtilities.atRoom
case .user(let user): case .user(let user):
return user.displayName == nil ? nil : user.id user.displayName == nil ? nil : user.id
case .room(let room): case .room(let room):
return room.canonicalAlias room.canonicalAlias
} }
} }
} }
@@ -110,8 +110,19 @@ struct CompletionSuggestion_Previews: PreviewProvider, TestablePreview {
// Putting them is VStack allows the preview to work properly in tests // Putting them is VStack allows the preview to work properly in tests
VStack(spacing: 8) { VStack(spacing: 8) {
CompletionSuggestionView(mediaProvider: MediaProviderMock(.init()), CompletionSuggestionView(mediaProvider: MediaProviderMock(.init()),
items: [.init(suggestionType: .user(.init(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)), range: .init(), rawSuggestionText: ""), items: [
.init(suggestionType: .user(.init(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: .mockMXCUserAvatar)), range: .init(), rawSuggestionText: "")]) { _ in } .init(suggestionType: .user(.init(id: "@user_mention_1:matrix.org",
displayName: "User 1",
avatarURL: nil)),
range: .init(),
rawSuggestionText: ""),
.init(suggestionType: .user(.init(id: "@user_mention_2:matrix.org",
displayName: "User 2",
avatarURL: .mockMXCUserAvatar,
status: .mockCall)),
range: .init(),
rawSuggestionText: "")
]) { _ in }
} }
VStack(spacing: 8) { VStack(spacing: 8) {
@@ -6,6 +6,7 @@
// Please see LICENSE files in the repository root for full details. // Please see LICENSE files in the repository root for full details.
// //
import Compound
import SwiftUI import SwiftUI
struct MentionSuggestionItemView: View { struct MentionSuggestionItemView: View {
@@ -13,19 +14,29 @@ struct MentionSuggestionItemView: View {
let item: SuggestionItem let item: SuggestionItem
var body: some View { var body: some View {
HStack(alignment: .center, spacing: 16) { HStack(alignment: .center, spacing: 12) {
avatar avatar
.accessibilityHidden(true) .accessibilityHidden(true)
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(item.displayName) HStack(spacing: 2) {
.font(.compound.bodyLG) Text(item.displayName)
.foregroundColor(.compound.textPrimary) .lineLimit(1)
.lineLimit(1) .padding(.vertical, 1) // Compensation for Figma line height.
if let statusEmoji = item.statusEmoji {
Text(String(statusEmoji))
}
}
.font(.compound.bodyLG)
.foregroundStyle(.compound.textPrimary)
if let subtitle = item.subtitle { if let subtitle = item.subtitle {
Text(subtitle) Text(subtitle)
.font(.compound.bodySM) .font(.compound.bodySM)
.foregroundColor(.compound.textSecondary) .foregroundStyle(.compound.textSecondary)
.lineLimit(1) .lineLimit(1)
.padding(.vertical, 1) // Compensation for Figma line height.
} }
} }
} }
@@ -49,12 +60,52 @@ struct MentionSuggestionItemView_Previews: PreviewProvider, TestablePreview {
static let mockMediaProvider = MediaProviderMock(.init()) static let mockMediaProvider = MediaProviderMock(.init())
static var previews: some View { static var previews: some View {
MentionSuggestionItemView(mediaProvider: mockMediaProvider, item: .init(suggestionType: .user(.init(id: "test", displayName: "Test", avatarURL: .mockMXCUserAvatar)), range: .init(), rawSuggestionText: "")) MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .user(.init(id: "test", displayName: "Test", avatarURL: .mockMXCUserAvatar)),
range: .init(),
rawSuggestionText: ""))
.previewDisplayName("User") .previewDisplayName("User")
MentionSuggestionItemView(mediaProvider: mockMediaProvider, item: .init(suggestionType: .user(.init(id: "test2", displayName: nil, avatarURL: nil)), range: .init(), rawSuggestionText: ""))
VStack(alignment: .leading, spacing: 8) {
MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .user(.init(id: "@john.smith:example.com",
displayName: "John Smith",
avatarURL: nil,
status: .mockHoliday)),
range: .init(),
rawSuggestionText: ""))
MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .user(.init(id: "@alice.liddel:example.com",
displayName: "Alice Liddell",
avatarURL: nil,
status: .mockCall)),
range: .init(),
rawSuggestionText: ""))
MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .user(.init(id: "@long.name:example.com",
displayName: "I have a long name that doesn't fit on one line",
avatarURL: nil,
status: .mock(text: "Travelling", emoji: "🚆"))),
range: .init(),
rawSuggestionText: ""))
}
.padding(16)
.previewDisplayName("User with status")
MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .user(.init(id: "test2", displayName: nil, avatarURL: nil)),
range: .init(),
rawSuggestionText: ""))
.previewDisplayName("User no display name") .previewDisplayName("User no display name")
MentionSuggestionItemView(mediaProvider: mockMediaProvider, item: .init(suggestionType: .allUsers(.room(id: "room", name: "Room", avatarURL: .mockMXCAvatar)), range: .init(), rawSuggestionText: ""))
MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .allUsers(.room(id: "room", name: "Room", avatarURL: .mockMXCAvatar)),
range: .init(),
rawSuggestionText: ""))
.previewDisplayName("All users") .previewDisplayName("All users")
MentionSuggestionItemView(mediaProvider: mockMediaProvider, MentionSuggestionItemView(mediaProvider: mockMediaProvider,
item: .init(suggestionType: .room(.init(id: "room", item: .init(suggestionType: .room(.init(id: "room",
canonicalAlias: "#room:matrix.org", canonicalAlias: "#room:matrix.org",