diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift index 378a53c6c..28b16168a 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/Coordinator/UserSessionDetailsCoordinator.swift @@ -18,7 +18,6 @@ import SwiftUI import CommonKit struct UserSessionDetailsCoordinatorParameters { - let session: MXSession let userSessionInfo: UserSessionInfo } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift index 317b2d015..3f9a2e634 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionDetails/MockUserSessionDetailsScreenState.swift @@ -33,7 +33,7 @@ enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable { /// A list of screen state definitions static var allCases: [MockUserSessionDetailsScreenState] { // Each of the presence statuses - return [.allSections, sessionSectionOnly] + return [.allSections, .sessionSectionOnly] } /// Generate the view struct for the screen state. diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift new file mode 100644 index 000000000..eedacbf9a --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Coordinator/UserSessionOverviewCoordinator.swift @@ -0,0 +1,77 @@ +// +// 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 CommonKit + +struct UserSessionOverviewCoordinatorParameters { + let userSessionInfo: UserSessionInfo + let isCurrentSession: Bool +} + +final class UserSessionOverviewCoordinator: Coordinator, Presentable { + + // MARK: - Properties + + // MARK: Private + + private let parameters: UserSessionOverviewCoordinatorParameters + private let userSessionOverviewHostingController: UIViewController + private var userSessionOverviewViewModel: UserSessionOverviewViewModelProtocol + + private var indicatorPresenter: UserIndicatorTypePresenterProtocol + private var loadingIndicator: UserIndicator? + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + var completion: ((UserSessionOverviewCoordinatorResult) -> Void)? + + // MARK: - Setup + + init(parameters: UserSessionOverviewCoordinatorParameters) { + self.parameters = parameters + let viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo, isCurrentSession: parameters.isCurrentSession) + let view = UserSessionOverview(viewModel: viewModel.context) + userSessionOverviewViewModel = viewModel + userSessionOverviewHostingController = VectorHostingController(rootView: view) + + indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionOverviewHostingController) + } + + // MARK: - Public + + func start() { + MXLog.debug("[UserSessionOverviewCoordinator] did start.") + userSessionOverviewViewModel.completion = { [weak self] result in + guard let self = self else { return } + MXLog.debug("[UserSessionOverviewCoordinator] UserSessionOverviewViewModel did complete with result: \(result).") + switch result { + case .verifyCurrentSession: + break // TODO + case let .showCurrentSessionDetails(sessionInfo: sessionInfo): + self.completion?(.openSessionDetails(session: sessionInfo)) + } + } + } + + func toPresentable() -> UIViewController { + return self.userSessionOverviewHostingController + } + + // MARK: - Private +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift new file mode 100644 index 000000000..a24ea52bb --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/MockUserSessionOverviewScreenState.swift @@ -0,0 +1,69 @@ +// +// 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. +enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable { + // A case for each state you want to represent + // with specific, minimal associated data that will allow you + // mock that screen. + case currentSession + case otherSession + + /// The associated screen + var screenType: Any.Type { + UserSessionOverview.self + } + + /// A list of screen state definitions + static var allCases: [MockUserSessionOverviewScreenState] { + // Each of the presence statuses + return [.currentSession, .otherSession] + } + + /// Generate the view struct for the screen state. + var screenView: ([Any], AnyView) { + let viewModel: UserSessionOverviewViewModel + switch self { + case .currentSession: + let currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "iOS", + deviceType: .mobile, + isVerified: false, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: true) + case .otherSession: + let currentSessionInfo = UserSessionInfo(sessionId: "session", + sessionName: "Mac", + deviceType: .desktop, + isVerified: true, + lastSeenIP: "10.0.0.10", + lastSeenTimestamp: Date().timeIntervalSince1970 - 100) + viewModel = UserSessionOverviewViewModel(userSessionInfo: currentSessionInfo, isCurrentSession: false) + } + + // can simulate service and viewModel actions here if needs be. + + return ( + [viewModel], + AnyView(UserSessionOverview(viewModel: viewModel.context)) + ) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.swift new file mode 100644 index 000000000..5958a9a0a --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/UI/UserSessionOverviewUITests.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 RiotSwiftUI + +class UserSessionOverviewUITests: MockScreenTestCase { + func testUserSessionOverviewPresenceIdle() { + let presence = UserSessionOverviewPresence.idle + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewPresenceOffline() { + let presence = UserSessionOverviewPresence.offline + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewPresenceOnline() { + let presence = UserSessionOverviewPresence.online + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.presence(presence).title) + + let presenceText = app.staticTexts["presenceText"] + XCTAssert(presenceText.exists) + XCTAssertEqual(presenceText.label, presence.title) + } + + func testUserSessionOverviewLongName() { + let name = "Somebody with a super long name we would like to test" + app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.longDisplayName(name).title) + + let displayNameText = app.staticTexts["displayNameText"] + XCTAssert(displayNameText.exists) + XCTAssertEqual(displayNameText.label, name) + } + +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.swift new file mode 100644 index 000000000..aac52cb0f --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/Test/Unit/UserSessionOverviewViewModelTests.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 XCTest +import Combine + +@testable import RiotSwiftUI + +class UserSessionOverviewViewModelTests: XCTestCase { + private enum Constants { + static let presenceInitialValue: UserSessionOverviewPresence = .offline + static let displayName = "Alice" + } + var service: MockUserSessionOverviewService! + var viewModel: UserSessionOverviewViewModelProtocol! + var context: UserSessionOverviewViewModelType.Context! + var cancellables = Set() + override func setUpWithError() throws { + service = MockUserSessionOverviewService(displayName: Constants.displayName, presence: Constants.presenceInitialValue) + viewModel = UserSessionOverviewViewModel.makeUserSessionOverviewViewModel(userSessionOverviewService: 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: UserSessionOverviewPresence = .online + let newPresenceValue2: UserSessionOverviewPresence = .idle + service.simulateUpdate(presence: newPresenceValue1) + service.simulateUpdate(presence: newPresenceValue2) + XCTAssertEqual(try awaitDeferred(), [Constants.presenceInitialValue, newPresenceValue1, newPresenceValue2]) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift new file mode 100644 index 000000000..40284c91c --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewModels.swift @@ -0,0 +1,42 @@ +// +// 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 UserSessionOverviewCoordinatorResult { + case openSessionDetails(session: UserSessionInfo) +} + +// MARK: View model + +enum UserSessionOverviewViewModelResult { + case showCurrentSessionDetails(sessionInfo: UserSessionInfo) + case verifyCurrentSession +} + +// MARK: View + +struct UserSessionOverviewViewState: BindableState { + let cardViewData: UserSessionCardViewData + let isCurrentSession: Bool +} + +enum UserSessionOverviewViewAction { + case verifyCurrentSession + case viewSessionDetails +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.swift new file mode 100644 index 000000000..3eb381e02 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModel.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 SwiftUI + +typealias UserSessionOverviewViewModelType = StateStoreViewModel + +class UserSessionOverviewViewModel: UserSessionOverviewViewModelType, UserSessionOverviewViewModelProtocol { + + // MARK: - Properties + + // MARK: Private + private let userSessionInfo: UserSessionInfo + // MARK: Public + + var completion: ((UserSessionOverviewViewModelResult) -> Void)? + + // MARK: - Setup + + init(userSessionInfo: UserSessionInfo, isCurrentSession: Bool) { + self.userSessionInfo = userSessionInfo + + let cardViewData = UserSessionCardViewData(userSessionInfo: userSessionInfo, isCurrentSessionDisplayMode: isCurrentSession) + let state = UserSessionOverviewViewState(cardViewData: cardViewData, isCurrentSession: isCurrentSession) + super.init(initialViewState: state) + } + + // MARK: - Public + + override func process(viewAction: UserSessionOverviewViewAction) { + switch viewAction { + case .verifyCurrentSession: + completion?(.verifyCurrentSession) + case .viewSessionDetails: + completion?(.showCurrentSessionDetails(sessionInfo: userSessionInfo)) + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift new file mode 100644 index 000000000..014dcd8a7 --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/UserSessionOverviewViewModelProtocol.swift @@ -0,0 +1,23 @@ +// +// 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 UserSessionOverviewViewModelProtocol { + + var completion: ((UserSessionOverviewViewModelResult) -> Void)? { get set } + var context: UserSessionOverviewViewModelType.Context { get } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.swift new file mode 100644 index 000000000..9e006289b --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverview.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 + +struct UserSessionOverview: View { + + // MARK: - Properties + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + @ObservedObject var viewModel: UserSessionOverviewViewModel.Context + + var body: some View { + ScrollView { + UserSessionCardView(viewData: viewModel.viewState.cardViewData, + onVerifyAction: { _ in + viewModel.send(viewAction: .verifyCurrentSession) + }, + onViewDetailsAction: { _ in + viewModel.send(viewAction: .viewSessionDetails) + }) + .padding(16) + SwiftUI.Section { + UserSessionOverviewDisclosureCell(title: "Session details", onBackgroundTap: { + viewModel.send(viewAction: .viewSessionDetails) + }) + } + } + .background(theme.colors.system.ignoresSafeArea()) + .frame(maxHeight: .infinity) + .navigationTitle(viewModel.viewState.isCurrentSession ? "Current session" : "Session") + } +} + +struct SeparatorLine: View { + + @Environment(\.theme) private var theme: ThemeSwiftUI + + var body: some View { + Rectangle() + .fill(theme.colors.quinaryContent) + .frame(maxWidth: .infinity, alignment: .trailing) + .frame(height: 1.0) + } +} + +// MARK: - Previews + +struct UserSessionOverview_Previews: PreviewProvider { + static let stateRenderer = MockUserSessionOverviewScreenState.stateRenderer + static var previews: some View { + stateRenderer.screenGroup(addNavigation: true).theme(.light).preferredColorScheme(.light) + stateRenderer.screenGroup(addNavigation: true).theme(.dark).preferredColorScheme(.dark) + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift new file mode 100644 index 000000000..64e66b5ee --- /dev/null +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionOverview/View/UserSessionOverviewDisclosureCell.swift @@ -0,0 +1,45 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct UserSessionOverviewDisclosureCell: View { + + @Environment(\.theme) private var theme: ThemeSwiftUI + + let title: String + var onBackgroundTap: (() -> (Void))? = nil + + var body: some View { + VStack(spacing: 0) { + SeparatorLine() + HStack() { + Text(title) + .font(theme.fonts.body) + .foregroundColor(theme.colors.primaryContent) + .frame(maxWidth: .infinity, alignment: .leading) + Image(Asset.Images.chevron.name) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + SeparatorLine() + } + .background(theme.colors.background) + .onTapGesture { + onBackgroundTap?() + } + } +} diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift index b4835c8e2..db751a80f 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsFlow/Coordinator/UserSessionsFlowCoordinator.swift @@ -48,7 +48,6 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { private func pushScreen(with coordinator: Coordinator & Presentable) { add(childCoordinator: coordinator) - self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in self?.remove(childCoordinator: coordinator) }) @@ -63,8 +62,8 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { coordinator.completion = { [weak self] result in guard let self = self else { return } switch result { - case let .openSessionDetails(session: session): - self.openSessionDetails(session: session) + case let .openSessionOverview(session: session, isCurrentSession: isCurrentSession): + self.openSessionOverview(session: session, isCurrentSession: isCurrentSession) } } return coordinator @@ -76,12 +75,27 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable { } private func createUserSessionDetailsCoordinator(session: UserSessionInfo) -> UserSessionDetailsCoordinator { - let parameters = UserSessionDetailsCoordinatorParameters( - session: parameters.session, - userSessionInfo: session) + let parameters = UserSessionDetailsCoordinatorParameters(userSessionInfo: session) return UserSessionDetailsCoordinator(parameters: parameters) } + private func openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) { + let coordinator = createUserSessionOverviewCoordinator(session: session, isCurrentSession: isCurrentSession) + coordinator.completion = { [weak self] result in + guard let self = self else { return } + switch result { + case let .openSessionDetails(session: session): + self.openSessionDetails(session: session) + } + } + pushScreen(with: coordinator) + } + + private func createUserSessionOverviewCoordinator(session: UserSessionInfo, isCurrentSession: Bool) -> UserSessionOverviewCoordinator { + let parameters = UserSessionOverviewCoordinatorParameters(userSessionInfo: session, isCurrentSession: isCurrentSession) + return UserSessionOverviewCoordinator(parameters: parameters) + } + // MARK: - Public func start() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift index f8726ba5c..b98f18fae 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Coordinator/UserSessionsOverviewCoordinator.swift @@ -72,12 +72,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { self.showAllInactiveSessions() case .verifyCurrentSession: self.startVerifyCurrentSession() - case .showCurrentSessionDetails: - self.showCurrentSessionDetails() + case let .showCurrentSessionOverview(sessionInfo): + self.showCurrentSessionOverview(sessionInfo: sessionInfo) case .showAllOtherSessions: self.showAllOtherSessions() - case .showUserSessionDetails(let sessionId): - self.showUserSessionDetails(sessionId: sessionId) + case let .showUserSessionOverview(sessionInfo): + self.showUserSessionOverview(sessionInfo: sessionInfo) } } } @@ -113,15 +113,12 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable { // TODO } - private func showCurrentSessionDetails() { - // TODO + private func showCurrentSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: true)) } - private func showUserSessionDetails(sessionId: String) { - guard let sessionInfo = service.getOtherSession(sessionId: sessionId) else { - return - } - completion?(.openSessionDetails(session: sessionInfo)) + private func showUserSessionOverview(sessionInfo: UserSessionInfo) { + completion?(.openSessionOverview(session: sessionInfo, isCurrentSession: false)) } private func showAllOtherSessions() { diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift index f0aa43861..d0621cd9c 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/Mock/MockUserSessionsOverviewService.swift @@ -17,13 +17,17 @@ import Foundation class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol { - + var lastOverviewData: UserSessionsOverviewData func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) { completion(.success(self.lastOverviewData)) } + func getOtherSession(sessionId: String) -> UserSessionInfo? { + nil + } + init() { let currentSessionInfo = UserSessionInfo(sessionId: "alice", sessionName: "iOS", deviceType: .mobile, isVerified: false, lastSeenIP: "10.0.0.10", lastSeenTimestamp: nil) diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift index e7774be69..dec3b06df 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/Service/UserSessionsOverviewServiceProtocol.swift @@ -29,4 +29,6 @@ protocol UserSessionsOverviewServiceProtocol { var lastOverviewData: UserSessionsOverviewData { get } func fetchUserSessionsOverviewData(completion: @escaping (Result) -> Void) -> Void + + func getOtherSession(sessionId: String) -> UserSessionInfo? } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift index 7f30f0fe3..dd27e0c63 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewModels.swift @@ -19,7 +19,7 @@ import Foundation // MARK: - Coordinator enum UserSessionsOverviewCoordinatorResult { - case openSessionDetails(session: UserSessionInfo) + case openSessionOverview(session: UserSessionInfo, isCurrentSession: Bool) } // MARK: View model @@ -28,9 +28,9 @@ enum UserSessionsOverviewViewModelResult { case showAllUnverifiedSessions case showAllInactiveSessions case verifyCurrentSession - case showCurrentSessionDetails + case showCurrentSessionOverview(sessionInfo: UserSessionInfo) case showAllOtherSessions - case showUserSessionDetails(_ sessionId: String) + case showUserSessionOverview(sessionInfo: UserSessionInfo) } // MARK: View diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift index fcc5d73f4..b3e185d86 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/UserSessionsOverviewViewModel.swift @@ -53,7 +53,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .verifyCurrentSession: self.completion?(.verifyCurrentSession) case .viewCurrentSessionDetails: - self.completion?(.showCurrentSessionDetails) + guard let currentSessionInfo = userSessionsOverviewService.lastOverviewData.currentSessionInfo else { + assertionFailure("currentSessionInfo should be present") + return + } + self.completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo)) case .viewAllUnverifiedSessions: self.completion?(.showAllUnverifiedSessions) case .viewAllInactiveSessions: @@ -61,7 +65,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess case .viewAllOtherSessions: self.completion?(.showAllOtherSessions) case .tapUserSession(let sessionId): - self.completion?(.showUserSessionDetails(sessionId)) + guard let sessionInfo = userSessionsOverviewService.getOtherSession(sessionId: sessionId) else { + assertionFailure("missing session info") + return + } + self.completion?(.showUserSessionOverview(sessionInfo: sessionInfo)) } } diff --git a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift index ae1b65893..7342764c4 100644 --- a/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift +++ b/RiotSwiftUI/Modules/UserSessions/UserSessionsOverview/View/UserSessionListItem.swift @@ -64,10 +64,7 @@ struct UserSessionListItem: View { // Separator // Note: Separator leading is matching the text leading, we could use alignment guide in the future - Rectangle() - .fill(theme.colors.quinaryContent) - .frame(maxWidth: .infinity, alignment: .trailing) - .frame(height: 1.0) + SeparatorLine() .padding(.leading, LayoutConstants.horizontalPadding + LayoutConstants.avatarRightMargin + LayoutConstants.avatarWidth) } .padding(.top, LayoutConstants.verticalPadding)