mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 04:18:31 +00:00
Add the UserStatus and a status field on UserProfile/RoomMember/TimelineItemSender.
This commit is contained in:
@@ -294,6 +294,7 @@
|
|||||||
"common_no_space_name" = "No space name";
|
"common_no_space_name" = "No space name";
|
||||||
"common_not_encrypted" = "Not encrypted";
|
"common_not_encrypted" = "Not encrypted";
|
||||||
"common_offline" = "Offline";
|
"common_offline" = "Offline";
|
||||||
|
"common_on_a_call" = "On a call";
|
||||||
"common_open_source_licenses" = "Open source licenses";
|
"common_open_source_licenses" = "Open source licenses";
|
||||||
"common_optic_id_ios" = "Optic ID";
|
"common_optic_id_ios" = "Optic ID";
|
||||||
"common_or" = "or";
|
"common_or" = "or";
|
||||||
|
|||||||
@@ -294,6 +294,7 @@
|
|||||||
"common_no_space_name" = "No space name";
|
"common_no_space_name" = "No space name";
|
||||||
"common_not_encrypted" = "Not encrypted";
|
"common_not_encrypted" = "Not encrypted";
|
||||||
"common_offline" = "Offline";
|
"common_offline" = "Offline";
|
||||||
|
"common_on_a_call" = "On a call";
|
||||||
"common_open_source_licenses" = "Open source licenses";
|
"common_open_source_licenses" = "Open source licenses";
|
||||||
"common_optic_id_ios" = "Optic ID";
|
"common_optic_id_ios" = "Optic ID";
|
||||||
"common_or" = "or";
|
"common_or" = "or";
|
||||||
|
|||||||
@@ -644,6 +644,8 @@ internal nonisolated enum L10n {
|
|||||||
internal static var commonNotEncrypted: String { return L10n.tr("Localizable", "common_not_encrypted") }
|
internal static var commonNotEncrypted: String { return L10n.tr("Localizable", "common_not_encrypted") }
|
||||||
/// Offline
|
/// Offline
|
||||||
internal static var commonOffline: String { return L10n.tr("Localizable", "common_offline") }
|
internal static var commonOffline: String { return L10n.tr("Localizable", "common_offline") }
|
||||||
|
/// On a call
|
||||||
|
internal static var commonOnACall: String { return L10n.tr("Localizable", "common_on_a_call") }
|
||||||
/// Open source licenses
|
/// Open source licenses
|
||||||
internal static var commonOpenSourceLicenses: String { return L10n.tr("Localizable", "common_open_source_licenses") }
|
internal static var commonOpenSourceLicenses: String { return L10n.tr("Localizable", "common_open_source_licenses") }
|
||||||
/// Optic ID
|
/// Optic ID
|
||||||
|
|||||||
@@ -10045,6 +10045,11 @@ nonisolated class RoomMemberProxyMock: RoomMemberProxyProtocol, @unchecked Senda
|
|||||||
nonisolated(unsafe) var displayName: String?
|
nonisolated(unsafe) var displayName: String?
|
||||||
nonisolated(unsafe) var disambiguatedDisplayName: String?
|
nonisolated(unsafe) var disambiguatedDisplayName: String?
|
||||||
nonisolated(unsafe) var avatarURL: URL?
|
nonisolated(unsafe) var avatarURL: URL?
|
||||||
|
var status: UserStatus {
|
||||||
|
get { return underlyingStatus }
|
||||||
|
set(value) { underlyingStatus = value }
|
||||||
|
}
|
||||||
|
nonisolated(unsafe) var underlyingStatus: UserStatus!
|
||||||
var membership: MembershipState {
|
var membership: MembershipState {
|
||||||
get { return underlyingMembership }
|
get { return underlyingMembership }
|
||||||
set(value) { underlyingMembership = value }
|
set(value) { underlyingMembership = value }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ nonisolated struct RoomMemberProxyMockConfiguration {
|
|||||||
var userID: String
|
var userID: String
|
||||||
var displayName: String?
|
var displayName: String?
|
||||||
var avatarURL: URL?
|
var avatarURL: URL?
|
||||||
|
var status = UserStatus()
|
||||||
|
|
||||||
var membership: MembershipState
|
var membership: MembershipState
|
||||||
var isIgnored = false
|
var isIgnored = false
|
||||||
@@ -23,14 +24,12 @@ nonisolated struct RoomMemberProxyMockConfiguration {
|
|||||||
extension RoomMemberProxyMock {
|
extension RoomMemberProxyMock {
|
||||||
convenience init(with configuration: RoomMemberProxyMockConfiguration) {
|
convenience init(with configuration: RoomMemberProxyMockConfiguration) {
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
userID = configuration.userID
|
userID = configuration.userID
|
||||||
displayName = configuration.displayName
|
displayName = configuration.displayName
|
||||||
|
disambiguatedDisplayName = configuration.displayName.map { "\($0) (\(userID))" }
|
||||||
if let displayName = configuration.displayName {
|
|
||||||
disambiguatedDisplayName = "\(displayName) (\(userID))"
|
|
||||||
}
|
|
||||||
|
|
||||||
avatarURL = configuration.avatarURL
|
avatarURL = configuration.avatarURL
|
||||||
|
status = configuration.status
|
||||||
|
|
||||||
membership = configuration.membership
|
membership = configuration.membership
|
||||||
isIgnored = configuration.isIgnored
|
isIgnored = configuration.isIgnored
|
||||||
@@ -93,6 +92,20 @@ extension RoomMemberProxyMock {
|
|||||||
membership: .join))
|
membership: .join))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var mockErin: RoomMemberProxyMock {
|
||||||
|
RoomMemberProxyMock(with: .init(userID: "@erin:matrix.org",
|
||||||
|
displayName: "Erin",
|
||||||
|
status: .mockFocussing,
|
||||||
|
membership: .join))
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockFrank: RoomMemberProxyMock {
|
||||||
|
RoomMemberProxyMock(with: .init(userID: "@frank:matrix.org",
|
||||||
|
displayName: "Frank",
|
||||||
|
status: .mockCall,
|
||||||
|
membership: .join))
|
||||||
|
}
|
||||||
|
|
||||||
static var mockVerbose: RoomMemberProxyMock {
|
static var mockVerbose: RoomMemberProxyMock {
|
||||||
RoomMemberProxyMock(with: .init(userID: "@charliev:matrix.org",
|
RoomMemberProxyMock(with: .init(userID: "@charliev:matrix.org",
|
||||||
displayName: "Charlie is the best display name",
|
displayName: "Charlie is the best display name",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension UserProfile {
|
extension UserProfile {
|
||||||
/// Mocks
|
|
||||||
static var mockAlice: UserProfile {
|
static var mockAlice: UserProfile {
|
||||||
.init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA")
|
.init(userID: "@alice:matrix.org", displayName: "Alice", avatarURL: "mxc://matrix.org/UcCimidcvpFvWkPzvjXMQPHA")
|
||||||
}
|
}
|
||||||
@@ -30,7 +29,33 @@ extension UserProfile {
|
|||||||
.init(userID: "@dan:matrix.org", displayName: "Dan", avatarURL: .mockMXCUserAvatar)
|
.init(userID: "@dan:matrix.org", displayName: "Dan", avatarURL: .mockMXCUserAvatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var mockErin: UserProfile {
|
||||||
|
.init(userID: "@erin:matrix.org", displayName: "Erin", status: .mockFocussing)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockFrank: UserProfile {
|
||||||
|
.init(userID: "@frank:matrix.org", displayName: "Frank", status: .mockCall)
|
||||||
|
}
|
||||||
|
|
||||||
static var mockVerbose: UserProfile {
|
static var mockVerbose: UserProfile {
|
||||||
.init(userID: "@charlie:matrix.org", displayName: "Charlie is the best display name", avatarURL: nil)
|
.init(userID: "@charlie:matrix.org", displayName: "Charlie is the best display name", avatarURL: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nonisolated extension UserStatus {
|
||||||
|
static var mockCall: UserStatus {
|
||||||
|
.init(userSet: nil, call: .init(startDate: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockFocussing: UserStatus {
|
||||||
|
.init(userSet: .init(text: "Focussing", emoji: "🧑💻"), call: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var mockHoliday: UserStatus {
|
||||||
|
.init(userSet: .init(text: "Holiday", emoji: "🏝️"), call: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func mock(text: String, emoji: Character) -> UserStatus {
|
||||||
|
.init(userSet: .init(text: text, emoji: emoji), call: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ nonisolated struct RoomMemberDetails: Identifiable, Hashable {
|
|||||||
let id: String
|
let id: String
|
||||||
let name: String?
|
let name: String?
|
||||||
let avatarURL: URL?
|
let avatarURL: URL?
|
||||||
|
let status: UserStatus
|
||||||
|
|
||||||
let permalink: URL?
|
let permalink: URL?
|
||||||
|
|
||||||
var isInvited: Bool
|
var isInvited: Bool
|
||||||
@@ -34,6 +36,7 @@ nonisolated extension RoomMemberDetails {
|
|||||||
id = proxy.userID
|
id = proxy.userID
|
||||||
name = proxy.displayName
|
name = proxy.displayName
|
||||||
avatarURL = proxy.avatarURL
|
avatarURL = proxy.avatarURL
|
||||||
|
status = proxy.status
|
||||||
permalink = proxy.permalink
|
permalink = proxy.permalink
|
||||||
isActive = proxy.isActive
|
isActive = proxy.isActive
|
||||||
isInvited = proxy.membership == .invite
|
isInvited = proxy.membership == .invite
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ final nonisolated class RoomMemberProxy: RoomMemberProxyProtocol {
|
|||||||
member.avatarUrl.flatMap(URL.init(string:))
|
member.avatarUrl.flatMap(URL.init(string:))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var status: UserStatus {
|
||||||
|
UserStatus()
|
||||||
|
}
|
||||||
|
|
||||||
var membership: MembershipState {
|
var membership: MembershipState {
|
||||||
member.membership
|
member.membership
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ nonisolated protocol RoomMemberProxyProtocol: AnyObject, Sendable {
|
|||||||
|
|
||||||
var displayName: String? { get }
|
var displayName: String? { get }
|
||||||
var disambiguatedDisplayName: String? { get }
|
var disambiguatedDisplayName: String? { get }
|
||||||
|
|
||||||
var avatarURL: URL? { get }
|
var avatarURL: URL? { get }
|
||||||
|
var status: UserStatus { get }
|
||||||
|
|
||||||
var membership: MembershipState { get }
|
var membership: MembershipState { get }
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ nonisolated struct TimelineItemSender: Identifiable, Hashable {
|
|||||||
let displayName: String?
|
let displayName: String?
|
||||||
let isDisplayNameAmbiguous: Bool
|
let isDisplayNameAmbiguous: Bool
|
||||||
let avatarURL: URL?
|
let avatarURL: URL?
|
||||||
|
let status: UserStatus
|
||||||
|
|
||||||
init(id: String, displayName: String? = nil, isDisplayNameAmbiguous: Bool = false, avatarURL: URL? = nil) {
|
init(id: String,
|
||||||
|
displayName: String? = nil,
|
||||||
|
isDisplayNameAmbiguous: Bool = false,
|
||||||
|
avatarURL: URL? = nil,
|
||||||
|
status: UserStatus = .init()) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.displayName = displayName
|
self.displayName = displayName
|
||||||
self.isDisplayNameAmbiguous = isDisplayNameAmbiguous
|
self.isDisplayNameAmbiguous = isDisplayNameAmbiguous
|
||||||
self.avatarURL = avatarURL
|
self.avatarURL = avatarURL
|
||||||
|
self.status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
init(senderID: String, senderProfile: ProfileDetails) {
|
init(senderID: String, senderProfile: ProfileDetails) {
|
||||||
|
|||||||
@@ -13,35 +13,41 @@ nonisolated struct UserProfile: Hashable, Identifiable {
|
|||||||
let id: String
|
let id: String
|
||||||
let displayName: String?
|
let displayName: String?
|
||||||
let avatarURL: URL?
|
let avatarURL: URL?
|
||||||
|
let status: UserStatus
|
||||||
|
|
||||||
init(userID: String, displayName: String? = nil, avatarURL: URL? = nil) {
|
init(userID: String, displayName: String? = nil, avatarURL: URL? = nil, status: UserStatus = .init()) {
|
||||||
id = userID
|
id = userID
|
||||||
self.displayName = displayName
|
self.displayName = displayName
|
||||||
self.avatarURL = avatarURL
|
self.avatarURL = avatarURL
|
||||||
|
self.status = status
|
||||||
}
|
}
|
||||||
|
|
||||||
init(member: RoomMemberDetails) {
|
init(member: RoomMemberDetails) {
|
||||||
id = member.id
|
id = member.id
|
||||||
displayName = member.isBanned ? nil : member.name
|
displayName = member.isBanned ? nil : member.name
|
||||||
avatarURL = member.isBanned ? nil : member.avatarURL
|
avatarURL = member.isBanned ? nil : member.avatarURL
|
||||||
|
status = member.status
|
||||||
}
|
}
|
||||||
|
|
||||||
init(sender: TimelineItemSender) {
|
init(sender: TimelineItemSender) {
|
||||||
id = sender.id
|
id = sender.id
|
||||||
displayName = sender.displayName
|
displayName = sender.displayName
|
||||||
avatarURL = sender.avatarURL
|
avatarURL = sender.avatarURL
|
||||||
|
status = sender.status
|
||||||
}
|
}
|
||||||
|
|
||||||
init(sdkUserProfile: MatrixRustSDK.UserProfile) {
|
init(sdkUserProfile: MatrixRustSDK.UserProfile) {
|
||||||
id = sdkUserProfile.userId
|
id = sdkUserProfile.userId
|
||||||
displayName = sdkUserProfile.displayName
|
displayName = sdkUserProfile.displayName
|
||||||
avatarURL = sdkUserProfile.avatarUrl.flatMap(URL.init(string:))
|
avatarURL = sdkUserProfile.avatarUrl.flatMap(URL.init(string:))
|
||||||
|
status = .init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(sdkRoomHero: MatrixRustSDK.RoomHero) {
|
init(sdkRoomHero: MatrixRustSDK.RoomHero) {
|
||||||
id = sdkRoomHero.userId
|
id = sdkRoomHero.userId
|
||||||
displayName = sdkRoomHero.displayName
|
displayName = sdkRoomHero.displayName
|
||||||
avatarURL = sdkRoomHero.avatarUrl.flatMap(URL.init(string:))
|
avatarURL = sdkRoomHero.avatarUrl.flatMap(URL.init(string:))
|
||||||
|
status = .init()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(member: RoomMemberProxyProtocol) {
|
init(member: RoomMemberProxyProtocol) {
|
||||||
@@ -55,6 +61,53 @@ nonisolated struct UserProfile: Hashable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nonisolated struct UserStatus: Hashable {
|
||||||
|
/// The status manually set by the user (if any).
|
||||||
|
let userSet: UserSet?
|
||||||
|
/// The status automatically set by Element Call (while in a call).
|
||||||
|
let call: Call?
|
||||||
|
|
||||||
|
/// The status that should be displayed on the user's profile.
|
||||||
|
var displayed: DisplayedStatus? {
|
||||||
|
// The user-set status takes precedence over the call indicator.
|
||||||
|
userSet.map { .userSet($0) } ?? call.map { .inCall($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated struct UserSet: Hashable {
|
||||||
|
let text: String
|
||||||
|
let emoji: Character
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated struct Call: Hashable {
|
||||||
|
let startDate: Date?
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated enum DisplayedStatus: Hashable {
|
||||||
|
case userSet(UserSet)
|
||||||
|
case inCall(Call)
|
||||||
|
|
||||||
|
var text: String {
|
||||||
|
switch self {
|
||||||
|
case .userSet(let userSet): userSet.text
|
||||||
|
case .inCall: L10n.commonOnACall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var emoji: Character {
|
||||||
|
switch self {
|
||||||
|
case .userSet(let userSet): userSet.emoji
|
||||||
|
case .inCall: "🎧"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nonisolated extension UserStatus {
|
||||||
|
init() {
|
||||||
|
self.init(userSet: nil, call: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct SearchUsersResults {
|
struct SearchUsersResults {
|
||||||
let results: [UserProfile]
|
let results: [UserProfile]
|
||||||
let limited: Bool
|
let limited: Bool
|
||||||
|
|||||||
Reference in New Issue
Block a user