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:
Alfonso Grillo
2023-05-26 15:47:12 +02:00
committed by GitHub
parent 524997438f
commit f2b7faa183
47 changed files with 1281 additions and 54 deletions
@@ -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) }))
}
}
+3 -1
View File
@@ -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(()) }
}
}