diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Coordinator/TemplateUserProfileCoordinator.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Coordinator/TemplateUserProfileCoordinator.swift index 9d1f1d9ab..43a155c8d 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Coordinator/TemplateUserProfileCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Coordinator/TemplateUserProfileCoordinator.swift @@ -20,56 +20,45 @@ import SwiftUI final class TemplateUserProfileCoordinator: Coordinator { - typealias Completion = () -> Void // MARK: - Properties // MARK: Private private let session: MXSession private let templateUserProfileViewController: UIViewController + private var templateUserProfileViewModel: TemplateUserProfileViewModelProtocol // MARK: Public // Must be used only internally var childCoordinators: [Coordinator] = [] - var completion: Completion? + var completion: (() -> Void)? // MARK: - Setup @available(iOS 14.0, *) init(session: MXSession) { self.session = session - let hostViewController = VectorHostingController() - templateUserProfileViewController = UINavigationController(rootViewController: hostViewController) - let rootView = TemplateUserProfile.instantiate(session: session, completion: self.userProfileCompletion(result:)) - hostViewController.setRoot(view: rootView) - } - - @available(iOS 14.0, *) - func userProfileCompletion(result: TemplateUserProfile.Result) { - switch result { - case .cancel, .done: - completion?() - break - } + let viewModel = TemplateUserProfileViewModel(userService: MXTemplateUserService(session: session)) + let view = TemplateUserProfile(viewModel: viewModel) + .addDependency(MXAvatarService.instantiate(mediaManager: session.mediaManager)) + templateUserProfileViewModel = viewModel + templateUserProfileViewController = VectorHostingController(rootView: view) } // MARK: - Public methods - func start() { - + templateUserProfileViewModel.completion = { [weak self] result in + guard let self = self else { return } + switch result { + case .cancel, .done: + self.completion?() + break + } + } } func toPresentable() -> UIViewController { return self.templateUserProfileViewController } } - -@available(iOS 14.0, *) -extension TemplateUserProfile { - static func instantiate(session: MXSession, completion: @escaping TemplateUserProfile.Completion) -> some View { - let templateUserProfileViewModel = TemplateUserProfileViewModel(userService: MXTemplateUserService(session: session)) - let templateUserProfile = TemplateUserProfile(viewModel: templateUserProfileViewModel, completion: completion) - return templateUserProfile.addDependency(MXAvatarService.instantiate(mediaManager: session.mediaManager)) - } -} diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateProfileViewAction.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateProfileViewAction.swift new file mode 100644 index 000000000..84a732dae --- /dev/null +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateProfileViewAction.swift @@ -0,0 +1,22 @@ +// +// 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 + +enum TemplateProfileViewAction { + case cancel + case done +} diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateUserProfileViewModelResult.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateUserProfileViewModelResult.swift new file mode 100644 index 000000000..2c2965f5b --- /dev/null +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Model/TemplateUserProfileViewModelResult.swift @@ -0,0 +1,22 @@ +// +// 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 + +enum TemplateUserProfileViewModelResult { + case cancel + case done +} diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/MatrixSDK/MXTemplateUserService.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/MatrixSDK/MXTemplateUserService.swift index 0aedaa277..fc2f3a7fe 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/MatrixSDK/MXTemplateUserService.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/MatrixSDK/MXTemplateUserService.swift @@ -18,24 +18,17 @@ import Foundation import Combine @available(iOS 14.0, *) -class MXTemplateUserService: TemplateUserServiceType { +class MXTemplateUserService: TemplateUserServiceProtocol { - let session: MXSession - var listenerReference: Any! - @Published var presence: TemplatePresence = .offline + // MARK: - Properties - init(session: MXSession) { - self.session = session - - let listenerReference = session.myUser.listen { [weak self] event in - guard let self = self, - let event = event, - case .presence = MXEventType(identifier: event.eventId) - else { return } - self.presence = TemplatePresence(mxPresence: self.session.myUser.presence) - } - self.listenerReference = listenerReference - } + // MARK: Private + + private let session: MXSession + private var listenerReference: Any! + @Published private var presence: TemplatePresence = .offline + + // MARK: Public var userId: String { return session.myUser.userId @@ -53,6 +46,21 @@ class MXTemplateUserService: TemplateUserServiceType { $presence.eraseToAnyPublisher() } + // MARK: - Setup + + init(session: MXSession) { + self.session = session + + let listenerReference = session.myUser.listen { [weak self] event in + guard let self = self, + let event = event, + case .presence = MXEventType(identifier: event.eventId) + else { return } + self.presence = TemplatePresence(mxPresence: self.session.myUser.presence) + } + self.listenerReference = listenerReference + } + deinit { session.myUser.removeListener(listenerReference) } @@ -61,7 +69,6 @@ class MXTemplateUserService: TemplateUserServiceType { fileprivate extension TemplatePresence { init(mxPresence: MXPresence) { - switch mxPresence { case MXPresenceOnline: self = .online diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/Mock/MockTemplateUserService.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/Mock/MockTemplateUserService.swift index 4c9fd734f..2f433f924 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/Mock/MockTemplateUserService.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/Mock/MockTemplateUserService.swift @@ -18,7 +18,7 @@ import Foundation import Combine @available(iOS 14.0, *) -class MockTemplateUserService: TemplateUserServiceType, ObservableObject { +class MockTemplateUserService: TemplateUserServiceProtocol, ObservableObject { static let example = MockTemplateUserService() @Published var presence: TemplatePresence = .online diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceType.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceProtocol.swift similarity index 91% rename from RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceType.swift rename to RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceProtocol.swift index e9b88ffba..06d3a3f1f 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceType.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/Service/TemplateUserServiceProtocol.swift @@ -18,7 +18,7 @@ import Foundation import Combine @available(iOS 14.0, *) -protocol TemplateUserServiceType: Avatarable { +protocol TemplateUserServiceProtocol: Avatarable { var userId: String { get } var displayName: String? { get } var avatarUrl: String? { get } @@ -26,7 +26,7 @@ protocol TemplateUserServiceType: Avatarable { } @available(iOS 14.0, *) -extension TemplateUserServiceType { +extension TemplateUserServiceProtocol { var mxContentUri: String? { avatarUrl } diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/View/TemplateUserProfile.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/View/TemplateUserProfile.swift index 057bf185c..29c80798e 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/View/TemplateUserProfile.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/View/TemplateUserProfile.swift @@ -18,28 +18,19 @@ import SwiftUI @available(iOS 14.0, *) struct TemplateUserProfile: View { - - enum Result { - case cancel - case done - } - - typealias Completion = (Result) -> Void - + @Environment(\.theme) var theme: ThemeSwiftUI @ObservedObject var viewModel: TemplateUserProfileViewModel - - var completion: Completion - + var leftButton: some View { Button(VectorL10n.cancel) { - completion(.cancel) + viewModel.proccess(viewAction: .cancel) } } var rightButton: some View { Button(VectorL10n.done) { - completion(.done) + viewModel.proccess(viewAction: .cancel) } } @@ -71,9 +62,7 @@ struct TemplateUserProfile: View { @available(iOS 14.0, *) struct TemplateUserProfile_Previews: PreviewProvider { static var previews: some View { - TemplateUserProfile(viewModel: TemplateUserProfileViewModel(userService: MockTemplateUserService.example)) { _ in - - } + TemplateUserProfile(viewModel: TemplateUserProfileViewModel(userService: MockTemplateUserService.example)) .addDependency(MockAvatarService.example) } } diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModel.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModel.swift index 5a98afa80..136717c89 100644 --- a/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModel.swift +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModel.swift @@ -16,20 +16,27 @@ import SwiftUI import Combine - + @available(iOS 14.0, *) -class TemplateUserProfileViewModel: ObservableObject { +class TemplateUserProfileViewModel: ObservableObject, TemplateUserProfileViewModelProtocol { - private let userService: TemplateUserServiceType - private var cancellables = Set() + // MARK: - Properties + // MARK: Private + private let userService: TemplateUserServiceProtocol + private var cancellables = Set() + + // MARK: Public @Published private(set) var viewState: TemplateUserProfileViewState - private static func defaultState(userService: TemplateUserServiceType) -> TemplateUserProfileViewState { + var completion: ((TemplateUserProfileViewModelResult) -> Void)? + + private static func defaultState(userService: TemplateUserServiceProtocol) -> TemplateUserProfileViewState { return TemplateUserProfileViewState(avatar: userService.avatarData, displayName: userService.displayName) } - init(userService: TemplateUserServiceType, initialState: TemplateUserProfileViewState? = nil) { + // MARK: - Setup + init(userService: TemplateUserServiceProtocol, initialState: TemplateUserProfileViewState? = nil) { self.userService = userService self.viewState = initialState ?? Self.defaultState(userService: userService) @@ -40,6 +47,17 @@ class TemplateUserProfileViewModel: ObservableObject { .store(in: &cancellables) } + // MARK: - Public methods + func proccess(viewAction: TemplateProfileViewAction) { + switch viewAction { + case .cancel: + self.cancel() + case .done: + self.done() + } + } + + // MARK: - Private methods /** Send state actions to mutate the state. */ @@ -58,4 +76,12 @@ class TemplateUserProfileViewModel: ObservableObject { state.presence = presence } } + + private func done() { + completion?(.done) + } + + private func cancel() { + completion?(.cancel) + } } diff --git a/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModelProtocol.swift b/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModelProtocol.swift new file mode 100644 index 000000000..4f038ae32 --- /dev/null +++ b/RiotSwiftUI/Modules/Template/SimpleProfileExample/ViewModel/TemplateUserProfileViewModelProtocol.swift @@ -0,0 +1,21 @@ +// +// 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 TemplateUserProfileViewModelProtocol { + var completion: ((TemplateUserProfileViewModelResult) -> Void)? { get set } +}