diff --git a/Riot/Modules/Room/RoomCoordinator.swift b/Riot/Modules/Room/RoomCoordinator.swift index 15b359bdb..db0e087a6 100644 --- a/Riot/Modules/Room/RoomCoordinator.swift +++ b/Riot/Modules/Room/RoomCoordinator.swift @@ -349,8 +349,8 @@ extension RoomCoordinator: RoomViewControllerDelegate { self.delegate?.roomCoordinator(self, didSelectRoomWithId: roomID, eventId: eventID) } - func roomViewController(_ roomViewController: RoomViewController, moveToRoomWithId roomID: String) { - self.delegate?.roomCoordinator(self, moveToRoomWithId: roomID) + func roomViewController(_ roomViewController: RoomViewController, didReplaceRoomWithReplacementId roomID: String) { + self.delegate?.roomCoordinator(self, didReplaceRoomWithReplacementId: roomID) } func roomViewController(_ roomViewController: RoomViewController, showMemberDetails roomMember: MXRoomMember) { diff --git a/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift index 0410f63d6..14a206214 100644 --- a/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomCoordinatorBridgePresenter.swift @@ -21,7 +21,7 @@ import Foundation func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didSelectRoomWithId roomId: String, eventId: String?) - func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, moveToRoomWithId roomId: String) + func roomCoordinatorBridgePresenter(_ bridgePresenter: RoomCoordinatorBridgePresenter, didReplaceRoomWithReplacementId roomId: String) func roomCoordinatorBridgePresenterDidDismissInteractively(_ bridgePresenter: RoomCoordinatorBridgePresenter) } @@ -180,8 +180,8 @@ extension RoomCoordinatorBridgePresenter: RoomCoordinatorDelegate { self.delegate?.roomCoordinatorBridgePresenter(self, didSelectRoomWithId: roomId, eventId: eventId) } - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, moveToRoomWithId roomId: String) { - self.delegate?.roomCoordinatorBridgePresenter(self, moveToRoomWithId: roomId) + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) { + self.delegate?.roomCoordinatorBridgePresenter(self, didReplaceRoomWithReplacementId: roomId) } func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) { diff --git a/Riot/Modules/Room/RoomCoordinatorProtocol.swift b/Riot/Modules/Room/RoomCoordinatorProtocol.swift index cbf4ef3c5..6b0d96813 100644 --- a/Riot/Modules/Room/RoomCoordinatorProtocol.swift +++ b/Riot/Modules/Room/RoomCoordinatorProtocol.swift @@ -22,7 +22,7 @@ protocol RoomCoordinatorDelegate: AnyObject { func roomCoordinatorDidLeaveRoom(_ coordinator: RoomCoordinatorProtocol) func roomCoordinatorDidCancelRoomPreview(_ coordinator: RoomCoordinatorProtocol) func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didSelectRoomWithId roomId: String, eventId: String?) - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, moveToRoomWithId roomId: String) + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) func roomCoordinatorDidDismissInteractively(_ coordinator: RoomCoordinatorProtocol) } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift index 90456e5a5..47a8c356a 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinator.swift @@ -226,7 +226,7 @@ extension RoomInfoCoordinator: RoomNotificationSettingsCoordinatorDelegate { } extension RoomInfoCoordinator: RoomSettingsViewControllerDelegate { - func roomSettingsViewController(_ controller: RoomSettingsViewController!, didMoveRoomTo newRoomId: String!) { - self.delegate?.roomInfoCoordinator(self, didMoveToRoomWithId: newRoomId) + func roomSettingsViewController(_ controller: RoomSettingsViewController!, didReplaceRoomWithReplacementId newRoomId: String!) { + self.delegate?.roomInfoCoordinator(self, didReplaceRoomWithReplacementId: newRoomId) } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift index 108e1d695..b8db2a66a 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorBridgePresenter.swift @@ -22,7 +22,7 @@ import Foundation func roomInfoCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter) func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didRequestMentionForMember member: MXRoomMember) func roomInfoCoordinatorBridgePresenterDelegateDidLeaveRoom(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter) - func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didMoveToRoomWithId roomId: String) + func roomInfoCoordinatorBridgePresenter(_ coordinatorBridgePresenter: RoomInfoCoordinatorBridgePresenter, didReplaceRoomWithReplacementId roomId: String) } /// RoomInfoCoordinatorBridgePresenter enables to start RoomInfoCoordinator from a view controller. @@ -126,8 +126,8 @@ extension RoomInfoCoordinatorBridgePresenter: RoomInfoCoordinatorDelegate { self.delegate?.roomInfoCoordinatorBridgePresenterDelegateDidLeaveRoom(self) } - func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didMoveToRoomWithId roomId: String) { - self.delegate?.roomInfoCoordinatorBridgePresenter(self, didMoveToRoomWithId: roomId) + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String) { + self.delegate?.roomInfoCoordinatorBridgePresenter(self, didReplaceRoomWithReplacementId: roomId) } } diff --git a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift index 3793272e0..80f696b0b 100644 --- a/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift +++ b/Riot/Modules/Room/RoomInfo/RoomInfoCoordinatorType.swift @@ -22,7 +22,7 @@ protocol RoomInfoCoordinatorDelegate: AnyObject { func roomInfoCoordinatorDidComplete(_ coordinator: RoomInfoCoordinatorType) func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didRequestMentionForMember member: MXRoomMember) func roomInfoCoordinatorDidLeaveRoom(_ coordinator: RoomInfoCoordinatorType) - func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didMoveToRoomWithId roomId: String) + func roomInfoCoordinator(_ coordinator: RoomInfoCoordinatorType, didReplaceRoomWithReplacementId roomId: String) } /// `RoomInfoCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. diff --git a/Riot/Modules/Room/RoomViewController.h b/Riot/Modules/Room/RoomViewController.h index 0aca0ca70..73f2ae6a5 100644 --- a/Riot/Modules/Room/RoomViewController.h +++ b/Riot/Modules/Room/RoomViewController.h @@ -162,13 +162,13 @@ extern NSNotificationName const RoomGroupCallTileTappedNotification; eventId:(nullable NSString *)eventID; /** - Tells the delegate that the room has been moved to a new room. + Tells the delegate that the room has replaced by a room with a specific replacement room ID. @param roomViewController the `RoomViewController` instance. @param roomID the replacement roomId */ - (void)roomViewController:(RoomViewController *)roomViewController - moveToRoomWithId:(NSString *)roomID; +didReplaceRoomWithReplacementId:(NSString *)roomID; /** Tells the delegate that the user wants to start a direct chat with a user. diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index c2822c603..186b07606 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -7050,11 +7050,11 @@ const NSTimeInterval kResizeComposerAnimationDuration = .05; } } -- (void)roomInfoCoordinatorBridgePresenter:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter didMoveToRoomWithId:(NSString *)roomId +- (void)roomInfoCoordinatorBridgePresenter:(RoomInfoCoordinatorBridgePresenter *)coordinatorBridgePresenter didReplaceRoomWithReplacementId:(NSString *)roomId { if (self.delegate) { - [self.delegate roomViewController:self moveToRoomWithId:roomId]; + [self.delegate roomViewController:self didReplaceRoomWithReplacementId:roomId]; } else { diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.h b/Riot/Modules/Room/Settings/RoomSettingsViewController.h index b1a73f271..02a4d4214 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.h +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.h @@ -69,6 +69,6 @@ typedef enum : NSUInteger { @protocol RoomSettingsViewControllerDelegate -- (void)roomSettingsViewController:(RoomSettingsViewController *)controller didMoveRoomTo:(NSString *)newRoomId; +- (void)roomSettingsViewController:(RoomSettingsViewController *)controller didReplaceRoomWithReplacementId:(NSString *)newRoomId; @end diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index 9be5547bf..cec6fca83 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -4209,7 +4209,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { if (![roomId isEqualToString: self.roomId]) { // Room Access Coordinator upgraded the actual room -> Need to move to replacement room - [self.delegate roomSettingsViewController:self didMoveRoomTo:roomId]; + [self.delegate roomSettingsViewController:self didReplaceRoomWithReplacementId:roomId]; } MXWeakify(self); @@ -4223,7 +4223,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti { if (![roomId isEqualToString: self.roomId]) { // Room Access Coordinator upgraded the actual room -> Need to move to replacement room - [self.delegate roomSettingsViewController:self didMoveRoomTo:roomId]; + [self.delegate roomSettingsViewController:self didReplaceRoomWithReplacementId:roomId]; } MXWeakify(self); @@ -4243,7 +4243,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti - (void)roomSuggestionCoordinatorBridgePresenterDelegateDidComplete:(RoomSuggestionCoordinatorBridgePresenter *)coordinatorBridgePresenter { + MXWeakify(self); [roomSuggestionPresenter dismissWithAnimated:YES completion:^{ + MXStrongifyAndReturnIfNil(self); self->roomSuggestionPresenter = nil; [self refreshRoomSettings]; }]; diff --git a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift index f855909c6..92dbbe1d2 100644 --- a/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift +++ b/Riot/Modules/Spaces/SpaceRoomList/ExploreRoomCoordinator.swift @@ -296,7 +296,7 @@ extension ExploreRoomCoordinator: RoomViewControllerDelegate { self.navigateTo(roomWith: roomID) } - func roomViewController(_ roomViewController: RoomViewController, moveToRoomWithId roomID: String) { + func roomViewController(_ roomViewController: RoomViewController, didReplaceRoomWithReplacementId roomID: String) { self.currentExploreRoomCoordinator?.reloadRooms() self.popToLastSpaceScreen(animated: false) self.navigateTo(roomWith: roomID, showSettingsInitially: true, animated: false) diff --git a/Riot/Modules/TabBar/TabBarCoordinator.swift b/Riot/Modules/TabBar/TabBarCoordinator.swift index 167c34be1..0bbd00800 100644 --- a/Riot/Modules/TabBar/TabBarCoordinator.swift +++ b/Riot/Modules/TabBar/TabBarCoordinator.swift @@ -743,7 +743,7 @@ extension TabBarCoordinator: RoomCoordinatorDelegate { self.showRoom(withId: roomId, eventId: eventId) } - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, moveToRoomWithId roomId: String) { + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) { guard let matrixSession = self.parameters.userSessionsService.mainUserSession?.matrixSession else { return } diff --git a/Riot/Modules/Threads/ThreadsCoordinator.swift b/Riot/Modules/Threads/ThreadsCoordinator.swift index b4c8e9c1c..2f49786c5 100644 --- a/Riot/Modules/Threads/ThreadsCoordinator.swift +++ b/Riot/Modules/Threads/ThreadsCoordinator.swift @@ -191,7 +191,7 @@ extension ThreadsCoordinator: RoomCoordinatorDelegate { } - func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, moveToRoomWithId roomId: String) { + func roomCoordinator(_ coordinator: RoomCoordinatorProtocol, didReplaceRoomWithReplacementId roomId: String) { } diff --git a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift index fd6359f17..b6051b39b 100644 --- a/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift +++ b/RiotSwiftUI/Modules/Common/Util/SecondaryActionButtonStyle.swift @@ -48,44 +48,31 @@ struct SecondaryActionButtonStyle: ButtonStyle { struct SecondaryActionButtonStyle_Previews: PreviewProvider { static var previews: some View { Group { - VStack { - Button("Enabled") { } - .buttonStyle(SecondaryActionButtonStyle()) - - Button("Disabled") { } - .buttonStyle(SecondaryActionButtonStyle()) - .disabled(true) - - Button { } label: { - Text("Clear BG") - .foregroundColor(.red) - } - .buttonStyle(SecondaryActionButtonStyle(customColor: .clear)) - - Button("Red BG") { } - .buttonStyle(SecondaryActionButtonStyle(customColor: .red)) - } - .padding() + buttonGroup }.theme(.light).preferredColorScheme(.light) Group { - VStack { - Button("Enabled") { } - .buttonStyle(SecondaryActionButtonStyle()) - - Button("Disabled") { } - .buttonStyle(SecondaryActionButtonStyle()) - .disabled(true) - - Button { } label: { - Text("Clear BG") - .foregroundColor(.red) - } - .buttonStyle(SecondaryActionButtonStyle(customColor: .clear)) - - Button("Red BG") { } - .buttonStyle(SecondaryActionButtonStyle(customColor: .red)) - } - .padding() + buttonGroup }.theme(.dark).preferredColorScheme(.dark) } + + static var buttonGroup: some View { + VStack { + Button("Enabled") { } + .buttonStyle(SecondaryActionButtonStyle()) + + Button("Disabled") { } + .buttonStyle(SecondaryActionButtonStyle()) + .disabled(true) + + Button { } label: { + Text("Clear BG") + .foregroundColor(.red) + } + .buttonStyle(SecondaryActionButtonStyle(customColor: .clear)) + + Button("Red BG") { } + .buttonStyle(SecondaryActionButtonStyle(customColor: .red)) + } + .padding() + } } diff --git a/RiotSwiftUI/Modules/Common/Util/View+Riot.swift b/RiotSwiftUI/Modules/Common/Util/View+Riot.swift index ad56d0233..d126ec8d0 100644 --- a/RiotSwiftUI/Modules/Common/Util/View+Riot.swift +++ b/RiotSwiftUI/Modules/Common/Util/View+Riot.swift @@ -26,74 +26,3 @@ extension View { } } } - -// MARK: - modal presentation mode - -fileprivate class PresentationControllerDelegate: NSObject, UIAdaptivePresentationControllerDelegate { - private let callback: (() -> Void)? - - init(callback: @escaping () -> Void) { - self.callback = callback - } - - func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { - callback?() - } -} -//fileprivate var currentPresentationControllerDelegate: PresentationControllerDelegate? = nil -fileprivate var presentationControllerDelegates: [String: PresentationControllerDelegate] = [:] - -@available(iOS 13.0, *) -//fileprivate var currentOverCurrentContextUIHost: UIHostingController? = nil -@available(iOS 13.0, *) -fileprivate var uiHosts: [String: UIHostingController] = [:] - -@available(iOS 13.0, *) -extension View { - - func modal( - withStyle modalPresentationStyle: UIModalPresentationStyle, - animated: Bool = true, - modalTransitionStyle: UIModalTransitionStyle? = nil, - id: String, - isPresented: Binding, - content: () -> Content - ) -> some View where Content: View { - if isPresented.wrappedValue && uiHosts[id] == nil { - let uiHost = UIHostingController(rootView: AnyView(content())) - uiHosts[id] = uiHost - - uiHost.modalPresentationStyle = modalPresentationStyle - if let modalTransitionStyle = modalTransitionStyle { - uiHost.modalTransitionStyle = modalTransitionStyle - } - uiHost.view.backgroundColor = UIColor.clear - presentationControllerDelegates[id] = PresentationControllerDelegate(callback: { - isPresented.wrappedValue = false - }) - uiHost.presentationController?.delegate = presentationControllerDelegates[id] - - if let rootVC = UIApplication.shared.windows.first?.rootViewController { - frontmostPresentedViewController(from: rootVC)?.present(uiHost, animated: animated, completion: nil) - } - } else { - if let uiHost = uiHosts[id] { - uiHost.dismiss(animated: animated, completion: {}) - uiHosts.removeValue(forKey: id) - presentationControllerDelegates.removeValue(forKey: id) - } - } - - return self - } - - fileprivate func frontmostPresentedViewController(from viewController: UIViewController) -> UIViewController? { - var frontMostController = viewController - while let presentedViewController = frontMostController.presentedViewController { - frontMostController = presentedViewController - } - - return frontMostController - } - -} diff --git a/RiotSwiftUI/Modules/Common/Util/WaitModalView.swift b/RiotSwiftUI/Modules/Common/Util/WaitModalView.swift deleted file mode 100644 index 1c177ab50..000000000 --- a/RiotSwiftUI/Modules/Common/Util/WaitModalView.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright 2021 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 SwiftUI - -@available(iOS 14.0, *) -struct WaitModalContentView: View { - - // MARK: - Properties - - var alignment: Alignment = .center - @State var message: String? = nil - - // MARK: Private - - @Environment(\.theme) private var theme: ThemeSwiftUI - - // MARK: - Public - - var body: some View { - ZStack { - Color.black.opacity(0.6) - VStack(spacing: 12) { - ProgressView() - .scaleEffect(1.3, anchor: .center) - .progressViewStyle(CircularProgressViewStyle(tint: theme.colors.secondaryContent)) - if let message = message { - Text(message) - .font(theme.fonts.callout) - .foregroundColor(theme.colors.secondaryContent) - } - } - .padding(12) - .background(RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(theme.colors.navigation.opacity(0.9))) - } - .edgesIgnoringSafeArea(.all) - } -} - -@available(iOS 14.0, *) -struct WaitModalView: ViewModifier { - // MARK: - Properties - - var alignment: Alignment = .center - @State var message: String? = nil - @Binding var isLoading: Bool - - // MARK: - Private - - @Environment(\.theme) private var theme: ThemeSwiftUI - - // MARK: - Public - - public func body(content: Content) -> some View - { - content - .modal(withStyle: .overFullScreen, - modalTransitionStyle: .crossDissolve, - id: "WaitModalView", - isPresented: $isLoading) { - WaitModalContentView(alignment: alignment, message: message) - } - } - - private var contentView: some View { - ZStack { - theme.colors.primaryContent.opacity(0.75) - VStack(spacing: 12) { - ProgressView() - .scaleEffect(1.3, anchor: .center) - .progressViewStyle(CircularProgressViewStyle(tint: theme.colors.secondaryContent)) - if let message = message { - Text(message) - .font(theme.fonts.callout) - .foregroundColor(theme.colors.secondaryContent) - } - } - .padding(12) - .background(RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(theme.colors.navigation.opacity(0.9))) - } - .edgesIgnoringSafeArea(.all) - } -} - -@available(iOS 14.0, *) -struct WaitModalView_Previews: PreviewProvider { - static var previews: some View { - Group { - VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) - } - .modifier(WaitModalView(isLoading: .constant(true))) - VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) - } - .modifier(WaitModalView(alignment:.topLeading, isLoading: .constant(true))) - VStack { - ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) - ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) - } - - .modifier(WaitModalView(isLoading: .constant(true))).theme(.dark) - .preferredColorScheme(.dark) - } - .padding() - } -} diff --git a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift index e6ce92145..b5cd70d1f 100644 --- a/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift +++ b/RiotSwiftUI/Modules/Common/Util/WaitOverlay.swift @@ -16,6 +16,32 @@ import SwiftUI +@available(iOS 14.0, *) +/// A modifier for showing the wait overlay view over a view. +struct WaitOverlayModifier: ViewModifier { + + var allowUserInteraction: Bool + var show: Bool + var message: String? + + @ViewBuilder + func body(content: Content) -> some View { + content + .modifier(WaitOverlay( + allowUserInteraction: allowUserInteraction, + message: message, + isLoading: show)) + } +} + +@available(iOS 14.0, *) +extension View { + @available(iOS 14.0, *) + func waitOverlay(show: Bool, message: String? = nil, allowUserInteraction: Bool = true) -> some View { + self.modifier(WaitOverlayModifier(allowUserInteraction: allowUserInteraction, show: show, message: message)) + } +} + /// `WaitOverlay` allows to easily add an overlay that covers the entire with an `ActivityIndicator` at the center @available(iOS 14.0, *) struct WaitOverlay: ViewModifier { @@ -23,9 +49,9 @@ struct WaitOverlay: ViewModifier { var alignment: Alignment = .center var allowUserInteraction: Bool = true - @Binding var message: String? - @Binding var isLoading: Bool - + var message: String? + var isLoading: Bool + // MARK: - Private @Environment(\.theme) private var theme: ThemeSwiftUI @@ -34,14 +60,14 @@ struct WaitOverlay: ViewModifier { init(alignment: Alignment = .center, allowUserInteraction: Bool = true, - message: Binding = .constant(nil), - isLoading: Binding) { - _message = message - _isLoading = isLoading + message: String? = nil, + isLoading: Bool) { + self.message = message + self.isLoading = isLoading self.alignment = alignment self.allowUserInteraction = allowUserInteraction } - + // MARK: - Public public func body(content: Content) -> some View @@ -78,7 +104,7 @@ struct WaitOverlay: ViewModifier { } @available(iOS 14.0, *) -struct WaitView_Previews: PreviewProvider { +struct WaitOverlay_Previews: PreviewProvider { static var previews: some View { Group { VStack { @@ -87,14 +113,14 @@ struct WaitView_Previews: PreviewProvider { ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) } - .modifier(WaitOverlay(isLoading: .constant(true))) + .modifier(WaitOverlay(isLoading: true)) VStack { ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}) ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}) ThemableNavigationBar(title: nil, showBackButton: false, backAction: {}, closeAction: {}) ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}) } - .modifier(WaitOverlay(alignment:.topLeading, isLoading: .constant(true))) + .modifier(WaitOverlay(alignment:.topLeading, isLoading: true)) VStack { ThemableNavigationBar(title: nil, showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) ThemableNavigationBar(title: "Some Title", showBackButton: true, backAction: {}, closeAction: {}).theme(.dark) @@ -102,7 +128,7 @@ struct WaitView_Previews: PreviewProvider { ThemableNavigationBar(title: "Some Title", showBackButton: false, backAction: {}, closeAction: {}).theme(.dark) } - .modifier(WaitOverlay(isLoading: .constant(true))).theme(.dark) + .modifier(WaitOverlay(isLoading: true)).theme(.dark) .preferredColorScheme(.dark) } .padding() diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift index 872c078b5..888e4c277 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinator.swift @@ -25,6 +25,7 @@ enum RoomAccessCoordinatorCoordinatorAction { } @objcMembers +@available(iOS 14.0, *) final class RoomAccessCoordinator: Coordinator { // MARK: - Properties @@ -52,6 +53,8 @@ final class RoomAccessCoordinator: Coordinator { return parameters.room.roomId } + private weak var accessCoordinator: RoomAccessTypeChooserCoordinator? + // MARK: - Setup init(parameters: RoomAccessCoordinatorParameters) { @@ -62,21 +65,20 @@ final class RoomAccessCoordinator: Coordinator { func start() { - if #available(iOS 14.0, *) { - MXLog.debug("[RoomAccessCoordinator] did start.") - let rootCoordinator = self.createRoomAccessTypeCoordinator() - rootCoordinator.start() - - self.add(childCoordinator: rootCoordinator) - - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in - self?.remove(childCoordinator: rootCoordinator) - }) - } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in - self?.remove(childCoordinator: rootCoordinator) - } + MXLog.debug("[RoomAccessCoordinator] did start.") + let rootCoordinator = self.createRoomAccessTypeCoordinator() + rootCoordinator.start() + + self.add(childCoordinator: rootCoordinator) + self.accessCoordinator = rootCoordinator + + if self.navigationRouter.modules.isEmpty == false { + self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + }) + } else { + self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + self?.remove(childCoordinator: rootCoordinator) } } } @@ -87,7 +89,6 @@ final class RoomAccessCoordinator: Coordinator { // MARK: - Private - @available(iOS 14.0, *) func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) @@ -98,7 +99,16 @@ final class RoomAccessCoordinator: Coordinator { coordinator.start() } - @available(iOS 14.0, *) + func popupScreen(with coordinator: Coordinator & Presentable) { + add(childCoordinator: coordinator) + + coordinator.toPresentable().modalPresentationStyle = .overFullScreen + coordinator.toPresentable().modalTransitionStyle = .crossDissolve + self.navigationRouter.present(coordinator, animated: true) + + coordinator.start() + } + private func createRoomAccessTypeCoordinator() -> RoomAccessTypeChooserCoordinator { let coordinator: RoomAccessTypeChooserCoordinator = RoomAccessTypeChooserCoordinator(parameters: RoomAccessTypeChooserCoordinatorParameters(roomId: parameters.room.roomId, session: parameters.room.mxSession)) coordinator.callback = { [weak self] result in @@ -112,12 +122,13 @@ final class RoomAccessCoordinator: Coordinator { case .spaceSelection(let roomId, _): self.upgradedRoomId = roomId self.pushScreen(with: self.createRestrictedAccessSpaceChooserCoordinator(with: roomId)) + case .roomUpgradeNeeded(let roomId, let versionOverride): + self.popupScreen(with: self.createUpgradeRoomCoordinator(withRoomWithId: roomId, to: versionOverride)) } } return coordinator } - @available(iOS 14.0, *) private func createRestrictedAccessSpaceChooserCoordinator(with roomId: String) -> MatrixItemChooserCoordinator { let paramaters = MatrixItemChooserCoordinatorParameters( session: parameters.room.mxSession, @@ -139,4 +150,23 @@ final class RoomAccessCoordinator: Coordinator { return coordinator } + private func createUpgradeRoomCoordinator(withRoomWithId roomId: String, to versionOverride: String) -> RoomUpgradeCoordinator { + let paramaters = RoomUpgradeCoordinatorParameters( + session: parameters.room.mxSession, + roomId: roomId, + versionOverride: versionOverride) + let coordinator = RoomUpgradeCoordinator(parameters: paramaters) + + coordinator.completion = { [weak self] result in + guard let self = self else { return } + + self.accessCoordinator?.handleRoomUpgradeResult(result) + + self.navigationRouter.dismissModule(animated: true) { + self.remove(childCoordinator: coordinator) + } + } + + return coordinator + } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift index 66ea6c7f0..36d48a88d 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/Coordinator/RoomAccessCoordinatorBridgePresenter.swift @@ -15,6 +15,7 @@ // import UIKit +@available(iOS 14.0, *) @objc protocol RoomAccessCoordinatorBridgePresenterDelegate { func roomAccessCoordinatorBridgePresenterDelegate(_ coordinatorBridgePresenter: RoomAccessCoordinatorBridgePresenter, didCancelRoomWithId roomId: String) func roomAccessCoordinatorBridgePresenterDelegate(_ coordinatorBridgePresenter: RoomAccessCoordinatorBridgePresenter, didCompleteRoomWithId roomId: String) @@ -25,6 +26,7 @@ import UIKit /// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers). /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers +@available(iOS 14.0, *) final class RoomAccessCoordinatorBridgePresenter: NSObject { // MARK: - Properties @@ -85,6 +87,7 @@ final class RoomAccessCoordinatorBridgePresenter: NSObject { // MARK: - UIAdaptivePresentationControllerDelegate +@available(iOS 14.0, *) extension RoomAccessCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift index 45f204da5..fdf510878 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Coordinator/RoomAccessTypeChooserCoordinator.swift @@ -63,6 +63,8 @@ final class RoomAccessTypeChooserCoordinator: Coordinator, Presentable { self.callback?(.done(roomId)) case .cancel(let roomId): self.callback?(.cancel(roomId)) + case .roomUpgradeNeeded(let roomId, let versionOverride): + self.callback?(.roomUpgradeNeeded(roomId, versionOverride)) } } } @@ -70,4 +72,8 @@ final class RoomAccessTypeChooserCoordinator: Coordinator, Presentable { func toPresentable() -> UIViewController { return self.roomAccessTypeChooserHostingController } + + func handleRoomUpgradeResult(_ result: RoomUpgradeCoordinatorResult) { + self.roomAccessTypeChooserViewModel.handleRoomUpgradeResult(result) + } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift index d039d8a2a..2797adab9 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserModels.swift @@ -35,6 +35,7 @@ struct RoomAccessTypeChooserAccessItem: Identifiable, Equatable { /// Actions returned by the coordinator callback enum RoomAccessTypeChooserCoordinatorAction { case spaceSelection(String, RoomAccessTypeChooserAccessType) + case roomUpgradeNeeded(String, String) case done(String) case cancel(String) } @@ -51,6 +52,7 @@ enum RoomAccessTypeChooserStateAction { /// Actions sent by the`ViewModel` to the `Coordinator`. enum RoomAccessTypeChooserViewModelAction { case spaceSelection(String, RoomAccessTypeChooserAccessType) + case roomUpgradeNeeded(String, String) case done(String) case cancel(String) } @@ -74,6 +76,4 @@ enum RoomAccessTypeChooserViewAction { case cancel case done case didSelectAccessType(RoomAccessTypeChooserAccessType) - case didCancelRoomUpgrade - case didAcceptRoomUpgrade(Bool) } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift index cd6d4bfc6..d21f86623 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModel.swift @@ -54,10 +54,14 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc .map(RoomAccessTypeChooserStateAction.updateAccessItems) .eraseToAnyPublisher() dispatch(actionPublisher: accessTypePublisher) - let showUpgradeRoomAlertPublisher = roomAccessTypeChooserService.roomUpgradeRequiredSubject - .map(RoomAccessTypeChooserStateAction.updateShowUpgradeRoomAlert) - .eraseToAnyPublisher() - dispatch(actionPublisher: showUpgradeRoomAlertPublisher) + roomAccessTypeChooserService + .roomUpgradeRequiredSubject + .sink { [weak self] isUpgradeRequired in + if isUpgradeRequired { + self?.upgradeRoom() + } + } + .store(in: &cancellables) let waitingMessagePublisher = roomAccessTypeChooserService.waitingMessageSubject .map(RoomAccessTypeChooserStateAction.updateWaitingMessage) .eraseToAnyPublisher() @@ -74,14 +78,6 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc done() case .cancel: cancel() - case .didCancelRoomUpgrade: - roomAccessTypeChooserService.upgradeRoom(accepted: false, autoInviteUsers: false) { _, _ in } - case .didAcceptRoomUpgrade(let autoInviteUsers): - roomAccessTypeChooserService.upgradeRoom(accepted: true, autoInviteUsers: autoInviteUsers) { [weak self] upgradeFinished, roomId in - if upgradeFinished { - self?.callback?(.spaceSelection(roomId, .restricted)) - } - } } } @@ -97,8 +93,27 @@ class RoomAccessTypeChooserViewModel: RoomAccessTypeChooserViewModelType, RoomAc } } + func handleRoomUpgradeResult(_ result: RoomUpgradeCoordinatorResult) { + switch result { + case .cancel(let roomId): + roomAccessTypeChooserService.updateRoomId(with: roomId) + case .done(let roomId): + roomAccessTypeChooserService.updateRoomId(with: roomId) + callback?(.spaceSelection(roomId, .restricted)) + } + } + // MARK: - Private + private func upgradeRoom() { + guard let versionOverride = roomAccessTypeChooserService.versionOverride else { + MXLog.error("[RoomAccessTypeChooserViewModel] upgradeRoom: versionOverride not found") + return + } + + callback?(.roomUpgradeNeeded(roomAccessTypeChooserService.currentRoomId, versionOverride)) + } + private func done() { roomAccessTypeChooserService.applySelection { [weak self] in guard let self = self else { return } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift index 151fbdfb8..4dd2d43ad 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/RoomAccessTypeChooserViewModelProtocol.swift @@ -20,4 +20,6 @@ protocol RoomAccessTypeChooserViewModelProtocol { var callback: ((RoomAccessTypeChooserViewModelAction) -> Void)? { get set } @available(iOS 14, *) var context: RoomAccessTypeChooserViewModelType.Context { get } + + func handleRoomUpgradeResult(_ result: RoomUpgradeCoordinatorResult) } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift index 869314c8c..68c5c5d28 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/MatrixSDK/RoomAccessTypeChooserService.swift @@ -50,7 +50,6 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } private var roomJoinRule: MXRoomJoinRule = .private private var currentOperation: MXHTTPOperation? - private let restrictedVersionOverride: String? // MARK: Public @@ -60,6 +59,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { private(set) var errorSubject: CurrentValueSubject private(set) var currentRoomId: String + private(set) var versionOverride: String? // MARK: - Setup @@ -67,7 +67,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { self.roomId = roomId self.session = session self.currentRoomId = roomId - restrictedVersionOverride = session.homeserverCapabilitiesService.versionOverrideForFeature(.restricted) + self.versionOverride = session.homeserverCapabilitiesService.versionOverrideForFeature(.restricted) roomUpgradeRequiredSubject = CurrentValueSubject(false) waitingMessageSubject = CurrentValueSubject(nil) @@ -85,6 +85,8 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } } + // MARK: - Public + func updateSelection(with selectedType: RoomAccessTypeChooserAccessType) { self.selectedType = selectedType @@ -97,7 +99,8 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { func applySelection(completion: @escaping () -> Void) { guard let room = session.room(withRoomId: currentRoomId) else { - fatalError("[RoomAccessTypeChooserService] applySelection: room with ID \(currentRoomId) not found") + MXLog.error("[RoomAccessTypeChooserService] applySelection: room with ID \(currentRoomId) not found") + return } let _joinRule: MXRoomJoinRule? @@ -117,6 +120,7 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } if let joinRule = _joinRule { + self.waitingMessageSubject.send(VectorL10n.roomAccessSettingsScreenSettingRoomAccess) room.setJoinRule(joinRule) { [weak self] response in guard let self = self else { return } @@ -132,38 +136,9 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } } - func upgradeRoom(accepted: Bool, autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { - roomUpgradeRequiredSubject.send(false) - - guard let restrictedVersionOverride = restrictedVersionOverride, accepted else { - setupDefaultSelectionType() - completion(false, currentRoomId) - return - } - - waitingMessageSubject.send(VectorL10n.roomAccessSettingsScreenUpgradeAlertUpgrading) - - if autoInviteUsers, let room = session.room(withRoomId: self.currentRoomId) { - self.currentOperation = room.members { [weak self] response in - guard let self = self else { return } - switch response { - case .success(let members): - let memberIds: [String] = members?.members.compactMap({ member in - guard member.membership == .join, member.userId != self.session.myUserId else { - return nil - } - - return member.userId - }) ?? [] - self.upgradeRoom(to: restrictedVersionOverride, inviteUsers: memberIds, completion: completion) - case .failure(let error): - self.waitingMessageSubject.send(nil) - self.errorSubject.send(error) - } - } - } else { - self.upgradeRoom(to: restrictedVersionOverride, inviteUsers: [], completion: completion) - } + func updateRoomId(with roomId: String) { + self.currentRoomId = roomId + readRoomState() } // MARK: - Private @@ -188,21 +163,22 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { private func readRoomState() { guard let room = session.room(withRoomId: currentRoomId) else { - fatalError("[RoomAccessTypeChooserService] readRoomState: room with ID \(currentRoomId) not found") + MXLog.error("[RoomAccessTypeChooserService] readRoomState: room with ID \(currentRoomId) not found") + return } room.state { [weak self] state in guard let self = self else { return } if let roomVersion = state?.stateEvents(with: .roomCreate)?.last?.wireContent["room_version"] as? String, let homeserverCapabilitiesService = self.session.homeserverCapabilitiesService { - self.roomUpgradeRequired = self.restrictedVersionOverride != nil && !homeserverCapabilitiesService.isFeatureSupported(.restricted, by: roomVersion) + self.roomUpgradeRequired = self.versionOverride != nil && !homeserverCapabilitiesService.isFeatureSupported(.restricted, by: roomVersion) } self.roomJoinRule = state?.joinRule ?? .private self.setupDefaultSelectionType() } } - + private func setupDefaultSelectionType() { switch roomJoinRule { case .restricted: @@ -212,82 +188,9 @@ class RoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { default: selectedType = .private } - } - - private func upgradeRoom(to restrictedVersionOverride: String, inviteUsers userIds: [String], completion: @escaping (Bool, String) -> Void) { - // Need to disable graph update during this process as a lot of syncs will occure - session.spaceService.graphUpdateEnabled = false - currentOperation = session.matrixRestClient.upgradeRoom(withId: self.currentRoomId, to: restrictedVersionOverride) { [weak self] response in - guard let self = self else { return } - - switch response { - case .success(let replacementRoomId): - let oldRoomId = self.currentRoomId - self.roomUpgradeRequired = false - self.currentRoomId = replacementRoomId - let parentSpaces = self.session.spaceService.directParentIds(ofRoomWithId: oldRoomId) - self.moveRoom(from: oldRoomId, to: replacementRoomId, within: Array(parentSpaces), at: 0) { - self.session.spaceService.graphUpdateEnabled = true - self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] notification in - guard let self = self else { return } - - if let observer = self.didBuildSpaceGraphObserver { - NotificationCenter.default.removeObserver(observer) - self.didBuildSpaceGraphObserver = nil - } - - DispatchQueue.main.async { - self.inviteUser(from: userIds, at: 0, completion: completion) - } - } - } - case .failure(let error): - self.session.spaceService.graphUpdateEnabled = true - self.waitingMessageSubject.send(nil) - self.errorSubject.send(error) - } - } - } - - private func moveRoom(from roomId: String, to newRoomId: String, within parentIds: [String], at index: Int, completion: @escaping () -> Void) { - guard index < parentIds.count else { - completion() - return - } - guard let space = session.spaceService.getSpace(withId: parentIds[index]) else { - MXLog.warning("[RoomAccessTypeChooserService] moveRoom \(roomId) to \(newRoomId) within \(parentIds[index]): space not found") - moveRoom(from: roomId, to: newRoomId, within: parentIds, at: index + 1, completion: completion) - return - } - - space.moveChild(withRoomId: roomId, to: newRoomId) { [weak self] response in - guard let self = self else { return } - - if let error = response.error { - MXLog.warning("[RoomAccessTypeChooserService] moveRoom \(roomId) to \(newRoomId) within \(space.spaceId): failed due to error: \(error)") - } - - self.moveRoom(from: roomId, to: newRoomId, within: parentIds, at: index + 1, completion: completion) - } - } - - private func inviteUser(from userIds: [String], at index: Int, completion: @escaping (Bool, String) -> Void) { - guard index < userIds.count else { - self.waitingMessageSubject.send(nil) - completion(true, currentRoomId) - return - } - - currentOperation = session.matrixRestClient.invite(.userId(userIds[index]), toRoom: currentRoomId) { [weak self] response in - guard let self = self else { return } - - self.currentOperation = nil - if let error = response.error { - MXLog.warning("[RoomAccessTypeChooserService] inviteUser: failed to invite \(userIds[index]) to \(self.currentRoomId) due to error: \(error)") - } - - self.inviteUser(from: userIds, at: index + 1, completion: completion) + if selectedType != .restricted { + roomUpgradeRequiredSubject.send(false) } } } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift index 104e2c8f5..5533c3a13 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/Mock/MockRoomAccessTypeChooserService.swift @@ -32,10 +32,11 @@ class MockRoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { private(set) var errorSubject: CurrentValueSubject private(set) var selectedType: RoomAccessTypeChooserAccessType = .private - var currentRoomId: String { - return "sldkfjsdljf:,atrix.org" + var currentRoomId: String = "!aaabaa:matrix.org" + var versionOverride: String? { + return "9" } - + init(accessItems: [RoomAccessTypeChooserAccessItem] = mockAccessItems) { accessItemsSubject = CurrentValueSubject(accessItems) roomUpgradeRequiredSubject = CurrentValueSubject(false) @@ -51,8 +52,8 @@ class MockRoomAccessTypeChooserService: RoomAccessTypeChooserServiceProtocol { } - func upgradeRoom(accepted: Bool, autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { - + func updateRoomId(with roomId: String) { + currentRoomId = roomId } func applySelection(completion: @escaping () -> Void) { diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift index f735c9e89..a246862e2 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/Service/RoomAccessTypeChooserServiceProtocol.swift @@ -26,8 +26,9 @@ protocol RoomAccessTypeChooserServiceProtocol { var selectedType: RoomAccessTypeChooserAccessType { get } var currentRoomId: String { get } + var versionOverride: String? { get } func updateSelection(with selectedType: RoomAccessTypeChooserAccessType) func applySelection(completion: @escaping () -> Void) - func upgradeRoom(accepted: Bool, autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) + func updateRoomId(with roomId: String) } diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift index 682ec42c9..8288e5280 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooser.swift @@ -32,16 +32,7 @@ struct RoomAccessTypeChooser: View { var body: some View { listContent - .modifier(WaitOverlay( - allowUserInteraction: false, - message: $viewModel.waitingMessage, - isLoading: $viewModel.isLoading)) - .modal(withStyle: .overFullScreen, - modalTransitionStyle: .crossDissolve, - id: "RoomAccessTypeChooser-RoomAccessTypeChooserUpgradeRoomAlert", - isPresented: $viewModel.showUpgradeRoomAlert) { - RoomAccessTypeChooserUpgradeRoomAlert(viewModel: viewModel) - } + .waitOverlay(show: viewModel.isLoading, message: viewModel.waitingMessage, allowUserInteraction: false) .navigationTitle(VectorL10n.roomAccessSettingsScreenNavTitle) .toolbar { ToolbarItem(placement: .cancellationAction) { @@ -57,6 +48,7 @@ struct RoomAccessTypeChooser: View { .disabled(viewModel.isLoading) } } + .accentColor(theme.colors.accent) } // MARK: Private diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift index e79b2a359..66b9b68e5 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserRow.swift @@ -44,15 +44,9 @@ struct RoomAccessTypeChooserRow: View { .font(theme.fonts.subheadline) } Spacer() - if isSelected { - Image(systemName: "checkmark.circle.fill") - .renderingMode(.template) - .foregroundColor(theme.colors.accent) - } else { - Image(systemName: "circle") - .renderingMode(.template) - .foregroundColor(theme.colors.quarterlyContent) - } + Image(systemName: isSelected ? "checkmark.circle.fill" : "circle") + .renderingMode(.template) + .foregroundColor(isSelected ? theme.colors.accent : theme.colors.quarterlyContent) } if let badgeText = badgeText { Text(badgeText) diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift index 3a94a2d67..15d334091 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/Service/MatrixSDK/RoomRestrictedAccessSpaceChooserItemsProcessor.swift @@ -40,10 +40,6 @@ class RoomRestrictedAccessSpaceChooserItemsProcessor: MatrixItemChooserProcessor } func computeSelection(withIds itemsIds: [String], completion: @escaping (Result) -> Void) { -// DispatchQueue.main.asyncAfter(deadline: .now() + 3) { -// completion(.success(())) -// } - session.matrixRestClient.setRoomJoinRule(.restricted, forRoomWithId: roomId, allowedParentIds: itemsIds) { response in switch response { case .failure(let error): diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift index 69418f9df..d6c00643d 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift +++ b/RiotSwiftUI/Modules/Room/RoomAccess/RoomRestrictedAccessSpaceChooser/View/RoomRestrictedAccessSpaceChooserSelector.swift @@ -33,18 +33,21 @@ struct RoomRestrictedAccessSpaceChooserSelector: View { MatrixItemChooser(viewModel: viewModel, listBottomPadding: nil) .background(theme.colors.background) .navigationTitle(VectorL10n.roomAccessSettingsScreenNavTitle) -// .navigationBarBackButtonHidden(true) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(VectorL10n.cancel) { viewModel.send(viewAction: .cancel) } + .foregroundColor(viewModel.viewState.loading ? theme.colors.quarterlyContent : theme.colors.accent) + .opacity(viewModel.viewState.loading ? 0.7 : 1) .disabled(viewModel.viewState.loading) } ToolbarItem(placement: .confirmationAction) { Button(VectorL10n.done) { viewModel.send(viewAction: .done) } + .foregroundColor(viewModel.viewState.selectedItemIds.isEmpty || viewModel.viewState.loading ? theme.colors.quarterlyContent : theme.colors.accent) + .opacity(viewModel.viewState.selectedItemIds.isEmpty || viewModel.viewState.loading ? 0.7 : 1) .disabled(viewModel.viewState.selectedItemIds.isEmpty || viewModel.viewState.loading) } } diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift index 707df6452..560f5a618 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinator.swift @@ -23,6 +23,7 @@ enum RoomSuggestionCoordinatorCoordinatorAction { } @objcMembers +@available(iOS 14.0, *) final class RoomSuggestionCoordinator: Coordinator { // MARK: - Properties @@ -52,21 +53,19 @@ final class RoomSuggestionCoordinator: Coordinator { func start() { - if #available(iOS 14.0, *) { - MXLog.debug("[RoomAccessCoordinator] did start.") - let rootCoordinator = self.createRoomSuggestionSpaceChooser() - rootCoordinator.start() - - self.add(childCoordinator: rootCoordinator) - - if self.navigationRouter.modules.isEmpty == false { - self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in - self?.remove(childCoordinator: rootCoordinator) - }) - } else { - self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in - self?.remove(childCoordinator: rootCoordinator) - } + MXLog.debug("[RoomSuggestionCoordinator] did start.") + let rootCoordinator = self.createRoomSuggestionSpaceChooser() + rootCoordinator.start() + + self.add(childCoordinator: rootCoordinator) + + if self.navigationRouter.modules.isEmpty == false { + self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in + self?.remove(childCoordinator: rootCoordinator) + }) + } else { + self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in + self?.remove(childCoordinator: rootCoordinator) } } } @@ -77,7 +76,6 @@ final class RoomSuggestionCoordinator: Coordinator { // MARK: - Private - @available(iOS 14.0, *) func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) @@ -88,7 +86,6 @@ final class RoomSuggestionCoordinator: Coordinator { coordinator.start() } - @available(iOS 14.0, *) private func createRoomSuggestionSpaceChooser() -> MatrixItemChooserCoordinator { let paramaters = MatrixItemChooserCoordinatorParameters( session: parameters.room.mxSession, diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift index a6bfa2ee5..f3b4132d3 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/Coordinator/RoomSuggestionCoordinatorBridgePresenter.swift @@ -15,6 +15,7 @@ // import UIKit +@available(iOS 14.0, *) @objc protocol RoomSuggestionCoordinatorBridgePresenterDelegate { func roomSuggestionCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: RoomSuggestionCoordinatorBridgePresenter) func roomSuggestionCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: RoomSuggestionCoordinatorBridgePresenter) @@ -25,6 +26,7 @@ import UIKit /// It breaks the Coordinator abstraction and it has been introduced for Objective-C compatibility (mainly for integration in legacy view controllers). /// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator. @objcMembers +@available(iOS 14.0, *) final class RoomSuggestionCoordinatorBridgePresenter: NSObject { // MARK: - Properties @@ -85,6 +87,7 @@ final class RoomSuggestionCoordinatorBridgePresenter: NSObject { // MARK: - UIAdaptivePresentationControllerDelegate +@available(iOS 14.0, *) extension RoomSuggestionCoordinatorBridgePresenter: UIAdaptivePresentationControllerDelegate { func roomNotificationSettingsCoordinatorDidComplete(_ presentationController: UIPresentationController) { diff --git a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift index f6b95a59e..016e17dd1 100644 --- a/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Room/RoomSuggestion/RoomSuggestionSpaceChooser/Service/MatrixSDK/RoomSuggestionSpaceChooserItemsProcessor.swift @@ -99,6 +99,8 @@ class RoomSuggestionSpaceChooserItemsProcessor: MatrixItemChooserProcessorProtoc // MARK: - Private + /// (Un)suggest room for spaces which ID is in `parentIds`. + /// Recurse to the next index once done. private func setRoom(suggested: Bool, forParentsWithId parentIds: [String], at index: Int = 0, completion: @escaping () -> Void) { guard index < parentIds.count else { completion() diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift new file mode 100644 index 000000000..bed0553b1 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Coordinator/RoomUpgradeCoordinator.swift @@ -0,0 +1,73 @@ +// +// Copyright 2021 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 SwiftUI +import MatrixSDK + +struct RoomUpgradeCoordinatorParameters { + let session: MXSession + let roomId: String + let versionOverride: String +} + +final class RoomUpgradeCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: RoomUpgradeCoordinatorParameters + private let roomUpgradeHostingController: UIViewController + private var roomUpgradeViewModel: RoomUpgradeViewModelProtocol + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((RoomUpgradeCoordinatorResult) -> Void)? + + // MARK: - Setup + + @available(iOS 14.0, *) + init(parameters: RoomUpgradeCoordinatorParameters) { + self.parameters = parameters + let viewModel = RoomUpgradeViewModel.makeRoomUpgradeViewModel(roomUpgradeService: RoomUpgradeService(session: parameters.session, roomId: parameters.roomId, versionOverride: parameters.versionOverride)) + let view = RoomUpgrade(viewModel: viewModel.context) + .addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager)) + roomUpgradeViewModel = viewModel + roomUpgradeHostingController = VectorHostingController(rootView: view) + roomUpgradeHostingController.view.backgroundColor = .clear + } + + // MARK: - Public + func start() { + MXLog.debug("[RoomUpgradeCoordinator] did start.") + roomUpgradeViewModel.completion = { [weak self] result in + MXLog.debug("[RoomUpgradeCoordinator] RoomUpgradeViewModel did complete with result: \(result).") + guard let self = self else { return } + switch result { + case .cancel(let roomId): + self.completion?(.cancel(roomId)) + case .done(let roomId): + self.completion?(.done(roomId)) + } + } + } + + func toPresentable() -> UIViewController { + return self.roomUpgradeHostingController + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift new file mode 100644 index 000000000..867b72f4e --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/MockRoomUpgradeScreenState.swift @@ -0,0 +1,56 @@ +// +// Copyright 2021 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 +import SwiftUI + +/// Using an enum for the screen allows you define the different state cases with +/// the relevant associated data for each case. +@available(iOS 14.0, *) +enum MockRoomUpgradeScreenState: MockScreenState, CaseIterable { + // A case for each state you want to represent + // with specific, minimal associated data that will allow you + // mock that screen. + case initial + + /// The associated screen + var screenType: Any.Type { + RoomUpgrade.self + } + + /// A list of screen state definitions + static var allCases: [MockRoomUpgradeScreenState] { + [.initial] + } + + /// Generate the view struct for the screen state. + var screenView: ([Any], AnyView) { + let service: MockRoomUpgradeService + switch self { + case .initial: + service = MockRoomUpgradeService() + } + let viewModel = RoomUpgradeViewModel.makeRoomUpgradeViewModel(roomUpgradeService: service) + + // can simulate service and viewModel actions here if needs be. + + return ( + [service, viewModel], + AnyView(RoomUpgrade(viewModel: viewModel.context) + .addDependency(MockAvatarService.example)) + ) + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift new file mode 100644 index 000000000..94592fa8a --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeModels.swift @@ -0,0 +1,47 @@ +// +// Copyright 2021 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 + +// MARK: - Coordinator + +enum RoomUpgradeCoordinatorResult { + case cancel(_ roomId: String) + case done(_ roomId: String) +} + +// MARK: View model + +enum RoomUpgradeViewModelResult { + case cancel(_ roomId: String) + case done(_ roomId: String) +} + +// MARK: View + +struct RoomUpgradeViewState: BindableState { + var bindings: RoomUpgradeViewModelBindings +} + +struct RoomUpgradeViewModelBindings { + var waitingMessage: String? + var isLoading: Bool +} + +enum RoomUpgradeViewAction { + case cancel + case done(_ autoInviteUsers: Bool) +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift new file mode 100644 index 000000000..3fbbce92f --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModel.swift @@ -0,0 +1,78 @@ +// +// Copyright 2021 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 SwiftUI +import Combine + +@available(iOS 14, *) +typealias RoomUpgradeViewModelType = StateStoreViewModel +@available(iOS 14, *) +class RoomUpgradeViewModel: RoomUpgradeViewModelType, RoomUpgradeViewModelProtocol { + + // MARK: - Properties + + // MARK: Private + + private let roomUpgradeService: RoomUpgradeServiceProtocol + + // MARK: Public + + var completion: ((RoomUpgradeViewModelResult) -> Void)? + + // MARK: - Setup + + static func makeRoomUpgradeViewModel(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewModelProtocol { + return RoomUpgradeViewModel(roomUpgradeService: roomUpgradeService) + } + + private init(roomUpgradeService: RoomUpgradeServiceProtocol) { + self.roomUpgradeService = roomUpgradeService + super.init(initialViewState: Self.defaultState(roomUpgradeService: roomUpgradeService)) + setupObservers() + } + + private static func defaultState(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewState { + let bindings = RoomUpgradeViewModelBindings(waitingMessage: nil, isLoading: false) + return RoomUpgradeViewState(bindings: bindings) + } + + private func setupObservers() { + roomUpgradeService + .upgradingSubject + .sink { [weak self] isUpgrading in + self?.state.bindings = RoomUpgradeViewModelBindings(waitingMessage: isUpgrading ? VectorL10n.roomAccessSettingsScreenUpgradeAlertUpgrading: nil, isLoading: isUpgrading) + } + .store(in: &cancellables) + } + + // MARK: - Public + + override func process(viewAction: RoomUpgradeViewAction) { + switch viewAction { + case .cancel: + completion?(.cancel(roomUpgradeService.currentRoomId)) + case .done(let autoInviteUsers): + roomUpgradeService.upgradeRoom(autoInviteUsers: autoInviteUsers) { [weak self] success, roomId in + guard let self = self else { return } + if success { + self.completion?(.done(self.roomUpgradeService.currentRoomId)) + } + } + } + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift new file mode 100644 index 000000000..88607384e --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/RoomUpgradeViewModelProtocol.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 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 + +protocol RoomUpgradeViewModelProtocol { + + var completion: ((RoomUpgradeViewModelResult) -> Void)? { get set } + @available(iOS 14, *) + static func makeRoomUpgradeViewModel(roomUpgradeService: RoomUpgradeServiceProtocol) -> RoomUpgradeViewModelProtocol + @available(iOS 14, *) + var context: RoomUpgradeViewModelType.Context { get } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift new file mode 100644 index 000000000..f05aa8256 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/MatrixSDK/RoomUpgradeService.swift @@ -0,0 +1,164 @@ +// +// Copyright 2021 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 +import Combine +import MatrixSDK + +@available(iOS 14.0, *) +class RoomUpgradeService: RoomUpgradeServiceProtocol { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + + private let versionOverride: String + private var currentOperation: MXHTTPOperation? + private var didBuildSpaceGraphObserver: Any? + + // MARK: Public + + private(set) var upgradingSubject: CurrentValueSubject + private(set) var errorSubject: CurrentValueSubject + private(set) var currentRoomId: String + + // MARK: - Setup + + init(session: MXSession, roomId: String, versionOverride: String) { + self.session = session + self.currentRoomId = roomId + self.versionOverride = versionOverride + self.upgradingSubject = CurrentValueSubject(false) + self.errorSubject = CurrentValueSubject(nil) + } + + deinit { + currentOperation?.cancel() + if let observer = self.didBuildSpaceGraphObserver { + NotificationCenter.default.removeObserver(observer) + } + } + + func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { + upgradingSubject.send(true) + + if autoInviteUsers, let room = session.room(withRoomId: self.currentRoomId) { + self.currentOperation = room.members { [weak self] response in + guard let self = self else { return } + switch response { + case .success(let members): + let memberIds: [String] = members?.members.compactMap({ member in + guard member.membership == .join, member.userId != self.session.myUserId else { + return nil + } + + return member.userId + }) ?? [] + self.upgradeRoom(to: self.versionOverride, inviteUsers: memberIds, completion: completion) + case .failure(let error): + self.upgradingSubject.send(false) + self.errorSubject.send(error) + } + } + } else { + self.upgradeRoom(to: versionOverride, inviteUsers: [], completion: completion) + } + } + + // MARK: - Private + + private func upgradeRoom(to versionOverride: String, inviteUsers userIds: [String], completion: @escaping (Bool, String) -> Void) { + // Need to disable graph update during this process as a lot of syncs will occure + session.spaceService.graphUpdateEnabled = false + currentOperation = session.matrixRestClient.upgradeRoom(withId: self.currentRoomId, to: versionOverride) { [weak self] response in + guard let self = self else { return } + + switch response { + case .success(let replacementRoomId): + let oldRoomId = self.currentRoomId + self.currentRoomId = replacementRoomId + let parentSpaces = self.session.spaceService.directParentIds(ofRoomWithId: oldRoomId) + self.moveRoom(from: oldRoomId, to: replacementRoomId, within: Array(parentSpaces), at: 0) { + self.session.spaceService.graphUpdateEnabled = true + self.didBuildSpaceGraphObserver = NotificationCenter.default.addObserver(forName: MXSpaceService.didBuildSpaceGraph, object: nil, queue: OperationQueue.main) { [weak self] notification in + guard let self = self else { return } + + if let observer = self.didBuildSpaceGraphObserver { + NotificationCenter.default.removeObserver(observer) + self.didBuildSpaceGraphObserver = nil + } + + DispatchQueue.main.async { + self.inviteUser(from: userIds, at: 0, completion: completion) + } + } + } + case .failure(let error): + self.session.spaceService.graphUpdateEnabled = true + self.upgradingSubject.send(false) + self.errorSubject.send(error) + } + } + } + + /// Move room with roomId to new room ID for each space which ID belongs to`parentIds` list. + /// Recurse to the next index once done. + private func moveRoom(from roomId: String, to newRoomId: String, within parentIds: [String], at index: Int, completion: @escaping () -> Void) { + guard index < parentIds.count else { + completion() + return + } + + guard let space = session.spaceService.getSpace(withId: parentIds[index]) else { + MXLog.warning("[RoomUpgradeService] moveRoom \(roomId) to \(newRoomId) within \(parentIds[index]): space not found") + moveRoom(from: roomId, to: newRoomId, within: parentIds, at: index + 1, completion: completion) + return + } + + space.moveChild(withRoomId: roomId, to: newRoomId) { [weak self] response in + guard let self = self else { return } + + if let error = response.error { + MXLog.warning("[RoomUpgradeService] moveRoom \(roomId) to \(newRoomId) within \(space.spaceId): failed due to error: \(error)") + } + + self.moveRoom(from: roomId, to: newRoomId, within: parentIds, at: index + 1, completion: completion) + } + } + + /// Invite all users within `userIds` list + /// Recurse to the next index once done. + private func inviteUser(from userIds: [String], at index: Int, completion: @escaping (Bool, String) -> Void) { + guard index < userIds.count else { + self.upgradingSubject.send(false) + completion(true, currentRoomId) + return + } + + currentOperation = session.matrixRestClient.invite(.userId(userIds[index]), toRoom: currentRoomId) { [weak self] response in + guard let self = self else { return } + + self.currentOperation = nil + if let error = response.error { + MXLog.warning("[RoomUpgradeService] inviteUser: failed to invite \(userIds[index]) to \(self.currentRoomId) due to error: \(error)") + } + + self.inviteUser(from: userIds, at: index + 1, completion: completion) + } + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift new file mode 100644 index 000000000..e04c592f1 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/Mock/MockRoomUpgradeService.swift @@ -0,0 +1,35 @@ +// +// Copyright 2021 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 +import Combine + +@available(iOS 14.0, *) +class MockRoomUpgradeService: RoomUpgradeServiceProtocol { + var currentRoomId: String = "!sfdlksjdflkfjds:matrix.org" + + var errorSubject: CurrentValueSubject + var upgradingSubject: CurrentValueSubject + + init() { + self.errorSubject = CurrentValueSubject(nil) + self.upgradingSubject = CurrentValueSubject(false) + } + + func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) { + + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift new file mode 100644 index 000000000..d07b84548 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Service/RoomUpgradeServiceProtocol.swift @@ -0,0 +1,27 @@ +// +// Copyright 2021 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 +import Combine + +@available(iOS 14.0, *) +protocol RoomUpgradeServiceProtocol { + var currentRoomId: String { get } + var upgradingSubject: CurrentValueSubject { get } + var errorSubject: CurrentValueSubject { get } + + func upgradeRoom(autoInviteUsers: Bool, completion: @escaping (Bool, String) -> Void) +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift new file mode 100644 index 000000000..f920d33ff --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/UI/RoomUpgradeUITests.swift @@ -0,0 +1,53 @@ +// +// Copyright 2021 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 XCTest +import RiotSwiftUI + +@available(iOS 14.0, *) +class RoomUpgradeUITests: MockScreenTest { + + override class var screenType: MockScreenState.Type { + return MockRoomUpgradeScreenState.self + } + + override class func createTest() -> MockScreenTest { + return RoomUpgradeUITests(selector: #selector(verifyRoomUpgradeScreen)) + } + + func verifyRoomUpgradeScreen() throws { + guard let screenState = screenState as? MockRoomUpgradeScreenState else { fatalError("no screen") } + switch screenState { + case .presence(let presence): + verifyRoomUpgradePresence(presence: presence) + case .longDisplayName(let name): + verifyRoomUpgradeLongName(name: name) + } + } + + func verifyRoomUpgradePresence(presence: RoomUpgradePresence) { + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func verifyRoomUpgradeLongName(name: String) { + let displayNameText = app.staticTexts["displayNameText"] + XCTAssert(displayNameText.exists) + XCTAssertEqual(displayNameText.label, name) + } + +} diff --git a/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift new file mode 100644 index 000000000..628b17812 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/Test/Unit/RoomUpgradeViewModelTests.swift @@ -0,0 +1,57 @@ +// +// Copyright 2021 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 XCTest +import Combine + +@testable import RiotSwiftUI + +@available(iOS 14.0, *) +class RoomUpgradeViewModelTests: XCTestCase { + private enum Constants { + static let presenceInitialValue: RoomUpgradePresence = .offline + static let displayName = "Alice" + } + var service: MockRoomUpgradeService! + var viewModel: RoomUpgradeViewModelProtocol! + var context: RoomUpgradeViewModelType.Context! + var cancellables = Set() + override func setUpWithError() throws { + service = MockRoomUpgradeService(displayName: Constants.displayName, presence: Constants.presenceInitialValue) + viewModel = RoomUpgradeViewModel.makeRoomUpgradeViewModel(roomUpgradeService: service) + context = viewModel.context + } + + func testInitialState() { + XCTAssertEqual(context.viewState.displayName, Constants.displayName) + XCTAssertEqual(context.viewState.presence, Constants.presenceInitialValue) + } + + func testFirstPresenceReceived() throws { + let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(1).first() + XCTAssertEqual(try xcAwait(presencePublisher), [Constants.presenceInitialValue]) + } + + func testPresenceUpdatesReceived() throws { + let presencePublisher = context.$viewState.map(\.presence).removeDuplicates().collect(3).first() + let awaitDeferred = xcAwaitDeferred(presencePublisher) + let newPresenceValue1: RoomUpgradePresence = .online + let newPresenceValue2: RoomUpgradePresence = .idle + service.simulateUpdate(presence: newPresenceValue1) + service.simulateUpdate(presence: newPresenceValue2) + XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2]) + } +} diff --git a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserUpgradeRoomAlert.swift b/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift similarity index 80% rename from RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserUpgradeRoomAlert.swift rename to RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift index b1efa0509..70d86d0ab 100644 --- a/RiotSwiftUI/Modules/Room/RoomAccess/RoomAccessTypeChooser/View/RoomAccessTypeChooserUpgradeRoomAlert.swift +++ b/RiotSwiftUI/Modules/Room/RoomUpgrade/View/RoomUpgrade.swift @@ -17,27 +17,27 @@ import SwiftUI @available(iOS 14.0, *) -struct RoomAccessTypeChooserUpgradeRoomAlert: View { - +struct RoomUpgrade: View { + // MARK: - Properties - @ObservedObject var viewModel: RoomAccessTypeChooserViewModelType.Context @State var autoInviteUsers: Bool = true - // MARK: - Private + // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI - + + // MARK: Public + + @ObservedObject var viewModel: RoomUpgradeViewModel.Context + // MARK: - Public var body: some View { ZStack { Color.black.opacity(0.6) alertContent - .modifier(WaitOverlay( - allowUserInteraction: false, - message: $viewModel.waitingMessage, - isLoading: $viewModel.isLoading)) + .waitOverlay(show: viewModel.isLoading, message: viewModel.waitingMessage, allowUserInteraction: false) } .edgesIgnoringSafeArea(.all) } @@ -58,17 +58,17 @@ struct RoomAccessTypeChooserUpgradeRoomAlert: View { .foregroundColor(theme.colors.secondaryContent) .padding(.bottom, 35) .padding(.horizontal, 12) - Toggle(isOn: $autoInviteUsers, label: { + Toggle(isOn: $autoInviteUsers) { Text(VectorL10n.roomAccessSettingsScreenUpgradeAlertAutoInviteSwitch) .font(theme.fonts.body) .foregroundColor(theme.colors.secondaryContent) - }) + } .toggleStyle(SwitchToggleStyle(tint: theme.colors.accent)) .padding(.horizontal, 28) Divider() .padding(.horizontal, 28) Button { - viewModel.send(viewAction: .didAcceptRoomUpgrade(autoInviteUsers)) + viewModel.send(viewAction: .done(autoInviteUsers)) } label: { Text(VectorL10n.roomAccessSettingsScreenUpgradeAlertUpgradeButton) } @@ -77,7 +77,7 @@ struct RoomAccessTypeChooserUpgradeRoomAlert: View { .padding(.horizontal, 24) .padding(.top, 16) Button { - viewModel.send(viewAction: .didCancelRoomUpgrade) + viewModel.send(viewAction: .cancel) } label: { Text(VectorL10n.cancel) } @@ -92,3 +92,12 @@ struct RoomAccessTypeChooserUpgradeRoomAlert: View { } } +// MARK: - Previews + +@available(iOS 14.0, *) +struct RoomUpgrade_Previews: PreviewProvider { + static let stateRenderer = MockRoomUpgradeScreenState.stateRenderer + static var previews: some View { + stateRenderer.screenGroup() + } +} diff --git a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift index a7ecc2265..194b60a44 100644 --- a/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift +++ b/RiotSwiftUI/Modules/Spaces/AddRoomSelector/View/AddRoomSelector.swift @@ -32,8 +32,8 @@ struct AddRoomSelector: View { var body: some View { MatrixItemChooser(viewModel: viewModel, listBottomPadding: nil) .background(theme.colors.background) - .navigationBarItems(leading: cancelButton, - trailing: doneButton) + .navigationBarItems(leading: cancelButton, trailing: doneButton) + .accentColor(theme.colors.accent) } // MARK: Private @@ -43,7 +43,6 @@ struct AddRoomSelector: View { viewModel.send(viewAction: .cancel) }) .font(theme.fonts.body) - .foregroundColor(theme.colors.accent) } private var doneButton: some View { @@ -51,7 +50,6 @@ struct AddRoomSelector: View { viewModel.send(viewAction: .done) }) .font(theme.fonts.body) - .foregroundColor(viewModel.viewState.selectedItemIds.isEmpty ? theme.colors.quarterlyContent : theme.colors.accent) .opacity(viewModel.viewState.selectedItemIds.isEmpty ? 0.7 : 1) .disabled(viewModel.viewState.selectedItemIds.isEmpty) } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift index 8dae7adef..3d781dec8 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomAncestorsDataSource.swift @@ -26,10 +26,10 @@ class MatrixItemChooserRoomAncestorsDataSource: MatrixItemChooserDataSource { } func sections(with session: MXSession, completion: @escaping (Result<[MatrixListItemSectionData], Error>) -> Void) { - let ancestorsId = session.spaceService.ancestorsPerRoomId[roomId] ?? [] + let ancestorsIds = session.spaceService.ancestorsPerRoomId[roomId] ?? [] completion(Result(catching: { return [ - MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), infoText: nil, items: ancestorsId.compactMap { spaceId in + MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), infoText: nil, items: ancestorsIds.compactMap { spaceId in guard let space = session.spaceService.getSpace(withId: spaceId) else { return nil } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift index 717860f9c..a5bbad42a 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomDirectParentsDataSource.swift @@ -34,7 +34,7 @@ class MatrixItemChooserRoomDirectParentsDataSource: MatrixItemChooserDataSource } func sections(with session: MXSession, completion: @escaping (Result<[MatrixListItemSectionData], Error>) -> Void) { - let ancestorsId = session.spaceService.directParentIds(ofRoomWithId: roomId) + let ancestorsIds = session.spaceService.directParentIds(ofRoomWithId: roomId) switch preselectionMode { case .none: @@ -45,7 +45,7 @@ class MatrixItemChooserRoomDirectParentsDataSource: MatrixItemChooserDataSource completion(Result(catching: { return [ - MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), infoText: nil, items: ancestorsId.compactMap { spaceId in + MatrixListItemSectionData(title: VectorL10n.roomAccessSpaceChooserKnownSpacesSection(session.room(withRoomId: roomId)?.displayName ?? ""), infoText: nil, items: ancestorsIds.compactMap { spaceId in guard let space = session.spaceService.getSpace(withId: spaceId) else { return nil } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift index 1e8d49298..df3018abb 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixItemChooserRoomRestrictedAllowedParentsDataSource.swift @@ -37,7 +37,7 @@ class MatrixItemChooserRoomRestrictedAllowedParentsDataSource: MatrixItemChooser guard let self = self else { return } let joinRuleEvent = state?.stateEvents(with: .roomJoinRules)?.last - let allowContent: [[String:String]] = joinRuleEvent?.wireContent["allow"] as? [[String:String]] ?? [] + let allowContent: [[String: String]] = joinRuleEvent?.wireContent["allow"] as? [[String: String]] ?? [] self.allowedParentIds = allowContent.compactMap { allowDictionnary in guard let type = allowDictionnary["type"], type == "m.room_membership" else { return nil diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift index 297a06018..5e8cc6119 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/Service/MatrixSDK/MatrixListItemData+Riot.swift @@ -23,13 +23,13 @@ extension MatrixListItemData { } init(mxRoom: MXRoom, spaceService: MXSpaceService) { - let parentSapceIds = mxRoom.summary.parentSpaceIds ?? Set() + let parentSpaceIds = mxRoom.summary.parentSpaceIds ?? Set() let detailText: String? - if parentSapceIds.isEmpty { + if parentSpaceIds.isEmpty { detailText = nil } else { - if let spaceName = spaceService.getSpace(withId: parentSapceIds.first ?? "")?.summary?.displayname { - let count = parentSapceIds.count - 1 + if let spaceName = spaceService.getSpace(withId: parentSpaceIds.first ?? "")?.summary?.displayname { + let count = parentSpaceIds.count - 1 switch count { case 0: detailText = VectorL10n.spacesCreationInSpacename(spaceName) @@ -39,8 +39,8 @@ extension MatrixListItemData { detailText = VectorL10n.spacesCreationInSpacenamePlusMany(spaceName, "\(count)") } } else { - if parentSapceIds.count > 1 { - detailText = VectorL10n.spacesCreationInManySpaces("\(parentSapceIds.count)") + if parentSpaceIds.count > 1 { + detailText = VectorL10n.spacesCreationInManySpaces("\(parentSpaceIds.count)") } else { detailText = VectorL10n.spacesCreationInOneSpace } diff --git a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift index 2a4a7919b..9c6b53f07 100644 --- a/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift +++ b/RiotSwiftUI/Modules/Spaces/MatrixItemChooser/View/MatrixItemChooser.swift @@ -44,7 +44,7 @@ struct MatrixItemChooser: View { var body: some View { listContent .background(Color.clear) - .modifier(WaitOverlay(isLoading: .constant(viewModel.viewState.loading))) + .waitOverlay(show: viewModel.viewState.loading) .alert(isPresented: .constant(viewModel.viewState.error != nil)) { Alert(title: Text(VectorL10n.error), message: Text(viewModel.viewState.error ?? ""), dismissButton: .cancel(Text(VectorL10n.ok))) } diff --git a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift index 874ed47a6..0c8f8884c 100644 --- a/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift +++ b/RiotSwiftUI/Modules/Spaces/RoomAncestorSelector/Service/MatrixSDK/RoomAncestorSelectorItemsProcessor.swift @@ -38,36 +38,10 @@ class RoomAncestorSelectorItemsProcessor: MatrixItemChooserProcessorProtocol { } func computeSelection(withIds itemsIds: [String], completion: @escaping (Result) -> Void) { -// addChild(from: itemsIds, at: 0, completion: completion) completion(Result.success(())) } func isItemIncluded(_ item: (MatrixListItemData)) -> Bool { return true } - - // MARK: Private - -// func addChild(from roomIds: [String], at index: Int, completion: @escaping (Result) -> Void) { -// guard index < roomIds.count else { -// completion(Result.success(())) -// return -// } -// -// let roomId = roomIds[index] -// -// guard !parentSpace.isRoomAChild(roomId: roomId) else { -// addChild(from: roomIds, at: index + 1, completion: completion) -// return -// } -// -// parentSpace.addChild(roomId: roomIds[index]) { [weak self] response in -// switch response { -// case .success: -// self?.addChild(from: roomIds, at: index + 1, completion: completion) -// case .failure(let error): -// completion(Result.failure(error)) -// } -// } -// } } diff --git a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift index b8e36720c..630847a13 100644 --- a/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift +++ b/RiotSwiftUI/Modules/Spaces/SpaceCreation/SpaceCreationEmailInvites/View/SpaceCreationEmailInvites.swift @@ -41,7 +41,7 @@ struct SpaceCreationEmailInvites: View { } mainView .animation(.easeInOut(duration: 0.2), value: viewModel.viewState.loading) - .modifier(WaitOverlay(isLoading: .constant(viewModel.viewState.loading))) + .waitOverlay(show: viewModel.viewState.loading) } .background(theme.colors.background) .navigationBarHidden(true)