mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Add a Developer Options button to the AuthenticationStartScreen on Nightly builds. (#5472)
* Add a Developer Options button to the AuthenticationStartScreen on Nightly builds. * Make sure the Developer Options are actually shown in the settings screen on Nightlies.
This commit is contained in:
@@ -662,6 +662,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appMediator: appMediator,
|
appMediator: appMediator,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
|
appHooks: appHooks,
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
coordinator.delegate = self
|
coordinator.delegate = self
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ final class AppSettings {
|
|||||||
@UserPreference(key: UserDefaultsKeys.automaticBackPaginationEnabled, defaultValue: false, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.automaticBackPaginationEnabled, defaultValue: false, storageType: .userDefaults(store))
|
||||||
var automaticBackPaginationEnabled
|
var automaticBackPaginationEnabled
|
||||||
|
|
||||||
@UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: appBuildType == .debug, storageType: .userDefaults(store))
|
@UserPreference(key: UserDefaultsKeys.developerOptionsEnabled, defaultValue: appBuildType != .release, storageType: .userDefaults(store))
|
||||||
var developerOptionsEnabled
|
var developerOptionsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
private let appMediator: AppMediatorProtocol
|
private let appMediator: AppMediatorProtocol
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
private let appHooks: AppHooks
|
||||||
private let analytics: AnalyticsService
|
private let analytics: AnalyticsService
|
||||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||||
|
|
||||||
@@ -46,6 +47,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
|
|
||||||
/// The screen to report an error.
|
/// The screen to report an error.
|
||||||
case bugReportFlow
|
case bugReportFlow
|
||||||
|
/// The screen to toggle feature flags.
|
||||||
|
case developerOptions
|
||||||
|
|
||||||
/// The flow is complete.
|
/// The flow is complete.
|
||||||
case complete
|
case complete
|
||||||
@@ -62,8 +65,6 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case loginWithQR
|
case loginWithQR
|
||||||
/// Show the server confirmation screen.
|
/// Show the server confirmation screen.
|
||||||
case confirmServer(AuthenticationFlow)
|
case confirmServer(AuthenticationFlow)
|
||||||
/// The user encountered a problem.
|
|
||||||
case reportProblem
|
|
||||||
|
|
||||||
/// The QR login flow was aborted.
|
/// The QR login flow was aborted.
|
||||||
case cancelledLoginWithQR
|
case cancelledLoginWithQR
|
||||||
@@ -84,9 +85,16 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
/// The password login was aborted.
|
/// The password login was aborted.
|
||||||
case cancelledPasswordLogin(previousState: State)
|
case cancelledPasswordLogin(previousState: State)
|
||||||
|
|
||||||
|
/// The user encountered a problem.
|
||||||
|
case reportProblem
|
||||||
/// The user has finished reporting a problem (or viewing the logs).
|
/// The user has finished reporting a problem (or viewing the logs).
|
||||||
case bugReportFlowComplete
|
case bugReportFlowComplete
|
||||||
|
|
||||||
|
/// The user wants to toggle a feature flag.
|
||||||
|
case developerOptions
|
||||||
|
/// The user finished toggling feature flags.
|
||||||
|
case dismissedDeveloperOptions
|
||||||
|
|
||||||
/// The user has successfully signed in. The new session can be found in the `userInfo`.
|
/// The user has successfully signed in. The new session can be found in the `userInfo`.
|
||||||
case signedIn
|
case signedIn
|
||||||
}
|
}
|
||||||
@@ -106,6 +114,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
navigationRootCoordinator: NavigationRootCoordinator,
|
navigationRootCoordinator: NavigationRootCoordinator,
|
||||||
appMediator: AppMediatorProtocol,
|
appMediator: AppMediatorProtocol,
|
||||||
appSettings: AppSettings,
|
appSettings: AppSettings,
|
||||||
|
appHooks: AppHooks,
|
||||||
analytics: AnalyticsService,
|
analytics: AnalyticsService,
|
||||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||||
self.authenticationService = authenticationService
|
self.authenticationService = authenticationService
|
||||||
@@ -113,6 +122,7 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
self.navigationRootCoordinator = navigationRootCoordinator
|
self.navigationRootCoordinator = navigationRootCoordinator
|
||||||
self.appMediator = appMediator
|
self.appMediator = appMediator
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
self.appHooks = appHooks
|
||||||
self.analytics = analytics
|
self.analytics = analytics
|
||||||
self.userIndicatorController = userIndicatorController
|
self.userIndicatorController = userIndicatorController
|
||||||
|
|
||||||
@@ -163,6 +173,8 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
navigationStackCoordinator.popToRoot(animated: animated)
|
navigationStackCoordinator.popToRoot(animated: animated)
|
||||||
case .bugReportFlow:
|
case .bugReportFlow:
|
||||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
case .developerOptions:
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
case .complete:
|
case .complete:
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
@@ -231,6 +243,13 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
stateMachine.addRoutes(event: .bugReportFlowComplete, transitions: [.bugReportFlow => .startScreen])
|
stateMachine.addRoutes(event: .bugReportFlowComplete, transitions: [.bugReportFlow => .startScreen])
|
||||||
|
|
||||||
|
// Developer Options
|
||||||
|
|
||||||
|
stateMachine.addRoutes(event: .developerOptions, transitions: [.startScreen => .developerOptions]) { [weak self] _ in
|
||||||
|
self?.showDeveloperOptionsScreen()
|
||||||
|
}
|
||||||
|
stateMachine.addRoutes(event: .dismissedDeveloperOptions, transitions: [.developerOptions => .startScreen])
|
||||||
|
|
||||||
// Completion
|
// Completion
|
||||||
|
|
||||||
stateMachine.addRoutes(event: .signedIn, transitions: [.qrCodeLoginScreen => .complete,
|
stateMachine.addRoutes(event: .signedIn, transitions: [.qrCodeLoginScreen => .complete,
|
||||||
@@ -285,13 +304,16 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.confirmServer(.login))
|
stateMachine.tryEvent(.confirmServer(.login))
|
||||||
case .register:
|
case .register:
|
||||||
stateMachine.tryEvent(.confirmServer(.register))
|
stateMachine.tryEvent(.confirmServer(.register))
|
||||||
case .reportProblem:
|
|
||||||
stateMachine.tryEvent(.reportProblem)
|
|
||||||
|
|
||||||
case .loginDirectlyWithOIDC(let oidcData, let window):
|
case .loginDirectlyWithOIDC(let oidcData, let window):
|
||||||
stateMachine.tryEvent(.continueWithOIDC, userInfo: (oidcData, window))
|
stateMachine.tryEvent(.continueWithOIDC, userInfo: (oidcData, window))
|
||||||
case .loginDirectlyWithPassword(let loginHint):
|
case .loginDirectlyWithPassword(let loginHint):
|
||||||
stateMachine.tryEvent(.continueWithPassword, userInfo: loginHint)
|
stateMachine.tryEvent(.continueWithPassword, userInfo: loginHint)
|
||||||
|
|
||||||
|
case .reportProblem:
|
||||||
|
stateMachine.tryEvent(.reportProblem)
|
||||||
|
case .developerOptions:
|
||||||
|
stateMachine.tryEvent(.developerOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -465,6 +487,28 @@ class AuthenticationFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
coordinator.start()
|
coordinator.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Developer Options
|
||||||
|
|
||||||
|
private func showDeveloperOptionsScreen() {
|
||||||
|
let stackCoordinator = NavigationStackCoordinator()
|
||||||
|
let coordinator = DeveloperOptionsScreenCoordinator(appSettings: appSettings,
|
||||||
|
appHooks: appHooks,
|
||||||
|
clientProxy: nil)
|
||||||
|
coordinator.actions
|
||||||
|
.sink { action in
|
||||||
|
switch action {
|
||||||
|
case .clearCache:
|
||||||
|
break // Not sent when clientProxy == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
stackCoordinator.setRootCoordinator(coordinator)
|
||||||
|
navigationStackCoordinator.setSheetCoordinator(stackCoordinator) { [weak self] in
|
||||||
|
self?.stateMachine.tryEvent(.dismissedDeveloperOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Completion
|
// MARK: - Completion
|
||||||
|
|
||||||
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
private func userHasSignedIn(userSession: UserSessionProtocol) {
|
||||||
|
|||||||
+8
-3
@@ -23,10 +23,12 @@ enum AuthenticationStartScreenCoordinatorAction {
|
|||||||
case loginWithQR
|
case loginWithQR
|
||||||
case login
|
case login
|
||||||
case register
|
case register
|
||||||
case reportProblem
|
|
||||||
|
|
||||||
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
||||||
case loginDirectlyWithPassword(loginHint: String?)
|
case loginDirectlyWithPassword(loginHint: String?)
|
||||||
|
|
||||||
|
case reportProblem
|
||||||
|
case developerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -62,13 +64,16 @@ final class AuthenticationStartScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.login)
|
actionsSubject.send(.login)
|
||||||
case .register:
|
case .register:
|
||||||
actionsSubject.send(.register)
|
actionsSubject.send(.register)
|
||||||
case .reportProblem:
|
|
||||||
actionsSubject.send(.reportProblem)
|
|
||||||
|
|
||||||
case .loginDirectlyWithOIDC(let data, let window):
|
case .loginDirectlyWithOIDC(let data, let window):
|
||||||
actionsSubject.send(.loginDirectlyWithOIDC(data: data, window: window))
|
actionsSubject.send(.loginDirectlyWithOIDC(data: data, window: window))
|
||||||
case .loginDirectlyWithPassword(let loginHint):
|
case .loginDirectlyWithPassword(let loginHint):
|
||||||
actionsSubject.send(.loginDirectlyWithPassword(loginHint: loginHint))
|
actionsSubject.send(.loginDirectlyWithPassword(loginHint: loginHint))
|
||||||
|
|
||||||
|
case .reportProblem:
|
||||||
|
actionsSubject.send(.reportProblem)
|
||||||
|
case .developerOptions:
|
||||||
|
actionsSubject.send(.developerOptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+5
-2
@@ -12,10 +12,12 @@ enum AuthenticationStartScreenViewModelAction: Equatable {
|
|||||||
case loginWithQR
|
case loginWithQR
|
||||||
case login
|
case login
|
||||||
case register
|
case register
|
||||||
case reportProblem
|
|
||||||
|
|
||||||
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
case loginDirectlyWithOIDC(data: OIDCAuthorizationDataProxy, window: UIWindow)
|
||||||
case loginDirectlyWithPassword(loginHint: String?)
|
case loginDirectlyWithPassword(loginHint: String?)
|
||||||
|
|
||||||
|
case reportProblem
|
||||||
|
case developerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AuthenticationStartScreenViewState: BindableState {
|
struct AuthenticationStartScreenViewState: BindableState {
|
||||||
@@ -56,11 +58,12 @@ enum AuthenticationStartScreenAlertType {
|
|||||||
enum AuthenticationStartScreenViewAction {
|
enum AuthenticationStartScreenViewAction {
|
||||||
/// Updates the window used as the OIDC presentation anchor.
|
/// Updates the window used as the OIDC presentation anchor.
|
||||||
case updateWindow(UIWindow)
|
case updateWindow(UIWindow)
|
||||||
|
case developerOptions
|
||||||
|
case reportProblem
|
||||||
|
|
||||||
case loginWithQR
|
case loginWithQR
|
||||||
case login
|
case login
|
||||||
case register
|
case register
|
||||||
case reportProblem
|
|
||||||
|
|
||||||
case continueWithClassic(ClassicAppAccount)
|
case continueWithClassic(ClassicAppAccount)
|
||||||
case otherOptions(ClassicAppAccount)
|
case otherOptions(ClassicAppAccount)
|
||||||
|
|||||||
+8
-4
@@ -83,16 +83,20 @@ class AuthenticationStartScreenViewModel: AuthenticationStartScreenViewModelType
|
|||||||
case .updateWindow(let window):
|
case .updateWindow(let window):
|
||||||
guard state.window != window else { return }
|
guard state.window != window else { return }
|
||||||
state.window = window
|
state.window = window
|
||||||
|
case .reportProblem:
|
||||||
|
if canReportProblem {
|
||||||
|
actionsSubject.send(.reportProblem)
|
||||||
|
}
|
||||||
|
case .developerOptions:
|
||||||
|
actionsSubject.send(.developerOptions)
|
||||||
|
|
||||||
case .loginWithQR:
|
case .loginWithQR:
|
||||||
actionsSubject.send(.loginWithQR)
|
actionsSubject.send(.loginWithQR)
|
||||||
case .login:
|
case .login:
|
||||||
Task { await login() }
|
Task { await login() }
|
||||||
case .register:
|
case .register:
|
||||||
actionsSubject.send(.register)
|
actionsSubject.send(.register)
|
||||||
case .reportProblem:
|
|
||||||
if canReportProblem {
|
|
||||||
actionsSubject.send(.reportProblem)
|
|
||||||
}
|
|
||||||
case .continueWithClassic(let account):
|
case .continueWithClassic(let account):
|
||||||
Task { await login(classicAppAccount: account) }
|
Task { await login(classicAppAccount: account) }
|
||||||
case .otherOptions(let account):
|
case .otherOptions(let account):
|
||||||
|
|||||||
+16
-2
@@ -123,12 +123,15 @@ struct AuthenticationStartScreen: View {
|
|||||||
versionText
|
versionText
|
||||||
.font(.compound.bodySM)
|
.font(.compound.bodySM)
|
||||||
.foregroundColor(.compound.textSecondary)
|
.foregroundColor(.compound.textSecondary)
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding(.top, 16)
|
|
||||||
.onTapGesture(count: 7) {
|
.onTapGesture(count: 7) {
|
||||||
context.send(viewAction: .reportProblem)
|
context.send(viewAction: .reportProblem)
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier(A11yIdentifiers.authenticationStartScreen.appVersion)
|
.accessibilityIdentifier(A11yIdentifiers.authenticationStartScreen.appVersion)
|
||||||
|
.overlay(alignment: .trailing) {
|
||||||
|
developerOptionsButton
|
||||||
|
.scaledOffset(x: 32, y: -0.5, relativeTo: .compound.bodySM)
|
||||||
|
}
|
||||||
|
.padding(.top, 16)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, verticalSizeClass == .compact ? 128 : 24)
|
.padding(.horizontal, verticalSizeClass == .compact ? 128 : 24)
|
||||||
.readableFrame()
|
.readableFrame()
|
||||||
@@ -140,6 +143,17 @@ struct AuthenticationStartScreen: View {
|
|||||||
return Text(L10n.screenOnboardingAppVersion(shortVersionString))
|
return Text(L10n.screenOnboardingAppVersion(shortVersionString))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var developerOptionsButton: some View {
|
||||||
|
if AppSettings.appBuildType != .release, !ProcessInfo.isRunningTests {
|
||||||
|
Button { context.send(viewAction: .developerOptions) } label: {
|
||||||
|
CompoundIcon(\.code)
|
||||||
|
.foregroundStyle(.compound.iconSecondary)
|
||||||
|
}
|
||||||
|
.accessibilityLabel(L10n.commonDeveloperOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
var toolbar: some ToolbarContent {
|
var toolbar: some ToolbarContent {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(appSettings: AppSettings, appHooks: AppHooks, clientProxy: ClientProxyProtocol) {
|
init(appSettings: AppSettings, appHooks: AppHooks, clientProxy: ClientProxyProtocol?) {
|
||||||
viewModel = DeveloperOptionsScreenViewModel(developerOptions: appSettings,
|
viewModel = DeveloperOptionsScreenViewModel(developerOptions: appSettings,
|
||||||
elementCallBaseURL: appSettings.elementCallBaseURL,
|
elementCallBaseURL: appSettings.elementCallBaseURL,
|
||||||
appHooks: appHooks,
|
appHooks: appHooks,
|
||||||
|
|||||||
+3
@@ -16,6 +16,9 @@ struct DeveloperOptionsScreenViewState: BindableState {
|
|||||||
let elementCallBaseURL: URL
|
let elementCallBaseURL: URL
|
||||||
let appHooks: AppHooks
|
let appHooks: AppHooks
|
||||||
var storeSizes: [StoreSize]?
|
var storeSizes: [StoreSize]?
|
||||||
|
let shouldShowClearCache: Bool
|
||||||
|
let isPresentedModally: Bool
|
||||||
|
|
||||||
var bindings: DeveloperOptionsScreenViewStateBindings
|
var bindings: DeveloperOptionsScreenViewStateBindings
|
||||||
|
|
||||||
struct StoreSize: Identifiable {
|
struct StoreSize: Identifiable {
|
||||||
|
|||||||
+4
-2
@@ -18,13 +18,15 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL, appHooks: AppHooks, clientProxy: ClientProxyProtocol) {
|
init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL, appHooks: AppHooks, clientProxy: ClientProxyProtocol?) {
|
||||||
super.init(initialViewState: .init(elementCallBaseURL: elementCallBaseURL,
|
super.init(initialViewState: .init(elementCallBaseURL: elementCallBaseURL,
|
||||||
appHooks: appHooks,
|
appHooks: appHooks,
|
||||||
|
shouldShowClearCache: clientProxy != nil,
|
||||||
|
isPresentedModally: clientProxy == nil,
|
||||||
bindings: .init(developerOptions: developerOptions)))
|
bindings: .init(developerOptions: developerOptions)))
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
if case let .success(sizes) = await clientProxy.storeSizes() {
|
if case let .success(sizes) = await clientProxy?.storeSizes() {
|
||||||
let formatter = ByteCountFormatStyle(style: .file)
|
let formatter = ByteCountFormatStyle(style: .file)
|
||||||
|
|
||||||
var components = [DeveloperOptionsScreenViewState.StoreSize]()
|
var components = [DeveloperOptionsScreenViewState.StoreSize]()
|
||||||
|
|||||||
+26
-10
@@ -9,6 +9,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DeveloperOptionsScreen: View {
|
struct DeveloperOptionsScreen: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@Bindable var context: DeveloperOptionsScreenViewModel.Context
|
@Bindable var context: DeveloperOptionsScreenViewModel.Context
|
||||||
|
|
||||||
@State private var showConfetti = false
|
@State private var showConfetti = false
|
||||||
@@ -121,7 +123,7 @@ struct DeveloperOptionsScreen: View {
|
|||||||
Text("WARNING: this feature is EXPERIMENTAL and not all security precautions are implemented. Do not enable on production accounts.")
|
Text("WARNING: this feature is EXPERIMENTAL and not all security precautions are implemented. Do not enable on production accounts.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section("Element Call remote URL override") {
|
||||||
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
|
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
@@ -134,8 +136,6 @@ struct DeveloperOptionsScreen: View {
|
|||||||
context.elementCallBaseURLOverride = url
|
context.elementCallBaseURLOverride = url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} header: {
|
|
||||||
Text("Element Call remote URL override")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Notifications") {
|
Section("Notifications") {
|
||||||
@@ -158,19 +158,22 @@ struct DeveloperOptionsScreen: View {
|
|||||||
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 } // Fix separator alignment
|
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 } // Fix separator alignment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
if context.viewState.shouldShowClearCache {
|
||||||
Button(role: .destructive) {
|
Section {
|
||||||
context.send(viewAction: .clearCache)
|
Button(role: .destructive) {
|
||||||
} label: {
|
context.send(viewAction: .clearCache)
|
||||||
Text("Clear cache")
|
} label: {
|
||||||
.frame(maxWidth: .infinity)
|
Text("Clear cache")
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(effectsView)
|
.overlay(effectsView)
|
||||||
.navigationTitle(L10n.commonDeveloperOptions)
|
.navigationTitle(L10n.commonDeveloperOptions)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar { toolbar }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -187,6 +190,19 @@ struct DeveloperOptionsScreen: View {
|
|||||||
try? await Task.sleep(for: .seconds(4))
|
try? await Task.sleep(for: .seconds(4))
|
||||||
showConfetti = false
|
showConfetti = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ToolbarContentBuilder
|
||||||
|
private var toolbar: some ToolbarContent {
|
||||||
|
if context.viewState.isPresentedModally {
|
||||||
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
if #available(iOS 26.0, *) {
|
||||||
|
Button(role: .close, action: dismiss.callAsFunction)
|
||||||
|
} else {
|
||||||
|
Button(L10n.actionDone, action: dismiss.callAsFunction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct LogLevelConfigurationView: View {
|
private struct LogLevelConfigurationView: View {
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ class MockScreen: Identifiable {
|
|||||||
navigationRootCoordinator: navigationRootCoordinator,
|
navigationRootCoordinator: navigationRootCoordinator,
|
||||||
appMediator: AppMediatorMock.default,
|
appMediator: AppMediatorMock.default,
|
||||||
appSettings: appSettings,
|
appSettings: appSettings,
|
||||||
|
appHooks: AppHooks(),
|
||||||
analytics: ServiceLocator.shared.analytics,
|
analytics: ServiceLocator.shared.analytics,
|
||||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||||
flowCoordinator.start()
|
flowCoordinator.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user