mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Show a verification badge on the Room Member/User Profile screens. (#3427)
* Add a badge for verified users/room members. * Reorder subviews. * Add a (disabled) button to verify other users. * PR comments. * Update the SDK. * Adopt the SDK changes introduced in matrix-rust-sdk/pull/4100 --------- Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
This commit is contained in:
@@ -899,6 +899,7 @@
|
|||||||
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
||||||
C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; };
|
C7774720A4B2E34693E3227C /* RoomNotificationSettingsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8896CDD20CA2D87EA3B848A1 /* RoomNotificationSettingsScreen.swift */; };
|
||||||
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
|
C7ABEBECDC513F7887DACF66 /* ProgressMaskModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68010886142843705E342645 /* ProgressMaskModifier.swift */; };
|
||||||
|
C7B07EBA0F12B5912DA9BB97 /* UserIdentitySDKMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */; };
|
||||||
C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; };
|
C80E06ED97CE52704A46C148 /* ClientBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A1C33355FFB0F0953C35036 /* ClientBuilder.swift */; };
|
||||||
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; };
|
C85C7A201E4CFDA477ACEBEB /* AppLockSetupSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8610C1D21565C950BCA6A454 /* AppLockSetupSettingsScreenViewModelProtocol.swift */; };
|
||||||
C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */; };
|
C8A9C595038AFA2D707AC8C1 /* NotificationPermissionsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20E69F67D2A70ABD08CA6D54 /* NotificationPermissionsScreenViewModelProtocol.swift */; };
|
||||||
@@ -2221,6 +2222,7 @@
|
|||||||
E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProvider.swift; sourceTree = "<group>"; };
|
E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProvider.swift; sourceTree = "<group>"; };
|
||||||
E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = "<group>"; };
|
E8AE4B3273BA189FDCD4055C /* UserIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicator.swift; sourceTree = "<group>"; };
|
||||||
E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = "<group>"; };
|
E8CA187FE656EE5A3F6C7DE5 /* UIFont+AttributedStringBuilder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+AttributedStringBuilder.m"; sourceTree = "<group>"; };
|
||||||
|
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIdentitySDKMock.swift; sourceTree = "<group>"; };
|
||||||
E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFixtures.swift; sourceTree = "<group>"; };
|
E96ED747FF90332EA1333C22 /* RoomTimelineItemFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemFixtures.swift; sourceTree = "<group>"; };
|
||||||
E992D7B8BE54B2AB454613AF /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = "<group>"; };
|
E992D7B8BE54B2AB454613AF /* XCUIElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElement.swift; sourceTree = "<group>"; };
|
||||||
E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInviteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
E9A3D3CFA199FA7897364547 /* CallInviteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallInviteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
@@ -5276,6 +5278,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
|
8EAF4A49F3ACD8BB8B0D2371 /* ClientSDKMock.swift */,
|
||||||
|
E8DE9D0D480D087D0F676B52 /* UserIdentitySDKMock.swift */,
|
||||||
);
|
);
|
||||||
path = SDK;
|
path = SDK;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -7007,6 +7010,7 @@
|
|||||||
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */,
|
828EA5009557C2B9DCD4CA0F /* UserDiscoverySection.swift in Sources */,
|
||||||
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */,
|
044DD8F80231BC30570F7965 /* UserDiscoveryService.swift in Sources */,
|
||||||
1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */,
|
1C409A26A99F0371C47AFA51 /* UserDiscoveryServiceProtocol.swift in Sources */,
|
||||||
|
C7B07EBA0F12B5912DA9BB97 /* UserIdentitySDKMock.swift in Sources */,
|
||||||
988BA75A182738150894A23F /* UserIndicator.swift in Sources */,
|
988BA75A182738150894A23F /* UserIndicator.swift in Sources */,
|
||||||
C4E0D03DF88242697545A9B7 /* UserIndicatorController.swift in Sources */,
|
C4E0D03DF88242697545A9B7 /* UserIndicatorController.swift in Sources */,
|
||||||
3467FEE8210D301FF1B77001 /* UserIndicatorControllerMock.swift in Sources */,
|
3467FEE8210D301FF1B77001 /* UserIndicatorControllerMock.swift in Sources */,
|
||||||
@@ -7818,7 +7822,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.58;
|
version = 1.0.59;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "753c5381ce88b3549cbd8ed9b839109ff143ecdd",
|
"revision" : "acd36f68f107f608c33b4d5b46be4ddea5537b1f",
|
||||||
"version" : "1.0.58"
|
"version" : "1.0.59"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -93,5 +93,7 @@ extension ClientProxyMock {
|
|||||||
|
|
||||||
return await .joined(JoinedRoomProxyMock(.init(id: room.id, name: room.name)))
|
return await .joined(JoinedRoomProxyMock(.init(id: room.id, name: room.name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userIdentityForReturnValue = .success(UserIdentitySDKMock(configuration: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4700,6 +4700,76 @@ class ClientProxyMock: ClientProxyProtocol {
|
|||||||
return resetIdentityReturnValue
|
return resetIdentityReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - userIdentity
|
||||||
|
|
||||||
|
var userIdentityForUnderlyingCallsCount = 0
|
||||||
|
var userIdentityForCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return userIdentityForUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = userIdentityForUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
userIdentityForUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
userIdentityForUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var userIdentityForCalled: Bool {
|
||||||
|
return userIdentityForCallsCount > 0
|
||||||
|
}
|
||||||
|
var userIdentityForReceivedUserID: String?
|
||||||
|
var userIdentityForReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var userIdentityForUnderlyingReturnValue: Result<UserIdentity?, ClientProxyError>!
|
||||||
|
var userIdentityForReturnValue: Result<UserIdentity?, ClientProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return userIdentityForUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<UserIdentity?, ClientProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = userIdentityForUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
userIdentityForUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
userIdentityForUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var userIdentityForClosure: ((String) async -> Result<UserIdentity?, ClientProxyError>)?
|
||||||
|
|
||||||
|
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError> {
|
||||||
|
userIdentityForCallsCount += 1
|
||||||
|
userIdentityForReceivedUserID = userID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.userIdentityForReceivedInvocations.append(userID)
|
||||||
|
}
|
||||||
|
if let userIdentityForClosure = userIdentityForClosure {
|
||||||
|
return await userIdentityForClosure(userID)
|
||||||
|
} else {
|
||||||
|
return userIdentityForReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - loadMediaContentForSource
|
//MARK: - loadMediaContentForSource
|
||||||
|
|
||||||
var loadMediaContentForSourceThrowableError: Error?
|
var loadMediaContentForSourceThrowableError: Error?
|
||||||
|
|||||||
@@ -6214,81 +6214,6 @@ open class EncryptionSDKMock: MatrixRustSDK.Encryption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - getUserIdentity
|
|
||||||
|
|
||||||
open var getUserIdentityUserIdThrowableError: Error?
|
|
||||||
var getUserIdentityUserIdUnderlyingCallsCount = 0
|
|
||||||
open var getUserIdentityUserIdCallsCount: Int {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return getUserIdentityUserIdUnderlyingCallsCount
|
|
||||||
} else {
|
|
||||||
var returnValue: Int? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = getUserIdentityUserIdUnderlyingCallsCount
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
getUserIdentityUserIdUnderlyingCallsCount = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
getUserIdentityUserIdUnderlyingCallsCount = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var getUserIdentityUserIdCalled: Bool {
|
|
||||||
return getUserIdentityUserIdCallsCount > 0
|
|
||||||
}
|
|
||||||
open var getUserIdentityUserIdReceivedUserId: String?
|
|
||||||
open var getUserIdentityUserIdReceivedInvocations: [String] = []
|
|
||||||
|
|
||||||
var getUserIdentityUserIdUnderlyingReturnValue: UserIdentity?
|
|
||||||
open var getUserIdentityUserIdReturnValue: UserIdentity? {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return getUserIdentityUserIdUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: UserIdentity?? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = getUserIdentityUserIdUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
getUserIdentityUserIdUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
getUserIdentityUserIdUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var getUserIdentityUserIdClosure: ((String) async throws -> UserIdentity?)?
|
|
||||||
|
|
||||||
open override func getUserIdentity(userId: String) async throws -> UserIdentity? {
|
|
||||||
if let error = getUserIdentityUserIdThrowableError {
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
getUserIdentityUserIdCallsCount += 1
|
|
||||||
getUserIdentityUserIdReceivedUserId = userId
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.getUserIdentityUserIdReceivedInvocations.append(userId)
|
|
||||||
}
|
|
||||||
if let getUserIdentityUserIdClosure = getUserIdentityUserIdClosure {
|
|
||||||
return try await getUserIdentityUserIdClosure(userId)
|
|
||||||
} else {
|
|
||||||
return getUserIdentityUserIdReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//MARK: - isLastDevice
|
//MARK: - isLastDevice
|
||||||
|
|
||||||
open var isLastDeviceThrowableError: Error?
|
open var isLastDeviceThrowableError: Error?
|
||||||
@@ -6753,6 +6678,81 @@ open class EncryptionSDKMock: MatrixRustSDK.Encryption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - userIdentity
|
||||||
|
|
||||||
|
open var userIdentityUserIdThrowableError: Error?
|
||||||
|
var userIdentityUserIdUnderlyingCallsCount = 0
|
||||||
|
open var userIdentityUserIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return userIdentityUserIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = userIdentityUserIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
userIdentityUserIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
userIdentityUserIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var userIdentityUserIdCalled: Bool {
|
||||||
|
return userIdentityUserIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var userIdentityUserIdReceivedUserId: String?
|
||||||
|
open var userIdentityUserIdReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var userIdentityUserIdUnderlyingReturnValue: UserIdentity?
|
||||||
|
open var userIdentityUserIdReturnValue: UserIdentity? {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return userIdentityUserIdUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: UserIdentity?? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = userIdentityUserIdUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
userIdentityUserIdUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
userIdentityUserIdUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var userIdentityUserIdClosure: ((String) async throws -> UserIdentity?)?
|
||||||
|
|
||||||
|
open override func userIdentity(userId: String) async throws -> UserIdentity? {
|
||||||
|
if let error = userIdentityUserIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
userIdentityUserIdCallsCount += 1
|
||||||
|
userIdentityUserIdReceivedUserId = userId
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.userIdentityUserIdReceivedInvocations.append(userId)
|
||||||
|
}
|
||||||
|
if let userIdentityUserIdClosure = userIdentityUserIdClosure {
|
||||||
|
return try await userIdentityUserIdClosure(userId)
|
||||||
|
} else {
|
||||||
|
return userIdentityUserIdReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - verificationState
|
//MARK: - verificationState
|
||||||
|
|
||||||
var verificationStateUnderlyingCallsCount = 0
|
var verificationStateUnderlyingCallsCount = 0
|
||||||
@@ -18042,34 +18042,9 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline {
|
|||||||
}
|
}
|
||||||
open var editEventOrTransactionIdNewContentReceivedArguments: (eventOrTransactionId: EventOrTransactionId, newContent: EditedContent)?
|
open var editEventOrTransactionIdNewContentReceivedArguments: (eventOrTransactionId: EventOrTransactionId, newContent: EditedContent)?
|
||||||
open var editEventOrTransactionIdNewContentReceivedInvocations: [(eventOrTransactionId: EventOrTransactionId, newContent: EditedContent)] = []
|
open var editEventOrTransactionIdNewContentReceivedInvocations: [(eventOrTransactionId: EventOrTransactionId, newContent: EditedContent)] = []
|
||||||
|
open var editEventOrTransactionIdNewContentClosure: ((EventOrTransactionId, EditedContent) async throws -> Void)?
|
||||||
|
|
||||||
var editEventOrTransactionIdNewContentUnderlyingReturnValue: Bool!
|
open override func edit(eventOrTransactionId: EventOrTransactionId, newContent: EditedContent) async throws {
|
||||||
open var editEventOrTransactionIdNewContentReturnValue: Bool! {
|
|
||||||
get {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
return editEventOrTransactionIdNewContentUnderlyingReturnValue
|
|
||||||
} else {
|
|
||||||
var returnValue: Bool? = nil
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
returnValue = editEventOrTransactionIdNewContentUnderlyingReturnValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
if Thread.isMainThread {
|
|
||||||
editEventOrTransactionIdNewContentUnderlyingReturnValue = newValue
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.sync {
|
|
||||||
editEventOrTransactionIdNewContentUnderlyingReturnValue = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
open var editEventOrTransactionIdNewContentClosure: ((EventOrTransactionId, EditedContent) async throws -> Bool)?
|
|
||||||
|
|
||||||
open override func edit(eventOrTransactionId: EventOrTransactionId, newContent: EditedContent) async throws -> Bool {
|
|
||||||
if let error = editEventOrTransactionIdNewContentThrowableError {
|
if let error = editEventOrTransactionIdNewContentThrowableError {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -18078,11 +18053,7 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline {
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.editEventOrTransactionIdNewContentReceivedInvocations.append((eventOrTransactionId: eventOrTransactionId, newContent: newContent))
|
self.editEventOrTransactionIdNewContentReceivedInvocations.append((eventOrTransactionId: eventOrTransactionId, newContent: newContent))
|
||||||
}
|
}
|
||||||
if let editEventOrTransactionIdNewContentClosure = editEventOrTransactionIdNewContentClosure {
|
try await editEventOrTransactionIdNewContentClosure?(eventOrTransactionId, newContent)
|
||||||
return try await editEventOrTransactionIdNewContentClosure(eventOrTransactionId, newContent)
|
|
||||||
} else {
|
|
||||||
return editEventOrTransactionIdNewContentReturnValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - endPoll
|
//MARK: - endPoll
|
||||||
@@ -20976,6 +20947,71 @@ open class UserIdentitySDKMock: MatrixRustSDK.UserIdentity {
|
|||||||
|
|
||||||
fileprivate var pointer: UnsafeMutableRawPointer!
|
fileprivate var pointer: UnsafeMutableRawPointer!
|
||||||
|
|
||||||
|
//MARK: - isVerified
|
||||||
|
|
||||||
|
var isVerifiedUnderlyingCallsCount = 0
|
||||||
|
open var isVerifiedCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return isVerifiedUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = isVerifiedUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
isVerifiedUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
isVerifiedUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var isVerifiedCalled: Bool {
|
||||||
|
return isVerifiedCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var isVerifiedUnderlyingReturnValue: Bool!
|
||||||
|
open var isVerifiedReturnValue: Bool! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return isVerifiedUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Bool? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = isVerifiedUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
isVerifiedUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
isVerifiedUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var isVerifiedClosure: (() -> Bool)?
|
||||||
|
|
||||||
|
open override func isVerified() -> Bool {
|
||||||
|
isVerifiedCallsCount += 1
|
||||||
|
if let isVerifiedClosure = isVerifiedClosure {
|
||||||
|
return isVerifiedClosure()
|
||||||
|
} else {
|
||||||
|
return isVerifiedReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - masterKey
|
//MARK: - masterKey
|
||||||
|
|
||||||
var masterKeyUnderlyingCallsCount = 0
|
var masterKeyUnderlyingCallsCount = 0
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2024 New Vector Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
// Please see LICENSE in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
|
extension UserIdentitySDKMock {
|
||||||
|
struct Configuration {
|
||||||
|
var isVerified = false
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(configuration: Configuration) {
|
||||||
|
self.init()
|
||||||
|
|
||||||
|
isVerifiedReturnValue = configuration.isVerified
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
private enum Badge: Hashable {
|
private enum Badge: Hashable {
|
||||||
case encrypted(Bool)
|
case encrypted(Bool)
|
||||||
case `public`
|
case `public`
|
||||||
|
case verified
|
||||||
}
|
}
|
||||||
|
|
||||||
private let avatarInfo: AvatarInfo
|
private let avatarInfo: AvatarInfo
|
||||||
@@ -70,6 +71,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(member: RoomMemberDetails,
|
init(member: RoomMemberDetails,
|
||||||
|
isVerified: Bool = false,
|
||||||
avatarSize: AvatarSize,
|
avatarSize: AvatarSize,
|
||||||
mediaProvider: MediaProviderProtocol? = nil,
|
mediaProvider: MediaProviderProtocol? = nil,
|
||||||
onAvatarTap: ((URL) -> Void)? = nil,
|
onAvatarTap: ((URL) -> Void)? = nil,
|
||||||
@@ -77,6 +79,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
let profile = UserProfileProxy(member: member)
|
let profile = UserProfileProxy(member: member)
|
||||||
|
|
||||||
self.init(user: profile,
|
self.init(user: profile,
|
||||||
|
isVerified: isVerified,
|
||||||
avatarSize: avatarSize,
|
avatarSize: avatarSize,
|
||||||
mediaProvider: mediaProvider,
|
mediaProvider: mediaProvider,
|
||||||
onAvatarTap: onAvatarTap,
|
onAvatarTap: onAvatarTap,
|
||||||
@@ -84,6 +87,7 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(user: UserProfileProxy,
|
init(user: UserProfileProxy,
|
||||||
|
isVerified: Bool,
|
||||||
avatarSize: AvatarSize,
|
avatarSize: AvatarSize,
|
||||||
mediaProvider: MediaProviderProtocol? = nil,
|
mediaProvider: MediaProviderProtocol? = nil,
|
||||||
onAvatarTap: ((URL) -> Void)? = nil,
|
onAvatarTap: ((URL) -> Void)? = nil,
|
||||||
@@ -96,27 +100,29 @@ struct AvatarHeaderView<Footer: View>: View {
|
|||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.onAvatarTap = onAvatarTap
|
self.onAvatarTap = onAvatarTap
|
||||||
self.footer = footer
|
self.footer = footer
|
||||||
badges = []
|
badges = isVerified ? [.verified] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
private var badgesStack: some View {
|
private var badgesStack: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
ForEach(badges, id: \.self) { badge in
|
ForEach(badges, id: \.self) { badge in
|
||||||
switch badge {
|
switch badge {
|
||||||
case .encrypted(let isEncrypted):
|
case .encrypted(true):
|
||||||
if isEncrypted {
|
|
||||||
BadgeLabel(title: L10n.screenRoomDetailsBadgeEncrypted,
|
BadgeLabel(title: L10n.screenRoomDetailsBadgeEncrypted,
|
||||||
icon: \.lockSolid,
|
icon: \.lockSolid,
|
||||||
isHighlighted: true)
|
isHighlighted: true)
|
||||||
} else {
|
case .encrypted(false):
|
||||||
BadgeLabel(title: L10n.screenRoomDetailsBadgeNotEncrypted,
|
BadgeLabel(title: L10n.screenRoomDetailsBadgeNotEncrypted,
|
||||||
icon: \.lockOff,
|
icon: \.lockOff,
|
||||||
isHighlighted: false)
|
isHighlighted: false)
|
||||||
}
|
|
||||||
case .public:
|
case .public:
|
||||||
BadgeLabel(title: L10n.screenRoomDetailsBadgePublic,
|
BadgeLabel(title: L10n.screenRoomDetailsBadgePublic,
|
||||||
icon: \.public,
|
icon: \.public,
|
||||||
isHighlighted: false)
|
isHighlighted: false)
|
||||||
|
case .verified:
|
||||||
|
BadgeLabel(title: L10n.commonVerified,
|
||||||
|
icon: \.verified,
|
||||||
|
isHighlighted: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,6 +226,11 @@ struct AvatarHeaderView_Previews: PreviewProvider, TestablePreview {
|
|||||||
avatarSize: .room(on: .details),
|
avatarSize: .room(on: .details),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init())) { Text("") }
|
mediaProvider: MediaProviderMock(configuration: .init())) { Text("") }
|
||||||
|
|
||||||
|
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBob),
|
||||||
|
isVerified: true,
|
||||||
|
avatarSize: .room(on: .details),
|
||||||
|
mediaProvider: MediaProviderMock(configuration: .init())) { Text("") }
|
||||||
|
|
||||||
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
AvatarHeaderView(member: RoomMemberDetails(withProxy: RoomMemberProxyMock.mockBanned[3]),
|
||||||
avatarSize: .room(on: .details),
|
avatarSize: .room(on: .details),
|
||||||
mediaProvider: MediaProviderMock(configuration: .init())) { Text("") }
|
mediaProvider: MediaProviderMock(configuration: .init())) { Text("") }
|
||||||
|
|||||||
@@ -16,11 +16,24 @@ enum RoomMemberDetailsScreenViewModelAction {
|
|||||||
struct RoomMemberDetailsScreenViewState: BindableState {
|
struct RoomMemberDetailsScreenViewState: BindableState {
|
||||||
let userID: String
|
let userID: String
|
||||||
var memberDetails: RoomMemberDetails?
|
var memberDetails: RoomMemberDetails?
|
||||||
|
var isVerified: Bool?
|
||||||
var isOwnMemberDetails = false
|
var isOwnMemberDetails = false
|
||||||
var isProcessingIgnoreRequest = false
|
var isProcessingIgnoreRequest = false
|
||||||
var dmRoomID: String?
|
var dmRoomID: String?
|
||||||
|
|
||||||
var bindings: RoomMemberDetailsScreenViewStateBindings
|
var bindings: RoomMemberDetailsScreenViewStateBindings
|
||||||
|
|
||||||
|
var showVerifiedBadge: Bool {
|
||||||
|
isVerified == true // We purposely show the badge on your own account for consistency with Web.
|
||||||
|
}
|
||||||
|
|
||||||
|
var showVerificationSection: Bool {
|
||||||
|
isVerified == false && !isOwnMemberDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
var verifyButtonTitle: String {
|
||||||
|
L10n.screenRoomMemberDetailsVerifyButtonTitle(memberDetails?.name ?? "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RoomMemberDetailsScreenViewStateBindings {
|
struct RoomMemberDetailsScreenViewStateBindings {
|
||||||
|
|||||||
+31
-19
@@ -43,26 +43,9 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
|||||||
|
|
||||||
showMemberLoadingIndicator()
|
showMemberLoadingIndicator()
|
||||||
Task {
|
Task {
|
||||||
defer {
|
await loadMember()
|
||||||
hideMemberLoadingIndicator()
|
hideMemberLoadingIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch await roomProxy.getMember(userID: userID) {
|
|
||||||
case .success(let member):
|
|
||||||
roomMemberProxy = member
|
|
||||||
state.memberDetails = RoomMemberDetails(withProxy: member)
|
|
||||||
state.isOwnMemberDetails = member.userID == roomProxy.ownUserID
|
|
||||||
switch await clientProxy.directRoomForUserID(member.userID) {
|
|
||||||
case .success(let roomID):
|
|
||||||
state.dmRoomID = roomID
|
|
||||||
case .failure:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case .failure(let error):
|
|
||||||
MXLog.warning("Failed to find member: \(error)")
|
|
||||||
actionsSubject.send(.openUserProfile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -95,7 +78,36 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
@MainActor
|
private func loadMember() async {
|
||||||
|
async let memberResult = roomProxy.getMember(userID: state.userID)
|
||||||
|
async let identityResult = clientProxy.userIdentity(for: state.userID)
|
||||||
|
|
||||||
|
switch await memberResult {
|
||||||
|
case .success(let member):
|
||||||
|
roomMemberProxy = member
|
||||||
|
state.memberDetails = RoomMemberDetails(withProxy: member)
|
||||||
|
state.isOwnMemberDetails = member.userID == roomProxy.ownUserID
|
||||||
|
switch await clientProxy.directRoomForUserID(member.userID) {
|
||||||
|
case .success(let roomID):
|
||||||
|
state.dmRoomID = roomID
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.warning("Failed to find member: \(error)")
|
||||||
|
// As we didn't find a member with the specified user ID in this room we instead
|
||||||
|
// fall back to showing a generic user profile screen as the source is likely
|
||||||
|
// a message containing a permalink to someone who's not in this room.
|
||||||
|
actionsSubject.send(.openUserProfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .success(.some(identity)) = await identityResult {
|
||||||
|
state.isVerified = identity.isVerified()
|
||||||
|
} else {
|
||||||
|
MXLog.error("Failed to find the member's identity.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func ignoreUser() async {
|
private func ignoreUser() async {
|
||||||
guard let roomMemberProxy else {
|
guard let roomMemberProxy else {
|
||||||
fatalError()
|
fatalError()
|
||||||
|
|||||||
+38
-15
@@ -15,6 +15,8 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
Form {
|
Form {
|
||||||
headerSection
|
headerSection
|
||||||
|
|
||||||
|
verificationSection
|
||||||
|
|
||||||
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
||||||
blockUserSection
|
blockUserSection
|
||||||
}
|
}
|
||||||
@@ -30,6 +32,25 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
private var headerSection: some View {
|
||||||
|
if let memberDetails = context.viewState.memberDetails {
|
||||||
|
AvatarHeaderView(member: memberDetails,
|
||||||
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
|
avatarSize: .user(on: .memberDetails),
|
||||||
|
mediaProvider: context.mediaProvider) { url in
|
||||||
|
context.send(viewAction: .displayAvatar(url))
|
||||||
|
} footer: {
|
||||||
|
otherUserFooter
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||||
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
|
avatarSize: .user(on: .memberDetails),
|
||||||
|
mediaProvider: context.mediaProvider,
|
||||||
|
footer: { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var otherUserFooter: some View {
|
private var otherUserFooter: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
|
||||||
@@ -62,20 +83,15 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var headerSection: some View {
|
var verificationSection: some View {
|
||||||
if let memberDetails = context.viewState.memberDetails {
|
if context.viewState.showVerificationSection {
|
||||||
AvatarHeaderView(member: memberDetails,
|
Section {
|
||||||
avatarSize: .user(on: .memberDetails),
|
ListRow(label: .default(title: context.viewState.verifyButtonTitle,
|
||||||
mediaProvider: context.mediaProvider) { url in
|
description: L10n.screenRoomMemberDetailsVerifyButtonSubtitle,
|
||||||
context.send(viewAction: .displayAvatar(url))
|
icon: \.lock),
|
||||||
} footer: {
|
kind: .button { })
|
||||||
otherUserFooter
|
.disabled(true)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
|
||||||
avatarSize: .user(on: .memberDetails),
|
|
||||||
mediaProvider: context.mediaProvider,
|
|
||||||
footer: { })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +133,15 @@ struct RoomMemberDetailsScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let otherUserViewModel = makeViewModel(member: .mockDan)
|
static let verifiedUserViewModel = makeViewModel(member: .mockDan)
|
||||||
|
static let otherUserViewModel = makeViewModel(member: .mockAlice)
|
||||||
static let accountOwnerViewModel = makeViewModel(member: .mockMe)
|
static let accountOwnerViewModel = makeViewModel(member: .mockMe)
|
||||||
static let ignoredUserViewModel = makeViewModel(member: .mockIgnored)
|
static let ignoredUserViewModel = makeViewModel(member: .mockIgnored)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
RoomMemberDetailsScreen(context: verifiedUserViewModel.context)
|
||||||
|
.previewDisplayName("Verified User")
|
||||||
|
.snapshotPreferences(delay: 0.25)
|
||||||
RoomMemberDetailsScreen(context: otherUserViewModel.context)
|
RoomMemberDetailsScreen(context: otherUserViewModel.context)
|
||||||
.previewDisplayName("Other User")
|
.previewDisplayName("Other User")
|
||||||
.snapshotPreferences(delay: 0.25)
|
.snapshotPreferences(delay: 0.25)
|
||||||
@@ -136,9 +156,12 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
static func makeViewModel(member: RoomMemberProxyMock) -> RoomMemberDetailsScreenViewModel {
|
static func makeViewModel(member: RoomMemberProxyMock) -> RoomMemberDetailsScreenViewModel {
|
||||||
let roomProxyMock = JoinedRoomProxyMock(.init(name: ""))
|
let roomProxyMock = JoinedRoomProxyMock(.init(name: ""))
|
||||||
roomProxyMock.getMemberUserIDReturnValue = .success(member)
|
roomProxyMock.getMemberUserIDReturnValue = .success(member)
|
||||||
|
|
||||||
let clientProxyMock = ClientProxyMock(.init())
|
let clientProxyMock = ClientProxyMock(.init())
|
||||||
|
|
||||||
|
clientProxyMock.userIdentityForClosure = { userID in
|
||||||
|
let isVerified = userID == RoomMemberProxyMock.mockDan.userID
|
||||||
|
return .success(UserIdentitySDKMock(configuration: .init(isVerified: isVerified)))
|
||||||
|
}
|
||||||
// to avoid mock the call state for the account owner test case
|
// to avoid mock the call state for the account owner test case
|
||||||
if member.userID != RoomMemberProxyMock.mockMe.userID {
|
if member.userID != RoomMemberProxyMock.mockMe.userID {
|
||||||
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
||||||
|
|||||||
@@ -284,14 +284,9 @@ extension FormatType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ComposerMode: Equatable {
|
enum ComposerMode: Equatable {
|
||||||
enum EditSource {
|
|
||||||
case timeline
|
|
||||||
case draftService
|
|
||||||
}
|
|
||||||
|
|
||||||
case `default`
|
case `default`
|
||||||
case reply(eventID: String, replyDetails: TimelineItemReplyDetails, isThread: Bool)
|
case reply(eventID: String, replyDetails: TimelineItemReplyDetails, isThread: Bool)
|
||||||
case edit(originalEventOrTransactionID: EventOrTransactionId, source: EditSource)
|
case edit(originalEventOrTransactionID: EventOrTransactionId)
|
||||||
case recordVoiceMessage(state: AudioRecorderState)
|
case recordVoiceMessage(state: AudioRecorderState)
|
||||||
case previewVoiceMessage(state: AudioPlayerState, waveform: WaveformSource, isUploading: Bool)
|
case previewVoiceMessage(state: AudioPlayerState, waveform: WaveformSource, isUploading: Bool)
|
||||||
|
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
case .newMessage:
|
case .newMessage:
|
||||||
set(mode: .default)
|
set(mode: .default)
|
||||||
case .edit(let eventID):
|
case .edit(let eventID):
|
||||||
set(mode: .edit(originalEventOrTransactionID: .eventId(eventId: eventID), source: .draftService))
|
set(mode: .edit(originalEventOrTransactionID: .eventId(eventId: eventID)))
|
||||||
case .reply(let eventID):
|
case .reply(let eventID):
|
||||||
set(mode: .reply(eventID: eventID, replyDetails: .loading(eventID: eventID), isThread: false))
|
set(mode: .reply(eventID: eventID, replyDetails: .loading(eventID: eventID), isThread: false))
|
||||||
replyLoadingTask = Task {
|
replyLoadingTask = Task {
|
||||||
@@ -314,7 +314,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
|||||||
switch state.composerMode {
|
switch state.composerMode {
|
||||||
case .default:
|
case .default:
|
||||||
type = .newMessage
|
type = .newMessage
|
||||||
case .edit(.eventId(let originalEventID), _):
|
case .edit(.eventId(let originalEventID)):
|
||||||
type = .edit(eventID: originalEventID)
|
type = .edit(eventID: originalEventID)
|
||||||
case .reply(let eventID, _, _):
|
case .reply(let eventID, _, _):
|
||||||
type = .reply(eventID: eventID)
|
type = .reply(eventID: eventID)
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
|
|||||||
messageComposer()
|
messageComposer()
|
||||||
|
|
||||||
messageComposer(.init(string: "Some message"),
|
messageComposer(.init(string: "Some message"),
|
||||||
mode: .edit(originalEventOrTransactionID: .eventId(eventId: UUID().uuidString), source: .timeline))
|
mode: .edit(originalEventOrTransactionID: .eventId(eventId: UUID().uuidString)))
|
||||||
|
|
||||||
messageComposer(mode: .reply(eventID: UUID().uuidString,
|
messageComposer(mode: .reply(eventID: UUID().uuidString,
|
||||||
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
replyDetails: .loaded(sender: .init(id: "Kirk"),
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class TimelineInteractionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Always update the mode first and then the text so that the composer has time to save the text draft
|
// Always update the mode first and then the text so that the composer has time to save the text draft
|
||||||
actionsSubject.send(.composer(action: .setMode(mode: .edit(originalEventOrTransactionID: eventOrTransactionID, source: .timeline))))
|
actionsSubject.send(.composer(action: .setMode(mode: .edit(originalEventOrTransactionID: eventOrTransactionID))))
|
||||||
actionsSubject.send(.composer(action: .setText(plainText: text, htmlText: htmlText)))
|
actionsSubject.send(.composer(action: .setText(plainText: text, htmlText: htmlText)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -602,9 +602,8 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
|||||||
html: html,
|
html: html,
|
||||||
inReplyToEventID: eventID,
|
inReplyToEventID: eventID,
|
||||||
intentionalMentions: intentionalMentions)
|
intentionalMentions: intentionalMentions)
|
||||||
case .edit(let originalEventOrTransactionID, let source):
|
case .edit(let originalEventOrTransactionID):
|
||||||
await timelineController.edit(originalEventOrTransactionID,
|
await timelineController.edit(originalEventOrTransactionID,
|
||||||
useTimeline: source == .timeline,
|
|
||||||
message: message,
|
message: message,
|
||||||
html: html,
|
html: html,
|
||||||
intentionalMentions: intentionalMentions)
|
intentionalMentions: intentionalMentions)
|
||||||
|
|||||||
@@ -19,10 +19,23 @@ struct UserProfileScreenViewState: BindableState {
|
|||||||
let isPresentedModally: Bool
|
let isPresentedModally: Bool
|
||||||
|
|
||||||
var userProfile: UserProfileProxy?
|
var userProfile: UserProfileProxy?
|
||||||
|
var isVerified: Bool?
|
||||||
var permalink: URL?
|
var permalink: URL?
|
||||||
var dmRoomID: String?
|
var dmRoomID: String?
|
||||||
|
|
||||||
var bindings: UserProfileScreenViewStateBindings
|
var bindings: UserProfileScreenViewStateBindings
|
||||||
|
|
||||||
|
var showVerifiedBadge: Bool {
|
||||||
|
isVerified == true // We purposely show the badge on your own account for consistency with Web.
|
||||||
|
}
|
||||||
|
|
||||||
|
var showVerificationSection: Bool {
|
||||||
|
isVerified == false && !isOwnUser
|
||||||
|
}
|
||||||
|
|
||||||
|
var verifyButtonTitle: String {
|
||||||
|
L10n.screenRoomMemberDetailsVerifyButtonTitle(userProfile?.displayName ?? "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UserProfileScreenViewStateBindings {
|
struct UserProfileScreenViewStateBindings {
|
||||||
|
|||||||
@@ -42,25 +42,9 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
|||||||
|
|
||||||
showLoadingIndicator(allowsInteraction: true)
|
showLoadingIndicator(allowsInteraction: true)
|
||||||
Task {
|
Task {
|
||||||
defer {
|
await loadProfile()
|
||||||
hideLoadingIndicator()
|
hideLoadingIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch await clientProxy.profile(for: userID) {
|
|
||||||
case .success(let userProfile):
|
|
||||||
state.userProfile = userProfile
|
|
||||||
state.permalink = (try? matrixToUserPermalink(userId: userID)).flatMap(URL.init(string:))
|
|
||||||
switch await clientProxy.directRoomForUserID(userProfile.userID) {
|
|
||||||
case .success(let roomID):
|
|
||||||
state.dmRoomID = roomID
|
|
||||||
case .failure:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case .failure(let error):
|
|
||||||
state.bindings.alertInfo = .init(id: .unknown)
|
|
||||||
MXLog.error("Failed to find user profile: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -87,6 +71,32 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func loadProfile() async {
|
||||||
|
async let profileResult = clientProxy.profile(for: state.userID)
|
||||||
|
async let identityResult = clientProxy.userIdentity(for: state.userID)
|
||||||
|
|
||||||
|
switch await profileResult {
|
||||||
|
case .success(let userProfile):
|
||||||
|
state.userProfile = userProfile
|
||||||
|
state.permalink = (try? matrixToUserPermalink(userId: state.userID)).flatMap(URL.init(string:))
|
||||||
|
switch await clientProxy.directRoomForUserID(userProfile.userID) {
|
||||||
|
case .success(let roomID):
|
||||||
|
state.dmRoomID = roomID
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
state.bindings.alertInfo = .init(id: .unknown)
|
||||||
|
MXLog.error("Failed to find user profile: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .success(.some(identity)) = await identityResult {
|
||||||
|
state.isVerified = identity.isVerified()
|
||||||
|
} else {
|
||||||
|
MXLog.error("Failed to find the user's identity.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func displayFullScreenAvatar(_ url: URL) async {
|
private func displayFullScreenAvatar(_ url: URL) async {
|
||||||
guard let userProfile = state.userProfile else { fatalError() }
|
guard let userProfile = state.userProfile else { fatalError() }
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ struct UserProfileScreen: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
headerSection
|
headerSection
|
||||||
|
|
||||||
|
verificationSection
|
||||||
}
|
}
|
||||||
.compoundList()
|
.compoundList()
|
||||||
.navigationTitle(L10n.screenRoomMemberDetailsTitle)
|
.navigationTitle(L10n.screenRoomMemberDetailsTitle)
|
||||||
@@ -27,6 +29,25 @@ struct UserProfileScreen: View {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
private var headerSection: some View {
|
||||||
|
if let userProfile = context.viewState.userProfile {
|
||||||
|
AvatarHeaderView(user: userProfile,
|
||||||
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
|
avatarSize: .user(on: .memberDetails),
|
||||||
|
mediaProvider: context.mediaProvider) { url in
|
||||||
|
context.send(viewAction: .displayAvatar(url))
|
||||||
|
} footer: {
|
||||||
|
otherUserFooter
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
||||||
|
isVerified: context.viewState.showVerifiedBadge,
|
||||||
|
avatarSize: .user(on: .memberDetails),
|
||||||
|
mediaProvider: context.mediaProvider,
|
||||||
|
footer: { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var otherUserFooter: some View {
|
private var otherUserFooter: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if context.viewState.userProfile != nil, !context.viewState.isOwnUser {
|
if context.viewState.userProfile != nil, !context.viewState.isOwnUser {
|
||||||
@@ -59,20 +80,15 @@ struct UserProfileScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var headerSection: some View {
|
var verificationSection: some View {
|
||||||
if let userProfile = context.viewState.userProfile {
|
if context.viewState.showVerificationSection {
|
||||||
AvatarHeaderView(user: userProfile,
|
Section {
|
||||||
avatarSize: .user(on: .memberDetails),
|
ListRow(label: .default(title: context.viewState.verifyButtonTitle,
|
||||||
mediaProvider: context.mediaProvider) { url in
|
description: L10n.screenRoomMemberDetailsVerifyButtonSubtitle,
|
||||||
context.send(viewAction: .displayAvatar(url))
|
icon: \.lock),
|
||||||
} footer: {
|
kind: .button { })
|
||||||
otherUserFooter
|
.disabled(true)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
AvatarHeaderView(user: UserProfileProxy(userID: context.viewState.userID),
|
|
||||||
avatarSize: .user(on: .memberDetails),
|
|
||||||
mediaProvider: context.mediaProvider,
|
|
||||||
footer: { })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,10 +107,14 @@ struct UserProfileScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
|
struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let otherUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockDan.userID)
|
static let verifiedUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockDan.userID)
|
||||||
|
static let otherUserViewModel = makeViewModel(userID: RoomMemberProxyMock.mockAlice.userID)
|
||||||
static let accountOwnerViewModel = makeViewModel(userID: RoomMemberProxyMock.mockMe.userID)
|
static let accountOwnerViewModel = makeViewModel(userID: RoomMemberProxyMock.mockMe.userID)
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
UserProfileScreen(context: verifiedUserViewModel.context)
|
||||||
|
.previewDisplayName("Verified User")
|
||||||
|
.snapshotPreferences(delay: 0.25)
|
||||||
UserProfileScreen(context: otherUserViewModel.context)
|
UserProfileScreen(context: otherUserViewModel.context)
|
||||||
.previewDisplayName("Other User")
|
.previewDisplayName("Other User")
|
||||||
.snapshotPreferences(delay: 0.25)
|
.snapshotPreferences(delay: 0.25)
|
||||||
@@ -105,6 +125,10 @@ struct UserProfileScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
|
|
||||||
static func makeViewModel(userID: String) -> UserProfileScreenViewModel {
|
static func makeViewModel(userID: String) -> UserProfileScreenViewModel {
|
||||||
let clientProxyMock = ClientProxyMock(.init())
|
let clientProxyMock = ClientProxyMock(.init())
|
||||||
|
clientProxyMock.userIdentityForClosure = { userID in
|
||||||
|
let isVerified = userID == RoomMemberProxyMock.mockDan.userID
|
||||||
|
return .success(UserIdentitySDKMock(configuration: .init(isVerified: isVerified)))
|
||||||
|
}
|
||||||
if userID != RoomMemberProxyMock.mockMe.userID {
|
if userID != RoomMemberProxyMock.mockMe.userID {
|
||||||
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
clientProxyMock.directRoomForUserIDReturnValue = .success("roomID")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -933,7 +933,7 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
MXLog.info("Pinning current identity for user: \(userID)")
|
MXLog.info("Pinning current identity for user: \(userID)")
|
||||||
|
|
||||||
do {
|
do {
|
||||||
guard let userIdentity = try await client.encryption().getUserIdentity(userId: userID) else {
|
guard let userIdentity = try await client.encryption().userIdentity(userId: userID) else {
|
||||||
MXLog.error("Failed retrieving identity for user: \(userID)")
|
MXLog.error("Failed retrieving identity for user: \(userID)")
|
||||||
return .failure(.failedRetrievingUserIdentity)
|
return .failure(.failedRetrievingUserIdentity)
|
||||||
}
|
}
|
||||||
@@ -952,6 +952,15 @@ class ClientProxy: ClientProxyProtocol {
|
|||||||
return .failure(.sdkError(error))
|
return .failure(.sdkError(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(client.encryption().userIdentity(userId: userID))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed retrieving user identity: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ClientProxy: MediaLoaderProtocol {
|
extension ClientProxy: MediaLoaderProtocol {
|
||||||
|
|||||||
@@ -204,4 +204,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
|
|||||||
|
|
||||||
func pinUserIdentity(_ userID: String) async -> Result<Void, ClientProxyError>
|
func pinUserIdentity(_ userID: String) async -> Result<Void, ClientProxyError>
|
||||||
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
|
func resetIdentity() async -> Result<IdentityResetHandle?, ClientProxyError>
|
||||||
|
|
||||||
|
func userIdentity(for userID: String) async -> Result<UserIdentity?, ClientProxyError>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
func toggleReaction(_ reaction: String, to eventID: EventOrTransactionId) async { }
|
func toggleReaction(_ reaction: String, to eventID: EventOrTransactionId) async { }
|
||||||
|
|
||||||
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
||||||
useTimeline: Bool,
|
|
||||||
message: String,
|
message: String,
|
||||||
html: String?,
|
html: String?,
|
||||||
intentionalMentions: IntentionalMentions) async { }
|
intentionalMentions: IntentionalMentions) async { }
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
||||||
useTimeline: Bool,
|
|
||||||
message: String,
|
message: String,
|
||||||
html: String?,
|
html: String?,
|
||||||
intentionalMentions: IntentionalMentions) async {
|
intentionalMentions: IntentionalMentions) async {
|
||||||
@@ -181,26 +180,12 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
html: html,
|
html: html,
|
||||||
intentionalMentions: intentionalMentions.toRustMentions())
|
intentionalMentions: intentionalMentions.toRustMentions())
|
||||||
|
|
||||||
if useTimeline {
|
|
||||||
switch await activeTimeline.edit(eventOrTransactionID, newContent: messageContent) {
|
switch await activeTimeline.edit(eventOrTransactionID, newContent: messageContent) {
|
||||||
case .success:
|
case .success:
|
||||||
MXLog.info("Finished editing message by event")
|
MXLog.info("Finished editing message by event")
|
||||||
case let .failure(error):
|
case let .failure(error):
|
||||||
MXLog.error("Failed editing message by event with error: \(error)")
|
MXLog.error("Failed editing message by event with error: \(error)")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
guard case let .eventId(eventID) = eventOrTransactionID else {
|
|
||||||
MXLog.error("Failed editing message, missing eventID.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch await roomProxy.edit(eventID: eventID, newContent: messageContent) {
|
|
||||||
case .success:
|
|
||||||
MXLog.info("Finished editing message by event ID")
|
|
||||||
case let .failure(error):
|
|
||||||
MXLog.error("Failed editing message by event ID with error: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func redact(_ eventOrTransactionID: EventOrTransactionId) async {
|
func redact(_ eventOrTransactionID: EventOrTransactionId) async {
|
||||||
|
|||||||
-1
@@ -52,7 +52,6 @@ protocol RoomTimelineControllerProtocol {
|
|||||||
intentionalMentions: IntentionalMentions) async
|
intentionalMentions: IntentionalMentions) async
|
||||||
|
|
||||||
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
func edit(_ eventOrTransactionID: EventOrTransactionId,
|
||||||
useTimeline: Bool,
|
|
||||||
message: String,
|
message: String,
|
||||||
html: String?,
|
html: String?,
|
||||||
intentionalMentions: IntentionalMentions) async
|
intentionalMentions: IntentionalMentions) async
|
||||||
|
|||||||
@@ -166,9 +166,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
|
|
||||||
func edit(_ eventOrTransactionID: EventOrTransactionId, newContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
|
func edit(_ eventOrTransactionID: EventOrTransactionId, newContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError> {
|
||||||
do {
|
do {
|
||||||
guard try await timeline.edit(eventOrTransactionId: eventOrTransactionID, newContent: .roomMessage(content: newContent)) == true else {
|
try await timeline.edit(eventOrTransactionId: eventOrTransactionID, newContent: .roomMessage(content: newContent))
|
||||||
return .failure(.failedEditing)
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("Finished editing timeline item: \(eventOrTransactionID)")
|
MXLog.info("Finished editing timeline item: \(eventOrTransactionID)")
|
||||||
|
|
||||||
@@ -460,13 +458,11 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
do {
|
do {
|
||||||
let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID)
|
let originalEvent = try await timeline.getEventTimelineItemByEventId(eventId: eventID)
|
||||||
|
|
||||||
guard try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId,
|
try await timeline.edit(eventOrTransactionId: originalEvent.eventOrTransactionId,
|
||||||
newContent: .pollStart(pollData: .init(question: question,
|
newContent: .pollStart(pollData: .init(question: question,
|
||||||
answers: answers,
|
answers: answers,
|
||||||
maxSelections: 1,
|
maxSelections: 1,
|
||||||
pollKind: .init(pollKind: pollKind)))) else {
|
pollKind: .init(pollKind: pollKind))))
|
||||||
return .failure(.failedEditing)
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("Finished editing poll with eventID: \(eventID)")
|
MXLog.info("Finished editing poll with eventID: \(eventID)")
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b32488a5a7c90b0c4b750d8cd822153782fa8493ac84d54af3496086a856d530
|
oid sha256:347ca4459368e8a76e3e5952ef0cdbbb76c89e099cd48f726a11bfa341b32c57
|
||||||
size 65677
|
size 106299
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:b32488a5a7c90b0c4b750d8cd822153782fa8493ac84d54af3496086a856d530
|
oid sha256:80805199a27b76e9e958197da199105217920310803d839e1347463e0f48e690
|
||||||
size 65677
|
size 106516
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7dbc686c75e95b8b0fbabad6f707d23bb5f0f5a6f37db69a98fd7a0138b4d0f3
|
oid sha256:c039a8c3cb9967aa355ef8472e30469e053a88f08b232df77bc1e1129ff58d60
|
||||||
size 39633
|
size 64744
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:7dbc686c75e95b8b0fbabad6f707d23bb5f0f5a6f37db69a98fd7a0138b4d0f3
|
oid sha256:246a69ad32fdbd7a2cae5851b9152d21cdf9263b0746cdbb9c3f2cbf38edd9cc
|
||||||
size 39633
|
size 64987
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:51a163146eacfa1147d3ca6b08a6f713c1d6e466ce2f990b7339a88edadc4bb9
|
oid sha256:c4a9d05d2aea48c83dd1f0891eccbdc2d99a9b38176971efd097597e69fda28f
|
||||||
size 109846
|
size 121678
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:057db6fe0f5d5a95482906bbe069f25ceeab1c8f5cc173f3c94014ac84fd828e
|
oid sha256:3f2806abf81f7d716dd4a740bde06ca6e7d488db513f42bcb70ca4ef8a5c2505
|
||||||
size 147921
|
size 120435
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:bf3638bf8eabd4c30cf9cc67bd55a71d3d7968dba02913b6a1c285b66abe86ca
|
||||||
|
size 151472
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2abc9dd95da1d42cc4e20163504253e48079a96253f0f6b020c9214cab3fc177
|
oid sha256:b9d5ad23bdf44dbe689d44c03ad2a6f12e993bcf6d8245d614b3d9e3b004b8b9
|
||||||
size 111394
|
size 127141
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:ce62f3154f37ec185e2323765a4191c6afe165fbb054f7638ed59d8617fff3e8
|
oid sha256:1dcb000d6d4caae5edbfef5e9007d327b72332708a98fc1df54a35622f8eb220
|
||||||
size 149231
|
size 125657
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0d0880d9fa7868f87803bf1a212194a7ca365ed7f10e85ab71561c19f735b121
|
||||||
|
size 152996
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9cf7ca6c0fa48a76bca67226963823759bd14dc871dea6a5eb373533d7d6b0e5
|
oid sha256:baa7127ec68c63490624fba7d6204063cdf950e941a55066291766e887c2281c
|
||||||
size 61377
|
size 71929
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:1a8128c33e14930422fd2047b329c2a496a2d561a45d4bbafc674013d57c6716
|
oid sha256:de7639ceab51d7eb8306c7df3a9a6899c1e78a120e22358908f95f9791cb417f
|
||||||
size 93275
|
size 70569
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:7b065c8fb99d1a1f5045db9cc52b328d1f2a48c3c062349c2d0348a28eec9640
|
||||||
|
size 96032
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:9d16eb4167974cc8231caaf02a91151eb22eab006953e0e43d8da3f047e32652
|
oid sha256:d624776c5e6a14ddb5c21e7349a9e2b29499d45137c4dbe906f3ad1014d9c6d2
|
||||||
size 68228
|
size 84082
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:35e4e002682e32a283edf58eb593463813b7a78d24bb17635eb07ce6d0365698
|
oid sha256:ddcf366a4fea427cd38681cff49842d7f5bc73ebc218efbe7ea9bc5914ae7b62
|
||||||
size 100293
|
size 82982
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:abbeec988102eb396909234e06388c1ab21c6f61875789722ab85a29cca1cc6a
|
||||||
|
size 104122
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:6a5d377d251dfe4cd7303973b586786db8a1185bd11061c31d78c5ab910a11c5
|
oid sha256:d5995f233036158f3adaff3e86899e86ccace5c6b6991626c78e1514eb63c5d5
|
||||||
size 101504
|
size 115587
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:8cfc307f7565b4f1e5cdef15055090205d69dabf36b19b3f538a331444e1cede
|
||||||
|
size 104896
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:903d91265fa95a4ad40c4131a90680e805c895e1f57b846838b317200b5e14cd
|
oid sha256:207bad6ef21783ebcc7c8189af09c233d191b01ddf7dc2fd92d3bbd1df1dd2ae
|
||||||
size 102481
|
size 120261
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:71303b4e0a9a1757e7658b0490a90d1cf438aade4466402e8777de94afa6d905
|
||||||
|
size 106067
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d2733defffabec358c0d86d9cbade1aba498c312db2c7f93eb045085d8ebb83c
|
oid sha256:5bd27ed946027cecc6286c1b4a0212b92addba260e7a17a650bb1f8a00ac0df9
|
||||||
size 53177
|
size 65717
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:4b4e47701bd9f9b3829f2d5a131aba025ab90a6c0a4b6e06ccfabcbf73e87c87
|
||||||
|
size 55937
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:5ce7e2162f0210a0d8ed409e089f7ab5dd62427c45728f5b543c24a5d70f7c6b
|
oid sha256:a5dca5794cc839ed0d28f7868e044ef5aba075e5e44e0b3e43c9b70df7e642b8
|
||||||
size 58590
|
size 78100
|
||||||
|
|||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d0412eab0f79b3baa004db621bf76b824c6817c2b90b7d7f8f4ab18f94bb2315
|
||||||
|
size 62260
|
||||||
@@ -41,14 +41,14 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testComposerFocus() {
|
func testComposerFocus() {
|
||||||
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: "mock"), source: .timeline)))
|
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: "mock"))))
|
||||||
XCTAssertTrue(viewModel.state.bindings.composerFocused)
|
XCTAssertTrue(viewModel.state.bindings.composerFocused)
|
||||||
viewModel.process(timelineAction: .removeFocus)
|
viewModel.process(timelineAction: .removeFocus)
|
||||||
XCTAssertFalse(viewModel.state.bindings.composerFocused)
|
XCTAssertFalse(viewModel.state.bindings.composerFocused)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testComposerMode() {
|
func testComposerMode() {
|
||||||
let mode: ComposerMode = .edit(originalEventOrTransactionID: .eventId(eventId: "mock"), source: .timeline)
|
let mode: ComposerMode = .edit(originalEventOrTransactionID: .eventId(eventId: "mock"))
|
||||||
viewModel.process(timelineAction: .setMode(mode: mode))
|
viewModel.process(timelineAction: .setMode(mode: mode))
|
||||||
XCTAssertEqual(viewModel.state.composerMode, mode)
|
XCTAssertEqual(viewModel.state.composerMode, mode)
|
||||||
viewModel.process(timelineAction: .clear)
|
viewModel.process(timelineAction: .clear)
|
||||||
@@ -56,7 +56,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testComposerModeIsPublished() {
|
func testComposerModeIsPublished() {
|
||||||
let mode: ComposerMode = .edit(originalEventOrTransactionID: .eventId(eventId: "mock"), source: .timeline)
|
let mode: ComposerMode = .edit(originalEventOrTransactionID: .eventId(eventId: "mock"))
|
||||||
let expectation = expectation(description: "Composer mode is published")
|
let expectation = expectation(description: "Composer mode is published")
|
||||||
let cancellable = viewModel
|
let cancellable = viewModel
|
||||||
.context
|
.context
|
||||||
@@ -236,7 +236,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.context.composerFormattingEnabled = false
|
viewModel.context.composerFormattingEnabled = false
|
||||||
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: "testID"), source: .timeline)))
|
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: "testID"))))
|
||||||
viewModel.context.plainComposerText = .init(string: "Hello world!")
|
viewModel.context.plainComposerText = .init(string: "Hello world!")
|
||||||
viewModel.saveDraft()
|
viewModel.saveDraft()
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
await fulfillment(of: [expectation], timeout: 10)
|
await fulfillment(of: [expectation], timeout: 10)
|
||||||
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
XCTAssertFalse(viewModel.context.composerFormattingEnabled)
|
||||||
XCTAssertEqual(viewModel.state.composerMode, .edit(originalEventOrTransactionID: .eventId(eventId: "testID"), source: .draftService))
|
XCTAssertEqual(viewModel.state.composerMode, .edit(originalEventOrTransactionID: .eventId(eventId: "testID")))
|
||||||
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: "Hello world!"))
|
XCTAssertEqual(viewModel.context.plainComposerText, NSAttributedString(string: "Hello world!"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +483,7 @@ class ComposerToolbarViewModelTests: XCTestCase {
|
|||||||
func testSaveVolatileDraftWhenEditing() {
|
func testSaveVolatileDraftWhenEditing() {
|
||||||
viewModel.context.composerFormattingEnabled = false
|
viewModel.context.composerFormattingEnabled = false
|
||||||
viewModel.context.plainComposerText = .init(string: "Hello world!")
|
viewModel.context.plainComposerText = .init(string: "Hello world!")
|
||||||
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: UUID().uuidString), source: .timeline)))
|
viewModel.process(timelineAction: .setMode(mode: .edit(originalEventOrTransactionID: .eventId(eventId: UUID().uuidString))))
|
||||||
|
|
||||||
let draft = draftServiceMock.saveVolatileDraftReceivedDraft
|
let draft = draftServiceMock.saveVolatileDraftReceivedDraft
|
||||||
XCTAssertNotNil(draft)
|
XCTAssertNotNil(draft)
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.58
|
exactVersion: 1.0.59
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
url: https://github.com/element-hq/compound-ios
|
url: https://github.com/element-hq/compound-ios
|
||||||
|
|||||||
Reference in New Issue
Block a user