Merge pull request #6609 from vector-im/steve/6585_session_mgmt_empty_screen

Device manager: Add new session management empty screen (PSG-675)
This commit is contained in:
SBiOSoftWhare 2022-08-23 11:02:51 +02:00 committed by GitHub
commit 69e70fb670
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 642 additions and 0 deletions

View file

@ -420,4 +420,8 @@ final class BuildSettings: NSObject {
// MARK: - New App Layout
static let newAppLayoutEnabled = false
// MARK: - Device manager
static let deviceManagerEnabled = false
}

View file

@ -894,6 +894,9 @@ Tap the + to start adding people.";
"manage_session_not_trusted" = "Not trusted";
"manage_session_sign_out" = "Sign out of this session";
// User sessions management
"user_sessions_settings" = "Manage sessions";
// AuthenticatedSessionViewControllerFactory
"authenticated_session_flow_not_supported" = "This app does not support the authentication mechanism on your homeserver.";
@ -2332,6 +2335,10 @@ To enable access, tap Settings> Location and select Always";
"location_sharing_live_lab_promotion_text" = "Please note: this is a labs feature using a temporary implementation that allows the history of your shared location to be permanently visible to other people in the room.";
"location_sharing_live_lab_promotion_activation" = "Enable live location sharing";
// MARK: User sessions management
"user_sessions_overview_title" = "Sessions";
// MARK: - MatrixKit

View file

@ -8391,6 +8391,14 @@ public class VectorL10n: NSObject {
public static var userIdTitle: String {
return VectorL10n.tr("Vector", "user_id_title")
}
/// Sessions
public static var userSessionsOverviewTitle: String {
return VectorL10n.tr("Vector", "user_sessions_overview_title")
}
/// Manage sessions
public static var userSessionsSettings: String {
return VectorL10n.tr("Vector", "user_sessions_settings")
}
/// If you didnt sign in to this session, your account may be compromised.
public static var userVerificationSessionDetailsAdditionalInformationUntrustedCurrentUser: String {
return VectorL10n.tr("Vector", "user_verification_session_details_additional_information_untrusted_current_user")

View file

@ -178,6 +178,7 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE)
typedef NS_ENUM(NSUInteger, SECURITY)
{
SECURITY_BUTTON_INDEX = 0,
DEVICE_MANAGER_INDEX
};
typedef void (^blockSettingsViewController_onReadyToDestroy)(void);
@ -284,6 +285,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
@property (nonatomic, strong) ThreadsBetaCoordinatorBridgePresenter *threadsBetaBridgePresenter;
@property (nonatomic, strong) ChangePasswordCoordinatorBridgePresenter *changePasswordBridgePresenter;
@property (nonatomic, strong) UserSessionsFlowCoordinatorBridgePresenter *userSessionsFlowCoordinatorBridgePresenter;
/**
Whether or not to check for contacts access after the user accepts the service terms. The value of this property is
@ -400,6 +402,13 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
Section *sectionSecurity = [Section sectionWithTag:SECTION_TAG_SECURITY];
[sectionSecurity addRowWithTag:SECURITY_BUTTON_INDEX];
if (BuildSettings.deviceManagerEnabled)
{
// NOTE: Add device manager entry point in the security section atm for debug purpose
[sectionSecurity addRowWithTag:DEVICE_MANAGER_INDEX];
}
sectionSecurity.headerTitle = [VectorL10n settingsSecurity];
[tmpSections addObject:sectionSecurity];
@ -2533,6 +2542,11 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
cell.textLabel.text = [VectorL10n securitySettingsTitle];
[cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme];
break;
case DEVICE_MANAGER_INDEX:
cell = [self getDefaultTableViewCell:tableView];
cell.textLabel.text = [VectorL10n userSessionsSettings];
[cell vc_setAccessoryDisclosureIndicatorWithCurrentTheme];
break;
}
}
else if (section == SECTION_TAG_DEACTIVATE_ACCOUNT)
@ -2885,6 +2899,11 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
[self pushViewController:securityViewController];
break;
}
case DEVICE_MANAGER_INDEX:
{
[self showUserSessionsFlow];
break;
}
}
}
else if (section == SECTION_TAG_NOTIFICATIONS)
@ -4523,4 +4542,35 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
self.changePasswordBridgePresenter = nil;
}
#pragma mark - User sessions management
- (void)showUserSessionsFlow
{
if (!self.mainSession)
{
MXLogError(@"[SettingsViewController] Cannot show user sessions flow, no user session available");
return;
}
if (!self.navigationController)
{
MXLogError(@"[SettingsViewController] Cannot show user sessions flow, no navigation controller available");
return;
}
UserSessionsFlowCoordinatorBridgePresenter *userSessionsFlowCoordinatorBridgePresenter = [[UserSessionsFlowCoordinatorBridgePresenter alloc] initWithMxSession:self.mainSession];
MXWeakify(self);
userSessionsFlowCoordinatorBridgePresenter.completion = ^{
MXStrongifyAndReturnIfNil(self);
self.userSessionsFlowCoordinatorBridgePresenter = nil;
};
self.userSessionsFlowCoordinatorBridgePresenter = userSessionsFlowCoordinatorBridgePresenter;
[self.userSessionsFlowCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES];
}
@end

View file

@ -0,0 +1,76 @@
//
// 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 CommonKit
struct UserSessionsFlowCoordinatorParameters {
let session: MXSession
let router: NavigationRouterType?
}
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())
}
// MARK: - Public
func start() {
MXLog.debug("[UserSessionsFlowCoordinator] did start.")
let rootCoordinatorParameters = UserSessionsOverviewCoordinatorParameters(session: self.parameters.session)
let rootCoordinator = UserSessionsOverviewCoordinator(parameters: rootCoordinatorParameters)
rootCoordinator.start()
self.add(childCoordinator: rootCoordinator)
if self.navigationRouter.modules.isEmpty == false {
self.navigationRouter.push(rootCoordinator, animated: true, popCompletion: { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
self?.completion?()
})
} else {
self.navigationRouter.setRootModule(rootCoordinator) { [weak self] in
self?.remove(childCoordinator: rootCoordinator)
self?.completion?()
}
}
}
func toPresentable() -> UIViewController {
return self.navigationRouter.toPresentable()
}
}

View file

@ -0,0 +1,83 @@
/*
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 MatrixSDK
import UIKit
/// UserSessionsFlowCoordinatorBridgePresenter enables to start UserSessionsFlowCoordinator from a view controller.
/// This bridge is used while waiting for global usage of coordinator pattern.
/// **WARNING**: This class breaks the Coordinator abstraction and it has been introduced for **Objective-C compatibility only** (mainly for integration in legacy view controllers).
/// 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()
}
// MARK: - Public
func push(from navigationController: UINavigationController, animated: Bool) {
self.startUserSessionsFlow(mxSession: self.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 userSessionsFlowCoordinator = UserSessionsFlowCoordinator(parameters: coordinatorParameters)
userSessionsFlowCoordinator.completion = { [weak self] in
guard let self = self else {
return
}
self.completion?()
self.coordinator = nil
}
userSessionsFlowCoordinator.start()
self.coordinator = userSessionsFlowCoordinator
}
}

View file

@ -0,0 +1,67 @@
//
// 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
import CommonKit
struct UserSessionsOverviewCoordinatorParameters {
let session: MXSession
}
final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
// MARK: - Properties
// MARK: Private
private let parameters: UserSessionsOverviewCoordinatorParameters
private let userSessionsOverviewHostingController: UIViewController
private var userSessionsOverviewViewModel: UserSessionsOverviewViewModelProtocol
// MARK: Public
// Must be used only internally
var childCoordinators: [Coordinator] = []
var completion: (() -> Void)?
// MARK: - Setup
init(parameters: UserSessionsOverviewCoordinatorParameters) {
self.parameters = parameters
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: UserSessionsOverviewService())
let view = UserSessionsOverview(viewModel: viewModel.context)
userSessionsOverviewViewModel = viewModel
userSessionsOverviewHostingController = VectorHostingController(rootView: view)
}
// MARK: - Public
func start() {
MXLog.debug("[UserSessionsOverviewCoordinator] did start.")
userSessionsOverviewViewModel.completion = { [weak self] result in
guard let self = self else { return }
MXLog.debug("[UserSessionsOverviewCoordinator] UserSessionsOverviewViewModel did complete with result: \(result).")
switch result {
case .done:
self.completion?()
}
}
}
func toPresentable() -> UIViewController {
return self.userSessionsOverviewHostingController
}
}

View file

@ -0,0 +1,57 @@
//
// 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
/// Using an enum for the screen allows you define the different state cases with
/// the relevant associated data for each case.
enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable {
// A case for each state you want to represent
// with specific, minimal associated data that will allow you
// mock that screen.
case verifiedSession
/// The associated screen
var screenType: Any.Type {
UserSessionsOverview.self
}
/// A list of screen state definitions
static var allCases: [MockUserSessionsOverviewScreenState] {
// Each of the presence statuses
return [.verifiedSession]
}
/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
let service: MockUserSessionsOverviewService = MockUserSessionsOverviewService()
switch self {
case .verifiedSession:
break
}
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
// can simulate service and viewModel actions here if needs be.
return (
[service, viewModel],
AnyView(UserSessionsOverview(viewModel: viewModel.context)
.addDependency(MockAvatarService.example))
)
}
}

View file

@ -0,0 +1,25 @@
//
// 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
class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
// MARK: - Setup
init() {
}
}

View file

@ -0,0 +1,20 @@
//
// 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
class MockUserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
}

View file

@ -0,0 +1,20 @@
//
// 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
protocol UserSessionsOverviewServiceProtocol {
}

View file

@ -0,0 +1,21 @@
//
// 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 XCTest
import RiotSwiftUI
class UserSessionsOverviewUITests: MockScreenTestCase {
}

View file

@ -0,0 +1,33 @@
//
// 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 XCTest
import Combine
@testable import RiotSwiftUI
class UserSessionsOverviewViewModelTests: XCTestCase {
var service: MockUserSessionsOverviewService!
var viewModel: UserSessionsOverviewViewModelProtocol!
var context: UserSessionsOverviewViewModelType.Context!
override func setUpWithError() throws {
service = MockUserSessionsOverviewService()
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
context = viewModel.context
}
}

View file

@ -0,0 +1,38 @@
//
// 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
// MARK: - Coordinator
// MARK: View model
enum UserSessionsOverviewViewModelResult {
case done
}
// MARK: View
struct UserSessionsOverviewViewState: BindableState {
}
enum UserSessionsOverviewViewAction {
case verifyCurrentSession
case viewCurrentSessionDetails
case viewAllUnverifiedSessions
case viewAllInactiveSessions
case viewAllOtherSessions
}

View file

@ -0,0 +1,61 @@
//
// 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
typealias UserSessionsOverviewViewModelType = StateStoreViewModel<UserSessionsOverviewViewState,
Never,
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 viewState = UserSessionsOverviewViewState()
super.init(initialViewState: viewState)
}
// MARK: - Public
override func process(viewAction: UserSessionsOverviewViewAction) {
switch viewAction {
case .verifyCurrentSession:
break
case .viewCurrentSessionDetails:
break
case .viewAllUnverifiedSessions:
break
case .viewAllInactiveSessions:
break
case .viewAllOtherSessions:
break
}
}
}

View file

@ -0,0 +1,24 @@
//
// 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
protocol UserSessionsOverviewViewModelProtocol {
var completion: ((UserSessionsOverviewViewModelResult) -> Void)? { get set }
var context: UserSessionsOverviewViewModelType.Context { get }
}

View file

@ -0,0 +1,47 @@
//
// 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 UserSessionsOverview: View {
// MARK: - Properties
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
// MARK: Public
@ObservedObject var viewModel: UserSessionsOverviewViewModel.Context
var body: some View {
VStack {
}
.background(theme.colors.background)
.frame(maxHeight: .infinity)
.navigationTitle(VectorL10n.userSessionsOverviewTitle)
}
}
// MARK: - Previews
struct UserSessionsOverview_Previews: PreviewProvider {
static let stateRenderer = MockUserSessionsOverviewScreenState.stateRenderer
static var previews: some View {
stateRenderer.screenGroup()
}
}

1
changelog.d/6585.wip Normal file
View file

@ -0,0 +1 @@
Device manager: Add new session management screen.