Leave Room (#699)
* created the row in the view and the alert, and added the new function to the RoomProxy * fixed an issue with the alert function * handling the navigation * fixed a bug with the detail coordinators being dismissed incorrectly when inside a stack * implementation completed * replaced UI screenshots * added a test for the fixed bug of the coordinators * trying to increase the wait time for the expectation * improved the test * improved the buttons UI * uploading artifacts for unit tests * added result bundle true * improved the tests * added a new test * pr suggestions * updating mock * PR suggestions * improved tests * fixed UI tests * pr should be ready now * removed testing code * reduced complexity * fixed test * added a an assert to the new test case * more tests and messages cases * pr comments addressed * completedpull/706/head^2
parent
7544619a55
commit
61d42a24ba
|
@ -41,6 +41,15 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
run: bundle exec fastlane unit_tests
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: test-output
|
||||
path: fastlane/test_output
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
|
|
@ -73,6 +73,10 @@
|
|||
"room_details_title" = "Info";
|
||||
"room_details_about_section_title" = "About";
|
||||
"room_details_copy_link" = "Copy Link";
|
||||
"room_details_leave_room_alert_subtitle" = "Are you sure that you want to leave the room?";
|
||||
"room_details_leave_private_room_alert_subtitle" = "Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite.";
|
||||
"room_details_leave_empty_room_alert_subtitle" = "Are you sure that you want to leave this room? You are the only person here. If you leave, no one will be able to join in the future, including you.";
|
||||
"room_details_room_left_toast" = "Room left";
|
||||
|
||||
// Onboarding
|
||||
"ftue_auth_carousel_welcome_title" = "Be in your Element";
|
||||
|
|
|
@ -528,7 +528,7 @@ class NavigationStackCoordinator: ObservableObject, CoordinatorProtocol, CustomS
|
|||
}
|
||||
|
||||
popToRoot(animated: false)
|
||||
|
||||
|
||||
rootModule = NavigationModule(coordinator, dismissalCallback: dismissalCallback)
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,14 @@ extension ElementL10n {
|
|||
public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title")
|
||||
/// Copy Link
|
||||
public static let roomDetailsCopyLink = ElementL10n.tr("Untranslated", "room_details_copy_link")
|
||||
/// Are you sure that you want to leave this room? You are the only person here. If you leave, no one will be able to join in the future, including you.
|
||||
public static let roomDetailsLeaveEmptyRoomAlertSubtitle = ElementL10n.tr("Untranslated", "room_details_leave_empty_room_alert_subtitle")
|
||||
/// Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite.
|
||||
public static let roomDetailsLeavePrivateRoomAlertSubtitle = ElementL10n.tr("Untranslated", "room_details_leave_private_room_alert_subtitle")
|
||||
/// Are you sure that you want to leave the room?
|
||||
public static let roomDetailsLeaveRoomAlertSubtitle = ElementL10n.tr("Untranslated", "room_details_leave_room_alert_subtitle")
|
||||
/// Room left
|
||||
public static let roomDetailsRoomLeftToast = ElementL10n.tr("Untranslated", "room_details_room_left_toast")
|
||||
/// Info
|
||||
public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title")
|
||||
/// Failed loading messages
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
// swiftlint:disable all
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
class BugReportServiceMock: BugReportServiceProtocol {
|
||||
var crashedLastRun: Bool {
|
||||
|
@ -49,6 +50,322 @@ class BugReportServiceMock: BugReportServiceProtocol {
|
|||
}
|
||||
}
|
||||
}
|
||||
class RoomProxyMock: RoomProxyProtocol {
|
||||
var id: String {
|
||||
get { return underlyingId }
|
||||
set(value) { underlyingId = value }
|
||||
}
|
||||
var underlyingId: String!
|
||||
var isDirect: Bool {
|
||||
get { return underlyingIsDirect }
|
||||
set(value) { underlyingIsDirect = value }
|
||||
}
|
||||
var underlyingIsDirect: Bool!
|
||||
var isPublic: Bool {
|
||||
get { return underlyingIsPublic }
|
||||
set(value) { underlyingIsPublic = value }
|
||||
}
|
||||
var underlyingIsPublic: Bool!
|
||||
var isSpace: Bool {
|
||||
get { return underlyingIsSpace }
|
||||
set(value) { underlyingIsSpace = value }
|
||||
}
|
||||
var underlyingIsSpace: Bool!
|
||||
var isEncrypted: Bool {
|
||||
get { return underlyingIsEncrypted }
|
||||
set(value) { underlyingIsEncrypted = value }
|
||||
}
|
||||
var underlyingIsEncrypted: Bool!
|
||||
var isTombstoned: Bool {
|
||||
get { return underlyingIsTombstoned }
|
||||
set(value) { underlyingIsTombstoned = value }
|
||||
}
|
||||
var underlyingIsTombstoned: Bool!
|
||||
var canonicalAlias: String?
|
||||
var alternativeAliases: [String] = []
|
||||
var hasUnreadNotifications: Bool {
|
||||
get { return underlyingHasUnreadNotifications }
|
||||
set(value) { underlyingHasUnreadNotifications = value }
|
||||
}
|
||||
var underlyingHasUnreadNotifications: Bool!
|
||||
var name: String?
|
||||
var displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
var loadAvatarURLForUserIdCallsCount = 0
|
||||
var loadAvatarURLForUserIdCalled: Bool {
|
||||
return loadAvatarURLForUserIdCallsCount > 0
|
||||
}
|
||||
var loadAvatarURLForUserIdReceivedUserId: String?
|
||||
var loadAvatarURLForUserIdReceivedInvocations: [String] = []
|
||||
var loadAvatarURLForUserIdReturnValue: Result<URL?, RoomProxyError>!
|
||||
var loadAvatarURLForUserIdClosure: ((String) async -> Result<URL?, RoomProxyError>)?
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError> {
|
||||
loadAvatarURLForUserIdCallsCount += 1
|
||||
loadAvatarURLForUserIdReceivedUserId = userId
|
||||
loadAvatarURLForUserIdReceivedInvocations.append(userId)
|
||||
if let loadAvatarURLForUserIdClosure = loadAvatarURLForUserIdClosure {
|
||||
return await loadAvatarURLForUserIdClosure(userId)
|
||||
} else {
|
||||
return loadAvatarURLForUserIdReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - loadDisplayNameForUserId
|
||||
|
||||
var loadDisplayNameForUserIdCallsCount = 0
|
||||
var loadDisplayNameForUserIdCalled: Bool {
|
||||
return loadDisplayNameForUserIdCallsCount > 0
|
||||
}
|
||||
var loadDisplayNameForUserIdReceivedUserId: String?
|
||||
var loadDisplayNameForUserIdReceivedInvocations: [String] = []
|
||||
var loadDisplayNameForUserIdReturnValue: Result<String?, RoomProxyError>!
|
||||
var loadDisplayNameForUserIdClosure: ((String) async -> Result<String?, RoomProxyError>)?
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||
loadDisplayNameForUserIdCallsCount += 1
|
||||
loadDisplayNameForUserIdReceivedUserId = userId
|
||||
loadDisplayNameForUserIdReceivedInvocations.append(userId)
|
||||
if let loadDisplayNameForUserIdClosure = loadDisplayNameForUserIdClosure {
|
||||
return await loadDisplayNameForUserIdClosure(userId)
|
||||
} else {
|
||||
return loadDisplayNameForUserIdReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - addTimelineListener
|
||||
|
||||
var addTimelineListenerListenerCallsCount = 0
|
||||
var addTimelineListenerListenerCalled: Bool {
|
||||
return addTimelineListenerListenerCallsCount > 0
|
||||
}
|
||||
var addTimelineListenerListenerReceivedListener: TimelineListener?
|
||||
var addTimelineListenerListenerReceivedInvocations: [TimelineListener] = []
|
||||
var addTimelineListenerListenerReturnValue: Result<[TimelineItem], RoomProxyError>!
|
||||
var addTimelineListenerListenerClosure: ((TimelineListener) -> Result<[TimelineItem], RoomProxyError>)?
|
||||
|
||||
func addTimelineListener(listener: TimelineListener) -> Result<[TimelineItem], RoomProxyError> {
|
||||
addTimelineListenerListenerCallsCount += 1
|
||||
addTimelineListenerListenerReceivedListener = listener
|
||||
addTimelineListenerListenerReceivedInvocations.append(listener)
|
||||
if let addTimelineListenerListenerClosure = addTimelineListenerListenerClosure {
|
||||
return addTimelineListenerListenerClosure(listener)
|
||||
} else {
|
||||
return addTimelineListenerListenerReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - removeTimelineListener
|
||||
|
||||
var removeTimelineListenerCallsCount = 0
|
||||
var removeTimelineListenerCalled: Bool {
|
||||
return removeTimelineListenerCallsCount > 0
|
||||
}
|
||||
var removeTimelineListenerClosure: (() -> Void)?
|
||||
|
||||
func removeTimelineListener() {
|
||||
removeTimelineListenerCallsCount += 1
|
||||
removeTimelineListenerClosure?()
|
||||
}
|
||||
//MARK: - paginateBackwards
|
||||
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount = 0
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsCalled: Bool {
|
||||
return paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount > 0
|
||||
}
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments: (requestSize: UInt, untilNumberOfItems: UInt)?
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations: [(requestSize: UInt, untilNumberOfItems: UInt)] = []
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue: Result<Void, RoomProxyError>!
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsClosure: ((UInt, UInt) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
|
||||
paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount += 1
|
||||
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedArguments = (requestSize: requestSize, untilNumberOfItems: untilNumberOfItems)
|
||||
paginateBackwardsRequestSizeUntilNumberOfItemsReceivedInvocations.append((requestSize: requestSize, untilNumberOfItems: untilNumberOfItems))
|
||||
if let paginateBackwardsRequestSizeUntilNumberOfItemsClosure = paginateBackwardsRequestSizeUntilNumberOfItemsClosure {
|
||||
return await paginateBackwardsRequestSizeUntilNumberOfItemsClosure(requestSize, untilNumberOfItems)
|
||||
} else {
|
||||
return paginateBackwardsRequestSizeUntilNumberOfItemsReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendReadReceipt
|
||||
|
||||
var sendReadReceiptForCallsCount = 0
|
||||
var sendReadReceiptForCalled: Bool {
|
||||
return sendReadReceiptForCallsCount > 0
|
||||
}
|
||||
var sendReadReceiptForReceivedEventID: String?
|
||||
var sendReadReceiptForReceivedInvocations: [String] = []
|
||||
var sendReadReceiptForReturnValue: Result<Void, RoomProxyError>!
|
||||
var sendReadReceiptForClosure: ((String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendReadReceiptForCallsCount += 1
|
||||
sendReadReceiptForReceivedEventID = eventID
|
||||
sendReadReceiptForReceivedInvocations.append(eventID)
|
||||
if let sendReadReceiptForClosure = sendReadReceiptForClosure {
|
||||
return await sendReadReceiptForClosure(eventID)
|
||||
} else {
|
||||
return sendReadReceiptForReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendMessage
|
||||
|
||||
var sendMessageInReplyToCallsCount = 0
|
||||
var sendMessageInReplyToCalled: Bool {
|
||||
return sendMessageInReplyToCallsCount > 0
|
||||
}
|
||||
var sendMessageInReplyToReceivedArguments: (message: String, eventID: String?)?
|
||||
var sendMessageInReplyToReceivedInvocations: [(message: String, eventID: String?)] = []
|
||||
var sendMessageInReplyToReturnValue: Result<Void, RoomProxyError>!
|
||||
var sendMessageInReplyToClosure: ((String, String?) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String?) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageInReplyToCallsCount += 1
|
||||
sendMessageInReplyToReceivedArguments = (message: message, eventID: eventID)
|
||||
sendMessageInReplyToReceivedInvocations.append((message: message, eventID: eventID))
|
||||
if let sendMessageInReplyToClosure = sendMessageInReplyToClosure {
|
||||
return await sendMessageInReplyToClosure(message, eventID)
|
||||
} else {
|
||||
return sendMessageInReplyToReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendReaction
|
||||
|
||||
var sendReactionToCallsCount = 0
|
||||
var sendReactionToCalled: Bool {
|
||||
return sendReactionToCallsCount > 0
|
||||
}
|
||||
var sendReactionToReceivedArguments: (reaction: String, eventID: String)?
|
||||
var sendReactionToReceivedInvocations: [(reaction: String, eventID: String)] = []
|
||||
var sendReactionToReturnValue: Result<Void, RoomProxyError>!
|
||||
var sendReactionToClosure: ((String, String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
sendReactionToCallsCount += 1
|
||||
sendReactionToReceivedArguments = (reaction: reaction, eventID: eventID)
|
||||
sendReactionToReceivedInvocations.append((reaction: reaction, eventID: eventID))
|
||||
if let sendReactionToClosure = sendReactionToClosure {
|
||||
return await sendReactionToClosure(reaction, eventID)
|
||||
} else {
|
||||
return sendReactionToReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - editMessage
|
||||
|
||||
var editMessageOriginalCallsCount = 0
|
||||
var editMessageOriginalCalled: Bool {
|
||||
return editMessageOriginalCallsCount > 0
|
||||
}
|
||||
var editMessageOriginalReceivedArguments: (newMessage: String, eventID: String)?
|
||||
var editMessageOriginalReceivedInvocations: [(newMessage: String, eventID: String)] = []
|
||||
var editMessageOriginalReturnValue: Result<Void, RoomProxyError>!
|
||||
var editMessageOriginalClosure: ((String, String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
editMessageOriginalCallsCount += 1
|
||||
editMessageOriginalReceivedArguments = (newMessage: newMessage, eventID: eventID)
|
||||
editMessageOriginalReceivedInvocations.append((newMessage: newMessage, eventID: eventID))
|
||||
if let editMessageOriginalClosure = editMessageOriginalClosure {
|
||||
return await editMessageOriginalClosure(newMessage, eventID)
|
||||
} else {
|
||||
return editMessageOriginalReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - redact
|
||||
|
||||
var redactCallsCount = 0
|
||||
var redactCalled: Bool {
|
||||
return redactCallsCount > 0
|
||||
}
|
||||
var redactReceivedEventID: String?
|
||||
var redactReceivedInvocations: [String] = []
|
||||
var redactReturnValue: Result<Void, RoomProxyError>!
|
||||
var redactClosure: ((String) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
redactCallsCount += 1
|
||||
redactReceivedEventID = eventID
|
||||
redactReceivedInvocations.append(eventID)
|
||||
if let redactClosure = redactClosure {
|
||||
return await redactClosure(eventID)
|
||||
} else {
|
||||
return redactReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - reportContent
|
||||
|
||||
var reportContentReasonCallsCount = 0
|
||||
var reportContentReasonCalled: Bool {
|
||||
return reportContentReasonCallsCount > 0
|
||||
}
|
||||
var reportContentReasonReceivedArguments: (eventID: String, reason: String?)?
|
||||
var reportContentReasonReceivedInvocations: [(eventID: String, reason: String?)] = []
|
||||
var reportContentReasonReturnValue: Result<Void, RoomProxyError>!
|
||||
var reportContentReasonClosure: ((String, String?) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> {
|
||||
reportContentReasonCallsCount += 1
|
||||
reportContentReasonReceivedArguments = (eventID: eventID, reason: reason)
|
||||
reportContentReasonReceivedInvocations.append((eventID: eventID, reason: reason))
|
||||
if let reportContentReasonClosure = reportContentReasonClosure {
|
||||
return await reportContentReasonClosure(eventID, reason)
|
||||
} else {
|
||||
return reportContentReasonReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - members
|
||||
|
||||
var membersCallsCount = 0
|
||||
var membersCalled: Bool {
|
||||
return membersCallsCount > 0
|
||||
}
|
||||
var membersReturnValue: Result<[RoomMemberProxy], RoomProxyError>!
|
||||
var membersClosure: (() async -> Result<[RoomMemberProxy], RoomProxyError>)?
|
||||
|
||||
func members() async -> Result<[RoomMemberProxy], RoomProxyError> {
|
||||
membersCallsCount += 1
|
||||
if let membersClosure = membersClosure {
|
||||
return await membersClosure()
|
||||
} else {
|
||||
return membersReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - retryDecryption
|
||||
|
||||
var retryDecryptionForCallsCount = 0
|
||||
var retryDecryptionForCalled: Bool {
|
||||
return retryDecryptionForCallsCount > 0
|
||||
}
|
||||
var retryDecryptionForReceivedSessionID: String?
|
||||
var retryDecryptionForReceivedInvocations: [String] = []
|
||||
var retryDecryptionForClosure: ((String) async -> Void)?
|
||||
|
||||
func retryDecryption(for sessionID: String) async {
|
||||
retryDecryptionForCallsCount += 1
|
||||
retryDecryptionForReceivedSessionID = sessionID
|
||||
retryDecryptionForReceivedInvocations.append(sessionID)
|
||||
await retryDecryptionForClosure?(sessionID)
|
||||
}
|
||||
//MARK: - leaveRoom
|
||||
|
||||
var leaveRoomCallsCount = 0
|
||||
var leaveRoomCalled: Bool {
|
||||
return leaveRoomCallsCount > 0
|
||||
}
|
||||
var leaveRoomReturnValue: Result<Void, RoomProxyError>!
|
||||
var leaveRoomClosure: (() async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError> {
|
||||
leaveRoomCallsCount += 1
|
||||
if let leaveRoomClosure = leaveRoomClosure {
|
||||
return await leaveRoomClosure()
|
||||
} else {
|
||||
return leaveRoomReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class SessionVerificationControllerProxyMock: SessionVerificationControllerProxyProtocol {
|
||||
var callbacks: PassthroughSubject<SessionVerificationControllerProxyCallback, Never> {
|
||||
get { return underlyingCallbacks }
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RoomProxyMockConfiguration {
|
||||
var id = UUID().uuidString
|
||||
let name: String? = nil
|
||||
let displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
var isDirect = Bool.random()
|
||||
var isSpace = Bool.random()
|
||||
var isPublic = Bool.random()
|
||||
var isEncrypted = Bool.random()
|
||||
var isTombstoned = Bool.random()
|
||||
var canonicalAlias: String?
|
||||
var alternativeAliases: [String] = []
|
||||
var hasUnreadNotifications = Bool.random()
|
||||
var members: [RoomMemberProxy]?
|
||||
}
|
||||
|
||||
extension RoomProxyMock {
|
||||
convenience init(with configuration: RoomProxyMockConfiguration) {
|
||||
self.init()
|
||||
|
||||
id = configuration.id
|
||||
name = configuration.name
|
||||
displayName = configuration.displayName
|
||||
topic = configuration.topic
|
||||
avatarURL = configuration.avatarURL
|
||||
isDirect = configuration.isDirect
|
||||
isSpace = configuration.isSpace
|
||||
isPublic = configuration.isPublic
|
||||
isEncrypted = configuration.isEncrypted
|
||||
isTombstoned = configuration.isTombstoned
|
||||
canonicalAlias = configuration.canonicalAlias
|
||||
alternativeAliases = configuration.alternativeAliases
|
||||
hasUnreadNotifications = configuration.hasUnreadNotifications
|
||||
|
||||
membersClosure = {
|
||||
if let members = configuration.members {
|
||||
return .success(members)
|
||||
}
|
||||
return .failure(.failedRetrievingMembers)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ protocol AlertItem {
|
|||
}
|
||||
|
||||
extension View {
|
||||
func alert<I, V>(item: Binding<I?>, actions: (I) -> V, message: (I) -> V) -> some View where I: AlertItem, V: View {
|
||||
func alert<Item, Actions, Message>(item: Binding<Item?>, actions: (Item) -> Actions, message: (Item) -> Message) -> some View where Item: AlertItem, Actions: View, Message: View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
|
@ -32,7 +32,7 @@ extension View {
|
|||
return alert(item.wrappedValue?.title ?? "", isPresented: binding, presenting: item.wrappedValue, actions: actions, message: message)
|
||||
}
|
||||
|
||||
func alert<I, V>(item: Binding<I?>, actions: (I) -> V) -> some View where I: AlertItem, V: View {
|
||||
func alert<Item, Actions>(item: Binding<Item?>, actions: (Item) -> Actions) -> some View where Item: AlertItem, Actions: View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
item.wrappedValue != nil
|
||||
}, set: { newValue in
|
||||
|
|
|
@ -41,6 +41,7 @@ struct FormButtonStyle: PrimitiveButtonStyle {
|
|||
func makeBody(configuration: Configuration) -> some View {
|
||||
Button(action: configuration.trigger) {
|
||||
configuration.label
|
||||
.labelStyle(FormRowLabelStyle(role: configuration.role))
|
||||
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
|
||||
}
|
||||
.buttonStyle(Style(accessory: accessory))
|
||||
|
@ -54,7 +55,7 @@ struct FormButtonStyle: PrimitiveButtonStyle {
|
|||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack {
|
||||
configuration.label
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
.labelStyle(FormRowLabelStyle(role: configuration.role))
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
|
@ -104,6 +105,11 @@ struct FormButtonStyles_Previews: PreviewProvider {
|
|||
Label("Show something", systemImage: "rectangle.portrait")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
|
||||
|
||||
Button(role: .destructive) { } label: {
|
||||
Label("Show destruction", systemImage: "rectangle.portrait")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
|
||||
|
||||
ShareLink(item: "test")
|
||||
.buttonStyle(FormButtonStyle())
|
||||
|
@ -117,6 +123,14 @@ struct FormButtonStyles_Previews: PreviewProvider {
|
|||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
Section {
|
||||
Button(role: .destructive) { } label: {
|
||||
Label("Destroy", systemImage: "x.circle")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,19 +19,44 @@ import SwiftUI
|
|||
struct FormRowLabelStyle: LabelStyle {
|
||||
@ScaledMetric private var menuIconSize = 30.0
|
||||
|
||||
var alignment: VerticalAlignment = .firstTextBaseline
|
||||
var alignment = VerticalAlignment.firstTextBaseline
|
||||
var role: ButtonRole?
|
||||
|
||||
private var titleColor: Color {
|
||||
if role == .destructive {
|
||||
return .element.alert
|
||||
} else {
|
||||
return .element.primaryContent
|
||||
}
|
||||
}
|
||||
|
||||
private var iconBackgroundColor: Color {
|
||||
if role == .destructive {
|
||||
return .element.alert.opacity(0.1)
|
||||
} else {
|
||||
return .element.formBackground
|
||||
}
|
||||
}
|
||||
|
||||
private var iconForegroundColor: Color {
|
||||
if role == .destructive {
|
||||
return .element.alert
|
||||
} else {
|
||||
return .element.secondaryContent
|
||||
}
|
||||
}
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
HStack(alignment: alignment, spacing: 16) {
|
||||
configuration.icon
|
||||
.foregroundColor(.element.secondaryContent)
|
||||
.foregroundColor(iconForegroundColor)
|
||||
.padding(4)
|
||||
.frame(width: menuIconSize, height: menuIconSize)
|
||||
.background(Color.element.formBackground)
|
||||
.background(iconBackgroundColor)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
configuration.title
|
||||
.font(.element.body)
|
||||
.foregroundColor(.element.primaryContent)
|
||||
.foregroundColor(titleColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +75,9 @@ struct FormRowLabelStyle_Previews: PreviewProvider {
|
|||
|
||||
Label("Help", systemImage: "questionmark")
|
||||
.labelStyle(FormRowLabelStyle())
|
||||
|
||||
Label("Destroy", systemImage: "x.circle")
|
||||
.labelStyle(FormRowLabelStyle(role: .destructive))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ struct UserIndicatorToastView: View {
|
|||
.padding(.horizontal, 12.0)
|
||||
.padding(.vertical, 10.0)
|
||||
.frame(minWidth: 150.0)
|
||||
.background(Color.element.quaternaryContent)
|
||||
.background(Color.element.system)
|
||||
.clipShape(RoundedCornerShape(radius: 24.0, corners: .allCorners))
|
||||
.shadow(color: .black.opacity(0.1), radius: 10.0, y: 4.0)
|
||||
.transition(toastTransition)
|
||||
|
|
|
@ -76,7 +76,7 @@ struct ReportContentScreen: View {
|
|||
// MARK: - Previews
|
||||
|
||||
struct ReportContent_Previews: PreviewProvider {
|
||||
static let viewModel = ReportContentViewModel(itemID: "", roomProxy: MockRoomProxy(displayName: nil))
|
||||
static let viewModel = ReportContentViewModel(itemID: "", roomProxy: RoomProxyMock(with: .init(displayName: nil)))
|
||||
|
||||
static var previews: some View {
|
||||
ReportContentScreen(context: viewModel.context)
|
||||
|
|
|
@ -24,6 +24,7 @@ struct RoomDetailsCoordinatorParameters {
|
|||
|
||||
enum RoomDetailsCoordinatorAction {
|
||||
case cancel
|
||||
case leftRoom
|
||||
}
|
||||
|
||||
final class RoomDetailsCoordinator: CoordinatorProtocol {
|
||||
|
@ -51,6 +52,8 @@ final class RoomDetailsCoordinator: CoordinatorProtocol {
|
|||
self.presentRoomMemberDetails(members)
|
||||
case .cancel:
|
||||
self.callback?(.cancel)
|
||||
case .leftRoom:
|
||||
self.callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import UIKit
|
|||
|
||||
enum RoomDetailsViewModelAction {
|
||||
case requestMemberDetailsPresentation([RoomMemberProxy])
|
||||
case leftRoom
|
||||
case cancel
|
||||
}
|
||||
|
||||
|
@ -49,15 +50,34 @@ struct RoomDetailsViewState: BindableState {
|
|||
struct RoomDetailsViewStateBindings {
|
||||
/// Information describing the currently displayed alert.
|
||||
var alertInfo: AlertInfo<RoomDetailsErrorType>?
|
||||
var leaveRoomAlertItem: LeaveRoomAlertItem?
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
struct LeaveRoomAlertItem: AlertItem {
|
||||
enum RoomState {
|
||||
case empty
|
||||
case `public`
|
||||
case `private`
|
||||
}
|
||||
|
||||
let state: RoomState
|
||||
let title = ElementL10n.roomProfileSectionMoreLeave
|
||||
let confirmationTitle = ElementL10n.actionLeave
|
||||
let cancelTitle = ElementL10n.actionCancel
|
||||
|
||||
var subtitle: String {
|
||||
switch state {
|
||||
case .empty: return ElementL10n.roomDetailsLeaveEmptyRoomAlertSubtitle
|
||||
case .private: return ElementL10n.roomDetailsLeavePrivateRoomAlertSubtitle
|
||||
case .public: return ElementL10n.roomDetailsLeaveRoomAlertSubtitle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomDetailsViewAction {
|
||||
case processTapPeople
|
||||
case processTapLeave
|
||||
case confirmLeave
|
||||
case copyRoomLink
|
||||
}
|
||||
|
||||
|
@ -72,3 +92,10 @@ struct RoomDetailsMember: Identifiable, Equatable {
|
|||
avatarURL = proxy.avatarURL
|
||||
}
|
||||
}
|
||||
|
||||
enum RoomDetailsErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert(String)
|
||||
/// Leaving room has failed..
|
||||
case unknown
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import SwiftUI
|
|||
typealias RoomDetailsViewModelType = StateStoreViewModel<RoomDetailsViewState, RoomDetailsViewAction>
|
||||
|
||||
class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtocol {
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private var members: [RoomMemberProxy] = [] {
|
||||
didSet {
|
||||
state.members = members.map { RoomDetailsMember(withProxy: $0) }
|
||||
|
@ -29,6 +30,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
|||
|
||||
init(roomProxy: RoomProxyProtocol,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
super.init(initialViewState: .init(roomId: roomProxy.id,
|
||||
canonicalAlias: roomProxy.canonicalAlias,
|
||||
isEncrypted: roomProxy.isEncrypted,
|
||||
|
@ -60,10 +62,20 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
|||
callback?(.requestMemberDetailsPresentation(members))
|
||||
case .copyRoomLink:
|
||||
copyRoomLink()
|
||||
case .processTapLeave:
|
||||
guard members.count > 1 else {
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: .empty)
|
||||
return
|
||||
}
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||
case .confirmLeave:
|
||||
await leaveRoom()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private static let leaveRoomLoadingID = "LeaveRoomLoading"
|
||||
|
||||
private func copyRoomLink() {
|
||||
if let roomLink = state.permalink {
|
||||
|
@ -73,4 +85,16 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
|||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: ElementL10n.unknownError))
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoom() async {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(id: Self.leaveRoomLoadingID, type: .modal, title: ElementL10n.loading, persistent: true))
|
||||
let result = await roomProxy.leaveRoom()
|
||||
ServiceLocator.shared.userIndicatorController.retractIndicatorWithId(Self.leaveRoomLoadingID)
|
||||
switch result {
|
||||
case .failure:
|
||||
state.bindings.alertInfo = AlertInfo(id: .unknown)
|
||||
case .success:
|
||||
callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,10 +30,15 @@ struct RoomDetailsScreen: View {
|
|||
if context.viewState.isEncrypted {
|
||||
securitySection
|
||||
}
|
||||
|
||||
leaveRoomSection
|
||||
}
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.element.formBackground.ignoresSafeArea())
|
||||
.alert(item: $context.alertInfo) { $0.alert }
|
||||
.alert(item: $context.leaveRoomAlertItem,
|
||||
actions: leaveRoomAlertActions,
|
||||
message: leaveRoomAlertMessage)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
@ -147,6 +152,30 @@ struct RoomDetailsScreen: View {
|
|||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
private var leaveRoomSection: some View {
|
||||
Section {
|
||||
Button(role: .destructive) {
|
||||
context.send(viewAction: .processTapLeave)
|
||||
} label: {
|
||||
Label(ElementL10n.roomProfileSectionMoreLeave, systemImage: "door.right.hand.open")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: nil))
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func leaveRoomAlertActions(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Button(item.cancelTitle, role: .cancel) { }
|
||||
Button(item.confirmationTitle, role: .destructive) {
|
||||
context.send(viewAction: .confirmLeave)
|
||||
}
|
||||
}
|
||||
|
||||
private func leaveRoomAlertMessage(_ item: LeaveRoomAlertItem) -> some View {
|
||||
Text(item.subtitle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
@ -158,12 +187,12 @@ struct RoomDetails_Previews: PreviewProvider {
|
|||
.mockBob,
|
||||
.mockCharlie
|
||||
]
|
||||
let roomProxy = MockRoomProxy(displayName: "Room A",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: false,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members)
|
||||
let roomProxy = RoomProxyMock(with: .init(displayName: "Room A",
|
||||
topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
||||
isDirect: false,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#alias:domain.com",
|
||||
members: members))
|
||||
|
||||
return RoomDetailsViewModel(roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider())
|
||||
|
|
|
@ -24,6 +24,10 @@ struct RoomScreenCoordinatorParameters {
|
|||
let emojiProvider: EmojiProviderProtocol
|
||||
}
|
||||
|
||||
enum RoomScreenCoordinatorAction {
|
||||
case leftRoom
|
||||
}
|
||||
|
||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
private var parameters: RoomScreenCoordinatorParameters
|
||||
|
||||
|
@ -31,6 +35,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||
private var navigationStackCoordinator: NavigationStackCoordinator {
|
||||
parameters.navigationStackCoordinator
|
||||
}
|
||||
|
||||
var callback: ((RoomScreenCoordinatorAction) -> Void)?
|
||||
|
||||
init(parameters: RoomScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
@ -111,8 +117,13 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
|||
roomProxy: parameters.roomProxy,
|
||||
mediaProvider: parameters.mediaProvider)
|
||||
let coordinator = RoomDetailsCoordinator(parameters: params)
|
||||
coordinator.callback = { [weak self] _ in
|
||||
self?.navigationStackCoordinator.pop()
|
||||
coordinator.callback = { [weak self] action in
|
||||
switch action {
|
||||
case .cancel:
|
||||
self?.navigationStackCoordinator.pop()
|
||||
case .leftRoom:
|
||||
self?.callback?(.leftRoom)
|
||||
}
|
||||
}
|
||||
|
||||
navigationStackCoordinator.push(coordinator)
|
||||
|
|
|
@ -50,9 +50,9 @@ class MockClientProxy: ClientProxyProtocol {
|
|||
|
||||
switch room {
|
||||
case .empty:
|
||||
return MockRoomProxy(displayName: "Empty room")
|
||||
return await RoomProxyMock(with: .init(displayName: "Empty room"))
|
||||
case .filled(let details), .invalidated(let details):
|
||||
return MockRoomProxy(displayName: details.name)
|
||||
return await RoomProxyMock(with: .init(displayName: details.name))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct MockRoomProxy: RoomProxyProtocol {
|
||||
var id = UUID().uuidString
|
||||
let name: String? = nil
|
||||
let displayName: String?
|
||||
var topic: String?
|
||||
var avatarURL: URL?
|
||||
var isDirect = Bool.random()
|
||||
var isSpace = Bool.random()
|
||||
var isPublic = Bool.random()
|
||||
var isEncrypted = Bool.random()
|
||||
var isTombstoned = Bool.random()
|
||||
var canonicalAlias: String?
|
||||
var alternativeAliases: [String] = []
|
||||
var hasUnreadNotifications = Bool.random()
|
||||
var members: [RoomMemberProxy]?
|
||||
|
||||
let timelineProvider: RoomTimelineProviderProtocol = MockRoomTimelineProvider()
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError> {
|
||||
.failure(.failedRetrievingMemberDisplayName)
|
||||
}
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError> {
|
||||
.failure(.failedRetrievingMemberAvatarURL)
|
||||
}
|
||||
|
||||
func addTimelineListener(listener: TimelineListener) -> Result<[TimelineItem], RoomProxyError> {
|
||||
.failure(.failedAddingTimelineListener)
|
||||
}
|
||||
|
||||
func removeTimelineListener() { }
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedPaginatingBackwards)
|
||||
}
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingReadReceipt)
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedSendingMessage)
|
||||
}
|
||||
|
||||
func redact(_ eventID: String) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedRedactingEvent)
|
||||
}
|
||||
|
||||
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError> {
|
||||
.failure(.failedReportingContent)
|
||||
}
|
||||
|
||||
func members() async -> Result<[RoomMemberProxy], RoomProxyError> {
|
||||
if let members {
|
||||
return .success(members)
|
||||
}
|
||||
return .failure(.failedRetrievingMembers)
|
||||
}
|
||||
|
||||
func retryDecryption(for sessionID: String) async { }
|
||||
}
|
|
@ -287,6 +287,23 @@ class RoomProxy: RoomProxyProtocol {
|
|||
self?.room.retryDecryption(sessionIds: [sessionID])
|
||||
}
|
||||
}
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
return await Task.dispatch(on: .global()) {
|
||||
do {
|
||||
try self.room.leave()
|
||||
return .success(())
|
||||
} catch {
|
||||
MXLog.error("Failed to leave the room: \(error)")
|
||||
return .failure(.failedLeavingRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
|
|
|
@ -31,9 +31,11 @@ enum RoomProxyError: Error {
|
|||
case failedReportingContent
|
||||
case failedAddingTimelineListener
|
||||
case failedRetrievingMembers
|
||||
case failedLeavingRoom
|
||||
}
|
||||
|
||||
@MainActor
|
||||
// sourcery: AutoMockable
|
||||
protocol RoomProxyProtocol {
|
||||
var id: String { get }
|
||||
var isDirect: Bool { get }
|
||||
|
@ -51,9 +53,7 @@ protocol RoomProxyProtocol {
|
|||
var topic: String? { get }
|
||||
|
||||
var avatarURL: URL? { get }
|
||||
|
||||
var permalink: URL? { get }
|
||||
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||
|
@ -79,6 +79,8 @@ protocol RoomProxyProtocol {
|
|||
func members() async -> Result<[RoomMemberProxy], RoomProxyError>
|
||||
|
||||
func retryDecryption(for sessionID: String) async
|
||||
|
||||
func leaveRoom() async -> Result<Void, RoomProxyError>
|
||||
}
|
||||
|
||||
extension RoomProxyProtocol {
|
||||
|
|
|
@ -173,6 +173,12 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||
mediaProvider: userSession.mediaProvider,
|
||||
emojiProvider: emojiProvider)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
coordinator.callback = { [weak self] action in
|
||||
switch action {
|
||||
case .leftRoom:
|
||||
self?.dismissRoom()
|
||||
}
|
||||
}
|
||||
|
||||
detailNavigationStackCoordinator.setRootCoordinator(coordinator) { [weak self, roomIdentifier] in
|
||||
guard let self else { return }
|
||||
|
@ -191,6 +197,11 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissRoom() {
|
||||
detailNavigationStackCoordinator.popToRoot(animated: true)
|
||||
navigationSplitCoordinator.setDetailCoordinator(nil)
|
||||
}
|
||||
|
||||
// MARK: Settings
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ class MockScreen: Identifiable {
|
|||
case .roomPlainNoAvatar:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Some room name", avatarURL: nil),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: nil)),
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -153,7 +153,7 @@ class MockScreen: Identifiable {
|
|||
case .roomEncryptedWithAvatar:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Some room name", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Some room name", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -165,7 +165,7 @@ class MockScreen: Identifiable {
|
|||
let timelineController = MockRoomTimelineController()
|
||||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "New room", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "New room", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -180,7 +180,7 @@ class MockScreen: Identifiable {
|
|||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk]
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Small timeline", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -195,7 +195,7 @@ class MockScreen: Identifiable {
|
|||
timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Small timeline, paginating", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Small timeline, paginating", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -210,7 +210,7 @@ class MockScreen: Identifiable {
|
|||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -226,7 +226,7 @@ class MockScreen: Identifiable {
|
|||
timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk]
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -241,7 +241,7 @@ class MockScreen: Identifiable {
|
|||
timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk
|
||||
timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage]
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory),
|
||||
roomProxy: RoomProxyMock(with: .init(displayName: "Large timeline", avatarURL: URL.picturesDirectory)),
|
||||
timelineController: timelineController,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
emojiProvider: EmojiProvider())
|
||||
|
@ -270,10 +270,10 @@ class MockScreen: Identifiable {
|
|||
return navigationSplitCoordinator
|
||||
case .roomDetailsScreen:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let roomProxy = MockRoomProxy(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
isEncrypted: true,
|
||||
members: [.mockAlice, .mockBob, .mockCharlie])
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
isEncrypted: true,
|
||||
members: [.mockAlice, .mockBob, .mockCharlie]))
|
||||
let coordinator = RoomDetailsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider()))
|
||||
|
@ -281,13 +281,13 @@ class MockScreen: Identifiable {
|
|||
return navigationStackCoordinator
|
||||
case .roomDetailsScreenWithRoomAvatar:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let roomProxy = MockRoomProxy(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
topic: "Bacon ipsum dolor amet commodo incididunt ribeye dolore cupidatat short ribs.",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#mock:room.org",
|
||||
members: [.mockAlice, .mockBob, .mockCharlie])
|
||||
let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier",
|
||||
displayName: "Room",
|
||||
topic: "Bacon ipsum dolor amet commodo incididunt ribeye dolore cupidatat short ribs.",
|
||||
avatarURL: URL.picturesDirectory,
|
||||
isEncrypted: true,
|
||||
canonicalAlias: "#mock:room.org",
|
||||
members: [.mockAlice, .mockBob, .mockCharlie]))
|
||||
let coordinator = RoomDetailsCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
|
||||
roomProxy: roomProxy,
|
||||
mediaProvider: MockMediaProvider()))
|
||||
|
@ -301,7 +301,7 @@ class MockScreen: Identifiable {
|
|||
return navigationStackCoordinator
|
||||
case .reportContent:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = ReportContentCoordinator(parameters: .init(itemID: "test", roomProxy: MockRoomProxy(displayName: "test")))
|
||||
let coordinator = ReportContentCoordinator(parameters: .init(itemID: "test", roomProxy: RoomProxyMock(with: .init(displayName: "test"))))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .startChat:
|
||||
|
|
|
@ -6,4 +6,4 @@ output:
|
|||
../../ElementX/Sources/Mocks/Generated/GeneratedMocks.swift
|
||||
args:
|
||||
automMockableTestableImports: []
|
||||
autoMockableImports: [Combine, MatrixRustSDK]
|
||||
autoMockableImports: [Combine, Foundation, MatrixRustSDK]
|
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.roomDetailsScreen.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.roomDetailsScreen.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPad-9th-generation.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.roomDetailsScreen.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.roomDetailsScreen.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/de-DE-iPhone-14.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreen.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreen.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreen.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreen.png (Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.roomDetailsScreenWithRoomAvatar.png (Stored with Git LFS)
Binary file not shown.
|
@ -255,6 +255,32 @@ class NavigationSplitCoordinatorTests: XCTestCase {
|
|||
}
|
||||
waitForExpectations(timeout: 1.0)
|
||||
}
|
||||
|
||||
func testSetRootDetailToNilAfterPoppingToRoot() {
|
||||
navigationSplitCoordinator = NavigationSplitCoordinator(placeholderCoordinator: SomeTestCoordinator())
|
||||
let sidebarCoordinator = NavigationStackCoordinator()
|
||||
sidebarCoordinator.setRootCoordinator(SomeTestCoordinator())
|
||||
|
||||
let detailCoordinator = NavigationStackCoordinator()
|
||||