Skip to content

Commit

Permalink
Merge pull request #6750 from vector-im/alex/6693_dm_session_details
Browse files Browse the repository at this point in the history
Device manager: User session details screen (PSG-685) #6693
  • Loading branch information
Aleksandrs Proskurins authored Sep 22, 2022
2 parents 29117e2 + 4bd3979 commit 5635bf7
Show file tree
Hide file tree
Showing 18 changed files with 725 additions and 18 deletions.
7 changes: 7 additions & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2384,6 +2384,13 @@ To enable access, tap Settings> Location and select Always";
"device_name_mobile" = "%@ Mobile";
"device_name_unknown" = "Unknown client";

"user_session_details_title" = "Session details";
"user_session_details_session_section_header" = "SESSION";
"user_session_details_device_section_header" = "DEVICE";
"user_session_details_session_name" = "Session name";
"user_session_details_session_id" = "Session ID";
"user_session_details_session_section_footer" = "Copy any data by tapping on it and holding it down.";
"user_session_details_device_ip_address" = "IP address";
// MARK: - MatrixKit


Expand Down
28 changes: 28 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8467,6 +8467,34 @@ public class VectorL10n: NSObject {
public static var userIdTitle: String {
return VectorL10n.tr("Vector", "user_id_title")
}
/// IP address
public static var userSessionDetailsDeviceIpAddress: String {
return VectorL10n.tr("Vector", "user_session_details_device_ip_address")
}
/// DEVICE
public static var userSessionDetailsDeviceSectionHeader: String {
return VectorL10n.tr("Vector", "user_session_details_device_section_header")
}
/// Session ID
public static var userSessionDetailsSessionId: String {
return VectorL10n.tr("Vector", "user_session_details_session_id")
}
/// Session name
public static var userSessionDetailsSessionName: String {
return VectorL10n.tr("Vector", "user_session_details_session_name")
}
/// Copy any data by tapping on it and holding it down.
public static var userSessionDetailsSessionSectionFooter: String {
return VectorL10n.tr("Vector", "user_session_details_session_section_footer")
}
/// SESSION
public static var userSessionDetailsSessionSectionHeader: String {
return VectorL10n.tr("Vector", "user_session_details_session_section_header")
}
/// Session details
public static var userSessionDetailsTitle: String {
return VectorL10n.tr("Vector", "user_session_details_title")
}
/// %@ · Last activity %@
public static func userSessionItemDetails(_ p1: String, _ p2: String) -> String {
return VectorL10n.tr("Vector", "user_session_item_details", p1, p2)
Expand Down
1 change: 1 addition & 0 deletions RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Foundation
enum MockAppScreens {
static let appScreens: [MockScreenState.Type] = [
MockUserSessionsOverviewScreenState.self,
MockUserSessionDetailsScreenState.self,
MockLiveLocationLabPromotionScreenState.self,
MockLiveLocationSharingViewerScreenState.self,
MockAuthenticationLoginScreenState.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// 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 UserSessionDetailsCoordinatorParameters {
let session: MXSession
let userSessionInfo: UserSessionInfo
}

final class UserSessionDetailsCoordinator: Coordinator, Presentable {

// MARK: - Properties

// MARK: Private

private let parameters: UserSessionDetailsCoordinatorParameters
private let userSessionDetailsHostingController: UIViewController
private var userSessionDetailsViewModel: UserSessionDetailsViewModelProtocol

private var indicatorPresenter: UserIndicatorTypePresenterProtocol
private var loadingIndicator: UserIndicator?

// MARK: Public

// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: ((UserSessionDetailsViewModelResult) -> Void)?

// MARK: - Setup

init(parameters: UserSessionDetailsCoordinatorParameters) {
self.parameters = parameters

let viewModel = UserSessionDetailsViewModel(userSessionInfo: parameters.userSessionInfo)
let view = UserSessionDetails(viewModel: viewModel.context)
userSessionDetailsViewModel = viewModel
userSessionDetailsHostingController = VectorHostingController(rootView: view)

indicatorPresenter = UserIndicatorTypePresenter(presentingViewController: userSessionDetailsHostingController)
}

// MARK: - Public

func start() {
MXLog.debug("[UserSessionDetailsCoordinator] did start.")
userSessionDetailsViewModel.completion = { [weak self] result in
guard let self = self else { return }
MXLog.debug("[UserSessionDetailsCoordinator] UserSessionDetailsViewModel did complete with result: \(result).")
self.completion?(result)
}
}

func toPresentable() -> UIViewController {
return self.userSessionDetailsHostingController
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// 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

/// Using an enum for the screen allows you define the different state cases with
/// the relevant associated data for each case.
enum MockUserSessionDetailsScreenState: MockScreenState, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case allSections
case sessionSectionOnly

/// The associated screen
var screenType: Any.Type {
UserSessionDetails.self
}

/// A list of screen state definitions
static var allCases: [MockUserSessionDetailsScreenState] {
// Each of the presence statuses
return [.allSections, sessionSectionOnly]
}

/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let currentSessionInfo: UserSessionInfo
switch self {
case .allSections:
currentSessionInfo = UserSessionInfo(sessionId: "session",
sessionName: "iOS",
deviceType: .mobile,
isVerified: false,
lastSeenIP: "10.0.0.10",
lastSeenTimestamp: Date().timeIntervalSince1970 - 100)
case .sessionSectionOnly:
currentSessionInfo = UserSessionInfo(sessionId: "session",
sessionName: "iOS",
deviceType: .mobile,
isVerified: false,
lastSeenIP: nil,
lastSeenTimestamp: Date().timeIntervalSince1970 - 100)
}
let viewModel = UserSessionDetailsViewModel(userSessionInfo: currentSessionInfo)

// can simulate service and viewModel actions here if needs be.

return (
[currentSessionInfo],
AnyView(UserSessionDetails(viewModel: viewModel.context))
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// 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 UserSessionDetailsUITests: MockScreenTestCase {

func test_longPressDetailsCell_CopiesValueToClipboard() throws {
app.goToScreenWithIdentifier(MockUserSessionDetailsScreenState.allSections.title)

UIPasteboard.general.string = ""

let tables = app.tables
let sessionNameIosCell = tables.cells["Session name, iOS"]
sessionNameIosCell.press(forDuration: 0.5)

app.buttons["Copy"].tap()

let clipboard = try XCTUnwrap(UIPasteboard.general.string)
XCTAssertEqual(clipboard,"iOS")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// 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

@testable import RiotSwiftUI

class UserSessionDetailsViewModelTests: XCTestCase {

func test_whenSessionNameAndLastSeenIPNil_viewStateCorrect() {
let userSessionInfo = createUserSessionInfo(sessionId: "session",
sessionName: nil,
lastSeenIP: nil)

var sessionItems = [UserSessionDetailsSectionItemViewData]()
sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId))

var sections = [UserSessionDetailsSectionViewData]()
sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader,
footer: VectorL10n.userSessionDetailsSessionSectionFooter,
items: sessionItems))
let expectedModel = UserSessionDetailsViewState(sections: sections)
let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo)

XCTAssertEqual(sut.state, expectedModel)
}

func test_whenSessionNameNotNilLastSeenIPNil_viewStateCorrect() {
let userSessionInfo = createUserSessionInfo(sessionId: "session",
sessionName: "session name",
lastSeenIP: nil)

var sessionItems = [UserSessionDetailsSectionItemViewData]()
sessionItems.append(sessionNameItem(sessionName: "session name"))
sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId))

var sections = [UserSessionDetailsSectionViewData]()
sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader,
footer: VectorL10n.userSessionDetailsSessionSectionFooter,
items: sessionItems))

let expectedModel = UserSessionDetailsViewState(sections: sections)
let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo)

XCTAssertEqual(sut.state, expectedModel)
}

func test_whenUserSessionInfoContainsAllValues_viewStateCorrect() {
let userSessionInfo = createUserSessionInfo(sessionId: "session",
sessionName: "session name",
lastSeenIP: "0.0.0.0")

var sessionItems = [UserSessionDetailsSectionItemViewData]()
sessionItems.append(sessionNameItem(sessionName: "session name"))
sessionItems.append(sessionIdItem(sessionId: userSessionInfo.sessionId))

var sections = [UserSessionDetailsSectionViewData]()
sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsSessionSectionHeader,
footer: VectorL10n.userSessionDetailsSessionSectionFooter,
items: sessionItems))

var deviceSectionItems = [UserSessionDetailsSectionItemViewData]()
deviceSectionItems.append(UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsDeviceIpAddress,
value: "0.0.0.0"))
sections.append(UserSessionDetailsSectionViewData(header: VectorL10n.userSessionDetailsDeviceSectionHeader,
footer: nil,
items: deviceSectionItems))

let expectedModel = UserSessionDetailsViewState(sections: sections)
let sut = UserSessionDetailsViewModel(userSessionInfo: userSessionInfo)

XCTAssertEqual(sut.state, expectedModel)
}

private func createUserSessionInfo(sessionId: String,
sessionName: String?,
deviceType: DeviceType = .mobile,
isVerified: Bool = false,
lastSeenIP: String?,
lastSeenTimestamp: TimeInterval = Date().timeIntervalSince1970) -> UserSessionInfo {
UserSessionInfo(sessionId: sessionId,
sessionName: sessionName,
deviceType: deviceType,
isVerified: isVerified,
lastSeenIP: lastSeenIP,
lastSeenTimestamp: lastSeenTimestamp)

}

private func sessionNameItem(sessionName: String) -> UserSessionDetailsSectionItemViewData {
UserSessionDetailsSectionItemViewData(title: VectorL10n.userSessionDetailsSessionName,
value: sessionName)
}

private func sessionIdItem(sessionId: String) -> UserSessionDetailsSectionItemViewData {
UserSessionDetailsSectionItemViewData(title: VectorL10n.keyVerificationManuallyVerifyDeviceIdTitle,
value: sessionId)
}
}
Loading

0 comments on commit 5635bf7

Please sign in to comment.