mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Room's details edit screen (#956)
* Add RoomDetailsEditScreen template * Add navigation to the edit screen * Delete template code * Start RoomDetailsEditScreen UI * Manage power levels in RoomDetailsScreen * Start RoomDetailsEditScreenViewModel logic * Inject initial room state * Add cancel action * Expose set name/topic APIs in RoomProxy * Refine RoomDetailsEditScreen UI * Add save logic * Add localisations * Fix avatar image * Update localisations * Add “Add topic” button * Add feature flag * Add dismiss on save logic * Reduce throttling * Improve form logic * Fix UT build errors * Add media sheet * Add media preprocessing * Add LoadableEditableAvatarImage * Add condition on delete image * Add avatar save button logic * Add remove avatar logic * Cleanup * Fix edit bug in DM * Add upload avatar * Add focus * Add RoomDetailsViewModel UTs * Fix button style * Add UTs * Add empty topic ui test * Fix iPad sheet presentation * Revert topic appearance in room’s details * Address PR comments * Add UI tests
This commit is contained in:
@@ -371,6 +371,27 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol {
|
||||
return unignoreUserReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - canSendStateEvent
|
||||
|
||||
var canSendStateEventTypeCallsCount = 0
|
||||
var canSendStateEventTypeCalled: Bool {
|
||||
return canSendStateEventTypeCallsCount > 0
|
||||
}
|
||||
var canSendStateEventTypeReceivedType: StateEventType?
|
||||
var canSendStateEventTypeReceivedInvocations: [StateEventType] = []
|
||||
var canSendStateEventTypeReturnValue: Bool!
|
||||
var canSendStateEventTypeClosure: ((StateEventType) -> Bool)?
|
||||
|
||||
func canSendStateEvent(type: StateEventType) -> Bool {
|
||||
canSendStateEventTypeCallsCount += 1
|
||||
canSendStateEventTypeReceivedType = type
|
||||
canSendStateEventTypeReceivedInvocations.append(type)
|
||||
if let canSendStateEventTypeClosure = canSendStateEventTypeClosure {
|
||||
return canSendStateEventTypeClosure(type)
|
||||
} else {
|
||||
return canSendStateEventTypeReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class RoomProxyMock: RoomProxyProtocol {
|
||||
var id: String {
|
||||
@@ -419,11 +440,6 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
set(value) { underlyingMembersPublisher = value }
|
||||
}
|
||||
var underlyingMembersPublisher: AnyPublisher<[RoomMemberProxyProtocol], Never>!
|
||||
var updatesPublisher: AnyPublisher<TimelineDiff, Never> {
|
||||
get { return underlyingUpdatesPublisher }
|
||||
set(value) { underlyingUpdatesPublisher = value }
|
||||
}
|
||||
var underlyingUpdatesPublisher: AnyPublisher<TimelineDiff, Never>!
|
||||
var invitedMembersCount: UInt {
|
||||
get { return underlyingInvitedMembersCount }
|
||||
set(value) { underlyingInvitedMembersCount = value }
|
||||
@@ -439,6 +455,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
set(value) { underlyingActiveMembersCount = value }
|
||||
}
|
||||
var underlyingActiveMembersCount: UInt!
|
||||
var updatesPublisher: AnyPublisher<TimelineDiff, Never> {
|
||||
get { return underlyingUpdatesPublisher }
|
||||
set(value) { underlyingUpdatesPublisher = value }
|
||||
}
|
||||
var underlyingUpdatesPublisher: AnyPublisher<TimelineDiff, Never>!
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
@@ -884,6 +905,86 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return inviteUserIDReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - setName
|
||||
|
||||
var setNameCallsCount = 0
|
||||
var setNameCalled: Bool {
|
||||
return setNameCallsCount > 0
|
||||
}
|
||||
var setNameReceivedName: String?
|
||||
var setNameReceivedInvocations: [String?] = []
|
||||
var setNameReturnValue: Result<Void, RoomProxyError>!
|
||||
var setNameClosure: ((String?) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func setName(_ name: String?) async -> Result<Void, RoomProxyError> {
|
||||
setNameCallsCount += 1
|
||||
setNameReceivedName = name
|
||||
setNameReceivedInvocations.append(name)
|
||||
if let setNameClosure = setNameClosure {
|
||||
return await setNameClosure(name)
|
||||
} else {
|
||||
return setNameReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - setTopic
|
||||
|
||||
var setTopicCallsCount = 0
|
||||
var setTopicCalled: Bool {
|
||||
return setTopicCallsCount > 0
|
||||
}
|
||||
var setTopicReceivedTopic: String?
|
||||
var setTopicReceivedInvocations: [String] = []
|
||||
var setTopicReturnValue: Result<Void, RoomProxyError>!
|
||||
var setTopicClosure: ((String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func setTopic(_ topic: String) async -> Result<Void, RoomProxyError> {
|
||||
setTopicCallsCount += 1
|
||||
setTopicReceivedTopic = topic
|
||||
setTopicReceivedInvocations.append(topic)
|
||||
if let setTopicClosure = setTopicClosure {
|
||||
return await setTopicClosure(topic)
|
||||
} else {
|
||||
return setTopicReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - removeAvatar
|
||||
|
||||
var removeAvatarCallsCount = 0
|
||||
var removeAvatarCalled: Bool {
|
||||
return removeAvatarCallsCount > 0
|
||||
}
|
||||
var removeAvatarReturnValue: Result<Void, RoomProxyError>!
|
||||
var removeAvatarClosure: (() async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func removeAvatar() async -> Result<Void, RoomProxyError> {
|
||||
removeAvatarCallsCount += 1
|
||||
if let removeAvatarClosure = removeAvatarClosure {
|
||||
return await removeAvatarClosure()
|
||||
} else {
|
||||
return removeAvatarReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - uploadAvatar
|
||||
|
||||
var uploadAvatarMediaCallsCount = 0
|
||||
var uploadAvatarMediaCalled: Bool {
|
||||
return uploadAvatarMediaCallsCount > 0
|
||||
}
|
||||
var uploadAvatarMediaReceivedMedia: MediaInfo?
|
||||
var uploadAvatarMediaReceivedInvocations: [MediaInfo] = []
|
||||
var uploadAvatarMediaReturnValue: Result<Void, RoomProxyError>!
|
||||
var uploadAvatarMediaClosure: ((MediaInfo) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func uploadAvatar(media: MediaInfo) async -> Result<Void, RoomProxyError> {
|
||||
uploadAvatarMediaCallsCount += 1
|
||||
uploadAvatarMediaReceivedMedia = media
|
||||
uploadAvatarMediaReceivedInvocations.append(media)
|
||||
if let uploadAvatarMediaClosure = uploadAvatarMediaClosure {
|
||||
return await uploadAvatarMediaClosure(media)
|
||||
} else {
|
||||
return uploadAvatarMediaReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
|
||||
@@ -23,11 +23,12 @@ struct RoomMemberProxyMockConfiguration {
|
||||
var avatarURL: URL?
|
||||
var membership: MembershipState
|
||||
var isNameAmbiguous = false
|
||||
var powerLevel: Int
|
||||
var normalizedPowerLevel: Int
|
||||
var powerLevel = 50
|
||||
var normalizedPowerLevel = 50
|
||||
var isAccountOwner = false
|
||||
var isIgnored = false
|
||||
var canInviteUsers = false
|
||||
var canSendStateEvent: (StateEventType) -> Bool = { _ in true }
|
||||
}
|
||||
|
||||
extension RoomMemberProxyMock {
|
||||
@@ -43,6 +44,7 @@ extension RoomMemberProxyMock {
|
||||
isAccountOwner = configuration.isAccountOwner
|
||||
isIgnored = configuration.isIgnored
|
||||
canInviteUsers = configuration.canInviteUsers
|
||||
canSendStateEventTypeClosure = configuration.canSendStateEvent
|
||||
}
|
||||
|
||||
// Mocks
|
||||
@@ -50,45 +52,35 @@ extension RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org",
|
||||
displayName: "Alice",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockInvitedAlice: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@alice:matrix.org",
|
||||
displayName: "Alice",
|
||||
avatarURL: nil,
|
||||
membership: .invite,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .invite))
|
||||
}
|
||||
|
||||
static var mockBob: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@bob:matrix.org",
|
||||
displayName: "Bob",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockCharlie: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@charlie:matrix.org",
|
||||
displayName: "Charlie",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockDan: RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@dan:matrix.org",
|
||||
displayName: "Dan",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50))
|
||||
membership: .join))
|
||||
}
|
||||
|
||||
static var mockMe: RoomMemberProxyMock {
|
||||
@@ -96,8 +88,6 @@ extension RoomMemberProxyMock {
|
||||
displayName: "Me",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50,
|
||||
isAccountOwner: true,
|
||||
canInviteUsers: true))
|
||||
}
|
||||
@@ -107,8 +97,14 @@ extension RoomMemberProxyMock {
|
||||
displayName: "Ignored",
|
||||
avatarURL: nil,
|
||||
membership: .join,
|
||||
powerLevel: 50,
|
||||
normalizedPowerLevel: 50,
|
||||
isIgnored: true))
|
||||
}
|
||||
|
||||
static func mockOwner(allowedStateEvents: [StateEventType]) -> RoomMemberProxyMock {
|
||||
RoomMemberProxyMock(with: .init(userID: "@foo:some.org",
|
||||
displayName: "User owner",
|
||||
membership: .join,
|
||||
isAccountOwner: true,
|
||||
canSendStateEvent: { allowedStateEvents.contains($0) }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import Foundation
|
||||
|
||||
struct RoomProxyMockConfiguration {
|
||||
var id = UUID().uuidString
|
||||
let name: String? = nil
|
||||
var name: String?
|
||||
let displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
@@ -75,5 +75,7 @@ extension RoomProxyMock {
|
||||
acceptInvitationClosure = { .success(()) }
|
||||
registerTimelineListenerIfNeededClosure = { .success([]) }
|
||||
underlyingUpdatesPublisher = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
setNameClosure = { _ in .success(()) }
|
||||
setTopicClosure = { _ in .success(()) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user