mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-13 11:19:59 +02:00
Merge pull request #7676 from vector-im/release/1.11.3/release
Release 1.11.3
This commit is contained in:
@@ -1,3 +1,11 @@
|
||||
## Changes in 1.11.3 (2023-09-13)
|
||||
|
||||
🐛 Bugfixes
|
||||
|
||||
- Show OIDC account management UI using embedded browser instead of system browser. ([#7671](https://github.com/vector-im/element-ios/issues/7671))
|
||||
- Hide Sign Out X/All Sessions buttons in the Device Manager when using OIDC. ([#7672](https://github.com/vector-im/element-ios/issues/7672))
|
||||
|
||||
|
||||
## Changes in 1.11.2 (2023-09-12)
|
||||
|
||||
🙌 Improvements
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
//
|
||||
|
||||
// Version
|
||||
MARKETING_VERSION = 1.11.2
|
||||
CURRENT_PROJECT_VERSION = 1.11.2
|
||||
MARKETING_VERSION = 1.11.3
|
||||
CURRENT_PROJECT_VERSION = 1.11.3
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Copyright 2020 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
|
||||
|
||||
@objcMembers
|
||||
/// A service for the SSOAuthenticationPresenter that allows to open an OIDC account management URL.
|
||||
///
|
||||
/// Both `callBackURLScheme` and `loginToken` are unneeded for this use case and return `nil`.
|
||||
final class SSOAccountService: NSObject, SSOAuthenticationServiceProtocol {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let accountURL: URL
|
||||
|
||||
let callBackURLScheme: String? = nil
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(accountURL: URL) {
|
||||
self.accountURL = accountURL
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func authenticationURL(for identityProvider: String?, transactionId: String) -> URL? {
|
||||
accountURL
|
||||
}
|
||||
|
||||
func loginToken(from url: URL) -> String? {
|
||||
MXLog.error("The account service shouldn't receive a completion callback.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ final class SSOAuthenticationPresenter: NSObject {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let ssoAuthenticationService: SSOAuthenticationService
|
||||
private let ssoAuthenticationService: SSOAuthenticationServiceProtocol
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -53,7 +53,7 @@ final class SSOAuthenticationPresenter: NSObject {
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(ssoAuthenticationService: SSOAuthenticationService) {
|
||||
init(ssoAuthenticationService: SSOAuthenticationServiceProtocol) {
|
||||
self.ssoAuthenticationService = ssoAuthenticationService
|
||||
super.init()
|
||||
}
|
||||
|
||||
@@ -22,8 +22,16 @@ enum SSOAuthenticationServiceError: Error {
|
||||
case unknown
|
||||
}
|
||||
|
||||
@objc protocol SSOAuthenticationServiceProtocol {
|
||||
var callBackURLScheme: String? { get }
|
||||
|
||||
func authenticationURL(for identityProvider: String?, transactionId: String) -> URL?
|
||||
|
||||
func loginToken(from url: URL) -> String?
|
||||
}
|
||||
|
||||
@objcMembers
|
||||
final class SSOAuthenticationService: NSObject {
|
||||
final class SSOAuthenticationService: NSObject, SSOAuthenticationServiceProtocol {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ enum {
|
||||
};
|
||||
|
||||
|
||||
@interface ManageSessionViewController () <UserVerificationCoordinatorBridgePresenterDelegate>
|
||||
@interface ManageSessionViewController () <UserVerificationCoordinatorBridgePresenterDelegate, SSOAuthenticationPresenterDelegate>
|
||||
{
|
||||
// The device to display
|
||||
MXDevice *device;
|
||||
@@ -64,6 +64,8 @@ enum {
|
||||
|
||||
@property (nonatomic, strong) ReauthenticationCoordinatorBridgePresenter *reauthenticationCoordinatorBridgePresenter;
|
||||
|
||||
@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ManageSessionViewController
|
||||
@@ -679,17 +681,19 @@ enum {
|
||||
{
|
||||
UIAlertController *alert = [UIAlertController alertControllerWithTitle: [VectorL10n manageSessionRedirect] message: nil preferredStyle:UIAlertControllerStyleAlert];
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
MXWeakify(self);
|
||||
UIAlertAction *action = [UIAlertAction actionWithTitle:[VectorL10n ok]
|
||||
style:UIAlertActionStyleDefault
|
||||
handler: ^(UIAlertAction * action) {
|
||||
[UIApplication.sharedApplication openURL:url options:@{} completionHandler:^(BOOL success) {
|
||||
if (success && weakSelf)
|
||||
{
|
||||
[weakSelf withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
}];
|
||||
MXStrongifyAndReturnIfNil(self);
|
||||
SSOAccountService *service = [[SSOAccountService alloc] initWithAccountURL:url];
|
||||
SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:service];
|
||||
presenter.delegate = self;
|
||||
self.ssoAuthenticationPresenter = presenter;
|
||||
|
||||
[presenter presentForIdentityProvider:nil with:@"" from:self animated:YES];
|
||||
}];
|
||||
|
||||
[alert addAction: action];
|
||||
[self presentViewController:alert animated:YES completion:nil];
|
||||
}
|
||||
@@ -755,4 +759,27 @@ enum {
|
||||
[self reloadDeviceWithCompletion:^{}];
|
||||
}
|
||||
|
||||
#pragma mark - SSOAuthenticationPresenterDelegate
|
||||
|
||||
- (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogDebug(@"OIDC account management complete.")
|
||||
[self withdrawViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogError(@"OIDC account management failed.")
|
||||
}
|
||||
|
||||
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter
|
||||
authenticationSucceededWithToken:(NSString *)token
|
||||
usingIdentityProvider:(SSOIdentityProvider *)identityProvider
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogWarning(@"Unexpected callback after OIDC account management.")
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -204,7 +204,8 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate,
|
||||
ServiceTermsModalCoordinatorBridgePresenterDelegate,
|
||||
TableViewSectionsDelegate,
|
||||
ThreadsBetaCoordinatorBridgePresenterDelegate,
|
||||
ChangePasswordCoordinatorBridgePresenterDelegate>
|
||||
ChangePasswordCoordinatorBridgePresenterDelegate,
|
||||
SSOAuthenticationPresenterDelegate>
|
||||
{
|
||||
// Current alert (if any).
|
||||
__weak UIAlertController *currentAlert;
|
||||
@@ -300,6 +301,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
|
||||
@property (nonatomic) BOOL isPreparingIdentityService;
|
||||
@property (nonatomic, strong) ServiceTermsModalCoordinatorBridgePresenter *serviceTermsModalCoordinatorBridgePresenter;
|
||||
|
||||
@property (nonatomic, strong) SSOAuthenticationPresenter *ssoAuthenticationPresenter;
|
||||
|
||||
@property (nonatomic) AnalyticsScreenTracker *screenTracker;
|
||||
|
||||
@end
|
||||
@@ -3926,7 +3929,12 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
|
||||
{
|
||||
NSURL *url = [NSURL URLWithString: self.mainSession.homeserverWellknown.authentication.account];
|
||||
if (url) {
|
||||
[UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil];
|
||||
SSOAccountService *service = [[SSOAccountService alloc] initWithAccountURL:url];
|
||||
SSOAuthenticationPresenter *presenter = [[SSOAuthenticationPresenter alloc] initWithSsoAuthenticationService:service];
|
||||
presenter.delegate = self;
|
||||
self.ssoAuthenticationPresenter = presenter;
|
||||
|
||||
[presenter presentForIdentityProvider:nil with:@"" from:self animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4602,4 +4610,26 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
|
||||
[self.userSessionsFlowCoordinatorBridgePresenter pushFrom:self.navigationController animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - SSOAuthenticationPresenterDelegate
|
||||
|
||||
- (void)ssoAuthenticationPresenterDidCancel:(SSOAuthenticationPresenter *)presenter
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogDebug(@"OIDC account management complete.")
|
||||
}
|
||||
|
||||
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter authenticationDidFailWithError:(NSError *)error
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogError(@"OIDC account management failed.")
|
||||
}
|
||||
|
||||
- (void)ssoAuthenticationPresenter:(SSOAuthenticationPresenter *)presenter
|
||||
authenticationSucceededWithToken:(NSString *)token
|
||||
usingIdentityProvider:(SSOIdentityProvider *)identityProvider
|
||||
{
|
||||
self.ssoAuthenticationPresenter = nil;
|
||||
MXLogWarning(@"Unexpected callback after OIDC account management.")
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -32,6 +32,7 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable {
|
||||
private var errorPresenter: MXKErrorPresentation
|
||||
private var indicatorPresenter: UserIndicatorTypePresenterProtocol
|
||||
private var loadingIndicator: UserIndicator?
|
||||
private var ssoAuthenticationPresenter: SSOAuthenticationPresenter?
|
||||
|
||||
/// The root coordinator for user session management.
|
||||
private weak var sessionsOverviewCoordinator: UserSessionsOverviewCoordinator?
|
||||
@@ -188,21 +189,25 @@ final class UserSessionsFlowCoordinator: NSObject, Coordinator, Presentable {
|
||||
private func createOtherSessionsCoordinator(sessionInfos: [UserSessionInfo],
|
||||
filterBy filter: UserOtherSessionsFilter,
|
||||
title: String) -> UserOtherSessionsCoordinator {
|
||||
let shouldShowDeviceLogout = parameters.session.homeserverWellknown.authentication == nil
|
||||
let parameters = UserOtherSessionsCoordinatorParameters(sessionInfos: sessionInfos,
|
||||
filter: filter,
|
||||
title: title)
|
||||
title: title,
|
||||
showDeviceLogout: shouldShowDeviceLogout)
|
||||
return UserOtherSessionsCoordinator(parameters: parameters)
|
||||
}
|
||||
|
||||
private func openDeviceLogoutRedirectURL(_ url: URL) {
|
||||
let alert = UIAlertController(title: VectorL10n.manageSessionRedirect, message: nil, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: VectorL10n.ok, style: .default) { [weak self] _ in
|
||||
UIApplication.shared.open(url) { [weak self] success in
|
||||
guard success else {
|
||||
return
|
||||
}
|
||||
self?.popToSessionsOverview()
|
||||
}
|
||||
guard let self else { return }
|
||||
|
||||
let service = SSOAccountService(accountURL: url)
|
||||
let presenter = SSOAuthenticationPresenter(ssoAuthenticationService: service)
|
||||
presenter.delegate = self
|
||||
self.ssoAuthenticationPresenter = presenter
|
||||
|
||||
presenter.present(forIdentityProvider: nil, with: "", from: self.toPresentable(), animated: true)
|
||||
})
|
||||
alert.popoverPresentationController?.sourceView = toPresentable().view
|
||||
navigationRouter.present(alert, animated: true)
|
||||
@@ -547,3 +552,25 @@ private extension UserOtherSessionsFilter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ASWebAuthenticationPresentationContextProviding
|
||||
|
||||
extension UserSessionsFlowCoordinator: SSOAuthenticationPresenterDelegate {
|
||||
func ssoAuthenticationPresenterDidCancel(_ presenter: SSOAuthenticationPresenter) {
|
||||
ssoAuthenticationPresenter = nil
|
||||
MXLog.info("OIDC account management complete.")
|
||||
popToSessionsOverview()
|
||||
}
|
||||
|
||||
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter, authenticationDidFailWithError error: Error) {
|
||||
ssoAuthenticationPresenter = nil
|
||||
MXLog.error("OIDC account management failed.")
|
||||
}
|
||||
|
||||
func ssoAuthenticationPresenter(_ presenter: SSOAuthenticationPresenter,
|
||||
authenticationSucceededWithToken token: String,
|
||||
usingIdentityProvider identityProvider: SSOIdentityProvider?) {
|
||||
ssoAuthenticationPresenter = nil
|
||||
MXLog.warning("Unexpected callback after OIDC account management.")
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -21,6 +21,7 @@ struct UserOtherSessionsCoordinatorParameters {
|
||||
let sessionInfos: [UserSessionInfo]
|
||||
let filter: UserOtherSessionsFilter
|
||||
let title: String
|
||||
let showDeviceLogout: Bool
|
||||
}
|
||||
|
||||
final class UserOtherSessionsCoordinator: Coordinator, Presentable {
|
||||
@@ -40,6 +41,7 @@ final class UserOtherSessionsCoordinator: Coordinator, Presentable {
|
||||
let viewModel = UserOtherSessionsViewModel(sessionInfos: parameters.sessionInfos,
|
||||
filter: parameters.filter,
|
||||
title: parameters.title,
|
||||
showDeviceLogout: parameters.showDeviceLogout,
|
||||
settingsService: RiotSettings.shared)
|
||||
let view = UserOtherSessions(viewModel: viewModel.context)
|
||||
userOtherSessionsViewModel = viewModel
|
||||
|
||||
+5
@@ -49,26 +49,31 @@ enum MockUserOtherSessionsScreenState: MockScreenState, CaseIterable {
|
||||
viewModel = UserOtherSessionsViewModel(sessionInfos: allSessions(),
|
||||
filter: .all,
|
||||
title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
case .none:
|
||||
viewModel = UserOtherSessionsViewModel(sessionInfos: [],
|
||||
filter: .all,
|
||||
title: VectorL10n.userSessionsOverviewOtherSessionsSectionTitle,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
case .inactiveSessions:
|
||||
viewModel = UserOtherSessionsViewModel(sessionInfos: inactiveSessions(),
|
||||
filter: .inactive,
|
||||
title: VectorL10n.userOtherSessionSecurityRecommendationTitle,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
case .unverifiedSessions:
|
||||
viewModel = UserOtherSessionsViewModel(sessionInfos: unverifiedSessions(),
|
||||
filter: .unverified,
|
||||
title: VectorL10n.userOtherSessionSecurityRecommendationTitle,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
case .verifiedSessions:
|
||||
viewModel = UserOtherSessionsViewModel(sessionInfos: verifiedSessions(),
|
||||
filter: .verified,
|
||||
title: VectorL10n.userOtherSessionSecurityRecommendationTitle,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
}
|
||||
|
||||
|
||||
+27
-13
@@ -63,7 +63,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoInactiveSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -81,7 +82,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -104,7 +106,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoUnverifiedSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(expectedItems.count, 2)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
@@ -123,7 +126,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoVerifiedSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -139,7 +143,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoVerifiedSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -155,7 +160,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoUnverifiedSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -171,7 +177,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: VectorL10n.userOtherSessionNoInactiveSessions,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -192,7 +199,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: true,
|
||||
enableSignOutButton: true,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -213,7 +221,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -233,7 +242,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: true,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -253,7 +263,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: true,
|
||||
enableSignOutButton: true,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -273,7 +284,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -296,7 +308,8 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
emptyItemsTitle: "",
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: false)
|
||||
showLocationInfo: false,
|
||||
showDeviceLogout: true)
|
||||
XCTAssertEqual(sut.state, expectedState)
|
||||
}
|
||||
|
||||
@@ -352,6 +365,7 @@ class UserOtherSessionsViewModelTests: XCTestCase {
|
||||
UserOtherSessionsViewModel(sessionInfos: sessionInfos,
|
||||
filter: filter,
|
||||
title: title,
|
||||
showDeviceLogout: true,
|
||||
settingsService: MockUserSessionSettings())
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ struct UserOtherSessionsViewState: BindableState, Equatable {
|
||||
var allItemsSelected: Bool
|
||||
var enableSignOutButton: Bool
|
||||
var showLocationInfo: Bool
|
||||
var showDeviceLogout: Bool
|
||||
}
|
||||
|
||||
struct UserOtherSessionsBindings: Equatable {
|
||||
|
||||
@@ -28,6 +28,7 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi
|
||||
init(sessionInfos: [UserSessionInfo],
|
||||
filter: UserOtherSessionsFilter,
|
||||
title: String,
|
||||
showDeviceLogout: Bool,
|
||||
settingsService: UserSessionSettingsProtocol) {
|
||||
self.sessionInfos = sessionInfos
|
||||
defaultTitle = title
|
||||
@@ -41,7 +42,8 @@ class UserOtherSessionsViewModel: UserOtherSessionsViewModelType, UserOtherSessi
|
||||
emptyItemsTitle: filter.userOtherSessionsViewEmptyResultsTitle,
|
||||
allItemsSelected: false,
|
||||
enableSignOutButton: false,
|
||||
showLocationInfo: settingsService.showIPAddressesInSessionsManager))
|
||||
showLocationInfo: settingsService.showIPAddressesInSessionsManager,
|
||||
showDeviceLogout: showDeviceLogout))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@@ -60,6 +60,7 @@ struct UserOtherSessions: View {
|
||||
set: { _ in withAnimation { viewModel.send(viewAction: .showLocationInfo) } }),
|
||||
allItemsSelected: viewModel.viewState.allItemsSelected,
|
||||
sessionCount: viewModel.viewState.sessionItems.count,
|
||||
showDeviceLogout: viewModel.viewState.showDeviceLogout,
|
||||
onToggleSelection: { viewModel.send(viewAction: .toggleAllSelection) },
|
||||
onSignOut: { viewModel.send(viewAction: .logoutAllUserSessions) })
|
||||
}
|
||||
|
||||
+9
-6
@@ -24,6 +24,7 @@ struct UserOtherSessionsToolbar: ToolbarContent {
|
||||
@Binding var isShowLocationEnabled: Bool
|
||||
let allItemsSelected: Bool
|
||||
let sessionCount: Int
|
||||
let showDeviceLogout: Bool
|
||||
let onToggleSelection: () -> Void
|
||||
let onSignOut: () -> Void
|
||||
|
||||
@@ -81,12 +82,14 @@ struct UserOtherSessionsToolbar: ToolbarContent {
|
||||
private func optionsMenu() -> some View {
|
||||
Button { } label: {
|
||||
Menu {
|
||||
Button {
|
||||
isEditModeEnabled = true
|
||||
} label: {
|
||||
Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle")
|
||||
if showDeviceLogout { // As you can only sign out the selected sessions, we don't allow selection when you're unable to sign out devices.
|
||||
Button {
|
||||
isEditModeEnabled = true
|
||||
} label: {
|
||||
Label(VectorL10n.userOtherSessionMenuSelectSessions, systemImage: "checkmark.circle")
|
||||
}
|
||||
.disabled(sessionCount == 0)
|
||||
}
|
||||
.disabled(sessionCount == 0)
|
||||
|
||||
Button {
|
||||
isShowLocationEnabled.toggle()
|
||||
@@ -94,7 +97,7 @@ struct UserOtherSessionsToolbar: ToolbarContent {
|
||||
Label(showLocationInfo: isShowLocationEnabled)
|
||||
}
|
||||
|
||||
if sessionCount > 0 {
|
||||
if sessionCount > 0, showDeviceLogout {
|
||||
DestructiveButton {
|
||||
onSignOut()
|
||||
} label: {
|
||||
|
||||
+4
-1
@@ -39,7 +39,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
|
||||
self.parameters = parameters
|
||||
service = parameters.service
|
||||
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service, settingsService: RiotSettings.shared)
|
||||
let shouldShowDeviceLogout = parameters.session.homeserverWellknown.authentication == nil
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service,
|
||||
settingsService: RiotSettings.shared,
|
||||
showDeviceLogout: shouldShowDeviceLogout)
|
||||
|
||||
hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context))
|
||||
hostingViewController.vc_setLargeTitleDisplayMode(.never)
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings(), showDeviceLogout: true)
|
||||
|
||||
return (
|
||||
[service, viewModel],
|
||||
|
||||
+12
-4
@@ -21,7 +21,9 @@ import XCTest
|
||||
|
||||
class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
func testInitialStateEmpty() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(),
|
||||
settingsService: MockUserSessionSettings(),
|
||||
showDeviceLogout: true)
|
||||
|
||||
XCTAssertNil(viewModel.state.currentSessionViewData)
|
||||
XCTAssertTrue(viewModel.state.unverifiedSessionsViewData.isEmpty)
|
||||
@@ -31,7 +33,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLoadOnDidAppear() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(),
|
||||
settingsService: MockUserSessionSettings(),
|
||||
showDeviceLogout: true)
|
||||
viewModel.process(viewAction: .viewAppeared)
|
||||
|
||||
XCTAssertNotNil(viewModel.state.currentSessionViewData)
|
||||
@@ -42,7 +46,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSimpleActionProcessing() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(),
|
||||
settingsService: MockUserSessionSettings(),
|
||||
showDeviceLogout: true)
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
@@ -69,7 +75,9 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
let service = MockUserSessionsOverviewService()
|
||||
service.updateOverviewData { _ in }
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service,
|
||||
settingsService: MockUserSessionSettings(),
|
||||
showDeviceLogout: true)
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
|
||||
@@ -51,6 +51,7 @@ struct UserSessionsOverviewViewState: BindableState {
|
||||
var showLoadingIndicator = false
|
||||
var linkDeviceButtonVisible = false
|
||||
var showLocationInfo: Bool
|
||||
var showDeviceLogout: Bool
|
||||
}
|
||||
|
||||
enum UserSessionsOverviewViewAction {
|
||||
|
||||
+2
-2
@@ -24,11 +24,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess
|
||||
|
||||
var completion: ((UserSessionsOverviewViewModelResult) -> Void)?
|
||||
|
||||
init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol, settingsService: UserSessionSettingsProtocol) {
|
||||
init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol, settingsService: UserSessionSettingsProtocol, showDeviceLogout: Bool) {
|
||||
self.userSessionsOverviewService = userSessionsOverviewService
|
||||
self.settingsService = settingsService
|
||||
|
||||
super.init(initialViewState: .init(showLocationInfo: settingsService.showIPAddressesInSessionsManager))
|
||||
super.init(initialViewState: .init(showLocationInfo: settingsService.showIPAddressesInSessionsManager, showDeviceLogout: showDeviceLogout))
|
||||
|
||||
userSessionsOverviewService.overviewDataPublisher.sink { [weak self] overviewData in
|
||||
self?.updateViewState(with: overviewData)
|
||||
|
||||
+7
-5
@@ -125,7 +125,7 @@ struct UserSessionsOverview: View {
|
||||
Label(VectorL10n.signOut, systemImage: "rectangle.portrait.and.arrow.right.fill")
|
||||
}
|
||||
}
|
||||
if viewModel.viewState.otherSessionsViewData.count > 0 {
|
||||
if viewModel.viewState.otherSessionsViewData.count > 0, viewModel.viewState.showDeviceLogout {
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOtherSessions)
|
||||
} label: {
|
||||
@@ -149,10 +149,12 @@ struct UserSessionsOverview: View {
|
||||
Label(showLocationInfo: viewModel.viewState.showLocationInfo)
|
||||
}
|
||||
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOtherSessions)
|
||||
} label: {
|
||||
Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill")
|
||||
if viewModel.viewState.showDeviceLogout {
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOtherSessions)
|
||||
} label: {
|
||||
Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
menuImage
|
||||
|
||||
Reference in New Issue
Block a user