Render Room and Message Pills (#3809)

* added a way to render the room and the message

pills, but is WIP

* permalinks now get converted into pills!

* fixed an issue where room address mentions

were not adding a URL properly but a string

* updated tests

* c

* Revert "c"

This reverts commit 5c80252fa23dba7e4d44f2a07fbf1e9500e37c82.

* updated tests

* more tests

* created APIs to get a specific RoomSummary

given the id or the alias

* small mention builder improvement

* pr suggestions
This commit is contained in:
Mauro
2025-02-25 14:46:01 +01:00
committed by GitHub
parent 944fe37fde
commit a2242c63e2
100 changed files with 1017 additions and 247 deletions
+62 -60
View File
@@ -10,75 +10,77 @@ import Foundation
@MainActor
final class PillContext: ObservableObject {
struct PillViewState: Equatable {
let isOwnMention: Bool
let displayText: String
}
@Published var viewState: PillViewState = .undefined
@Published private(set) var viewState: PillViewState
private var cancellable: AnyCancellable?
let data: PillTextAttachmentData
var cancellable: AnyCancellable?
init(timelineContext: TimelineViewModel.Context, data: PillTextAttachmentData) {
switch data.type {
case let .user(id):
let isOwnMention = id == timelineContext.viewState.ownUserID
if let profile = timelineContext.viewState.members[id] {
var name = id
if let displayName = profile.displayName {
name = "@\(displayName)"
}
viewState = PillViewState(isOwnMention: isOwnMention, displayText: name)
} else {
viewState = PillViewState(isOwnMention: isOwnMention, displayText: id)
cancellable = timelineContext.$viewState.sink { [weak self] viewState in
guard let self else {
return
}
if let profile = viewState.members[id] {
var name = id
if let displayName = profile.displayName {
name = "@\(displayName)"
}
self.viewState = PillViewState(isOwnMention: isOwnMention, displayText: name)
cancellable = nil
}
}
}
case .allUsers:
viewState = PillViewState(isOwnMention: true, displayText: PillConstants.atRoom)
}
self.data = data
timelineContext.viewState.pillContextUpdater?(self)
}
}
extension PillContext {
enum MockType {
case loadUser(isOwn: Bool)
case loadedUser(isOwn: Bool)
case allUsers
static func mock(viewState: PillViewState, delay: Duration? = nil) -> PillContext {
// This is just for previews so the internal data doesn't really matter
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: .allUsers, font: .preferredFont(forTextStyle: .body)))
if let delay {
viewModel.viewState = .mention(isOwnMention: false, displayText: "placeholder")
Task {
try? await Task.sleep(for: delay)
viewModel.viewState = viewState
}
} else {
viewModel.viewState = viewState
}
return viewModel
}
}
enum PillViewState: Equatable {
enum PillImage: Equatable {
case link
case roomAvatar(RoomAvatar)
}
static func mock(type: MockType) -> PillContext {
let testID = "@test:test.com"
let pillType: PillType
switch type {
case .loadUser(let isOwn):
pillType = .user(userID: testID)
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: testID)
Task {
try? await Task.sleep(for: .seconds(2))
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Test Long Display Text")
}
return viewModel
case .loadedUser(let isOwn):
pillType = .user(userID: "@test:test.com")
let viewModel = PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
viewModel.viewState = PillViewState(isOwnMention: isOwn, displayText: "@Very Very Long Test Display Text")
return viewModel
case .allUsers:
pillType = .allUsers
return PillContext(timelineContext: TimelineViewModel.mock.context, data: PillTextAttachmentData(type: pillType, font: .preferredFont(forTextStyle: .body)))
case mention(isOwnMention: Bool, displayText: String)
case reference(avatar: PillImage, displayText: String)
case undefined
var isOwnMention: Bool {
switch self {
case .mention(let isOwnMention, _):
return isOwnMention
default:
return false
}
}
var displayText: String {
switch self {
case .mention(_, let displayText), .reference(_, let displayText):
return displayText
case .undefined:
return ""
}
}
var isUndefined: Bool {
switch self {
case .undefined:
return true
default:
return false
}
}
var image: PillImage? {
switch self {
case .reference(let avatar, _):
return avatar
default:
return nil
}
}
}