Separate Manage account from Manage devices. (#1698)

Update the SDK.
This commit is contained in:
Doug
2023-09-13 15:49:01 +01:00
committed by GitHub
parent 8268ecc036
commit da29c7efde
15 changed files with 138 additions and 90 deletions
+1 -1
View File
@@ -5613,7 +5613,7 @@
repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift";
requirement = { requirement = {
kind = exactVersion; kind = exactVersion;
version = 1.1.12; version = 1.1.13;
}; };
}; };
821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = {
@@ -129,8 +129,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/matrix-org/matrix-rust-components-swift", "location" : "https://github.com/matrix-org/matrix-rust-components-swift",
"state" : { "state" : {
"revision" : "8c2307180e468fc40ab72e6a5e8b31a4fbbea4d5", "revision" : "482c8c04019e6e6ac9638ae792adae9d67b08114",
"version" : "1.1.12" "version" : "1.1.13"
} }
}, },
{ {
@@ -35,6 +35,8 @@
"action_learn_more" = "Learn more"; "action_learn_more" = "Learn more";
"action_leave" = "Leave"; "action_leave" = "Leave";
"action_leave_room" = "Leave room"; "action_leave_room" = "Leave room";
"action_manage_account" = "Manage account";
"action_manage_devices" = "Manage devices";
"action_next" = "Next"; "action_next" = "Next";
"action_no" = "No"; "action_no" = "No";
"action_not_now" = "Not now"; "action_not_now" = "Not now";
+4
View File
@@ -86,6 +86,10 @@ public enum L10n {
public static var actionLeave: String { return L10n.tr("Localizable", "action_leave") } public static var actionLeave: String { return L10n.tr("Localizable", "action_leave") }
/// Leave room /// Leave room
public static var actionLeaveRoom: String { return L10n.tr("Localizable", "action_leave_room") } public static var actionLeaveRoom: String { return L10n.tr("Localizable", "action_leave_room") }
/// Manage account
public static var actionManageAccount: String { return L10n.tr("Localizable", "action_manage_account") }
/// Manage devices
public static var actionManageDevices: String { return L10n.tr("Localizable", "action_manage_devices") }
/// Next /// Next
public static var actionNext: String { return L10n.tr("Localizable", "action_next") } public static var actionNext: String { return L10n.tr("Localizable", "action_next") }
/// No /// No
@@ -33,19 +33,27 @@ class SDKClientMock: SDKClientProtocol {
} }
//MARK: - accountUrl //MARK: - accountUrl
public var accountUrlCallsCount = 0 public var accountUrlActionThrowableError: Error?
public var accountUrlCalled: Bool { public var accountUrlActionCallsCount = 0
return accountUrlCallsCount > 0 public var accountUrlActionCalled: Bool {
return accountUrlActionCallsCount > 0
} }
public var accountUrlReturnValue: String? public var accountUrlActionReceivedAction: AccountManagementAction?
public var accountUrlClosure: (() -> String?)? public var accountUrlActionReceivedInvocations: [AccountManagementAction?] = []
public var accountUrlActionReturnValue: String?
public var accountUrlActionClosure: ((AccountManagementAction?) throws -> String?)?
public func accountUrl() -> String? { public func accountUrl(action: AccountManagementAction?) throws -> String? {
accountUrlCallsCount += 1 if let error = accountUrlActionThrowableError {
if let accountUrlClosure = accountUrlClosure { throw error
return accountUrlClosure() }
accountUrlActionCallsCount += 1
accountUrlActionReceivedAction = action
accountUrlActionReceivedInvocations.append(action)
if let accountUrlActionClosure = accountUrlActionClosure {
return try accountUrlActionClosure(action)
} else { } else {
return accountUrlReturnValue return accountUrlActionReturnValue
} }
} }
//MARK: - avatarUrl //MARK: - avatarUrl
@@ -48,23 +48,25 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
switch action { switch action {
case .close: case .close:
self.callback?(.dismiss) callback?(.dismiss)
case .account: case .accountProfile:
self.presentAccountSettings() presentAccountProfileURL()
case .analytics: case .analytics:
self.presentAnalyticsScreen() presentAnalyticsScreen()
case .reportBug: case .reportBug:
self.presentBugReportScreen() presentBugReportScreen()
case .about: case .about:
self.presentLegalInformationScreen() presentLegalInformationScreen()
case .sessionVerification: case .sessionVerification:
self.verifySession() verifySession()
case .accountSessionsList:
presentAccountSessionsListURL()
case .developerOptions: case .developerOptions:
self.presentDeveloperOptions() presentDeveloperOptions()
case .logout: case .logout:
self.callback?(.logout) callback?(.logout)
case .notifications: case .notifications:
self.presentNotificationSettings() presentNotificationSettings()
} }
} }
} }
@@ -75,15 +77,26 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
AnyView(SettingsScreen(context: viewModel.context)) AnyView(SettingsScreen(context: viewModel.context))
} }
// MARK: - Private // MARK: - OIDC Account Management
private var accountSettingsPresenter: OIDCAccountSettingsPresenter? private func presentAccountProfileURL() {
private func presentAccountSettings() { guard let url = viewModel.context.viewState.accountProfileURL else {
guard let accountURL = viewModel.context.viewState.accountURL else {
MXLog.error("Account URL is missing.") MXLog.error("Account URL is missing.")
return return
} }
presentAccountManagementURL(url)
}
private func presentAccountSessionsListURL() {
guard let url = viewModel.context.viewState.accountSessionsListURL else {
MXLog.error("Account URL is missing.")
return
}
presentAccountManagementURL(url)
}
private var accountSettingsPresenter: OIDCAccountSettingsPresenter?
private func presentAccountManagementURL(_ url: URL) {
guard let window = viewModel.context.viewState.window else { guard let window = viewModel.context.viewState.window else {
MXLog.error("The window is missing.") MXLog.error("The window is missing.")
return return
@@ -91,10 +104,12 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
// Note to anyone in the future if you come back here to make this open in Safari instead of a WAS. // Note to anyone in the future if you come back here to make this open in Safari instead of a WAS.
// As of iOS 16, there is an issue on the simulator with accessing the cookie but it works on a device. 🤷‍♂️ // As of iOS 16, there is an issue on the simulator with accessing the cookie but it works on a device. 🤷‍♂️
accountSettingsPresenter = OIDCAccountSettingsPresenter(accountURL: accountURL, presentationAnchor: window) accountSettingsPresenter = OIDCAccountSettingsPresenter(accountURL: url, presentationAnchor: window)
accountSettingsPresenter?.start() accountSettingsPresenter?.start()
} }
// MARK: - Private
private func presentAnalyticsScreen() { private func presentAnalyticsScreen() {
let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: ServiceLocator.shared.settings, let coordinator = AnalyticsSettingsScreenCoordinator(parameters: .init(appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics)) analytics: ServiceLocator.shared.analytics))
@@ -19,11 +19,12 @@ import UIKit
enum SettingsScreenViewModelAction { enum SettingsScreenViewModelAction {
case close case close
case account case accountProfile
case analytics case analytics
case reportBug case reportBug
case about case about
case sessionVerification case sessionVerification
case accountSessionsList
case developerOptions case developerOptions
case notifications case notifications
case logout case logout
@@ -33,7 +34,8 @@ struct SettingsScreenViewState: BindableState {
var bindings: SettingsScreenViewStateBindings var bindings: SettingsScreenViewStateBindings
var deviceID: String? var deviceID: String?
var userID: String var userID: String
var accountURL: URL? var accountProfileURL: URL?
var accountSessionsListURL: URL?
var userAvatarURL: URL? var userAvatarURL: URL?
var userDisplayName: String? var userDisplayName: String?
var showSessionVerificationSection: Bool var showSessionVerificationSection: Bool
@@ -49,13 +51,14 @@ struct SettingsScreenViewStateBindings {
enum SettingsScreenViewAction { enum SettingsScreenViewAction {
case close case close
case account case accountProfile
case analytics case analytics
case reportBug case reportBug
case about case about
case sessionVerification case sessionVerification
case logout case logout
case changedTimelineStyle case changedTimelineStyle
case accountSessionsList
case developerOptions case developerOptions
case notifications case notifications
@@ -39,7 +39,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
super.init(initialViewState: .init(bindings: bindings, super.init(initialViewState: .init(bindings: bindings,
deviceID: userSession.deviceID, deviceID: userSession.deviceID,
userID: userSession.userID, userID: userSession.userID,
accountURL: userSession.clientProxy.accountURL, accountProfileURL: userSession.clientProxy.accountURL(action: .profile),
accountSessionsListURL: userSession.clientProxy.accountURL(action: .sessionsList),
showSessionVerificationSection: showSessionVerificationSection, showSessionVerificationSection: showSessionVerificationSection,
showDeveloperOptions: appSettings.canShowDeveloperOptions), showDeveloperOptions: appSettings.canShowDeveloperOptions),
imageProvider: userSession.mediaProvider) imageProvider: userSession.mediaProvider)
@@ -81,8 +82,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
switch viewAction { switch viewAction {
case .close: case .close:
callback?(.close) callback?(.close)
case .account: case .accountProfile:
callback?(.account) callback?(.accountProfile)
case .analytics: case .analytics:
callback?(.analytics) callback?(.analytics)
case .reportBug: case .reportBug:
@@ -97,6 +98,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
appSettings.timelineStyle = state.bindings.timelineStyle appSettings.timelineStyle = state.bindings.timelineStyle
case .notifications: case .notifications:
callback?(.notifications) callback?(.notifications)
case .accountSessionsList:
callback?(.accountSessionsList)
case .developerOptions: case .developerOptions:
callback?(.developerOptions) callback?(.developerOptions)
@@ -15,6 +15,7 @@
// //
import Compound import Compound
import SFSafeSymbols
import SwiftUI import SwiftUI
struct SettingsScreen: View { struct SettingsScreen: View {
@@ -30,7 +31,11 @@ struct SettingsScreen: View {
sessionVerificationSection sessionVerificationSection
} }
simplifiedSection mainSection
if context.viewState.accountSessionsListURL != nil {
manageSessionsSection
}
if context.viewState.showDeveloperOptions { if context.viewState.showDeveloperOptions {
developerOptionsSection developerOptionsSection
@@ -41,20 +46,12 @@ struct SettingsScreen: View {
.compoundList() .compoundList()
.navigationTitle(L10n.commonSettings) .navigationTitle(L10n.commonSettings)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar { toolbar }
ToolbarItem(placement: .confirmationAction) {
doneButton
}
}
.introspect(.window, on: .iOS(.v16, .v17)) { window in .introspect(.window, on: .iOS(.v16, .v17)) { window in
context.send(viewAction: .updateWindow(window)) context.send(viewAction: .updateWindow(window))
} }
} }
private var versionText: Text {
Text(L10n.settingsVersionNumber(InfoPlistReader.main.bundleShortVersionString, InfoPlistReader.main.bundleVersion))
}
private var userSection: some View { private var userSection: some View {
Section { Section {
ListRow(kind: .custom { ListRow(kind: .custom {
@@ -90,25 +87,13 @@ struct SettingsScreen: View {
} }
} }
private var developerOptionsSection: some View { private var mainSection: some View {
Section {
ListRow(label: .default(title: L10n.commonDeveloperOptions,
systemIcon: .hammerCircle),
kind: .navigationLink {
context.send(viewAction: .developerOptions)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
}
}
private var simplifiedSection: some View {
Section { Section {
// Account // Account
if context.viewState.accountURL != nil { if context.viewState.accountProfileURL != nil {
ListRow(label: .default(title: L10n.screenSettingsOidcAccount, ListRow(label: .default(title: L10n.actionManageAccount, systemIcon: .person),
systemIcon: .person),
kind: .button { kind: .button {
context.send(viewAction: .account) context.send(viewAction: .accountProfile)
}) })
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.account) .accessibilityIdentifier(A11yIdentifiers.settingsScreen.account)
} }
@@ -157,6 +142,36 @@ struct SettingsScreen: View {
} }
} }
private var manageSessionsSection: some View {
Section {
ListRow(label: .default(title: L10n.actionManageDevices, systemIcon: deviceIcon),
kind: .button {
context.send(viewAction: .accountSessionsList)
})
}
}
private var deviceIcon: SFSymbol {
if ProcessInfo.processInfo.isiOSAppOnMac {
return .macbookAndIphone
} else if UIDevice.current.userInterfaceIdiom == .pad {
return .ipad
} else {
return .iphone
}
}
private var developerOptionsSection: some View {
Section {
ListRow(label: .default(title: L10n.commonDeveloperOptions,
systemIcon: .hammerCircle),
kind: .navigationLink {
context.send(viewAction: .developerOptions)
})
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.developerOptions)
}
}
private var signOutSection: some View { private var signOutSection: some View {
Section { Section {
ListRow(label: .default(title: L10n.screenSignoutPreferenceItem, ListRow(label: .default(title: L10n.screenSignoutPreferenceItem,
@@ -166,9 +181,9 @@ struct SettingsScreen: View {
}) })
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout) .accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout)
.alert(L10n.screenSignoutConfirmationDialogTitle, isPresented: $showingLogoutConfirmation) { .alert(L10n.screenSignoutConfirmationDialogTitle, isPresented: $showingLogoutConfirmation) {
Button(L10n.screenSignoutConfirmationDialogSubmit, Button(L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) {
role: .destructive, context.send(viewAction: .logout)
action: logout) }
} message: { } message: {
Text(L10n.screenSignoutConfirmationDialogContent) Text(L10n.screenSignoutConfirmationDialogContent)
} }
@@ -186,18 +201,16 @@ struct SettingsScreen: View {
.padding(.top, 24) .padding(.top, 24)
} }
} }
private var doneButton: some View { private var versionText: Text {
Button(L10n.actionDone, action: close) Text(L10n.settingsVersionNumber(InfoPlistReader.main.bundleShortVersionString, InfoPlistReader.main.bundleVersion))
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.done)
} }
private func close() { private var toolbar: some ToolbarContent {
context.send(viewAction: .close) ToolbarItem(placement: .confirmationAction) {
} Button(L10n.actionDone) { context.send(viewAction: .close) }
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.done)
private func logout() { }
context.send(viewAction: .logout)
} }
} }
@@ -220,8 +233,7 @@ struct SettingsScreen_Previews: PreviewProvider {
verificationController.isVerified = false verificationController.isVerified = false
let userSession = MockUserSession(sessionVerificationController: verificationController, let userSession = MockUserSession(sessionVerificationController: verificationController,
clientProxy: MockClientProxy(userID: "@userid:example.com", clientProxy: MockClientProxy(userID: "@userid:example.com",
deviceID: "AAAAAAAAAAA", deviceID: "AAAAAAAAAAA"),
accountURL: "https://matrix.org/account"),
mediaProvider: MockMediaProvider()) mediaProvider: MockMediaProvider())
return SettingsScreenViewModel(userSession: userSession, return SettingsScreenViewModel(userSession: userSession,
appSettings: ServiceLocator.shared.settings) appSettings: ServiceLocator.shared.settings)
@@ -125,10 +125,6 @@ class ClientProxy: ClientProxyProtocol {
return nil return nil
} }
} }
var accountURL: URL? {
client.accountUrl().flatMap(URL.init(string:))
}
func startSync() { func startSync() {
guard !hasEncounteredAuthError else { guard !hasEncounteredAuthError else {
@@ -155,6 +151,10 @@ class ClientProxy: ClientProxyProtocol {
} }
} }
func accountURL(action: AccountManagementAction) -> URL? {
try? client.accountUrl(action: action).flatMap(URL.init(string:))
}
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> { func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
await Task.dispatch(on: clientQueue) { await Task.dispatch(on: clientQueue) {
do { do {
@@ -83,8 +83,6 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
var restorationToken: RestorationToken? { get } var restorationToken: RestorationToken? { get }
var accountURL: URL? { get }
var roomSummaryProvider: RoomSummaryProviderProtocol? { get } var roomSummaryProvider: RoomSummaryProviderProtocol? { get }
var inviteSummaryProvider: RoomSummaryProviderProtocol? { get } var inviteSummaryProvider: RoomSummaryProviderProtocol? { get }
@@ -95,6 +93,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol {
func stopSync() func stopSync()
func accountURL(action: AccountManagementAction) -> URL?
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError>
func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result<String, ClientProxyError> func createDirectRoom(with userID: String, expectedRoomName: String?) async -> Result<String, ClientProxyError>
@@ -27,7 +27,6 @@ class MockClientProxy: ClientProxyProtocol {
let deviceID: String? let deviceID: String?
let homeserver = "" let homeserver = ""
let restorationToken: RestorationToken? = nil let restorationToken: RestorationToken? = nil
let accountURL: URL?
var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()
@@ -37,21 +36,22 @@ class MockClientProxy: ClientProxyProtocol {
var notificationSettings: NotificationSettingsProxyProtocol = NotificationSettingsProxyMock(with: .init()) var notificationSettings: NotificationSettingsProxyProtocol = NotificationSettingsProxyMock(with: .init())
init(userID: String, deviceID: String? = nil, accountURL: URL? = nil, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) { init(userID: String, deviceID: String? = nil, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) {
self.userID = userID self.userID = userID
self.deviceID = deviceID self.deviceID = deviceID
self.accountURL = accountURL
self.roomSummaryProvider = roomSummaryProvider self.roomSummaryProvider = roomSummaryProvider
} }
func loadUserAvatarURL() async { } func loadUserAvatarURL() async { }
func startSync() { } func startSync() { }
func stopSync(completionHandler: () -> Void) { }
func stopSync() { } func stopSync() { }
func accountURL(action: AccountManagementAction) -> URL? {
"https://matrix.org/account"
}
func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> { func directRoomForUserID(_ userID: String) async -> Result<String?, ClientProxyError> {
.failure(.failedRetrievingDirectRoom) .failure(.failedRetrievingDirectRoom)
} }
@@ -170,7 +170,7 @@ class MockScreen: Identifiable {
return navigationStackCoordinator return navigationStackCoordinator
case .settings: case .settings:
let navigationStackCoordinator = NavigationStackCoordinator() let navigationStackCoordinator = NavigationStackCoordinator()
let clientProxy = MockClientProxy(userID: "@mock:client.com", accountURL: "https://matrix.org/account") let clientProxy = MockClientProxy(userID: "@mock:client.com")
let coordinator = SettingsScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator, let coordinator = SettingsScreenCoordinator(parameters: .init(navigationStackCoordinator: navigationStackCoordinator,
userIndicatorController: nil, userIndicatorController: nil,
userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()), userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()),
+1
View File
@@ -0,0 +1 @@
Separate Manage account from Manage devices
+1 -1
View File
@@ -46,7 +46,7 @@ packages:
# Element/Matrix dependencies # Element/Matrix dependencies
MatrixRustSDK: MatrixRustSDK:
url: https://github.com/matrix-org/matrix-rust-components-swift url: https://github.com/matrix-org/matrix-rust-components-swift
exactVersion: 1.1.12 exactVersion: 1.1.13
# path: ../matrix-rust-sdk # path: ../matrix-rust-sdk
DesignKit: DesignKit:
path: DesignKit path: DesignKit