Cleaned up various things, mostly removed unnecessary selfs, comments and bad formatting

This commit is contained in:
Stefan Ceriu 2022-09-26 18:21:34 +03:00 committed by Stefan Ceriu
parent 492b7410b9
commit 148bc1624a
39 changed files with 141 additions and 350 deletions

View file

@ -17,7 +17,6 @@
import SwiftUI
struct SeparatorLine: View {
@Environment(\.theme) private var theme: ThemeSwiftUI
var body: some View {

View file

@ -19,7 +19,6 @@ import DesignKit
/// Avatar view for device
struct DeviceAvatarView: View {
@Environment(\.theme) var theme: ThemeSwiftUI
var viewData: DeviceAvatarViewData
@ -55,7 +54,6 @@ struct DeviceAvatarView: View {
}
struct DeviceAvatarViewListPreview: View {
var viewDataList: [DeviceAvatarViewData] {
return [
DeviceAvatarViewData(deviceType: .desktop, isVerified: true),
@ -78,7 +76,6 @@ struct DeviceAvatarViewListPreview: View {
}
struct DeviceAvatarView_Previews: PreviewProvider {
static var previews: some View {
Group {
DeviceAvatarViewListPreview().theme(.light).preferredColorScheme(.light)

View file

@ -19,8 +19,6 @@ import SwiftUI
/// View data for DeviceAvatarView
struct DeviceAvatarViewData {
let deviceType: DeviceType
let isVerified: Bool?
}

View file

@ -1,58 +0,0 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
import SwiftUI
extension DeviceType {
var image: Image {
let image: Image
switch self {
case .desktop:
image = Image(Asset.Images.deviceTypeDesktop.name)
case .web:
image = Image(Asset.Images.deviceTypeWeb.name)
case .mobile:
image = Image(Asset.Images.deviceTypeMobile.name)
case .unknown:
image = Image(Asset.Images.deviceTypeUnknown.name)
}
return image
}
var name: String {
let name: String
let appName = AppInfo.current.displayName
switch self {
case .desktop:
name = VectorL10n.deviceNameDesktop(appName)
case .web:
name = VectorL10n.deviceNameWeb(appName)
case .mobile:
name = VectorL10n.deviceNameMobile(appName)
case .unknown:
name = VectorL10n.deviceNameUnknown
}
return name
}
}

View file

@ -23,4 +23,32 @@ enum DeviceType {
case web
case mobile
case unknown
var image: Image {
switch self {
case .desktop:
return Image(Asset.Images.deviceTypeDesktop.name)
case .web:
return Image(Asset.Images.deviceTypeWeb.name)
case .mobile:
return Image(Asset.Images.deviceTypeMobile.name)
case .unknown:
return Image(Asset.Images.deviceTypeUnknown.name)
}
}
var name: String {
let appName = AppInfo.current.displayName
switch self {
case .desktop:
return VectorL10n.deviceNameDesktop(appName)
case .web:
return VectorL10n.deviceNameWeb(appName)
case .mobile:
return VectorL10n.deviceNameMobile(appName)
case .unknown:
return VectorL10n.deviceNameUnknown
}
}
}

View file

@ -18,7 +18,6 @@ import SwiftUI
import DesignKit
struct UserSessionCardView: View {
@Environment(\.theme) var theme: ThemeSwiftUI
var viewData: UserSessionCardViewData
@ -82,7 +81,7 @@ struct UserSessionCardView: View {
.multilineTextAlignment(.center)
}
if self.showExtraInformations {
if showExtraInformations {
VStack(spacing: 2) {
if let lastActivityDateString = viewData.lastActivityDateString, lastActivityDateString.isEmpty == false {
Text(lastActivityDateString)
@ -126,13 +125,12 @@ struct UserSessionCardView: View {
.padding(24)
.frame(maxWidth: .infinity)
.background(theme.colors.background)
.clipShape(self.backgroundShape)
.shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: self.backgroundShape)
.clipShape(backgroundShape)
.shapedBorder(color: theme.colors.quinaryContent, borderWidth: 1.0, shape: backgroundShape)
}
}
struct UserSessionCardViewPreview: View {
@Environment(\.theme) var theme: ThemeSwiftUI
let viewData: UserSessionCardViewData
@ -145,7 +143,7 @@ struct UserSessionCardViewPreview: View {
var body: some View {
VStack {
UserSessionCardView(viewData: self.viewData)
UserSessionCardView(viewData: viewData)
}
.frame(maxWidth: .infinity)
.background(theme.colors.system)
@ -154,7 +152,6 @@ struct UserSessionCardViewPreview: View {
}
struct UserSessionCardView_Previews: PreviewProvider {
static var previews: some View {
Group {
UserSessionCardViewPreview(isCurrentSessionInfo: true).theme(.light).preferredColorScheme(.light)

View file

@ -18,13 +18,8 @@ import Foundation
/// View data for UserSessionCardView
struct UserSessionCardViewData {
// MARK: - Constants
private static let userSessionNameFormatter = UserSessionNameFormatter()
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
// MARK: - Properties
var id: String {
return sessionId
@ -45,8 +40,6 @@ struct UserSessionCardViewData {
/// Indicate if the current user session is shown and to adpat the layout
let isCurrentSessionDisplayMode: Bool
// MARK: - Setup
init(sessionId: String,
sessionDisplayName: String?,
deviceType: DeviceType,
@ -55,7 +48,7 @@ struct UserSessionCardViewData {
lastSeenIP: String?,
isCurrentSessionDisplayMode: Bool = false) {
self.sessionId = sessionId
self.sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
self.isVerified = isVerified
var lastActivityDateString: String?
@ -65,16 +58,21 @@ struct UserSessionCardViewData {
}
self.lastActivityDateString = lastActivityDateString
self.lastSeenIPInfo = lastSeenIP
self.deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: nil)
lastSeenIPInfo = lastSeenIP
deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: nil)
self.isCurrentSessionDisplayMode = isCurrentSessionDisplayMode
}
}
extension UserSessionCardViewData {
init(userSessionInfo: UserSessionInfo, isCurrentSessionDisplayMode: Bool = false) {
self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityTimestamp: userSessionInfo.lastSeenTimestamp, lastSeenIP: userSessionInfo.lastSeenIP, isCurrentSessionDisplayMode: isCurrentSessionDisplayMode)
self.init(sessionId: userSessionInfo.sessionId,
sessionDisplayName: userSessionInfo.sessionName,
deviceType: userSessionInfo.deviceType,
isVerified: userSessionInfo.isVerified,
lastActivityTimestamp: userSessionInfo.lastSeenTimestamp,
lastSeenIP: userSessionInfo.lastSeenIP,
isCurrentSessionDisplayMode: isCurrentSessionDisplayMode)
}
}

View file

@ -22,11 +22,6 @@ struct UserSessionDetailsCoordinatorParameters {
}
final class UserSessionDetailsCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: UserSessionDetailsCoordinatorParameters
private let userSessionDetailsHostingController: UIViewController
private var userSessionDetailsViewModel: UserSessionDetailsViewModelProtocol
@ -58,13 +53,16 @@ final class UserSessionDetailsCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[UserSessionDetailsCoordinator] did start.")
userSessionDetailsViewModel.completion = { [weak self] result in
guard let self = self else { return }
guard let self = self else {
return
}
MXLog.debug("[UserSessionDetailsCoordinator] UserSessionDetailsViewModel did complete with result: \(result).")
self.completion?(result)
}
}
func toPresentable() -> UIViewController {
return self.userSessionDetailsHostingController
return userSessionDetailsHostingController
}
}

View file

@ -18,7 +18,6 @@ import XCTest
import RiotSwiftUI
class UserSessionDetailsUITests: MockScreenTestCase {
func test_longPressDetailsCell_CopiesValueToClipboard() throws {
app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title)

View file

@ -19,7 +19,6 @@ import XCTest
@testable import RiotSwiftUI
class UserSessionDetailsViewModelTests: XCTestCase {
func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() {
let userSessionInfo = createUserSessionInfo(sessionId: "session",
sessionName: nil,

View file

@ -29,7 +29,6 @@ enum UserSessionDetailsViewAction {
}
struct UserSessionDetailsViewState: BindableState, Equatable {
let sections: [UserSessionDetailsSectionViewData]
}
@ -47,7 +46,6 @@ struct UserSessionDetailsSectionItemViewData: Identifiable {
}
extension UserSessionDetailsSectionViewData: Equatable {
static func == (lhs: UserSessionDetailsSectionViewData, rhs: UserSessionDetailsSectionViewData) -> Bool {
lhs.header == rhs.header &&
lhs.footer == rhs.footer &&
@ -56,7 +54,6 @@ extension UserSessionDetailsSectionViewData: Equatable {
}
extension UserSessionDetailsSectionItemViewData: Equatable {
static func == (lhs: UserSessionDetailsSectionItemViewData, rhs: UserSessionDetailsSectionItemViewData) -> Bool {
lhs.title == rhs.title &&
lhs.value == rhs.value

View file

@ -21,17 +21,8 @@ typealias UserSessionDetailsViewModelType = StateStoreViewModel<UserSessionDetai
UserSessionDetailsViewAction>
class UserSessionDetailsViewModel: UserSessionDetailsViewModelType, UserSessionDetailsViewModelProtocol {
// MARK: - Properties
// MARK: Private
// MARK: Public
var completion: ((UserSessionDetailsViewModelResult) -> Void)?
// MARK: - Setup
init(userSessionInfo: UserSessionInfo) {
super.init(initialViewState: UserSessionDetailsViewState(sections: []))
updateViewState(userSessionInfo: userSessionInfo)

View file

@ -17,7 +17,6 @@
import Foundation
protocol UserSessionDetailsViewModelProtocol {
var completion: ((UserSessionDetailsViewModelResult) -> Void)? { get set }
var context: UserSessionDetailsViewModelType.Context { get }
}

View file

@ -17,11 +17,6 @@
import SwiftUI
struct UserSessionDetails: View {
// MARK: - Properties
// MARK: Private
private enum LayoutConstants {
static let listItemHorizontalPadding: CGFloat = 20
static let sectionVerticalPadding: CGFloat = 8
@ -29,8 +24,6 @@ struct UserSessionDetails: View {
@Environment(\.theme) private var theme: ThemeSwiftUI
// MARK: Public
@ObservedObject var viewModel: UserSessionDetailsViewModel.Context
var body: some View {

View file

@ -17,15 +17,8 @@
import SwiftUI
struct UserSessionDetailsItem: View {
// MARK: - Properties
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
// MARK: Public
let viewData: UserSessionDetailsSectionItemViewData
let horizontalPadding: CGFloat

View file

@ -18,9 +18,6 @@ import Foundation
/// Enables to build last activity date string
class UserSessionLastActivityFormatter {
// MARK: - Constants
private static var lastActivityDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
@ -30,8 +27,6 @@ class UserSessionLastActivityFormatter {
return dateFormatter
}()
// MARK: - Public
/// Session last activity string
func lastActivityDateString(from lastActivityTimestamp: TimeInterval) -> String {

View file

@ -17,11 +17,9 @@
import Foundation
/// Enables to build user session name
class UserSessionNameFormatter {
class UserSessionNameFormatter {
/// Session name with client name and session display name
func sessionName(deviceType: DeviceType, sessionDisplayName: String?) -> String {
let sessionName: String
let clientName = deviceType.name

View file

@ -18,7 +18,6 @@ import Foundation
/// Represents a user session information
struct UserSessionInfo: Identifiable {
/// Delay after which session is considered inactive, 90 days
static let inactiveSessionDurationTreshold: TimeInterval = 90 * 86400
@ -67,12 +66,9 @@ struct UserSessionInfo: Identifiable {
if let lastSeenTimestamp = lastSeenTimestamp {
let elapsedTime = Date().timeIntervalSince1970 - lastSeenTimestamp
let isSessionInactive = elapsedTime >= Self.inactiveSessionDurationTreshold
self.isSessionActive = !isSessionInactive
isSessionActive = elapsedTime < Self.inactiveSessionDurationTreshold
} else {
self.isSessionActive = true
isSessionActive = true
}
}
}

View file

@ -23,14 +23,9 @@ struct UserSessionOverviewCoordinatorParameters {
}
final class UserSessionOverviewCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: UserSessionOverviewCoordinatorParameters
private let userSessionOverviewHostingController: UIViewController
private var userSessionOverviewViewModel: UserSessionOverviewViewModelProtocol
private let hostingController: UIViewController
private var viewModel: UserSessionOverviewViewModelProtocol
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var loadingIndicator: UserIndicator?
@ -45,20 +40,20 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable {
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)
viewModel = UserSessionOverviewViewModel(userSessionInfo: parameters.userSessionInfo,
isCurrentSession: parameters.isCurrentSession)
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionOverviewHostingController)
hostingController = VectorHostingController(rootView: UserSessionOverview(viewModel: viewModel.context))
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingController)
}
// MARK: - Public
func start() {
MXLog.debug("[UserSessionOverviewCoordinator] did start.")
userSessionOverviewViewModel.completion = { [weak self] result in
viewModel.completion = { [weak self] result in
guard let self = self else { return }
MXLog.debug("[UserSessionOverviewCoordinator] UserSessionOverviewViewModel did complete with result: \(result).")
switch result {
@ -71,7 +66,7 @@ final class UserSessionOverviewCoordinator: Coordinator, Presentable {
}
func toPresentable() -> UIViewController {
return self.userSessionOverviewHostingController
return hostingController
}
// MARK: - Private

View file

@ -60,10 +60,6 @@ enum MockUserSessionOverviewScreenState: MockScreenState, CaseIterable {
}
// can simulate service and viewModel actions here if needs be.
return (
[viewModel],
AnyView(UserSessionOverview(viewModel: viewModel.context))
)
return ([viewModel], AnyView(UserSessionOverview(viewModel: viewModel.context)))
}
}

View file

@ -18,7 +18,6 @@ import XCTest
import RiotSwiftUI
class UserSessionOverviewUITests: MockScreenTestCase {
func test_whenCurrentSessionSelected_correctNavTittleDisplayed() {
app.goToScreenWithIdentifier(MockUserSessionOverviewScreenState.currentSession.title)
let navTitle = VectorL10n.userSessionOverviewCurrentSessionTitle

View file

@ -20,7 +20,6 @@ import Combine
@testable import RiotSwiftUI
class UserSessionOverviewViewModelTests: XCTestCase {
var sut: UserSessionOverviewViewModel!
func test_whenVerifyCurrentSessionProcessed_completionWithVerifyCurrentSessionCalled() {

View file

@ -21,17 +21,10 @@ typealias UserSessionOverviewViewModelType = StateStoreViewModel<UserSessionOver
UserSessionOverviewViewAction>
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

View file

@ -17,7 +17,6 @@
import Foundation
protocol UserSessionOverviewViewModelProtocol {
var completion: ((UserSessionOverviewViewModelResult) -> Void)? { get set }
var context: UserSessionOverviewViewModelType.Context { get }
}

View file

@ -17,15 +17,8 @@
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 {

View file

@ -17,7 +17,6 @@
import SwiftUI
struct UserSessionOverviewDisclosureCell: View {
@Environment(\.theme) private var theme: ThemeSwiftUI
let title: String

View file

@ -22,33 +22,24 @@ struct UserSessionsFlowCoordinatorParameters {
}
final class UserSessionsFlowCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: UserSessionsFlowCoordinatorParameters
private let navigationRouter: NavigationRouterType
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: (() -> Void)?
// MARK: - Setup
init(parameters: UserSessionsFlowCoordinatorParameters) {
self.parameters = parameters
self.navigationRouter = parameters.router ?? NavigationRouter(navigationController: RiotNavigationController())
navigationRouter = parameters.router ?? NavigationRouter(navigationController: RiotNavigationController())
}
// MARK: - Private
private func pushScreen(with coordinator: Coordinator & Presentable) {
add(childCoordinator: coordinator)
self.navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in
navigationRouter.push(coordinator, animated: true, popCompletion: { [weak self] in
self?.remove(childCoordinator: coordinator)
})
@ -56,7 +47,7 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable {
}
private func createUserSessionsOverviewCoordinator() -> UserSessionsOverviewCoordinator {
let parameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session)
let parameters = UserSessionsOverviewCoordinatorParameters(session: parameters.session)
let coordinator = UserSessionsOverviewCoordinator(parameters: parameters)
coordinator.completion = { [weak self] result in
@ -104,15 +95,15 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable {
let rootCoordinator = createUserSessionsOverviewCoordinator()
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
add(childCoordinator: rootCoordinator)
if self.navigationRouter.modules.isEmpty == false {
self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in
if navigationRouter.modules.isEmpty == false {
navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
self?.completion?()
})
} else {
self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in
navigationRouter.setRootModule(rootCoordinator) { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
self?.completion?()
}
@ -120,6 +111,6 @@ final class UserSessionsFlowCoordinator: Coordinator, Presentable {
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
return navigationRouter.toPresentable()
}
}

View file

@ -24,22 +24,11 @@ import UIKit
/// Each bridge should be removed once the underlying Coordinator has been integrated by another Coordinator.
@objcMembers
final class UserSessionsFlowCoordinatorBridgePresenter: NSObject {
// MARK: - Constants
// MARK: - Properties
// MARK: Private
private let mxSession: MXSession
private var coordinator: UserSessionsFlowCoordinator?
// MARK: Public
var completion: (() -> Void)?
// MARK: - Setup
init(mxSession: MXSession) {
self.mxSession = mxSession
super.init()
@ -48,26 +37,22 @@ final class UserSessionsFlowCoordinatorBridgePresenter: NSObject {
// MARK: - Public
func push(from navigationController: UINavigationController, animated: Bool) {
self.startUserSessionsFlow(mxSession: self.mxSession, navigationController: navigationController)
startUserSessionsFlow(mxSession: mxSession, navigationController: navigationController)
}
// MARK: - Private
private func startUserSessionsFlow(mxSession: MXSession, navigationController: UINavigationController?) {
var navigationRouter: NavigationRouterType?
if let navigationController = navigationController {
navigationRouter = NavigationRouterStore.shared.navigationRouter(for: navigationController)
}
let coordinatorParameters = UserSessionsFlowCoordinatorParameters(session: mxSession, router: navigationRouter)
let parameters = UserSessionsFlowCoordinatorParameters(session: mxSession, router: navigationRouter)
let coordinator = UserSessionsFlowCoordinator(parameters: parameters)
let userSessionsFlowCoordinator = UserSessionsFlowCoordinator(parameters: coordinatorParameters)
userSessionsFlowCoordinator.completion = { [weak self] in
coordinator.completion = { [weak self] in
guard let self = self else {
return
}
@ -76,8 +61,8 @@ final class UserSessionsFlowCoordinatorBridgePresenter: NSObject {
self.coordinator = nil
}
userSessionsFlowCoordinator.start()
coordinator.start()
self.coordinator = userSessionsFlowCoordinator
self.coordinator = coordinator
}
}

View file

@ -22,39 +22,24 @@ struct UserSessionsOverviewCoordinatorParameters {
}
final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: UserSessionsOverviewCoordinatorParameters
private let userSessionsOverviewHostingController: UIViewController
private var userSessionsOverviewViewModel: UserSessionsOverviewViewModelProtocol
private let hostingViewController: UIViewController
private var viewModel: UserSessionsOverviewViewModelProtocol
private let service: UserSessionsOverviewService
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var loadingIndicator: UserIndicator?
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: ((UserSessionsOverviewCoordinatorResult) -> Void)?
// MARK: - Setup
init(parameters: UserSessionsOverviewCoordinatorParameters) {
self.parameters = parameters
let service = UserSessionsOverviewService(mxSession: parameters.session)
self.service = service
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
let view = UserSessionsOverview(viewModel: viewModel.context)
userSessionsOverviewViewModel = viewModel
let hostingViewController = VectorHostingController(rootView: view)
userSessionsOverviewHostingController = hostingViewController
service = UserSessionsOverviewService(mxSession: parameters.session)
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context))
indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: hostingViewController)
}
@ -62,9 +47,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
func start() {
MXLog.debug("[UserSessionsOverviewCoordinator] did start.")
userSessionsOverviewViewModel.completion = { [weak self] result in
viewModel.completion = { [weak self] result in
guard let self = self else { return }
MXLog.debug("[UserSessionsOverviewCoordinator] UserSessionsOverviewViewModel did complete with result: \(result).")
switch result {
case .showAllUnverifiedSessions:
self.showAllUnverifiedSessions()
@ -83,7 +69,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
}
func toPresentable() -> UIViewController {
return self.userSessionsOverviewHostingController
return hostingViewController
}
// MARK: - Private

View file

@ -18,33 +18,25 @@ import Foundation
import MatrixSDK
class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
// MARK: - Constants
// MARK: - Properties
// MARK: Private
private let mxSession: MXSession
// MARK: Public
private(set) var lastOverviewData: UserSessionsOverviewData
// MARK: - Setup
init(mxSession: MXSession) {
self.mxSession = mxSession
self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: nil, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: [])
lastOverviewData = UserSessionsOverviewData(currentSessionInfo: nil,
unverifiedSessionsInfo: [],
inactiveSessionsInfo: [],
otherSessionsInfo: [])
self.setupInitialOverviewData()
setupInitialOverviewData()
}
// MARK: - Public
func fetchUserSessionsOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) {
self.mxSession.matrixRestClient.devices { response in
mxSession.matrixRestClient.devices { response in
switch response {
case .success(let devices):
self.lastOverviewData = self.userSessionsOverviewData(from: devices)
@ -62,21 +54,20 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
// MARK: - Private
private func setupInitialOverviewData() {
let currentSessionInfo = self.getCurrentUserSessionInfoFromCache()
let currentSessionInfo = getCurrentUserSessionInfoFromCache()
self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: [])
lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: [], inactiveSessionsInfo: [], otherSessionsInfo: [])
}
private func getCurrentUserSessionInfoFromCache() -> UserSessionInfo? {
guard let mainAccount = MXKAccountManager.shared().activeAccounts.first, let device = mainAccount.device else {
return nil
}
return self.userSessionInfo(from: device)
return userSessionInfo(from: device)
}
private func userSessionInfo(from device: MXDevice) -> UserSessionInfo {
let deviceInfo = self.getDeviceInfo(for: device.deviceId)
let deviceInfo = getDeviceInfo(for: device.deviceId)
let isSessionVerified = deviceInfo?.trustLevel.isVerified ?? false
@ -95,20 +86,20 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
}
private func getDeviceInfo(for deviceId: String) -> MXDeviceInfo? {
guard let userId = self.mxSession.myUserId else {
guard let userId = mxSession.myUserId else {
return nil
}
return self.mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId)
return mxSession.crypto.device(withDeviceId: deviceId, ofUser: userId)
}
private func userSessionsOverviewData(from devices: [MXDevice]) -> UserSessionsOverviewData {
let sortedDevices = devices.sorted { device1, device2 in
device1.lastSeenTs > device2.lastSeenTs
}
let allUserSessionInfo = sortedDevices.map { device in
return self.userSessionInfo(from: device)
return userSessionInfo(from: device)
}
var currentSessionInfo: UserSessionInfo?
@ -118,7 +109,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
var otherSessionsInfo: [UserSessionInfo] = []
for userSessionInfo in allUserSessionInfo {
if userSessionInfo.sessionId == self.mxSession.myDeviceId {
if userSessionInfo.sessionId == mxSession.myDeviceId {
currentSessionInfo = userSessionInfo
} else {
otherSessionsInfo.append(userSessionInfo)

View file

@ -17,11 +17,10 @@
import Foundation
class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
var lastOverviewData: UserSessionsOverviewData
func fetchUserSessionsOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) {
completion(.success(self.lastOverviewData))
completion(.success(lastOverviewData))
}
func getOtherSession(sessionId: String) -> UserSessionInfo? {
@ -41,6 +40,9 @@ class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
UserSessionInfo(sessionId: "3", sessionName: "Android", deviceType: .mobile, isVerified: false, lastSeenIP: "3.0.0.3", lastSeenTimestamp: (Date().timeIntervalSince1970 - 10))
]
self.lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo, unverifiedSessionsInfo: unverifiedSessionsInfo, inactiveSessionsInfo: inactiveSessionsInfo, otherSessionsInfo: otherSessionsInfo)
lastOverviewData = UserSessionsOverviewData(currentSessionInfo: currentSessionInfo,
unverifiedSessionsInfo: unverifiedSessionsInfo,
inactiveSessionsInfo: inactiveSessionsInfo,
otherSessionsInfo: otherSessionsInfo)
}
}

View file

@ -17,7 +17,6 @@
import Foundation
struct UserSessionsOverviewData {
let currentSessionInfo: UserSessionInfo?
let unverifiedSessionsInfo: [UserSessionInfo]
let inactiveSessionsInfo: [UserSessionInfo]
@ -25,7 +24,6 @@ struct UserSessionsOverviewData {
}
protocol UserSessionsOverviewServiceProtocol {
var lastOverviewData: UserSessionsOverviewData { get }
func fetchUserSessionsOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) -> Void

View file

@ -20,7 +20,6 @@ import Combine
@testable import RiotSwiftUI
class UserSessionsOverviewViewModelTests: XCTestCase {
var service: MockUserSessionsOverviewService!
var viewModel: UserSessionsOverviewViewModelProtocol!
var context: UserSessionsOverviewViewModelType.Context!

View file

@ -36,14 +36,13 @@ enum UserSessionsOverviewViewModelResult {
// MARK: View
struct UserSessionsOverviewViewState: BindableState {
var unverifiedSessionsViewData: [UserSessionListItemViewData]
var inactiveSessionsViewData: [UserSessionListItemViewData]
var currentSessionViewData: UserSessionCardViewData?
var otherSessionsViewData: [UserSessionListItemViewData]
var unverifiedSessionsViewData = [UserSessionListItemViewData]()
var inactiveSessionsViewData = [UserSessionListItemViewData]()
var otherSessionsViewData = [UserSessionListItemViewData]()
var showLoadingIndicator: Bool = false
}

View file

@ -21,91 +21,63 @@ typealias UserSessionsOverviewViewModelType = StateStoreViewModel<UserSessionsOv
UserSessionsOverviewViewAction>
class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSessionsOverviewViewModelProtocol {
// MARK: - Properties
// MARK: Private
private let userSessionsOverviewService: UserSessionsOverviewServiceProtocol
// MARK: Public
var completion: ((UserSessionsOverviewViewModelResult) -> Void)?
// MARK: - Setup
init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol) {
self.userSessionsOverviewService = userSessionsOverviewService
let initialViewState = UserSessionsOverviewViewState(unverifiedSessionsViewData: [], inactiveSessionsViewData: [], currentSessionViewData: nil, otherSessionsViewData: [])
super.init(initialViewState: .init())
super.init(initialViewState: initialViewState)
self.updateViewState(with: userSessionsOverviewService.lastOverviewData)
updateViewState(with: userSessionsOverviewService.lastOverviewData)
}
// MARK: - Public
override func process(viewAction: UserSessionsOverviewViewAction) {
switch viewAction {
case .viewAppeared:
self.loadData()
loadData()
case .verifyCurrentSession:
self.completion?(.verifyCurrentSession)
completion?(.verifyCurrentSession)
case .viewCurrentSessionDetails:
guard let currentSessionInfo = userSessionsOverviewService.lastOverviewData.currentSessionInfo else {
assertionFailure("currentSessionInfo should be present")
return
return
}
self.completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo))
completion?(.showCurrentSessionOverview(sessionInfo: currentSessionInfo))
case .viewAllUnverifiedSessions:
self.completion?(.showAllUnverifiedSessions)
completion?(.showAllUnverifiedSessions)
case .viewAllInactiveSessions:
self.completion?(.showAllInactiveSessions)
completion?(.showAllInactiveSessions)
case .viewAllOtherSessions:
self.completion?(.showAllOtherSessions)
completion?(.showAllOtherSessions)
case .tapUserSession(let sessionId):
guard let sessionInfo = userSessionsOverviewService.getOtherSession(sessionId: sessionId) else {
assertionFailure("missing session info")
return
}
self.completion?(.showUserSessionOverview(sessionInfo: sessionInfo))
completion?(.showUserSessionOverview(sessionInfo: sessionInfo))
}
}
// MARK: - Private
private func updateViewState(with userSessionsViewData: UserSessionsOverviewData) {
let unverifiedSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.unverifiedSessionsInfo)
let inactiveSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.inactiveSessionsInfo)
var currentSessionViewData: UserSessionCardViewData?
let otherSessionsViewData = self.userSessionListItemViewDataList(from: userSessionsViewData.otherSessionsInfo)
state.unverifiedSessionsViewData = userSessionsViewData.unverifiedSessionsInfo.asViewData()
state.inactiveSessionsViewData = userSessionsViewData.inactiveSessionsInfo.asViewData()
state.otherSessionsViewData = userSessionsViewData.otherSessionsInfo.asViewData()
if let currentSessionInfo = userSessionsViewData.currentSessionInfo {
currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true)
}
self.state.unverifiedSessionsViewData = unverifiedSessionsViewData
self.state.inactiveSessionsViewData = inactiveSessionsViewData
self.state.currentSessionViewData = currentSessionViewData
self.state.otherSessionsViewData = otherSessionsViewData
}
private func userSessionListItemViewDataList(from userSessionInfoList: [UserSessionInfo]) -> [UserSessionListItemViewData] {
return userSessionInfoList.map {
return UserSessionListItemViewData(userSessionInfo: $0)
state.currentSessionViewData = UserSessionCardViewData(userSessionInfo: currentSessionInfo, isCurrentSessionDisplayMode: true)
}
}
private func loadData() {
state.showLoadingIndicator = true
self.state.showLoadingIndicator = true
self.userSessionsOverviewService.fetchUserSessionsOverviewData { [weak self] result in
userSessionsOverviewService.fetchUserSessionsOverviewData { [weak self] result in
guard let self = self else {
return
}
@ -122,3 +94,9 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess
}
}
}
private extension Collection where Element == UserSessionInfo {
func asViewData() -> [UserSessionListItemViewData] {
map { UserSessionListItemViewData(userSessionInfo: $0) }
}
}

View file

@ -17,7 +17,6 @@
import Foundation
protocol UserSessionsOverviewViewModelProtocol {
var completion: ((UserSessionsOverviewViewModelResult) -> Void)? { get set }
var context: UserSessionsOverviewViewModelType.Context { get }

View file

@ -17,9 +17,6 @@
import SwiftUI
struct UserSessionListItem: View {
// MARK: - Constants
private enum LayoutConstants {
static let horizontalPadding: CGFloat = 15
static let verticalPadding: CGFloat = 16
@ -27,23 +24,16 @@ struct UserSessionListItem: View {
static let avatarRightMargin: CGFloat = 18
}
// MARK: - Properties
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
// MARK: Public
let viewData: UserSessionListItemViewData
var onBackgroundTap: ((String) -> (Void))? = nil
// MARK: - Body
var body: some View {
Button(action: { onBackgroundTap?(self.viewData.sessionId)
}) {
Button {
onBackgroundTap?(viewData.sessionId)
} label: {
VStack(alignment: .leading, spacing: LayoutConstants.verticalPadding) {
HStack(spacing: LayoutConstants.avatarRightMargin) {
DeviceAvatarView(viewData: viewData.deviceAvatarViewData)
@ -74,7 +64,6 @@ struct UserSessionListItem: View {
}
struct UserSessionListPreview: View {
let userSessionsOverviewService: UserSessionsOverviewServiceProtocol = MockUserSessionsOverviewService()
var body: some View {

View file

@ -18,14 +18,9 @@ import Foundation
/// View data for UserSessionListItem
struct UserSessionListItemViewData: Identifiable {
// MARK: - Constants
private static let userSessionNameFormatter = UserSessionNameFormatter()
private static let lastActivityDateFormatter = UserSessionLastActivityFormatter()
// MARK: - Properties
var id: String {
return sessionId
}
@ -38,8 +33,6 @@ struct UserSessionListItemViewData: Identifiable {
let deviceAvatarViewData: DeviceAvatarViewData
// MARK: - Setup
init(sessionId: String,
sessionDisplayName: String?,
deviceType: DeviceType,
@ -47,15 +40,14 @@ struct UserSessionListItemViewData: Identifiable {
lastActivityDate: TimeInterval?) {
self.sessionId = sessionId
self.sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
self.sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate)
self.deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified)
sessionName = Self.userSessionNameFormatter.sessionName(deviceType: deviceType, sessionDisplayName: sessionDisplayName)
sessionDetails = Self.buildSessionDetails(isVerified: isVerified, lastActivityDate: lastActivityDate)
deviceAvatarViewData = DeviceAvatarViewData(deviceType: deviceType, isVerified: isVerified)
}
// MARK: - Private
private static func buildSessionDetails(isVerified: Bool, lastActivityDate: TimeInterval?) -> String {
let sessionDetailsString: String
let sessionStatusText = isVerified ? VectorL10n.userSessionVerifiedShort : VectorL10n.userSessionUnverifiedShort
@ -77,7 +69,6 @@ struct UserSessionListItemViewData: Identifiable {
}
extension UserSessionListItemViewData {
init(userSessionInfo: UserSessionInfo) {
self.init(sessionId: userSessionInfo.sessionId, sessionDisplayName: userSessionInfo.sessionName, deviceType: userSessionInfo.deviceType, isVerified: userSessionInfo.isVerified, lastActivityDate: userSessionInfo.lastSeenTimestamp)
}

View file

@ -17,11 +17,6 @@
import SwiftUI
struct UserSessionsOverview: View {
// MARK: - Properties
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
@ViewBuilder
@ -52,7 +47,6 @@ struct UserSessionsOverview: View {
var body: some View {
ScrollView {
// Security recommendations section
if viewModel.viewState.unverifiedSessionsViewData.isEmpty == false || viewModel.viewState.inactiveSessionsViewData.isEmpty == false {
@ -64,7 +58,7 @@ struct UserSessionsOverview: View {
// Other sessions section
if viewModel.viewState.otherSessionsViewData.isEmpty == false {
self.otherSessionsSection
otherSessionsSection
}
}
.background(theme.colors.system.ignoresSafeArea())
@ -77,7 +71,6 @@ struct UserSessionsOverview: View {
}
private var otherSessionsSection: some View {
SwiftUI.Section {
// Device list
LazyVStack(spacing: 0) {