mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Rename screens (#826)
* Rename: AnalyticsPrompt -> AnalyticsPromptScreen * Rename: AnalyticsSettings -> AnalyticsSettingsScreen * Rename: Settings -> SettingsScreen * Rename: SessionVerification -> SessionVerificationScreen * Rename: BugReport -> BugReportScreen * Rename: FilePreview -> FilePreviewScreen * Rename: AnalyticsPrompt -> AnalyticsPromptScreen * Rename: Invites -> InvitesScreen * Rename: InviteUsers -> InviteUsersScreen * Rename: MediaPicker -> MediaPickerScreen * Delete unused UIActivityViewControllerWrapper * Rename: ReportContent -> ReportContentScreen * Rename: RoomDetails -> RoomDetailsScreen * Rename: RoomMemberDetails -> RoomMemberDetailsScreen * Rename: RoomMemberList -> RoomMemberListScreen * Rename: Cleanup * Renaming missed items * Low effort attempt at fixing the flakey unit tests
This commit is contained in:
@@ -1,123 +0,0 @@
|
||||
//
|
||||
// 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 SettingsScreenCoordinatorParameters {
|
||||
weak var navigationStackCoordinator: NavigationStackCoordinator?
|
||||
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
||||
let userSession: UserSessionProtocol
|
||||
let bugReportService: BugReportServiceProtocol
|
||||
}
|
||||
|
||||
enum SettingsScreenCoordinatorAction {
|
||||
case dismiss
|
||||
case logout
|
||||
}
|
||||
|
||||
final class SettingsScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: SettingsScreenCoordinatorParameters
|
||||
private var viewModel: SettingsScreenViewModelProtocol
|
||||
|
||||
var callback: ((SettingsScreenCoordinatorAction) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
init(parameters: SettingsScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = SettingsScreenViewModel(withUserSession: parameters.userSession)
|
||||
viewModel.callback = { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .close:
|
||||
self.callback?(.dismiss)
|
||||
case .analytics:
|
||||
self.presentAnalyticsScreen()
|
||||
case .reportBug:
|
||||
self.presentBugReportScreen()
|
||||
case .sessionVerification:
|
||||
self.verifySession()
|
||||
case .developerOptions:
|
||||
self.presentDeveloperOptions()
|
||||
case .logout:
|
||||
self.callback?(.logout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(SettingsScreen(context: viewModel.context))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func presentAnalyticsScreen() {
|
||||
let coordinator = AnalyticsSettingsScreenCoordinator()
|
||||
parameters.navigationStackCoordinator?.push(coordinator)
|
||||
}
|
||||
|
||||
private func presentBugReportScreen() {
|
||||
let params = BugReportCoordinatorParameters(bugReportService: parameters.bugReportService,
|
||||
userID: parameters.userSession.userID,
|
||||
deviceID: parameters.userSession.deviceID,
|
||||
userIndicatorController: parameters.userIndicatorController,
|
||||
screenshot: nil,
|
||||
isModallyPresented: false)
|
||||
let coordinator = BugReportCoordinator(parameters: params)
|
||||
coordinator.completion = { [weak self] result in
|
||||
switch result {
|
||||
case .finish:
|
||||
self?.showSuccess(label: L10n.actionDone)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self?.parameters.navigationStackCoordinator?.pop()
|
||||
}
|
||||
|
||||
parameters.navigationStackCoordinator?.push(coordinator)
|
||||
}
|
||||
|
||||
private func verifySession() {
|
||||
guard let sessionVerificationController = parameters.userSession.sessionVerificationController else {
|
||||
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||
}
|
||||
|
||||
let verificationParameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
let coordinator = SessionVerificationCoordinator(parameters: verificationParameters)
|
||||
|
||||
coordinator.callback = { [weak self] in
|
||||
self?.parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
}
|
||||
|
||||
parameters.navigationStackCoordinator?.setSheetCoordinator(coordinator) { [weak self] in
|
||||
self?.parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentDeveloperOptions() {
|
||||
let coordinator = DeveloperOptionsScreenCoordinator()
|
||||
parameters.navigationStackCoordinator?.push(coordinator)
|
||||
}
|
||||
|
||||
private func showSuccess(label: String) {
|
||||
parameters.userIndicatorController?.submitIndicator(UserIndicator(title: label))
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//
|
||||
// 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 UIKit
|
||||
|
||||
enum SettingsScreenViewModelAction {
|
||||
case close
|
||||
case analytics
|
||||
case reportBug
|
||||
case sessionVerification
|
||||
case developerOptions
|
||||
case logout
|
||||
}
|
||||
|
||||
struct SettingsScreenViewState: BindableState {
|
||||
var bindings: SettingsScreenViewStateBindings
|
||||
var deviceID: String?
|
||||
var userID: String
|
||||
var userAvatarURL: URL?
|
||||
var userDisplayName: String?
|
||||
var showSessionVerificationSection: Bool
|
||||
var showDeveloperOptions: Bool
|
||||
}
|
||||
|
||||
struct SettingsScreenViewStateBindings {
|
||||
var timelineStyle: TimelineStyle
|
||||
}
|
||||
|
||||
enum SettingsScreenViewAction {
|
||||
case close
|
||||
case analytics
|
||||
case reportBug
|
||||
case sessionVerification
|
||||
case logout
|
||||
case changedTimelineStyle
|
||||
case developerOptions
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
//
|
||||
// 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 Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias SettingsScreenViewModelType = StateStoreViewModel<SettingsScreenViewState, SettingsScreenViewAction>
|
||||
|
||||
class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewModelProtocol {
|
||||
private let userSession: UserSessionProtocol
|
||||
|
||||
var callback: ((SettingsScreenViewModelAction) -> Void)?
|
||||
|
||||
init(withUserSession userSession: UserSessionProtocol) {
|
||||
self.userSession = userSession
|
||||
let bindings = SettingsScreenViewStateBindings(timelineStyle: ServiceLocator.shared.settings.timelineStyle)
|
||||
|
||||
var showSessionVerificationSection = false
|
||||
if let sessionVerificationController = userSession.sessionVerificationController {
|
||||
showSessionVerificationSection = !sessionVerificationController.isVerified
|
||||
}
|
||||
|
||||
super.init(initialViewState: .init(bindings: bindings,
|
||||
deviceID: userSession.deviceID,
|
||||
userID: userSession.userID,
|
||||
showSessionVerificationSection: showSessionVerificationSection,
|
||||
showDeveloperOptions: ServiceLocator.shared.settings.canShowDeveloperOptions),
|
||||
imageProvider: userSession.mediaProvider)
|
||||
|
||||
ServiceLocator.shared.settings.$timelineStyle
|
||||
.weakAssign(to: \.state.bindings.timelineStyle, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSession.clientProxy.avatarURLPublisher
|
||||
.weakAssign(to: \.state.userAvatarURL, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
Task {
|
||||
await userSession.clientProxy.loadUserAvatarURL()
|
||||
}
|
||||
|
||||
Task {
|
||||
if case let .success(userDisplayName) = await self.userSession.clientProxy.loadUserDisplayName() {
|
||||
state.userDisplayName = userDisplayName
|
||||
}
|
||||
}
|
||||
|
||||
userSession.callbacks
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] callback in
|
||||
switch callback {
|
||||
case .sessionVerificationNeeded:
|
||||
self?.state.showSessionVerificationSection = true
|
||||
case .didVerifySession:
|
||||
self?.state.showSessionVerificationSection = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func process(viewAction: SettingsScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .close:
|
||||
callback?(.close)
|
||||
case .analytics:
|
||||
callback?(.analytics)
|
||||
case .reportBug:
|
||||
callback?(.reportBug)
|
||||
case .logout:
|
||||
callback?(.logout)
|
||||
case .sessionVerification:
|
||||
callback?(.sessionVerification)
|
||||
case .changedTimelineStyle:
|
||||
ServiceLocator.shared.settings.timelineStyle = state.bindings.timelineStyle
|
||||
case .developerOptions:
|
||||
callback?(.developerOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
@MainActor
|
||||
protocol SettingsScreenViewModelProtocol {
|
||||
var callback: ((SettingsScreenViewModelAction) -> Void)? { get set }
|
||||
var context: SettingsScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
//
|
||||
// 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 Compound
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsScreen: View {
|
||||
@State private var showingLogoutConfirmation = false
|
||||
|
||||
@ObservedObject var context: SettingsScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
userSection
|
||||
|
||||
if context.viewState.showSessionVerificationSection {
|
||||
sessionVerificationSection
|
||||
}
|
||||
|
||||
simplifiedSection
|
||||
|
||||
if context.viewState.showDeveloperOptions {
|
||||
developerOptionsSection
|
||||
}
|
||||
|
||||
signOutSection
|
||||
}
|
||||
.compoundForm()
|
||||
.navigationTitle(L10n.commonSettings)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
doneButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var versionText: some View {
|
||||
Text(L10n.settingsVersionNumber(InfoPlistReader.main.bundleShortVersionString, InfoPlistReader.main.bundleVersion))
|
||||
}
|
||||
|
||||
private var userSection: some View {
|
||||
Section {
|
||||
HStack(spacing: 12) {
|
||||
LoadableAvatarImage(url: context.viewState.userAvatarURL,
|
||||
name: context.viewState.userDisplayName,
|
||||
contentID: context.viewState.userID,
|
||||
avatarSize: .user(on: .settings),
|
||||
imageProvider: context.imageProvider)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(context.viewState.userDisplayName ?? "")
|
||||
.font(.compound.headingMD)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
Text(context.viewState.userID)
|
||||
.font(.compound.bodySM)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
.compoundFormSection()
|
||||
}
|
||||
|
||||
private var sessionVerificationSection: some View {
|
||||
Section {
|
||||
Button { context.send(viewAction: .sessionVerification) } label: {
|
||||
Label(L10n.actionCompleteVerification, systemImage: "checkmark.shield")
|
||||
}
|
||||
.buttonStyle(.compoundForm())
|
||||
}
|
||||
.compoundFormSection()
|
||||
}
|
||||
|
||||
private var developerOptionsSection: some View {
|
||||
Section {
|
||||
Button { context.send(viewAction: .developerOptions) } label: {
|
||||
Label(L10n.commonDeveloperOptions, systemImage: "hammer.circle")
|
||||
}
|
||||
.buttonStyle(.compoundForm(accessory: .navigationLink))
|
||||
.accessibilityIdentifier("developerOptionsButton")
|
||||
}
|
||||
.compoundFormSection()
|
||||
}
|
||||
|
||||
private var simplifiedSection: some View {
|
||||
Section {
|
||||
Picker(selection: $context.timelineStyle) {
|
||||
ForEach(TimelineStyle.allCases, id: \.self) { style in
|
||||
Text(style.name)
|
||||
.tag(style)
|
||||
}
|
||||
} label: {
|
||||
Label(L10n.commonMessageLayout, systemImage: "rectangle.grid.1x2")
|
||||
}
|
||||
.labelStyle(.compoundFormRow())
|
||||
.accessibilityIdentifier("timelineStylePicker")
|
||||
.onChange(of: context.timelineStyle) { _ in
|
||||
context.send(viewAction: .changedTimelineStyle)
|
||||
}
|
||||
|
||||
// Analytics
|
||||
Button { context.send(viewAction: .analytics) } label: {
|
||||
Label(L10n.commonAnalytics, systemImage: "chart.bar")
|
||||
}
|
||||
.buttonStyle(.compoundForm(accessory: .navigationLink))
|
||||
.accessibilityIdentifier("analyticsButton")
|
||||
|
||||
// Report Bug
|
||||
Button { context.send(viewAction: .reportBug) } label: {
|
||||
Label(L10n.actionReportBug, systemImage: "questionmark.circle")
|
||||
}
|
||||
.buttonStyle(.compoundForm(accessory: .navigationLink))
|
||||
.accessibilityIdentifier("reportBugButton")
|
||||
}
|
||||
.compoundFormSection()
|
||||
}
|
||||
|
||||
private var signOutSection: some View {
|
||||
Section {
|
||||
Button { showingLogoutConfirmation = true } label: {
|
||||
Label(L10n.screenSignoutPreferenceItem, systemImage: "rectangle.portrait.and.arrow.right")
|
||||
}
|
||||
.buttonStyle(.compoundForm())
|
||||
.accessibilityIdentifier("logoutButton")
|
||||
.alert(L10n.screenSignoutConfirmationDialogTitle, isPresented: $showingLogoutConfirmation) {
|
||||
Button(L10n.screenSignoutConfirmationDialogSubmit,
|
||||
role: .destructive,
|
||||
action: logout)
|
||||
} message: {
|
||||
Text(L10n.screenSignoutConfirmationDialogContent)
|
||||
}
|
||||
} footer: {
|
||||
VStack {
|
||||
versionText
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
context.viewState.deviceID.map(Text.init)
|
||||
}
|
||||
.compoundFormSectionFooter()
|
||||
.padding(.top, 24)
|
||||
}
|
||||
.compoundFormSection()
|
||||
}
|
||||
|
||||
private var doneButton: some View {
|
||||
Button(L10n.actionDone, action: close)
|
||||
}
|
||||
|
||||
private func close() {
|
||||
context.send(viewAction: .close)
|
||||
}
|
||||
|
||||
private func logout() {
|
||||
context.send(viewAction: .logout)
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineStyle {
|
||||
var name: String {
|
||||
switch self {
|
||||
case .plain:
|
||||
return L10n.commonModern
|
||||
case .bubbles:
|
||||
return L10n.commonBubbles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct SettingsScreen_Previews: PreviewProvider {
|
||||
static let viewModel = {
|
||||
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "@userid:example.com"),
|
||||
mediaProvider: MockMediaProvider())
|
||||
return SettingsScreenViewModel(withUserSession: userSession)
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
SettingsScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user