mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Minimal Settings screen UI (#85)
* #37 Remove sign out button from home screen * #37 Move crash button visibility flag to BuildSettings, import BuildSettings from test targets * #37 Add log out button into the settings screen * #37 Add version into the settings screen * #37 enhance bug report parameters * #37 Remove sign out button from home screen * #37 Move crash button visibility flag to BuildSettings, import BuildSettings from test targets * #37 Add log out button into the settings screen * #37 Add version into the settings screen * #37 enhance bug report parameters * #37 Add changelog * #37 Fix code smells and PR remarks
This commit is contained in:
@@ -197,8 +197,6 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
guard let self = self else { return }
|
||||
|
||||
switch action {
|
||||
case .logout:
|
||||
self.stateMachine.processEvent(.attemptSignOut)
|
||||
case .presentRoom(let roomIdentifier):
|
||||
self.stateMachine.processEvent(.showRoomScreen(roomId: roomIdentifier))
|
||||
case .presentSettings:
|
||||
@@ -218,6 +216,13 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator {
|
||||
let parameters = SettingsCoordinatorParameters(navigationRouter: navigationRouter,
|
||||
bugReportService: bugReportService)
|
||||
let coordinator = SettingsCoordinator(parameters: parameters)
|
||||
coordinator.callback = { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
switch action {
|
||||
case .logout:
|
||||
self.stateMachine.processEvent(.attemptSignOut)
|
||||
}
|
||||
}
|
||||
|
||||
add(childCoordinator: coordinator)
|
||||
coordinator.start()
|
||||
|
||||
@@ -80,10 +80,10 @@ class AppCoordinatorStateMachine {
|
||||
machine.addRoutes(event: .succeededRestoringSession, transitions: [ .restoringSession => .homeScreen ])
|
||||
machine.addRoutes(event: .failedRestoringSession, transitions: [ .restoringSession => .signedOut ])
|
||||
|
||||
machine.addRoutes(event: .attemptSignOut, transitions: [ .homeScreen => .signingOut ])
|
||||
machine.addRoutes(event: .attemptSignOut, transitions: [ .settingsScreen => .signingOut ])
|
||||
|
||||
machine.addRoutes(event: .succeededSigningOut, transitions: [ .signingOut => .signedOut ])
|
||||
machine.addRoutes(event: .failedSigningOut, transitions: [ .signingOut => .homeScreen ])
|
||||
machine.addRoutes(event: .failedSigningOut, transitions: [ .signingOut => .settingsScreen ])
|
||||
machine.addRoutes(event: .showSettingsScreen, transitions: [ .homeScreen => .settingsScreen ])
|
||||
machine.addRoutes(event: .dismissedSettingsScreen, transitions: [ .settingsScreen => .homeScreen ])
|
||||
|
||||
|
||||
@@ -19,4 +19,7 @@ final class BuildSettings {
|
||||
|
||||
static let bugReportGHLabels: [String] = ["Element-X"]
|
||||
|
||||
// MARK: - Settings screen
|
||||
static let settingsCrashButtonVisible: Bool = true
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ struct HomeScreenCoordinatorParameters {
|
||||
}
|
||||
|
||||
enum HomeScreenCoordinatorAction {
|
||||
case logout
|
||||
case presentRoom(roomIdentifier: String)
|
||||
case presentSettings
|
||||
}
|
||||
@@ -63,8 +62,6 @@ final class HomeScreenCoordinator: Coordinator, Presentable {
|
||||
guard let self = self else { return }
|
||||
|
||||
switch action {
|
||||
case .logout:
|
||||
self.callback?(.logout)
|
||||
case .selectRoom(let roomIdentifier):
|
||||
self.callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
case .tapUserAvatar:
|
||||
|
||||
@@ -18,13 +18,11 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
enum HomeScreenViewModelAction {
|
||||
case logout
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
}
|
||||
|
||||
enum HomeScreenViewAction {
|
||||
case logout
|
||||
case loadRoomData(roomIdentifier: String)
|
||||
case selectRoom(roomIdentifier: String)
|
||||
case tapUserAvatar
|
||||
|
||||
@@ -45,8 +45,6 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
override func process(viewAction: HomeScreenViewAction) async {
|
||||
switch viewAction {
|
||||
case .logout:
|
||||
callback?(.logout)
|
||||
case .loadRoomData(let roomIdentifier):
|
||||
loadRoomDataForIdentifier(roomIdentifier)
|
||||
case .selectRoom(let roomIdentifier):
|
||||
|
||||
@@ -106,11 +106,6 @@ struct HomeScreen: View {
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Logout") {
|
||||
context.send(viewAction: .logout)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ struct SettingsCoordinatorParameters {
|
||||
let bugReportService: BugReportServiceProtocol
|
||||
}
|
||||
|
||||
enum SettingsCoordinatorAction {
|
||||
case logout
|
||||
}
|
||||
|
||||
final class SettingsCoordinator: Coordinator, Presentable {
|
||||
|
||||
// MARK: - Properties
|
||||
@@ -39,6 +43,7 @@ final class SettingsCoordinator: Coordinator, Presentable {
|
||||
|
||||
// Must be used only internally
|
||||
var childCoordinators: [Coordinator] = []
|
||||
var callback: ((SettingsCoordinatorAction) -> Void)?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
@@ -60,6 +65,8 @@ final class SettingsCoordinator: Coordinator, Presentable {
|
||||
self.presentBugReportScreen()
|
||||
case .crash:
|
||||
self.parameters.bugReportService.crash()
|
||||
case .logout:
|
||||
self.callback?(.logout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ import Foundation
|
||||
enum SettingsViewModelAction {
|
||||
case reportBug
|
||||
case crash
|
||||
case logout
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct SettingsViewState: BindableState {
|
||||
var crashButtonVisible: Bool
|
||||
var bindings: SettingsViewStateBindings
|
||||
}
|
||||
|
||||
@@ -39,4 +39,5 @@ struct SettingsViewStateBindings {
|
||||
enum SettingsViewAction {
|
||||
case reportBug
|
||||
case crash
|
||||
case logout
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
|
||||
|
||||
init() {
|
||||
let bindings = SettingsViewStateBindings()
|
||||
super.init(initialViewState: .init(crashButtonVisible: true, bindings: bindings))
|
||||
super.init(initialViewState: .init(bindings: bindings))
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -45,6 +45,8 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol {
|
||||
callback?(.reportBug)
|
||||
case .crash:
|
||||
callback?(.crash)
|
||||
case .logout:
|
||||
callback?(.logout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import SwiftUI
|
||||
struct Settings: View {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@State private var showingLogoutConfirmation = false
|
||||
|
||||
// MARK: Public
|
||||
|
||||
@@ -28,24 +30,45 @@ struct Settings: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Button { context.send(viewAction: .reportBug) } label: {
|
||||
Text(ElementL10n.sendBugReport)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.accessibilityIdentifier("reportBugButton")
|
||||
Section {
|
||||
Button { context.send(viewAction: .reportBug) } label: {
|
||||
Text(ElementL10n.sendBugReport)
|
||||
}
|
||||
.foregroundColor(Color.element.primaryContent)
|
||||
.accessibilityIdentifier("reportBugButton")
|
||||
|
||||
if context.viewState.crashButtonVisible {
|
||||
Button { context.send(viewAction: .crash) } label: {
|
||||
Text("Crash the app")
|
||||
if BuildSettings.settingsCrashButtonVisible {
|
||||
Button("Crash the app",
|
||||
role: .destructive) { context.send(viewAction: .crash)
|
||||
}
|
||||
.accessibilityIdentifier("crashButton")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Button { showingLogoutConfirmation = true } label: {
|
||||
Text(ElementL10n.actionSignOut)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.accessibilityIdentifier("crashButton")
|
||||
.foregroundColor(Color.element.primaryContent)
|
||||
.accessibilityIdentifier("logoutButton")
|
||||
.confirmationDialog(ElementL10n.actionSignOutConfirmationSimple,
|
||||
isPresented: $showingLogoutConfirmation,
|
||||
titleVisibility: .visible) {
|
||||
Button(ElementL10n.actionSignOut,
|
||||
role: .destructive) { context.send(viewAction: .logout)
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
versionText
|
||||
}
|
||||
}
|
||||
.navigationTitle(ElementL10n.settings)
|
||||
}
|
||||
|
||||
var versionText: some View {
|
||||
Text(ElementL10n.settingsVersion + ": " + ElementInfoPlist.cfBundleShortVersionString + " (" + ElementInfoPlist.cfBundleVersion + ")")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
@@ -141,23 +141,34 @@ class BugReportService: BugReportServiceProtocol {
|
||||
// MARK: - Private
|
||||
|
||||
private var defaultParams: [MultipartFormData] {
|
||||
[
|
||||
let (localTime, utcTime) = localAndUTCTime(for: Date())
|
||||
return [
|
||||
MultipartFormData(key: "user_agent", type: .text(value: "iOS")),
|
||||
MultipartFormData(key: "app", type: .text(value: applicationId)),
|
||||
MultipartFormData(key: "version", type: .text(value: version)),
|
||||
MultipartFormData(key: "version", type: .text(value: ElementInfoPlist.cfBundleShortVersionString)),
|
||||
MultipartFormData(key: "build", type: .text(value: ElementInfoPlist.cfBundleVersion)),
|
||||
MultipartFormData(key: "os", type: .text(value: os)),
|
||||
MultipartFormData(key: "client", type: .text(value: "Element-X"))
|
||||
MultipartFormData(key: "resolved_language", type: .text(value: Bundle.preferredLanguages[0])),
|
||||
MultipartFormData(key: "user_language", type: .text(value: Bundle.elementLanguage ?? "null")),
|
||||
MultipartFormData(key: "fallback_language", type: .text(value: Bundle.elementFallbackLanguage ?? "null")),
|
||||
MultipartFormData(key: "local_time", type: .text(value: localTime)),
|
||||
MultipartFormData(key: "utc_time", type: .text(value: utcTime))
|
||||
]
|
||||
}
|
||||
|
||||
private func localAndUTCTime(for date: Date) -> (String, String) {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
let localTime = dateFormatter.string(from: date)
|
||||
dateFormatter.timeZone = TimeZone(identifier: "UTC")
|
||||
let utcTime = dateFormatter.string(from: date)
|
||||
return (localTime, utcTime)
|
||||
}
|
||||
|
||||
private var os: String {
|
||||
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
|
||||
}
|
||||
|
||||
private var version: String {
|
||||
ElementInfoPlist.cfBundleShortVersionString
|
||||
}
|
||||
|
||||
private func zipFiles(includeLogs: Bool,
|
||||
includeCrashLog: Bool) async throws -> [URL] {
|
||||
MXLog.debug("[BugReportService] zipFiles: includeLogs: \(includeLogs), includeCrashLog: \(includeCrashLog)")
|
||||
|
||||
Reference in New Issue
Block a user