A couple of small tweaks. (#4569)

* Stop ignoring the safe area with the suggestions view.

They weren't visible on iPad (hidden behind the sidebar) and I can't find any instance when they're shown where the safe area comes into play.

* Make sure the room list deselects the cell when dismissed a room.

* Use the backports pattern in more places.

* Fix the annoying header on macOS.

* Silence some warnings

* Fix compound CI branch name.
This commit is contained in:
Doug
2025-10-03 12:09:17 +01:00
committed by GitHub
parent c4aebc47ee
commit 6377d9be67
31 changed files with 110 additions and 116 deletions
@@ -280,6 +280,8 @@ class ChatsFlowCoordinator: FlowCoordinatorProtocol {
switch context.toState {
case .roomList(detailState: .room(let detailStateRoomID)):
self?.selectedRoomSubject.send(detailStateRoomID)
case .roomList(detailState: nil):
self?.selectedRoomSubject.send(nil)
default:
break
}
@@ -1,30 +0,0 @@
//
// Copyright 2024 New Vector 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 {
/// A convenience modifier to conditionally apply `.navigationTransition(.zoom(…))` when available.
@ViewBuilder
func zoomTransition(sourceID: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
navigationTransition(.zoom(sourceID: sourceID, in: namespace))
} else {
self
}
}
/// A convenience modifier to conditionally apply `.matchedTransitionSource(…)` when available.
@ViewBuilder
func zoomTransitionSource(id: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
matchedTransitionSource(id: id, in: namespace)
} else {
self
}
}
}
+58 -2
View File
@@ -8,12 +8,68 @@
import SwiftUI
extension View {
@ViewBuilder
func backportTabBarMinimizeBehaviorOnScrollDown() -> some View {
// MARK: iOS 18
/// Uses the old page style modal so that on iPadOS 18 the presentation detents have no effect.
@ViewBuilder func backportPresentationSizingPage() -> some View {
if #available(iOS 18.0, *) {
presentationSizing(.page)
} else {
self
}
}
/// A convenience modifier to conditionally apply `.navigationTransition(.zoom(…))` when available.
@ViewBuilder func backportNavigationTransitionZoom(sourceID: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
navigationTransition(.zoom(sourceID: sourceID, in: namespace))
} else {
self
}
}
/// A convenience modifier to conditionally apply `.matchedTransitionSource(…)` when available.
@ViewBuilder func backportMatchedTransitionSource(id: some Hashable, in namespace: Namespace.ID) -> some View {
if #available(iOS 18.0, *) {
matchedTransitionSource(id: id, in: namespace)
} else {
self
}
}
@ViewBuilder func backportAccessibilityHint(_ hint: String, isEnabled: Bool) -> some View {
if #available(iOS 18, *) {
accessibilityHint(hint, isEnabled: isEnabled)
} else {
self
}
}
// MARK: iOS 26
@ViewBuilder func backportTabBarMinimizeBehaviorOnScrollDown() -> some View {
if #available(iOS 26.0, *) {
tabBarMinimizeBehavior(.onScrollDown)
} else {
self
}
}
@ViewBuilder func backportScrollEdgeEffectHidden() -> some View {
if #available(iOS 26, *) {
scrollEdgeEffectHidden()
} else {
self
}
}
}
extension ToolbarContent {
@ToolbarContentBuilder func backportSharedBackgroundVisibility(_ visibility: Visibility) -> some ToolbarContent {
if #available(iOS 26.0, *) {
sharedBackgroundVisibility(visibility)
} else {
self
}
}
}
@@ -33,7 +33,7 @@ extension TextFieldStyle where Self == ElementTextFieldStyle {
}
/// The text field style used in authentication screens.
struct ElementTextFieldStyle: TextFieldStyle {
struct ElementTextFieldStyle: @MainActor TextFieldStyle {
enum State {
case success
case error
@@ -18,8 +18,9 @@ struct RoomHeaderView: View {
let mediaProvider: MediaProviderProtocol?
var body: some View {
if ProcessInfo.isRunningAccessibilityTests {
// Accessibility tests scale up the dynamic size in real time which may break the view
if ProcessInfo.isRunningAccessibilityTests || ProcessInfo.processInfo.isiOSAppOnMac {
// Accessibility tests scale up the dynamic size in real time which may break the view.
// macOS really doesn't like the greedy size and does weird things to it.
content
} else {
content
@@ -22,14 +22,7 @@ struct HomeScreen: View {
actions: leaveRoomAlertActions,
message: leaveRoomAlertMessage)
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
.toolbar {
if #available(iOS 26, *) {
toolbar
.sharedBackgroundVisibility(.hidden)
} else {
toolbar
}
}
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.track(screen: .Home)
.bloom()
@@ -43,10 +36,12 @@ struct HomeScreen: View {
ToolbarItem(placement: .navigationBarLeading) {
settingsButton
}
.backportSharedBackgroundVisibility(.hidden)
ToolbarItem(placement: .primaryAction) {
newRoomButton
}
.backportSharedBackgroundVisibility(.hidden)
}
private var settingsButton: some View {
@@ -47,15 +47,11 @@ struct MediaEventsTimelineScreen: View {
if context.viewState.shouldShowEmptyState {
emptyState
} else {
if #available(iOS 26, *) {
scrollView
// Remove the glass effect of iOS 26+
// A flipped table view will always trigger it
// since the nav bar thinks is always at the bottom.
.scrollEdgeEffectHidden()
} else {
scrollView
}
scrollView
// Remove the glass effect of iOS 26+
// A flipped table view will always trigger it
// since the nav bar thinks is always at the bottom.
.backportScrollEdgeEffectHidden()
}
}
@@ -99,7 +99,6 @@ private struct BackgroundView<Content: View>: View {
.clipShape(RoundedCornerShape(radius: shadowRadius, corners: [.topLeft, .topRight]))
.shadow(color: .black.opacity(0.20), radius: 20.0, x: 0.0, y: 3.0)
.mask(Rectangle().padding(.init(top: -(shadowRadius * 2), leading: 0.0, bottom: 0.0, trailing: 0.0)))
.edgesIgnoringSafeArea(.all)
}
}
@@ -20,14 +20,7 @@ struct SpaceListScreen: View {
}
.navigationTitle(L10n.screenSpaceListTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
if #available(iOS 26, *) {
toolbar
.sharedBackgroundVisibility(.hidden)
} else {
toolbar
}
}
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.bloom()
}
@@ -90,11 +83,13 @@ struct SpaceListScreen: View {
}
.accessibilityLabel(L10n.commonSettings)
}
.backportSharedBackgroundVisibility(.hidden)
ToolbarItem(placement: .principal) {
// Hides the navigationTitle (which is set for the navigation stack label).
Text("").accessibilityHidden(true)
}
.backportSharedBackgroundVisibility(.hidden)
}
}
@@ -52,7 +52,7 @@ struct TimelineItemMenu: View {
}
}
.accessibilityIdentifier(A11yIdentifiers.roomScreen.timelineItemActionMenu)
.presentationPage()
.backportPresentationSizingPage()
.presentationDetents([.medium, .large])
.presentationBackground(Color.compound.bgCanvasDefault)
.presentationDragIndicator(.visible)
@@ -251,17 +251,6 @@ private extension EncryptionAuthenticity {
}
}
private extension View {
/// Uses the old page style modal so that on iPadOS 18 the presentation detents have no effect.
@ViewBuilder func presentationPage() -> some View {
if #available(iOS 18.0, *) {
presentationSizing(.page)
} else {
self
}
}
}
// MARK: - Previews
struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
@@ -92,13 +92,9 @@ struct PollView: View {
private var optionsView: some View {
ForEach(poll.options, id: \.id) { option in
if #available(iOS 18, *) {
pollOption(option: option)
.accessibilityHint(L10n.a11yPollsWillRemoveSelection,
isEnabled: isRemovePreviousSelectionHintEnabled(option: option))
} else {
pollOption(option: option)
}
pollOption(option: option)
.backportAccessibilityHint(L10n.a11yPollsWillRemoveSelection,
isEnabled: isRemovePreviousSelectionHintEnabled(option: option))
}
}
@@ -55,7 +55,7 @@ struct RoomEventStringBuilder {
default: L10n.commonWaitingForDecryptionKey
}
return prefix(errorMessage, with: displayName, isOutgoing: isOutgoing)
case .other(eventType: let eventType):
case .other:
return nil // We shouldn't receive these without asking for custom event types.
}
case .failedToParseMessageLike, .failedToParseState:
@@ -40,7 +40,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return buildRedactedTimelineItem(eventItemProxy, messageLikeContent, isOutgoing)
case .unableToDecrypt(let encryptedMessage):
return buildEncryptedTimelineItem(eventItemProxy, messageLikeContent, encryptedMessage, isOutgoing)
case .other(eventType: let eventType):
case .other:
return nil // We shouldn't receive these without asking for custom event types.
}
case .failedToParseMessageLike(let eventType, let error):
@@ -467,7 +467,7 @@ final class TimelineProxy: TimelineProxyProtocol {
MXLog.info("Toggling reaction \(reaction) for event: \(eventOrTransactionID)")
do {
try await timeline.toggleReaction(itemId: eventOrTransactionID.rustValue, key: reaction)
_ = try await timeline.toggleReaction(itemId: eventOrTransactionID.rustValue, key: reaction)
MXLog.info("Finished toggling reaction for event: \(eventOrTransactionID)")
return .success(())
} catch {
@@ -57,7 +57,7 @@ extension RestorationToken: Codable {
}
}
extension MatrixRustSDK.Session: Codable {
extension MatrixRustSDK.Session: @retroactive Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -9,7 +9,7 @@ import Combine
import KZFileWatchers
import SwiftUI
extension Notification.Name: Codable { }
extension Notification.Name: @retroactive Codable { }
enum UITestsSignal: Codable, Equatable {
/// An internal signal used to indicate that one side of the connection is ready.