From 24febbeaee563dbad40756dbb0fca86d0a90cb5d Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Fri, 28 Nov 2025 14:37:37 +0100 Subject: [PATCH] drop iOS 17 and removed all #available and #unavaible checks for 17 and 18 --- ElementX.xcodeproj/project.pbxproj | 4 +- .../Navigation/NavigationTabCoordinator.swift | 5 +- .../Sources/Other/SwiftUI/Backports.swift | 37 ------- ElementX/Sources/Other/SwiftUI/Search.swift | 10 +- .../RoomScreen/View/SwipeRightAction.swift | 97 +++++++++---------- .../ItemMenu/TimelineItemMacContextMenu.swift | 22 ++--- .../View/ItemMenu/TimelineItemMenu.swift | 2 +- .../Timeline/View/Polls/PollView.swift | 3 +- .../View/Style/LongPressWithFeedback.swift | 31 +++--- .../RoomSummary/RoomSummaryProvider.swift | 11 +-- .../Sources/Compound/List/ListRow.swift | 15 +-- project.yml | 2 +- 12 files changed, 79 insertions(+), 160 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 5d6bd3bfa..1e1bf8c09 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -9075,7 +9075,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.5; MARKETING_VERSION = 25.12.0; @@ -9151,7 +9151,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 17.5; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; KEYCHAIN_ACCESS_GROUP_IDENTIFIER = "$(DEVELOPMENT_TEAM).$(BASE_BUNDLE_IDENTIFIER)"; MACOSX_DEPLOYMENT_TARGET = 14.5; MARKETING_VERSION = 25.12.0; diff --git a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift index 304466ac8..ab0cb3949 100644 --- a/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift +++ b/ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift @@ -39,10 +39,7 @@ import SwiftUI } func barVisibility(in horizontalSizeClass: UserInterfaceSizeClass?) -> Visibility { - if #unavailable(iOS 18.0) { - // There are glitches with the tab bar on iPadOS 17, so disable the tab bar until we have fixed it. - .hidden - } else if let barVisibilityOverride { + if let barVisibilityOverride { barVisibilityOverride } else if horizontalSizeClass == .compact, navigationSplitCoordinator?.detailCoordinator != nil { // Whilst we support pushing screens on the stack in the sidebarCoordinator, in practice diff --git a/ElementX/Sources/Other/SwiftUI/Backports.swift b/ElementX/Sources/Other/SwiftUI/Backports.swift index c6ba3b3a2..f990e584a 100644 --- a/ElementX/Sources/Other/SwiftUI/Backports.swift +++ b/ElementX/Sources/Other/SwiftUI/Backports.swift @@ -10,43 +10,6 @@ import Compound import SwiftUI extension 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 { diff --git a/ElementX/Sources/Other/SwiftUI/Search.swift b/ElementX/Sources/Other/SwiftUI/Search.swift index e70d0ab36..0ac57ce7b 100644 --- a/ElementX/Sources/Other/SwiftUI/Search.swift +++ b/ElementX/Sources/Other/SwiftUI/Search.swift @@ -228,13 +228,9 @@ private struct FocusSearchIfHardwareKeyboardAvailableModifier: ViewModifier { @FocusState private var isFocused func body(content: Content) -> some View { - if #available(iOS 18.0, *) { - content - .searchFocused($isFocused) - .onAppear(perform: focusIfHardwareKeyboardAvailable) - } else { - content - } + content + .searchFocused($isFocused) + .onAppear(perform: focusIfHardwareKeyboardAvailable) } func focusIfHardwareKeyboardAvailable() { diff --git a/ElementX/Sources/Screens/RoomScreen/View/SwipeRightAction.swift b/ElementX/Sources/Screens/RoomScreen/View/SwipeRightAction.swift index ecd5d58f7..702334cd8 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/SwipeRightAction.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/SwipeRightAction.swift @@ -51,59 +51,52 @@ struct SwipeRightAction: ViewModifier { @ViewBuilder private func mainContent(content: Content) -> some View { - if #available(iOS 18, *) { - content - .offset(x: xOffset, y: 0.0) - .animation(.interactiveSpring().speed(0.5), value: xOffset) - .gesture(PanGestureRepresentable { gesture in - switch gesture.state { - case .ended, .cancelled, .failed: - if xOffset > actionThreshold { - action() - } - - xOffset = 0.0 - default: - guard shouldStartAction() else { - return - } - let translation = gesture.translation(in: nil) - - // Due to https://forums.developer.apple.com/forums/thread/760035 we had to make - // the drag a simultaneous gesture otherwise it was impossible to scroll the timeline. - // Therefore we need to prevent the animation to run if the user is to scrolling vertically. - // It would be nice if we could somehow abort the gesture in this case. - let width: CGFloat = if translation.x > abs(translation.y) { - translation.x - } else { - 0.0 - } - - // We want to add a spring like behaviour to the drag in which the view - // moves slower the more it's dragged. We use a circular easing function - // to generate those values up to the `swipeThreshold` - // The final translation will be between 0 and `swipeThreshold` with the action being enabled from - // `actionThreshold` onwards - let screenWidthNormalisedTranslation = max(0.0, min(width, swipeThreshold)) / swipeThreshold - let easedTranslation = circularEaseOut(screenWidthNormalisedTranslation) - xOffset = easedTranslation * xOffsetThreshold - - if xOffset > actionThreshold { - if !hasReachedActionThreshold { - feedbackGenerator.impactOccurred() - hasReachedActionThreshold = true - } - } else { - hasReachedActionThreshold = false - } + content + .offset(x: xOffset, y: 0.0) + .animation(.interactiveSpring().speed(0.5), value: xOffset) + .gesture(PanGestureRepresentable { gesture in + switch gesture.state { + case .ended, .cancelled, .failed: + if xOffset > actionThreshold { + action() } - }) - } else { - content - .offset(x: xOffset, y: 0.0) - .animation(.interactiveSpring().speed(0.5), value: xOffset) - .gesture(oldGesture) - } + + xOffset = 0.0 + default: + guard shouldStartAction() else { + return + } + let translation = gesture.translation(in: nil) + + // Due to https://forums.developer.apple.com/forums/thread/760035 we had to make + // the drag a simultaneous gesture otherwise it was impossible to scroll the timeline. + // Therefore we need to prevent the animation to run if the user is to scrolling vertically. + // It would be nice if we could somehow abort the gesture in this case. + let width: CGFloat = if translation.x > abs(translation.y) { + translation.x + } else { + 0.0 + } + + // We want to add a spring like behaviour to the drag in which the view + // moves slower the more it's dragged. We use a circular easing function + // to generate those values up to the `swipeThreshold` + // The final translation will be between 0 and `swipeThreshold` with the action being enabled from + // `actionThreshold` onwards + let screenWidthNormalisedTranslation = max(0.0, min(width, swipeThreshold)) / swipeThreshold + let easedTranslation = circularEaseOut(screenWidthNormalisedTranslation) + xOffset = easedTranslation * xOffsetThreshold + + if xOffset > actionThreshold { + if !hasReachedActionThreshold { + feedbackGenerator.impactOccurred() + hasReachedActionThreshold = true + } + } else { + hasReachedActionThreshold = false + } + } + }) } private var oldGesture: some Gesture { diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMacContextMenu.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMacContextMenu.swift index 62193f832..4c5ddfd5a 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMacContextMenu.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMacContextMenu.swift @@ -21,25 +21,19 @@ struct TimelineItemMacContextMenu: View { if let menuActions = actionProvider.makeActions() { Section { if !menuActions.reactions.isEmpty { - if #available(iOS 17.0, *) { - let reactions = (item as? EventBasedTimelineItemProtocol)?.properties.reactions ?? [] - ControlGroup { - ForEach(menuActions.reactions, id: \.key) { - ReactionToggle(reaction: $0, reactions: reactions) { - send(.toggleReaction(key: $0)) - } - } - - Button { send(.react) } label: { - CompoundIcon(\.reactionAdd) + let reactions = (item as? EventBasedTimelineItemProtocol)?.properties.reactions ?? [] + ControlGroup { + ForEach(menuActions.reactions, id: \.key) { + ReactionToggle(reaction: $0, reactions: reactions) { + send(.toggleReaction(key: $0)) } } - .controlGroupStyle(.palette) - } else { + Button { send(.react) } label: { - TimelineItemMenuAction.react.label + CompoundIcon(\.reactionAdd) } } + .controlGroupStyle(.palette) } ForEach(menuActions.actions) { action in diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift index f1050bdcb..aa77399f6 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenu.swift @@ -53,7 +53,7 @@ struct TimelineItemMenu: View { } } .accessibilityIdentifier(A11yIdentifiers.roomScreen.timelineItemActionMenu) - .backportPresentationSizingPage() + .presentationSizing(.page) .presentationDetents([.medium, .large]) .presentationBackground(Color.compound.bgCanvasDefault) .presentationDragIndicator(.visible) diff --git a/ElementX/Sources/Screens/Timeline/View/Polls/PollView.swift b/ElementX/Sources/Screens/Timeline/View/Polls/PollView.swift index 9f2f83989..482fac9aa 100644 --- a/ElementX/Sources/Screens/Timeline/View/Polls/PollView.swift +++ b/ElementX/Sources/Screens/Timeline/View/Polls/PollView.swift @@ -94,8 +94,7 @@ struct PollView: View { private var optionsView: some View { ForEach(poll.options, id: \.id) { option in pollOption(option: option) - .backportAccessibilityHint(L10n.a11yPollsWillRemoveSelection, - isEnabled: isRemovePreviousSelectionHintEnabled(option: option)) + .accessibilityHint(L10n.a11yPollsWillRemoveSelection, isEnabled: isRemovePreviousSelectionHintEnabled(option: option)) } } diff --git a/ElementX/Sources/Screens/Timeline/View/Style/LongPressWithFeedback.swift b/ElementX/Sources/Screens/Timeline/View/Style/LongPressWithFeedback.swift index c486989c3..4c1d81ac3 100644 --- a/ElementX/Sources/Screens/Timeline/View/Style/LongPressWithFeedback.swift +++ b/ElementX/Sources/Screens/Timeline/View/Style/LongPressWithFeedback.swift @@ -16,26 +16,19 @@ struct LongPressWithFeedback: ViewModifier { private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy) func body(content: Content) -> some View { - if #available(iOS 18, *) { - mainContent(content: content) - .gesture(LongPressGestureRepresentable { gesture in - switch gesture.state { - case .began: - handleLongPress(isPressing: true) - case .ended, .cancelled, .failed: - handleLongPress(isPressing: false) - case .possible, .changed: - break - @unknown default: - break - } - }) - } else { - mainContent(content: content) - .onLongPressGesture(minimumDuration: 0.25) { } onPressingChanged: { isPressing in - handleLongPress(isPressing: isPressing) + mainContent(content: content) + .gesture(LongPressGestureRepresentable { gesture in + switch gesture.state { + case .began: + handleLongPress(isPressing: true) + case .ended, .cancelled, .failed: + handleLongPress(isPressing: false) + case .possible, .changed: + break + @unknown default: + break } - } + }) } // The gesture's minimum duration doesn't actually invoke the perform block when elapsed (thus diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 093012810..d5cf18b3e 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -119,14 +119,9 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } func setFilter(_ filter: RoomSummaryProviderFilter) { - let baseFilter: [RoomListEntriesDynamicFilterKind] = if #available(iOS 18.0, *) { - [.any(filters: [.all(filters: [.nonSpace, .nonLeft]), - .all(filters: [.space, .invite])]), - .deduplicateVersions] - } else { - // Don't show space invites on iOS 17 given that the tab bar is disabled due to glitches on iPad. - [.nonLeft, .nonSpace, .deduplicateVersions] - } + let baseFilter: [RoomListEntriesDynamicFilterKind] = [.any(filters: [.all(filters: [.nonSpace, .nonLeft]), + .all(filters: [.space, .invite])]), + .deduplicateVersions] switch filter { case .excludeAll: diff --git a/compound-ios/Sources/Compound/List/ListRow.swift b/compound-ios/Sources/Compound/List/ListRow.swift index 1e736da58..12e75eff2 100644 --- a/compound-ios/Sources/Compound/List/ListRow.swift +++ b/compound-ios/Sources/Compound/List/ListRow.swift @@ -123,12 +123,12 @@ public struct ListRow some View { - if #available(iOS 17.0, *) { - accessibilityAddTraits(.isToggle) - } else { - self - } - } -} - // MARK: - Previews // swiftlint:disable print_deprecation diff --git a/project.yml b/project.yml index b4ae5ed97..78e9ce864 100644 --- a/project.yml +++ b/project.yml @@ -11,7 +11,7 @@ options: groupSortPosition: bottom createIntermediateGroups: true deploymentTarget: - iOS: '17.5' + iOS: '18.5' macOS: '14.5' groupOrdering: - order: