mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Scope dark mode change only to the presenting sheet (#6144)
* Scope dark mode change only to the presenting sheet, instead of the whole window * Apply review suggestions to the presentation colour scheme modifier API: - Take a `ColorScheme?` at the SwiftUI level instead of a `UIUserInterfaceStyle` and map it once inside the representable (`nil` means no override). - Rename to `presentationColorScheme` and the file to match. - Restore the `preferredColorScheme` property on the media preview details and redact confirmation views, and the `nil` at the media timeline call site, so no UIKit types leak into the views. Cleanup: - Walk the responder chain with `sequence(first:next:)` instead of a while loop. * Re-record the media upload preview iPad snapshots The Cancel button no longer has an explicit tint, so its label colour changed very slightly from `textActionPrimary` to the default. Only the iPad references include the toolbar, which is why the iPhone ones were unaffected.
This commit is contained in:
@@ -453,6 +453,7 @@
|
|||||||
4949C8C12669D1B5E082366E /* QRCodeLoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA9EA59D5C0DA1BFC7B3621 /* QRCodeLoginScreen.swift */; };
|
4949C8C12669D1B5E082366E /* QRCodeLoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFA9EA59D5C0DA1BFC7B3621 /* QRCodeLoginScreen.swift */; };
|
||||||
49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */; };
|
49500BBA1CD65A5AE252D970 /* RoomDirectorySearchScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 41BB37D96C3EA18F3CE8675D /* RoomDirectorySearchScreenModels.swift */; };
|
||||||
49BBEC46D523BF6A41400048 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34956C87731AB094DB33A /* URLTests.swift */; };
|
49BBEC46D523BF6A41400048 /* URLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB34956C87731AB094DB33A /* URLTests.swift */; };
|
||||||
|
4A091BAF20F4D27DF66B7F0A /* PresentationColorSchemeModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF6E8208542BE7A02A94939 /* PresentationColorSchemeModifier.swift */; };
|
||||||
4A361E8BAFFA20B9719B47F5 /* InviteUsersConfirmationSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E97CF050B0168F3D605F0E9 /* InviteUsersConfirmationSheetView.swift */; };
|
4A361E8BAFFA20B9719B47F5 /* InviteUsersConfirmationSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E97CF050B0168F3D605F0E9 /* InviteUsersConfirmationSheetView.swift */; };
|
||||||
4A4110369DBB79E4A314F415 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */; };
|
4A4110369DBB79E4A314F415 /* ComposerToolbarViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0618820D26F9871A4BBB40E /* ComposerToolbarViewModelProtocol.swift */; };
|
||||||
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; };
|
4A618590DEB72C4F186BFED4 /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */; };
|
||||||
@@ -3150,6 +3151,7 @@
|
|||||||
ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
|
ED49073BB1C1FC649DAC2CCD /* LocationRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||||
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
|
ED60E4D2CD678E1EBF16F77A /* BlockedUsersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreen.swift; sourceTree = "<group>"; };
|
||||||
EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = "<group>"; };
|
EDDE826EAB1BAB80C1104980 /* SpaceFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
EDF6E8208542BE7A02A94939 /* PresentationColorSchemeModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationColorSchemeModifier.swift; sourceTree = "<group>"; };
|
||||||
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||||
EE61568A225F7AA068583AA7 /* SignallingTimelineItemProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignallingTimelineItemProviderMock.swift; sourceTree = "<group>"; };
|
EE61568A225F7AA068583AA7 /* SignallingTimelineItemProviderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignallingTimelineItemProviderMock.swift; sourceTree = "<group>"; };
|
||||||
EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
EE8BCD14EFED23459A43FDFF /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
@@ -3466,6 +3468,7 @@
|
|||||||
2A38B60F40D47397CB3B3C9C /* CollapsedInPlaceModifier.swift */,
|
2A38B60F40D47397CB3B3C9C /* CollapsedInPlaceModifier.swift */,
|
||||||
F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */,
|
F03AEAA2F66E796C365EFD58 /* ElementNavigationStack.swift */,
|
||||||
55BA4D3DFEA16CDD3FE99064 /* InverseMask.swift */,
|
55BA4D3DFEA16CDD3FE99064 /* InverseMask.swift */,
|
||||||
|
EDF6E8208542BE7A02A94939 /* PresentationColorSchemeModifier.swift */,
|
||||||
5DB56ABD78E60920ACE43087 /* PreviewScrollView.swift */,
|
5DB56ABD78E60920ACE43087 /* PreviewScrollView.swift */,
|
||||||
D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */,
|
D2C513A6CD99E6C3C163DA1E /* RowDivider.swift */,
|
||||||
693E16574C6F7F9FA1015A8C /* Search.swift */,
|
693E16574C6F7F9FA1015A8C /* Search.swift */,
|
||||||
@@ -9121,6 +9124,7 @@
|
|||||||
1307268DC41730E5BCF7D9A0 /* PollView.swift in Sources */,
|
1307268DC41730E5BCF7D9A0 /* PollView.swift in Sources */,
|
||||||
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
|
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
|
||||||
CF7D38F6478CDA5E48CA185D /* PresenceService.swift in Sources */,
|
CF7D38F6478CDA5E48CA185D /* PresenceService.swift in Sources */,
|
||||||
|
4A091BAF20F4D27DF66B7F0A /* PresentationColorSchemeModifier.swift in Sources */,
|
||||||
BDCF42966007770A33D90E15 /* PreviewScrollView.swift in Sources */,
|
BDCF42966007770A33D90E15 /* PreviewScrollView.swift in Sources */,
|
||||||
6793E75E3EBE48EBB8F857AF /* ProcessInfo.swift in Sources */,
|
6793E75E3EBE48EBB8F857AF /* ProcessInfo.swift in Sources */,
|
||||||
69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */,
|
69DE29C3E3180BB17D840690 /* ProgressCursorModifier.swift in Sources */,
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2026 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
/// Forces the colour scheme of the sheet or popover containing this view without touching the
|
||||||
|
/// rest of the app. Does nothing when the view isn't presented modally or the scheme is `nil`.
|
||||||
|
///
|
||||||
|
/// Prefer this over `preferredColorScheme` for modals: SwiftUI applies that modifier to the whole
|
||||||
|
/// window and can leave it applied when the presentation is torn down abnormally (#6093).
|
||||||
|
func presentationColorScheme(_ colorScheme: ColorScheme?) -> some View {
|
||||||
|
background(PresentationColorSchemeView(colorScheme: colorScheme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PresentationColorSchemeView: UIViewRepresentable {
|
||||||
|
let colorScheme: ColorScheme?
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> InterfaceStyleView {
|
||||||
|
InterfaceStyleView(style: UIUserInterfaceStyle(colorScheme))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: InterfaceStyleView, context: Context) {
|
||||||
|
uiView.style = UIUserInterfaceStyle(colorScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterfaceStyleView: UIView {
|
||||||
|
var style: UIUserInterfaceStyle {
|
||||||
|
didSet { applyStyle() }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(style: UIUserInterfaceStyle) {
|
||||||
|
self.style = style
|
||||||
|
super.init(frame: .zero)
|
||||||
|
isUserInteractionEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didMoveToWindow() {
|
||||||
|
super.didMoveToWindow()
|
||||||
|
applyStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyStyle() {
|
||||||
|
guard window != nil else { return }
|
||||||
|
targetViewController?.overrideUserInterfaceStyle = style
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The view controller that was actually presented, so the override covers its navigation bar too.
|
||||||
|
private var targetViewController: UIViewController? {
|
||||||
|
guard let next else { return nil }
|
||||||
|
return sequence(first: next) { $0.next }
|
||||||
|
.lazy
|
||||||
|
.compactMap { $0 as? UIViewController }
|
||||||
|
// Children of a presented controller also report a presentingViewController, so check the parent.
|
||||||
|
.first { $0.presentingViewController != nil && $0.parent == nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UIUserInterfaceStyle {
|
||||||
|
init(_ colorScheme: ColorScheme?) {
|
||||||
|
self = switch colorScheme {
|
||||||
|
case .light: .light
|
||||||
|
case .dark: .dark
|
||||||
|
case .none: .unspecified
|
||||||
|
@unknown default: .unspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -35,7 +35,7 @@ struct TimelineMediaPreviewDetailsView: View {
|
|||||||
.presentationDetents([.height(sheetHeight)])
|
.presentationDetents([.height(sheetHeight)])
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
.presentationBackground(.compound.bgCanvasDefault)
|
.presentationBackground(.compound.bgCanvasDefault)
|
||||||
.preferredColorScheme(preferredColorScheme)
|
.presentationColorScheme(preferredColorScheme)
|
||||||
.sheet(item: $context.redactConfirmationItem) { item in
|
.sheet(item: $context.redactConfirmationItem) { item in
|
||||||
TimelineMediaPreviewRedactConfirmationView(item: item,
|
TimelineMediaPreviewRedactConfirmationView(item: item,
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ struct TimelineMediaPreviewRedactConfirmationView: View {
|
|||||||
.presentationDetents([.height(sheetHeight + topPadding)])
|
.presentationDetents([.height(sheetHeight + topPadding)])
|
||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
.presentationBackground(.compound.bgCanvasDefault)
|
.presentationBackground(.compound.bgCanvasDefault)
|
||||||
.preferredColorScheme(preferredColorScheme)
|
.presentationColorScheme(preferredColorScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var header: some View {
|
private var header: some View {
|
||||||
|
|||||||
+7
-12
@@ -16,8 +16,6 @@ import SwiftUI
|
|||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
struct MediaUploadPreviewScreen: View {
|
struct MediaUploadPreviewScreen: View {
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
|
||||||
|
|
||||||
@Bindable var context: MediaUploadPreviewScreenViewModel.Context
|
@Bindable var context: MediaUploadPreviewScreenViewModel.Context
|
||||||
|
|
||||||
@State private var captionWarningFrame: CGRect = .zero
|
@State private var captionWarningFrame: CGRect = .zero
|
||||||
@@ -28,8 +26,10 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
ProcessInfo.processInfo.isiOSAppOnMac ? context.viewState.title ?? "" : ""
|
ProcessInfo.processInfo.isiOSAppOnMac ? context.viewState.title ?? "" : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private var colorSchemeOverride: ColorScheme {
|
/// Matches the dark chrome of the QLPreviewController. Scoped to the sheet rather than
|
||||||
ProcessInfo.processInfo.isiOSAppOnMac ? colorScheme : .dark
|
/// using `preferredColorScheme` which leaks into the whole app if the sheet fails to present.
|
||||||
|
private var colorSchemeOverride: ColorScheme? {
|
||||||
|
ProcessInfo.processInfo.isiOSAppOnMac ? nil : .dark
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -49,7 +49,7 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
.disabled(context.viewState.shouldDisableInteraction)
|
.disabled(context.viewState.shouldDisableInteraction)
|
||||||
.interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
.presentationBackground(.background) // Fix a bug introduced by the caption warning.
|
.presentationBackground(.background) // Fix a bug introduced by the caption warning.
|
||||||
.preferredColorScheme(colorSchemeOverride)
|
.presentationColorScheme(colorSchemeOverride)
|
||||||
.onAppear(perform: focusComposerIfHardwareKeyboardConnected)
|
.onAppear(perform: focusComposerIfHardwareKeyboardConnected)
|
||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
.sheet(isPresented: $context.isPresentingMediaEditor) {
|
.sheet(isPresented: $context.isPresentingMediaEditor) {
|
||||||
@@ -60,6 +60,7 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
context.isPresentingMediaEditor = false
|
context.isPresentingMediaEditor = false
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
.presentationColorScheme(colorSchemeOverride)
|
||||||
// Make sure out of bound error alerts are shown even if the sheet is presented
|
// Make sure out of bound error alerts are shown even if the sheet is presented
|
||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,7 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
.presentationDragIndicator(.visible)
|
.presentationDragIndicator(.visible)
|
||||||
.padding(.top, 19) // For the drag indicator
|
.padding(.top, 19) // For the drag indicator
|
||||||
.presentationBackground(.compound.bgCanvasDefault)
|
.presentationBackground(.compound.bgCanvasDefault)
|
||||||
.preferredColorScheme(colorSchemeOverride)
|
.presentationColorScheme(colorSchemeOverride)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,9 +166,6 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
Button { context.send(viewAction: .cancel) } label: {
|
Button { context.send(viewAction: .cancel) } label: {
|
||||||
Text(L10n.actionCancel)
|
Text(L10n.actionCancel)
|
||||||
}
|
}
|
||||||
// Fix a bug with the preferredColorScheme on iOS 18 where the button doesn't
|
|
||||||
// follow the dark colour scheme on devices running with dark mode disabled.
|
|
||||||
.tint(.compound.textActionPrimary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isCurrentMediaImage {
|
if isCurrentMediaImage {
|
||||||
@@ -175,9 +173,6 @@ struct MediaUploadPreviewScreen: View {
|
|||||||
Button { context.isPresentingMediaEditor = true } label: {
|
Button { context.isPresentingMediaEditor = true } label: {
|
||||||
CompoundIcon(\.crop)
|
CompoundIcon(\.crop)
|
||||||
}
|
}
|
||||||
// Fix a bug with the preferredColorScheme on iOS 18 where the button doesn't
|
|
||||||
// follow the dark colour scheme on devices running with dark mode disabled.
|
|
||||||
.tint(.compound.textActionPrimary)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:228dfc2372e109619f2627527c5124a76401ce24300112837be4b9e5d2f42964
|
oid sha256:f327e6dfe4c81dfaa2a654b89c973293e61bc16147595ab031eb6676f8313f3e
|
||||||
size 86282
|
size 86285
|
||||||
|
|||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:fd75072b7457c5bbbaab3876368794273590c3e83ea0ea7cc9020f57c41fdf9a
|
oid sha256:7397c1698bd8524eb1cdf99667fc1c0916b58d4fd59a752c15f6c71e89a30ae3
|
||||||
size 87335
|
size 87348
|
||||||
|
|||||||
Reference in New Issue
Block a user