Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Device manager: User session details screen (PSG-685) #6693 #6750

Merged
merged 10 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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