From f4b51c06c1107cd8149a8e07a0f652ac6816ee3a Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Thu, 12 Sep 2024 10:30:58 -0400 Subject: [PATCH] Raise min deployment target to iOS 16 --- .../NotificationService.swift | 5 +- Packages/ComposeUI/Package.swift | 14 ++- .../Controllers/AttachmentRowController.swift | 15 +-- .../AttachmentsListController.swift | 38 ------- .../Controllers/ComposeController.swift | 35 +----- .../FocusedAttachmentController.swift | 5 +- .../Controllers/PollController.swift | 2 +- .../Controllers/ToolbarController.swift | 5 +- .../Sources/ComposeUI/KeyboardReader.swift | 1 - .../ComposeUI/View+ForwardsCompat.swift | 26 ----- Packages/Duckable/Package.swift | 9 +- Packages/GalleryVC/Package.swift | 14 ++- Packages/InstanceFeatures/Package.swift | 14 ++- .../MatchedGeometryPresentation/Package.swift | 9 +- Packages/Pachyderm/Package.swift | 12 +- Packages/PushNotifications/Package.swift | 15 ++- Packages/TTTKit/Package.swift | 14 ++- Packages/TuskerComponents/Package.swift | 9 +- .../TuskerComponents/AsyncPicker.swift | 25 +---- .../TuskerComponents/AsyncToggle.swift | 25 +---- .../Sources/TuskerComponents/MenuPicker.swift | 4 +- Packages/TuskerPreferences/Package.swift | 14 ++- Packages/UserAccounts/Package.swift | 9 +- Tusker.xcodeproj/project.pbxproj | 30 +++-- Tusker/Caching/DiskCache.swift | 19 +++- Tusker/CoreData/TimelinePosition.swift | 11 +- Tusker/Extensions/View+AppListStyle.swift | 15 +-- Tusker/HTMLConverter.swift | 5 +- Tusker/MultiThreadDictionary.swift | 104 ------------------ Tusker/Scenes/MainSceneDelegate.swift | 3 +- .../Announcements/AddReactionView.swift | 13 +-- .../Announcements/AnnouncementListRow.swift | 18 +-- .../AddHashtagPinnedTimelineView.swift | 17 --- .../CustomizeTimelinesView.swift | 11 +- .../Customize Timelines/EditFilterView.swift | 14 +-- .../PinnedTimelinesView.swift | 10 -- .../Explore/InlineTrendsViewController.swift | 4 +- .../Explore/TrendsViewController.swift | 17 +-- .../Gallery/VideoControlsViewController.swift | 29 +---- .../VideoGalleryContentViewController.swift | 17 --- .../Gallery/VideoOverlayViewController.swift | 15 --- .../EditListAccountsViewController.swift | 29 ++--- Tusker/Screens/Mute/MuteAccountView.swift | 11 +- ...lowRequestNotificationViewController.swift | 8 +- ...otificationsCollectionViewController.swift | 8 +- .../InstanceSelectorTableViewController.swift | 4 +- .../Screens/Preferences/About/AboutView.swift | 10 +- .../Appearance/AppearancePrefsView.swift | 9 +- .../NotificationsPrefsView.swift | 7 +- .../PushInstanceSettingsView.swift | 2 +- .../OppositeCollapseKeywordsView.swift | 12 -- .../Screens/Report/ReportAddStatusView.swift | 48 ++++---- .../Report/ReportSelectRulesView.swift | 18 +-- Tusker/Screens/Report/ReportView.swift | 17 +-- .../Search/MastodonSearchController.swift | 12 +- .../EnhancedNavigationViewController.swift | 30 ++--- Tusker/Screens/Utilities/Previewing.swift | 21 +--- .../UserActivityHandlingContext.swift | 6 +- Tusker/TuskerNavigationDelegate.swift | 3 +- Tusker/Views/AccountDisplayNameView.swift | 9 +- Tusker/Views/Attachments/AttachmentView.swift | 12 +- Tusker/Views/BaseEmojiLabel.swift | 15 ++- Tusker/Views/ContentTextView.swift | 6 +- ...opyableLable.swift => CopyableLabel.swift} | 14 +-- .../Profile Header/ProfileHeaderView.xib | 19 ++-- ...ersationMainStatusCollectionViewCell.swift | 5 +- 66 files changed, 266 insertions(+), 740 deletions(-) delete mode 100644 Packages/ComposeUI/Sources/ComposeUI/View+ForwardsCompat.swift delete mode 100644 Tusker/MultiThreadDictionary.swift rename Tusker/Views/{CopyableLable.swift => CopyableLabel.swift} (73%) diff --git a/NotificationExtension/NotificationService.swift b/NotificationExtension/NotificationService.swift index 6270a33583..a2754ee4ba 100644 --- a/NotificationExtension/NotificationService.swift +++ b/NotificationExtension/NotificationService.swift @@ -371,14 +371,13 @@ private struct HTMLCallbacks: HTMLConversionCallbacks { // Converting WebURL to URL is a small but non-trivial expense (since it works by // serializing the WebURL as a string and then having Foundation parse it again), // so, if available, use the system parser which doesn't require another round trip. - if #available(iOS 16.0, macOS 13.0, *), - let url = try? URL.ParseStrategy().parse(string) { + if let url = try? URL.ParseStrategy().parse(string) { url } else if let web = WebURL(string), let url = URL(web) { url } else { - URL(string: string) + nil } } diff --git a/Packages/ComposeUI/Package.swift b/Packages/ComposeUI/Package.swift index adbd2bbaaa..434658b206 100644 --- a/Packages/ComposeUI/Package.swift +++ b/Packages/ComposeUI/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "ComposeUI", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -26,9 +26,15 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "ComposeUI", - dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation"]), + dependencies: ["Pachyderm", "InstanceFeatures", "TuskerComponents", "MatchedGeometryPresentation"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), .testTarget( name: "ComposeUITests", - dependencies: ["ComposeUI"]), + dependencies: ["ComposeUI"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), ] ) diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentRowController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentRowController.swift index ff11ef043a..c729b26e42 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentRowController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentRowController.swift @@ -156,7 +156,7 @@ class AttachmentRowController: ViewController { Button(role: .destructive, action: controller.removeAttachment) { Label("Delete", systemImage: "trash") } - } previewIfAvailable: { + } preview: { ControllerView(controller: { controller.thumbnailController }) } @@ -221,16 +221,3 @@ extension AttachmentRowController { case allowEntry, recognizingText } } - -private extension View { - @available(iOS, obsoleted: 16.0) - @available(visionOS 1.0, *) - @ViewBuilder - func contextMenu(@ViewBuilder menuItems: () -> M, @ViewBuilder previewIfAvailable preview: () -> P) -> some View { - if #available(iOS 16.0, *) { - self.contextMenu(menuItems: menuItems, preview: preview) - } else { - self.contextMenu(menuItems: menuItems) - } - } -} diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentsListController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentsListController.swift index 56bae119e9..98af19262d 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentsListController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/AttachmentsListController.swift @@ -214,44 +214,6 @@ fileprivate extension View { self } } - - @available(iOS, obsoleted: 16.0) - @ViewBuilder - func sheetOrPopover(isPresented: Binding, @ViewBuilder content: @escaping () -> some View) -> some View { - if #available(iOS 16.0, *) { - self.modifier(SheetOrPopover(isPresented: isPresented, view: content)) - } else { - self.popover(isPresented: isPresented, content: content) - } - } - - @available(iOS, obsoleted: 16.0) - @ViewBuilder - func withSheetDetentsIfAvailable() -> some View { - if #available(iOS 16.0, *) { - self - .presentationDetents([.medium, .large]) - .presentationDragIndicator(.visible) - } else { - self - } - } -} - -@available(iOS 16.0, *) -fileprivate struct SheetOrPopover: ViewModifier { - @Binding var isPresented: Bool - @ViewBuilder let view: () -> V - - @Environment(\.horizontalSizeClass) var sizeClass - - func body(content: Content) -> some View { - if sizeClass == .compact { - content.sheet(isPresented: $isPresented, content: view) - } else { - content.popover(isPresented: $isPresented, content: view) - } - } } @available(visionOS 1.0, *) diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift index da478b5bb6..d63cd73039 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ComposeController.swift @@ -125,9 +125,7 @@ public final class ComposeController: ViewController { self.toolbarController = ToolbarController(parent: self) self.attachmentsListController = AttachmentsListController(parent: self) - if #available(iOS 16.0, *) { - NotificationCenter.default.addObserver(self, selector: #selector(currentInputModeChanged), name: UITextInputMode.currentInputModeDidChangeNotification, object: nil) - } + NotificationCenter.default.addObserver(self, selector: #selector(currentInputModeChanged), name: UITextInputMode.currentInputModeDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: DraftsPersistentContainer.shared.viewContext) } @@ -324,10 +322,6 @@ public final class ComposeController: ViewController { ControllerView(controller: { controller.toolbarController }) #endif } - #if !os(visionOS) - // on iPadOS15, the toolbar ends up below the keyboard's toolbar without this - .padding(.bottom, keyboardInset) - #endif .transition(.move(edge: .bottom)) } } @@ -436,7 +430,7 @@ public final class ComposeController: ViewController { } .listStyle(.plain) #if !os(visionOS) - .scrollDismissesKeyboardInteractivelyIfAvailable() + .scrollDismissesKeyboard(.interactively) #endif .disabled(controller.isPosting) } @@ -487,31 +481,6 @@ public final class ComposeController: ViewController { .keyboardShortcut(.return, modifiers: .command) .disabled(!controller.postButtonEnabled) } - - #if !os(visionOS) - @available(iOS, obsoleted: 16.0) - private var keyboardInset: CGFloat { - if #unavailable(iOS 16.0), - UIDevice.current.userInterfaceIdiom == .pad, - keyboardReader.isVisible { - return ToolbarController.height - } else { - return 0 - } - } - #endif - } -} - -private extension View { - @available(iOS, obsoleted: 16.0) - @ViewBuilder - func scrollDismissesKeyboardInteractivelyIfAvailable() -> some View { - if #available(iOS 16.0, *) { - self.scrollDismissesKeyboard(.interactively) - } else { - self - } } } diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/FocusedAttachmentController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/FocusedAttachmentController.swift index 436724d654..9369df647b 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/FocusedAttachmentController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/FocusedAttachmentController.swift @@ -51,14 +51,11 @@ class FocusedAttachmentController: ViewController { .onAppear { player.play() } - } else if #available(iOS 16.0, *) { + } else { ZoomableScrollView { attachmentView .matchedGeometryDestination(id: attachment.id) } - } else { - attachmentView - .matchedGeometryDestination(id: attachment.id) } Spacer(minLength: 0) diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/PollController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/PollController.swift index 7a1e2cb9ef..18f6b10158 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/PollController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/PollController.swift @@ -96,7 +96,7 @@ class PollController: ViewController { .onMove(perform: controller.moveOptions) } .listStyle(.plain) - .scrollDisabledIfAvailable(true) + .scrollDisabled(true) .frame(height: 44 * CGFloat(poll.options.count)) Button(action: controller.addOption) { diff --git a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ToolbarController.swift b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ToolbarController.swift index bcef0cf0ac..5528c4a6e0 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/Controllers/ToolbarController.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/Controllers/ToolbarController.swift @@ -66,7 +66,7 @@ class ToolbarController: ViewController { } }) } - .scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0) + .scrollDisabled(realWidth ?? 0 <= minWidth ?? 0) .frame(height: ToolbarController.height) .frame(maxWidth: .infinity) .background(.regularMaterial, ignoresSafeAreaEdges: [.bottom, .leading, .trailing]) @@ -122,8 +122,7 @@ class ToolbarController: ViewController { Spacer() - if #available(iOS 16.0, *), - composeController.mastodonController.instanceFeatures.createStatusWithLanguage { + if composeController.mastodonController.instanceFeatures.createStatusWithLanguage { LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $composeController.hasChangedLanguageSelection) } } diff --git a/Packages/ComposeUI/Sources/ComposeUI/KeyboardReader.swift b/Packages/ComposeUI/Sources/ComposeUI/KeyboardReader.swift index 11aa3771fa..2c049b2aa8 100644 --- a/Packages/ComposeUI/Sources/ComposeUI/KeyboardReader.swift +++ b/Packages/ComposeUI/Sources/ComposeUI/KeyboardReader.swift @@ -10,7 +10,6 @@ import UIKit import Combine -@available(iOS, obsoleted: 16.0) class KeyboardReader: ObservableObject { // @Published var isVisible = false @Published var keyboardHeight: CGFloat = 0 diff --git a/Packages/ComposeUI/Sources/ComposeUI/View+ForwardsCompat.swift b/Packages/ComposeUI/Sources/ComposeUI/View+ForwardsCompat.swift deleted file mode 100644 index 7b1dc3c398..0000000000 --- a/Packages/ComposeUI/Sources/ComposeUI/View+ForwardsCompat.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// View+ForwardsCompat.swift -// ComposeUI -// -// Created by Shadowfacts on 3/25/23. -// - -import SwiftUI - -extension View { - #if os(visionOS) - func scrollDisabledIfAvailable(_ disabled: Bool) -> some View { - self.scrollDisabled(disabled) - } - #else - @available(iOS, obsoleted: 16.0) - @ViewBuilder - func scrollDisabledIfAvailable(_ disabled: Bool) -> some View { - if #available(iOS 16.0, *) { - self.scrollDisabled(disabled) - } else { - self - } - } - #endif -} diff --git a/Packages/Duckable/Package.swift b/Packages/Duckable/Package.swift index ca4e357042..830960d674 100644 --- a/Packages/Duckable/Package.swift +++ b/Packages/Duckable/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "Duckable", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -23,7 +23,10 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "Duckable", - dependencies: []), + dependencies: [], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), // .testTarget( // name: "DuckableTests", // dependencies: ["Duckable"]), diff --git a/Packages/GalleryVC/Package.swift b/Packages/GalleryVC/Package.swift index e2cb517198..5445640726 100644 --- a/Packages/GalleryVC/Package.swift +++ b/Packages/GalleryVC/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "GalleryVC", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -18,9 +18,15 @@ let package = Package( // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "GalleryVC"), + name: "GalleryVC", + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), .testTarget( name: "GalleryVCTests", - dependencies: ["GalleryVC"]), + dependencies: ["GalleryVC"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), ] ) diff --git a/Packages/InstanceFeatures/Package.swift b/Packages/InstanceFeatures/Package.swift index 1927a00bc8..3da3f9a84d 100644 --- a/Packages/InstanceFeatures/Package.swift +++ b/Packages/InstanceFeatures/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "InstanceFeatures", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -23,9 +23,15 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "InstanceFeatures", - dependencies: ["Pachyderm"]), + dependencies: ["Pachyderm"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), .testTarget( name: "InstanceFeaturesTests", - dependencies: ["InstanceFeatures"]), + dependencies: ["InstanceFeatures"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), ] ) diff --git a/Packages/MatchedGeometryPresentation/Package.swift b/Packages/MatchedGeometryPresentation/Package.swift index e293b7d72f..a5d53636d5 100644 --- a/Packages/MatchedGeometryPresentation/Package.swift +++ b/Packages/MatchedGeometryPresentation/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "MatchedGeometryPresentation", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -18,7 +18,10 @@ let package = Package( // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "MatchedGeometryPresentation"), + name: "MatchedGeometryPresentation", + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), // .testTarget( // name: "MatchedGeometryPresentationTests", // dependencies: ["MatchedGeometryPresentation"]), diff --git a/Packages/Pachyderm/Package.swift b/Packages/Pachyderm/Package.swift index d7aeb347ae..a1be7f1d9e 100644 --- a/Packages/Pachyderm/Package.swift +++ b/Packages/Pachyderm/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "Pachyderm", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -26,9 +26,15 @@ let package = Package( dependencies: [ .product(name: "WebURL", package: "swift-url"), .product(name: "WebURLFoundationExtras", package: "swift-url"), + ], + swiftSettings: [ + .swiftLanguageMode(.v5) ]), .testTarget( name: "PachydermTests", - dependencies: ["Pachyderm"]), + dependencies: ["Pachyderm"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), ] ) diff --git a/Packages/PushNotifications/Package.swift b/Packages/PushNotifications/Package.swift index 8fc9df2276..e4a2c5c92c 100644 --- a/Packages/PushNotifications/Package.swift +++ b/Packages/PushNotifications/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.10 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "PushNotifications", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -23,10 +23,17 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "PushNotifications", - dependencies: ["UserAccounts", "Pachyderm"] + dependencies: ["UserAccounts", "Pachyderm"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] ), .testTarget( name: "PushNotificationsTests", - dependencies: ["PushNotifications"]), + dependencies: ["PushNotifications"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] + ), ] ) diff --git a/Packages/TTTKit/Package.swift b/Packages/TTTKit/Package.swift index 6d046421b4..64ab3e0395 100644 --- a/Packages/TTTKit/Package.swift +++ b/Packages/TTTKit/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "TTTKit", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -23,9 +23,15 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "TTTKit", - dependencies: []), + dependencies: [], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), .testTarget( name: "TTTKitTests", - dependencies: ["TTTKit"]), + dependencies: ["TTTKit"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), ] ) diff --git a/Packages/TuskerComponents/Package.swift b/Packages/TuskerComponents/Package.swift index ad33dadfb8..350c14613e 100644 --- a/Packages/TuskerComponents/Package.swift +++ b/Packages/TuskerComponents/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "TuskerComponents", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -23,7 +23,10 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "TuskerComponents", - dependencies: []), + dependencies: [], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), // .testTarget( // name: "TuskerComponentsTests", // dependencies: ["TuskerComponents"]), diff --git a/Packages/TuskerComponents/Sources/TuskerComponents/AsyncPicker.swift b/Packages/TuskerComponents/Sources/TuskerComponents/AsyncPicker.swift index d1b78a2cac..5777b69494 100644 --- a/Packages/TuskerComponents/Sources/TuskerComponents/AsyncPicker.swift +++ b/Packages/TuskerComponents/Sources/TuskerComponents/AsyncPicker.swift @@ -9,21 +9,14 @@ import SwiftUI public struct AsyncPicker: View { let titleKey: LocalizedStringKey - #if !os(visionOS) - @available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent") - let labelHidden: Bool - #endif let alignment: Alignment @Binding var value: V let onChange: (V) async -> Bool let content: Content @State private var isLoading = false - public init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, alignment: Alignment = .center, value: Binding, onChange: @escaping (V) async -> Bool, @ViewBuilder content: () -> Content) { + public init(_ titleKey: LocalizedStringKey, alignment: Alignment = .center, value: Binding, onChange: @escaping (V) async -> Bool, @ViewBuilder content: () -> Content) { self.titleKey = titleKey - #if !os(visionOS) - self.labelHidden = labelHidden - #endif self.alignment = alignment self._value = value self.onChange = onChange @@ -31,25 +24,9 @@ public struct AsyncPicker: View { } public var body: some View { - #if os(visionOS) LabeledContent(titleKey) { picker } - #else - if #available(iOS 16.0, *) { - LabeledContent(titleKey) { - picker - } - } else if labelHidden { - picker - } else { - HStack { - Text(titleKey) - Spacer() - picker - } - } - #endif } private var picker: some View { diff --git a/Packages/TuskerComponents/Sources/TuskerComponents/AsyncToggle.swift b/Packages/TuskerComponents/Sources/TuskerComponents/AsyncToggle.swift index 4e0b00ace3..9352daf686 100644 --- a/Packages/TuskerComponents/Sources/TuskerComponents/AsyncToggle.swift +++ b/Packages/TuskerComponents/Sources/TuskerComponents/AsyncToggle.swift @@ -10,42 +10,19 @@ import SwiftUI public struct AsyncToggle: View { let titleKey: LocalizedStringKey - #if !os(visionOS) - @available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent") - let labelHidden: Bool - #endif @Binding var mode: Mode let onChange: (Bool) async -> Bool - public init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, mode: Binding, onChange: @escaping (Bool) async -> Bool) { + public init(_ titleKey: LocalizedStringKey, mode: Binding, onChange: @escaping (Bool) async -> Bool) { self.titleKey = titleKey - #if !os(visionOS) - self.labelHidden = labelHidden - #endif self._mode = mode self.onChange = onChange } public var body: some View { - #if os(visionOS) LabeledContent(titleKey) { toggleOrSpinner } - #else - if #available(iOS 16.0, *) { - LabeledContent(titleKey) { - toggleOrSpinner - } - } else if labelHidden { - toggleOrSpinner - } else { - HStack { - Text(titleKey) - Spacer() - toggleOrSpinner - } - } - #endif } @ViewBuilder diff --git a/Packages/TuskerComponents/Sources/TuskerComponents/MenuPicker.swift b/Packages/TuskerComponents/Sources/TuskerComponents/MenuPicker.swift index 08f67c1ed0..80bc60723b 100644 --- a/Packages/TuskerComponents/Sources/TuskerComponents/MenuPicker.swift +++ b/Packages/TuskerComponents/Sources/TuskerComponents/MenuPicker.swift @@ -47,9 +47,7 @@ public struct MenuPicker: UIViewRepresentable { private func makeConfiguration() -> UIButton.Configuration { var config = UIButton.Configuration.borderless() - if #available(iOS 16.0, *) { - config.indicator = .popup - } + config.indicator = .popup if buttonStyle.hasIcon { config.image = selectedOption.image } diff --git a/Packages/TuskerPreferences/Package.swift b/Packages/TuskerPreferences/Package.swift index 7392f93149..58aa2cf84c 100644 --- a/Packages/TuskerPreferences/Package.swift +++ b/Packages/TuskerPreferences/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "TuskerPreferences", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. @@ -22,11 +22,17 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "TuskerPreferences", - dependencies: ["Pachyderm"] + dependencies: ["Pachyderm"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] ), .testTarget( name: "TuskerPreferencesTests", - dependencies: ["TuskerPreferences"] + dependencies: ["TuskerPreferences"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ] ) ] ) diff --git a/Packages/UserAccounts/Package.swift b/Packages/UserAccounts/Package.swift index cb707d15e4..fc1b8c5b4b 100644 --- a/Packages/UserAccounts/Package.swift +++ b/Packages/UserAccounts/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,7 +6,7 @@ import PackageDescription let package = Package( name: "UserAccounts", platforms: [ - .iOS(.v15), + .iOS(.v16), ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -23,7 +23,10 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "UserAccounts", - dependencies: ["Pachyderm"]), + dependencies: ["Pachyderm"], + swiftSettings: [ + .swiftLanguageMode(.v5) + ]), // .testTarget( // name: "UserAccountsTests", // dependencies: ["UserAccounts"]), diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index e9b86e8a32..61b01eff36 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -141,7 +141,6 @@ D64B96812BC3279D002C8990 /* PrefsAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B96802BC3279D002C8990 /* PrefsAccountView.swift */; }; D64B96842BC3893C002C8990 /* PushSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64B96832BC3893C002C8990 /* PushSubscriptionView.swift */; }; D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; }; - D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */; }; D651C5B42915B00400236EF6 /* ProfileFieldsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */; }; D6531DEE246B81C9000F9538 /* GifvPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvPlayerView.swift */; }; D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; }; @@ -337,7 +336,7 @@ D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F582A13293200AB2315 /* BackgroundManager.swift */; }; D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; }; D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; }; - D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; }; + D6D9498F298EB79400C59229 /* CopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLabel.swift */; }; D6DD8FFD298495A8002AD3FD /* LogoutService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD8FFC298495A8002AD3FD /* LogoutService.swift */; }; D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD8FFE2984D327002AD3FD /* BookmarksViewController.swift */; }; D6DD996B2998611A0015C962 /* SuggestedProfilesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DD996A2998611A0015C962 /* SuggestedProfilesViewController.swift */; }; @@ -572,7 +571,6 @@ D64B96802BC3279D002C8990 /* PrefsAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsAccountView.swift; sourceTree = ""; }; D64B96832BC3893C002C8990 /* PushSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSubscriptionView.swift; sourceTree = ""; }; D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; - D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = ""; }; D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldsView.swift; sourceTree = ""; }; D6531DED246B81C9000F9538 /* GifvPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvPlayerView.swift; sourceTree = ""; }; D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = ""; }; @@ -778,7 +776,7 @@ D6D79F582A13293200AB2315 /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = ""; }; D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; }; D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = ""; }; - D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = ""; }; + D6D9498E298EB79400C59229 /* CopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLabel.swift; sourceTree = ""; }; D6DD8FFC298495A8002AD3FD /* LogoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutService.swift; sourceTree = ""; }; D6DD8FFE2984D327002AD3FD /* BookmarksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = ""; }; D6DD996A2998611A0015C962 /* SuggestedProfilesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedProfilesViewController.swift; sourceTree = ""; }; @@ -1490,7 +1488,7 @@ D6ADB6EF28ED1F25009924AB /* CachedImageView.swift */, D6895DC328D65342006341DA /* ConfirmReblogStatusPreviewView.swift */, D620483523D38075008A63EF /* ContentTextView.swift */, - D6D9498E298EB79400C59229 /* CopyableLable.swift */, + D6D9498E298EB79400C59229 /* CopyableLabel.swift */, D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */, D6969E9F240C8384002843CE /* EmojiLabel.swift */, D61F75B8293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift */, @@ -1627,7 +1625,6 @@ D6DEBA8C2B6579830008629A /* MainThreadBox.swift */, D6B81F432560390300F6E31D /* MenuController.swift */, D6CF5B842AC7C56F00F15D83 /* MultiColumnCollectionViewLayout.swift */, - D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */, D6945C2E23AC47C3005C403C /* SavedDataManager.swift */, D61F759E29385AD800C0B37F /* SemiCaseSensitiveComparator.swift */, D6895DE828D962C2006341DA /* TimelineLikeController.swift */, @@ -2306,7 +2303,7 @@ D61ABEFE28F1C92600B29151 /* FavoriteService.swift in Sources */, D61F75AB293AF11400C0B37F /* FilterKeywordMO.swift in Sources */, D65B4B5A29720AB000DABDFB /* ReportStatusView.swift in Sources */, - D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */, + D6D9498F298EB79400C59229 /* CopyableLabel.swift in Sources */, D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */, D6D706A029466649000827ED /* ScrollingSegmentedControl.swift in Sources */, D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */, @@ -2390,7 +2387,6 @@ D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */, D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */, D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */, - D64D8CA92463B494006B0BAA /* MultiThreadDictionary.swift in Sources */, D630C3CA2BC59FF500208903 /* MastodonController+Push.swift in Sources */, D6F6A554291F0D9600F496A8 /* DeleteListService.swift in Sources */, D68E6F5F253C9B2D001A1B4C /* BaseEmojiLabel.swift in Sources */, @@ -2547,6 +2543,7 @@ INFOPLIST_FILE = NotificationExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2579,6 +2576,7 @@ INFOPLIST_FILE = NotificationExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2610,6 +2608,7 @@ INFOPLIST_FILE = NotificationExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = NotificationExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2700,6 +2699,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2743,7 +2743,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = TuskerUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2766,6 +2766,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = OpenInTusker/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2793,6 +2794,7 @@ INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2821,6 +2823,7 @@ INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -2849,6 +2852,7 @@ INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Shadowfacts. All rights reserved."; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3004,6 +3008,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3036,6 +3041,7 @@ CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3100,7 +3106,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = TuskerUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3120,7 +3126,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; INFOPLIST_FILE = TuskerUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3143,6 +3149,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = OpenInTusker/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3167,6 +3174,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(CURRENT_PROJECT_VERSION)"; INFOPLIST_FILE = OpenInTusker/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Tusker/Caching/DiskCache.swift b/Tusker/Caching/DiskCache.swift index aaf8b86f06..d13b68b4e2 100644 --- a/Tusker/Caching/DiskCache.swift +++ b/Tusker/Caching/DiskCache.swift @@ -8,6 +8,7 @@ import Foundation import CryptoKit +import os struct DiskCacheTransformer { let toData: (T) throws -> Data @@ -21,7 +22,7 @@ class DiskCache { let defaultExpiry: CacheExpiry let transformer: DiskCacheTransformer - private var fileStates = MultiThreadDictionary() + private var fileStates = OSAllocatedUnfairLock(initialState: [String: FileState]()) init(name: String, defaultExpiry: CacheExpiry, transformer: DiskCacheTransformer, fileManager: FileManager = .default) throws { self.defaultExpiry = defaultExpiry @@ -59,7 +60,9 @@ class DiskCache { } private func fileState(forKey key: String) -> FileState { - return fileStates[key] ?? .unknown + return fileStates.withLock { + $0[key] ?? .unknown + } } func setObject(_ object: T, forKey key: String) throws { @@ -68,13 +71,17 @@ class DiskCache { guard fileManager.createFile(atPath: path, contents: data, attributes: [.modificationDate: defaultExpiry.date]) else { throw Error.couldNotCreateFile } - fileStates[key] = .exists + fileStates.withLock { + $0[key] = .exists + } } func removeObject(forKey key: String) throws { let path = makeFilePath(for: key) try fileManager.removeItem(atPath: path) - fileStates[key] = .doesNotExist + fileStates.withLock { + $0[key] = .doesNotExist + } } func existsObject(forKey key: String) throws -> Bool { @@ -105,7 +112,9 @@ class DiskCache { } guard date.timeIntervalSinceNow >= 0 else { try fileManager.removeItem(atPath: path) - fileStates[key] = .doesNotExist + fileStates.withLock { + $0[key] = .doesNotExist + } throw Error.expired } diff --git a/Tusker/CoreData/TimelinePosition.swift b/Tusker/CoreData/TimelinePosition.swift index ac29440bce..d340d6fcbb 100644 --- a/Tusker/CoreData/TimelinePosition.swift +++ b/Tusker/CoreData/TimelinePosition.swift @@ -76,17 +76,10 @@ func fromTimelineKind(_ kind: String) -> Timeline { } else if kind == "direct" { return .direct } else if kind.starts(with: "hashtag:") { - return .tag(hashtag: String(trimmingPrefix("hashtag:", of: kind))) + return .tag(hashtag: String(kind.trimmingPrefix("hashtag:"))) } else if kind.starts(with: "list:") { - return .list(id: String(trimmingPrefix("list:", of: kind))) + return .list(id: String(kind.trimmingPrefix("list:"))) } else { fatalError("invalid timeline kind \(kind)") } } - -// replace with Collection.trimmingPrefix -@available(iOS, obsoleted: 16.0) -@available(visionOS 1.0, *) -private func trimmingPrefix(_ prefix: String, of str: String) -> Substring { - return str[str.index(str.startIndex, offsetBy: prefix.count)...] -} diff --git a/Tusker/Extensions/View+AppListStyle.swift b/Tusker/Extensions/View+AppListStyle.swift index 472bfc3c1f..2d0e79651b 100644 --- a/Tusker/Extensions/View+AppListStyle.swift +++ b/Tusker/Extensions/View+AppListStyle.swift @@ -36,19 +36,12 @@ private struct AppGroupedListBackground: ViewModifier { } func body(content: Content) -> some View { - if #available(iOS 16.0, *) { - if colorScheme == .dark, !pureBlackDarkMode { - content - .scrollContentBackground(.hidden) - .background(Color.appGroupedBackground.edgesIgnoringSafeArea(.all)) - } else { - content - } + if colorScheme == .dark, !pureBlackDarkMode { + content + .scrollContentBackground(.hidden) + .background(Color.appGroupedBackground.edgesIgnoringSafeArea(.all)) } else { content - .onAppear { - UITableView.appearance(whenContainedInInstancesOf: [container]).backgroundColor = .appGroupedBackground - } } } } diff --git a/Tusker/HTMLConverter.swift b/Tusker/HTMLConverter.swift index 26dd6ccf8c..c1975d232f 100644 --- a/Tusker/HTMLConverter.swift +++ b/Tusker/HTMLConverter.swift @@ -48,14 +48,13 @@ extension HTMLConverter { // Converting WebURL to URL is a small but non-trivial expense (since it works by // serializing the WebURL as a string and then having Foundation parse it again), // so, if available, use the system parser which doesn't require another round trip. - if #available(iOS 16.0, macOS 13.0, *), - let url = try? URL.ParseStrategy().parse(string) { + if let url = try? URL.ParseStrategy().parse(string) { url } else if let web = WebURL(string), let url = URL(web) { url } else { - URL(string: string) + nil } } diff --git a/Tusker/MultiThreadDictionary.swift b/Tusker/MultiThreadDictionary.swift deleted file mode 100644 index 8f015d777a..0000000000 --- a/Tusker/MultiThreadDictionary.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// MultiThreadDictionary.swift -// Tusker -// -// Created by Shadowfacts on 5/6/20. -// Copyright © 2020 Shadowfacts. All rights reserved. -// - -import Foundation -import os - -// once we target iOS 16, replace uses of this with OSAllocatedUnfairLock<[Key: Value]> -// to make the lock semantics more clear -@available(iOS, obsoleted: 16.0) -@available(visionOS 1.0, *) -final class MultiThreadDictionary: @unchecked Sendable { - #if os(visionOS) - private let lock = OSAllocatedUnfairLock(initialState: [Key: Value]()) - #else - private let lock: any Lock<[Key: Value]> - #endif - - init() { - #if !os(visionOS) - if #available(iOS 16.0, *) { - self.lock = OSAllocatedUnfairLock(initialState: [:]) - } else { - self.lock = UnfairLock(initialState: [:]) - } - #endif - } - - subscript(key: Key) -> Value? { - get { - return lock.withLock { dict in - dict[key] - } - } - set(value) { - #if os(visionOS) - lock.withLock { dict in - dict[key] = value - } - #else - _ = lock.withLock { dict in - dict[key] = value - } - #endif - } - } - - /// If the result of this function is unused, it is preferable to use `removeValueWithoutReturning` as it executes asynchronously and doesn't block the calling thread. - func removeValue(forKey key: Key) -> Value? { - return lock.withLock { dict in - dict.removeValue(forKey: key) - } - } - - func contains(key: Key) -> Bool { - return lock.withLock { dict in - dict.keys.contains(key) - } - } - - // TODO: this should really be throws/rethrows but the stupid type-erased lock makes that not possible - func withLock(_ body: @Sendable (inout [Key: Value]) throws -> R) rethrows -> R where R: Sendable { - return try lock.withLock { dict in - return try body(&dict) - } - } -} - -#if !os(visionOS) -// TODO: replace this only with OSAllocatedUnfairLock -@available(iOS, obsoleted: 16.0) -fileprivate protocol Lock { - associatedtype State - func withLock(_ body: @Sendable (inout State) throws -> R) rethrows -> R where R: Sendable -} - -@available(iOS 16.0, *) -extension OSAllocatedUnfairLock: Lock { -} - -// from http://www.russbishop.net/the-law -fileprivate class UnfairLock: Lock { - private var lock: UnsafeMutablePointer - private var state: State - init(initialState: State) { - self.state = initialState - self.lock = .allocate(capacity: 1) - self.lock.initialize(to: os_unfair_lock()) - } - deinit { - self.lock.deinitialize(count: 1) - self.lock.deallocate() - } - func withLock(_ body: (inout State) throws -> R) rethrows -> R where R: Sendable { - os_unfair_lock_lock(lock) - defer { os_unfair_lock_unlock(lock) } - return try body(&state) - } -} -#endif diff --git a/Tusker/Scenes/MainSceneDelegate.swift b/Tusker/Scenes/MainSceneDelegate.swift index 31e5b6678d..ffb6486c04 100644 --- a/Tusker/Scenes/MainSceneDelegate.swift +++ b/Tusker/Scenes/MainSceneDelegate.swift @@ -274,8 +274,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate } else { mainVC = MainSplitViewController(mastodonController: mastodonController) } - if UIDevice.current.userInterfaceIdiom == .phone, - #available(iOS 16.0, *) { + if UIDevice.current.userInterfaceIdiom == .phone { // TODO: maybe the duckable container should be outside the account switching container return DuckableContainerViewController(child: mainVC) } else { diff --git a/Tusker/Screens/Announcements/AddReactionView.swift b/Tusker/Screens/Announcements/AddReactionView.swift index f365ef1bee..c5ca7269f7 100644 --- a/Tusker/Screens/Announcements/AddReactionView.swift +++ b/Tusker/Screens/Announcements/AddReactionView.swift @@ -86,7 +86,7 @@ struct AddReactionView: View { } } .navigationViewStyle(.stack) - .mediumPresentationDetentIfAvailable() + .presentationDetents([.medium, .large]) .alertWithData("Error Adding Reaction", data: $error, actions: { _ in Button("OK") {} }, message: { error in @@ -171,17 +171,6 @@ private struct AddReactionButton: View { } private extension View { - @available(iOS, obsoleted: 16.0) - @available(visionOS 1.0, *) - @ViewBuilder - func mediumPresentationDetentIfAvailable() -> some View { - if #available(iOS 16.0, *) { - self.presentationDetents([.medium, .large]) - } else { - self - } - } - @available(iOS, obsoleted: 17.1) @available(visionOS 1.0, *) @ViewBuilder diff --git a/Tusker/Screens/Announcements/AnnouncementListRow.swift b/Tusker/Screens/Announcements/AnnouncementListRow.swift index 4123387798..13371c20fa 100644 --- a/Tusker/Screens/Announcements/AnnouncementListRow.swift +++ b/Tusker/Screens/Announcements/AnnouncementListRow.swift @@ -20,14 +20,10 @@ struct AnnouncementListRow: View { @State private var isShowingAddReactionSheet = false var body: some View { - if #available(iOS 16.0, *) { - mostOfTheBody - .alignmentGuide(.listRowSeparatorLeading, computeValue: { dimension in - dimension[.leading] - }) - } else { - mostOfTheBody - } + mostOfTheBody + .alignmentGuide(.listRowSeparatorLeading, computeValue: { dimension in + dimension[.leading] + }) } private var mostOfTheBody: some View { @@ -54,11 +50,7 @@ struct AnnouncementListRow: View { Label { Text("Add Reaction") } icon: { - if #available(iOS 16.0, *) { - Image("face.smiling.badge.plus") - } else { - Image(systemName: "face.smiling") - } + Image("face.smiling.badge.plus") } } .labelStyle(.iconOnly) diff --git a/Tusker/Screens/Customize Timelines/AddHashtagPinnedTimelineView.swift b/Tusker/Screens/Customize Timelines/AddHashtagPinnedTimelineView.swift index 30e4d53cd9..1c66346d46 100644 --- a/Tusker/Screens/Customize Timelines/AddHashtagPinnedTimelineView.swift +++ b/Tusker/Screens/Customize Timelines/AddHashtagPinnedTimelineView.swift @@ -9,20 +9,6 @@ import SwiftUI import Pachyderm -@available(iOS, obsoleted: 16.0) -struct AddHashtagPinnedTimelineRepresentable: UIViewControllerRepresentable { - typealias UIViewControllerType = UIHostingController - - @Binding var pinnedTimelines: [PinnedTimeline] - - func makeUIViewController(context: Context) -> UIHostingController { - return UIHostingController(rootView: AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)) - } - - func updateUIViewController(_ uiViewController: UIHostingController, context: Context) { - } -} - struct AddHashtagPinnedTimelineView: View { @EnvironmentObject private var mastodonController: MastodonController @Environment(\.dismiss) private var dismiss @@ -49,9 +35,6 @@ struct AddHashtagPinnedTimelineView: View { var body: some View { NavigationView { list - #if !os(visionOS) - .appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self) - #endif .listStyle(.grouped) .navigationTitle("Add Hashtag") .navigationBarTitleDisplayMode(.inline) diff --git a/Tusker/Screens/Customize Timelines/CustomizeTimelinesView.swift b/Tusker/Screens/Customize Timelines/CustomizeTimelinesView.swift index e7c32a6423..8eabf79b56 100644 --- a/Tusker/Screens/Customize Timelines/CustomizeTimelinesView.swift +++ b/Tusker/Screens/Customize Timelines/CustomizeTimelinesView.swift @@ -36,15 +36,8 @@ struct CustomizeTimelinesList: View { } var body: some View { - if #available(iOS 16.0, *) { - NavigationStack { - navigationBody - } - } else { - NavigationView { - navigationBody - } - .navigationViewStyle(.stack) + NavigationStack { + navigationBody } } diff --git a/Tusker/Screens/Customize Timelines/EditFilterView.swift b/Tusker/Screens/Customize Timelines/EditFilterView.swift index acfa70f025..c6452dffbe 100644 --- a/Tusker/Screens/Customize Timelines/EditFilterView.swift +++ b/Tusker/Screens/Customize Timelines/EditFilterView.swift @@ -149,7 +149,7 @@ struct EditFilterView: View { } .appGroupedListBackground(container: UIHostingController.self) #if !os(visionOS) - .scrollDismissesKeyboardInteractivelyIfAvailable() + .scrollDismissesKeyboard(.interactively) #endif .navigationTitle(create ? "Add Filter" : "Edit Filter") .navigationBarTitleDisplayMode(.inline) @@ -226,18 +226,6 @@ private struct FilterContextToggleStyle: ToggleStyle { } } -private extension View { - @available(iOS, obsoleted: 16.0) - @ViewBuilder - func scrollDismissesKeyboardInteractivelyIfAvailable() -> some View { - if #available(iOS 16.0, *) { - self.scrollDismissesKeyboard(.interactively) - } else { - self - } - } -} - //struct EditFilterView_Previews: PreviewProvider { // static var previews: some View { // EditFilterView() diff --git a/Tusker/Screens/Customize Timelines/PinnedTimelinesView.swift b/Tusker/Screens/Customize Timelines/PinnedTimelinesView.swift index c355f285ba..381cd4f362 100644 --- a/Tusker/Screens/Customize Timelines/PinnedTimelinesView.swift +++ b/Tusker/Screens/Customize Timelines/PinnedTimelinesView.swift @@ -115,18 +115,8 @@ struct PinnedTimelinesModifier: ViewModifier { func body(content: Content) -> some View { content .sheet(isPresented: $isShowingAddHashtagSheet, content: { - #if os(visionOS) AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines) .edgesIgnoringSafeArea(.bottom) - #else - if #available(iOS 16.0, *) { - AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines) - .edgesIgnoringSafeArea(.bottom) - } else { - AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines) - .edgesIgnoringSafeArea(.bottom) - } - #endif }) .sheet(isPresented: $isShowingAddInstanceSheet, content: { AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines) diff --git a/Tusker/Screens/Explore/InlineTrendsViewController.swift b/Tusker/Screens/Explore/InlineTrendsViewController.swift index 74ab5e36b9..8a4cd6fadd 100644 --- a/Tusker/Screens/Explore/InlineTrendsViewController.swift +++ b/Tusker/Screens/Explore/InlineTrendsViewController.swift @@ -41,9 +41,7 @@ class InlineTrendsViewController: UIViewController { navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false - if #available(iOS 16.0, *) { - navigationItem.preferredSearchBarPlacement = .stacked - } + navigationItem.preferredSearchBarPlacement = .stacked let trends = TrendsViewController(mastodonController: mastodonController) trends.view.translatesAutoresizingMaskIntoConstraints = false diff --git a/Tusker/Screens/Explore/TrendsViewController.swift b/Tusker/Screens/Explore/TrendsViewController.swift index 4b09e2e79d..511c22b8ff 100644 --- a/Tusker/Screens/Explore/TrendsViewController.swift +++ b/Tusker/Screens/Explore/TrendsViewController.swift @@ -525,12 +525,12 @@ extension TrendsViewController: UICollectionViewDelegate { } } - @available(iOS, obsoleted: 16.0) - @available(visionOS 1.0, *) - func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - guard let item = dataSource.itemIdentifier(for: indexPath) else { + func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? { + guard indexPaths.count == 1, + let item = dataSource.itemIdentifier(for: indexPaths[0]) else { return nil } + let indexPath = indexPaths[0] switch item { case .loadingIndicator, .confirmLoadMoreStatuses(_): @@ -584,15 +584,6 @@ extension TrendsViewController: UICollectionViewDelegate { } } - // implementing the highlightPreviewForItemAt method seems to prevent the old, single IndexPath variant of this method from being called on iOS 16 - @available(iOS 16.0, visionOS 1.0, *) - func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? { - guard indexPaths.count == 1 else { - return nil - } - return self.collectionView(collectionView, contextMenuConfigurationForItemAt: indexPaths[0], point: point) - } - func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self) } diff --git a/Tusker/Screens/Gallery/VideoControlsViewController.swift b/Tusker/Screens/Gallery/VideoControlsViewController.swift index d2c582e6d2..f8748981f7 100644 --- a/Tusker/Screens/Gallery/VideoControlsViewController.swift +++ b/Tusker/Screens/Gallery/VideoControlsViewController.swift @@ -18,9 +18,6 @@ class VideoControlsViewController: UIViewController { }() private let player: AVPlayer - #if !os(visionOS) - @Box private var playbackSpeed: Float - #endif private lazy var muteButton = MuteButton().configure { $0.addTarget(self, action: #selector(muteButtonPressed), for: .touchUpInside) @@ -46,13 +43,8 @@ class VideoControlsViewController: UIViewController { private lazy var optionsButton = MenuButton { [unowned self] in let imageName: String - #if os(visionOS) - let playbackSpeed = player.defaultRate - #else - let playbackSpeed = self.playbackSpeed - #endif if #available(iOS 17.0, *) { - switch playbackSpeed { + switch player.defaultRate { case 0.5: imageName = "gauge.with.dots.needle.0percent" case 1: @@ -68,12 +60,8 @@ class VideoControlsViewController: UIViewController { imageName = "speedometer" } let speedMenu = UIMenu(title: "Playback Speed", image: UIImage(systemName: imageName), children: PlaybackSpeed.allCases.map { speed in - UIAction(title: speed.displayName, state: playbackSpeed == speed.rate ? .on : .off) { [unowned self] _ in - #if os(visionOS) + UIAction(title: speed.displayName, state: self.player.defaultRate == speed.rate ? .on : .off) { [unowned self] _ in self.player.defaultRate = speed.rate - #else - self.playbackSpeed = speed.rate - #endif if self.player.rate > 0 { self.player.rate = speed.rate } @@ -101,20 +89,11 @@ class VideoControlsViewController: UIViewController { private var scrubbingTargetTime: CMTime? private var isSeeking = false - #if os(visionOS) init(player: AVPlayer) { self.player = player super.init(nibName: nil, bundle: nil) } - #else - init(player: AVPlayer, playbackSpeed: Box) { - self.player = player - self._playbackSpeed = playbackSpeed - - super.init(nibName: nil, bundle: nil) - } - #endif required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -198,11 +177,7 @@ class VideoControlsViewController: UIViewController { @objc private func scrubbingEnded() { scrubbingChanged() if wasPlayingWhenScrubbingStarted { - #if os(visionOS) player.play() - #else - player.rate = playbackSpeed - #endif } } diff --git a/Tusker/Screens/Gallery/VideoGalleryContentViewController.swift b/Tusker/Screens/Gallery/VideoGalleryContentViewController.swift index 797c72a376..756fae5589 100644 --- a/Tusker/Screens/Gallery/VideoGalleryContentViewController.swift +++ b/Tusker/Screens/Gallery/VideoGalleryContentViewController.swift @@ -17,11 +17,6 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon private var item: AVPlayerItem let player: AVPlayer - #if !os(visionOS) - @available(iOS, obsoleted: 16.0, message: "Use AVPlayer.defaultRate") - @Box private var playbackSpeed: Float = 1 - #endif - private var isGrayscale: Bool private var presentationSizeObservation: NSKeyValueObservation? @@ -161,11 +156,7 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon player.replaceCurrentItem(with: item) updateItemObservations() if isPlaying { - #if os(visionOS) player.play() - #else - player.rate = playbackSpeed - #endif } } } @@ -196,20 +187,12 @@ class VideoGalleryContentViewController: UIViewController, GalleryContentViewCon [VideoActivityItemSource(asset: item.asset, url: url)] } - #if os(visionOS) private lazy var overlayVC = VideoOverlayViewController(player: player) - #else - private lazy var overlayVC = VideoOverlayViewController(player: player, playbackSpeed: _playbackSpeed) - #endif var contentOverlayAccessoryViewController: UIViewController? { overlayVC } - #if os(visionOS) private(set) lazy var bottomControlsAccessoryViewController: UIViewController? = VideoControlsViewController(player: player) - #else - private(set) lazy var bottomControlsAccessoryViewController: UIViewController? = VideoControlsViewController(player: player, playbackSpeed: _playbackSpeed) - #endif func setControlsVisible(_ visible: Bool, animated: Bool, dueToUserInteraction: Bool) { overlayVC.setVisible(visible) diff --git a/Tusker/Screens/Gallery/VideoOverlayViewController.swift b/Tusker/Screens/Gallery/VideoOverlayViewController.swift index cbc7d02c45..40515704bf 100644 --- a/Tusker/Screens/Gallery/VideoOverlayViewController.swift +++ b/Tusker/Screens/Gallery/VideoOverlayViewController.swift @@ -15,9 +15,6 @@ class VideoOverlayViewController: UIViewController { private static let pauseImage = UIImage(systemName: "pause.fill")! private let player: AVPlayer - #if !os(visionOS) - @Box private var playbackSpeed: Float - #endif private var dimmingView: UIView! private var controlsStack: UIStackView! @@ -26,18 +23,10 @@ class VideoOverlayViewController: UIViewController { private var rateObservation: NSKeyValueObservation? - #if os(visionOS) init(player: AVPlayer) { self.player = player super.init(nibName: nil, bundle: nil) } - #else - init(player: AVPlayer, playbackSpeed: Box) { - self.player = player - self._playbackSpeed = playbackSpeed - super.init(nibName: nil, bundle: nil) - } - #endif required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -109,11 +98,7 @@ class VideoOverlayViewController: UIViewController { if player.currentTime() >= player.currentItem!.duration { player.seek(to: .zero) } - #if os(visionOS) player.play() - #else - player.rate = playbackSpeed - #endif } } diff --git a/Tusker/Screens/Lists/EditListAccountsViewController.swift b/Tusker/Screens/Lists/EditListAccountsViewController.swift index b3a8ed0c6e..31b1e249d3 100644 --- a/Tusker/Screens/Lists/EditListAccountsViewController.swift +++ b/Tusker/Screens/Lists/EditListAccountsViewController.swift @@ -100,28 +100,13 @@ class EditListAccountsViewController: UIViewController, CollectionViewController navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false - if #available(iOS 16.0, *) { - navigationItem.preferredSearchBarPlacement = .stacked - - navigationItem.renameDelegate = self - navigationItem.titleMenuProvider = { [unowned self] suggested in - var children = suggested - children.append(contentsOf: self.listSettingsMenuElements()) - return UIMenu(children: children) - } - } else { - navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Edit", menu: UIMenu(children: [ - // uncached so that menu always reflects the current state of the list - UIDeferredMenuElement.uncached({ [unowned self] elementHandler in - var elements = self.listSettingsMenuElements() - elements.insert(UIAction(title: "Rename…", image: UIImage(systemName: "pencil"), handler: { [unowned self] _ in - RenameListService(list: self.list, mastodonController: self.mastodonController, present: { - self.present($0, animated: true) - }).run() - }), at: 0) - elementHandler(elements) - }) - ])) + navigationItem.preferredSearchBarPlacement = .stacked + + navigationItem.renameDelegate = self + navigationItem.titleMenuProvider = { [unowned self] suggested in + var children = suggested + children.append(contentsOf: self.listSettingsMenuElements()) + return UIMenu(children: children) } } diff --git a/Tusker/Screens/Mute/MuteAccountView.swift b/Tusker/Screens/Mute/MuteAccountView.swift index 5cdc72a4c8..8f1168c131 100644 --- a/Tusker/Screens/Mute/MuteAccountView.swift +++ b/Tusker/Screens/Mute/MuteAccountView.swift @@ -41,15 +41,8 @@ struct MuteAccountView: View { @State private var error: Error? var body: some View { - if #available(iOS 16.0, *) { - NavigationStack { - navigationViewContent - } - } else { - NavigationView { - navigationViewContent - } - .navigationViewStyle(.stack) + NavigationStack { + navigationViewContent } } diff --git a/Tusker/Screens/Notifications/FollowRequestNotificationViewController.swift b/Tusker/Screens/Notifications/FollowRequestNotificationViewController.swift index 5229470275..3d53b7738e 100644 --- a/Tusker/Screens/Notifications/FollowRequestNotificationViewController.swift +++ b/Tusker/Screens/Notifications/FollowRequestNotificationViewController.swift @@ -101,14 +101,8 @@ extension FollowRequestNotificationViewController: UICollectionViewDelegate { UIAction(title: "Accept", image: UIImage(systemName: "checkmark.circle"), handler: { _ in cell.acceptButtonPressed() }), UIAction(title: "Reject", image: UIImage(systemName: "xmark.circle"), handler: { _ in cell.rejectButtonPressed() }), ] - let acceptRejectMenu: UIMenu - if #available(iOS 16.0, *) { - acceptRejectMenu = UIMenu(options: .displayInline, preferredElementSize: .medium, children: acceptRejectChildren) - } else { - acceptRejectMenu = UIMenu(options: .displayInline, children: acceptRejectChildren) - } return UIMenu(children: [ - acceptRejectMenu, + UIMenu(options: .displayInline, preferredElementSize: .medium, children: acceptRejectChildren), UIMenu(options: .displayInline, children: self.actionsForProfile(accountID: accountID, source: .view(cell))), ]) } diff --git a/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift b/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift index eb042b7240..acd5b01a94 100644 --- a/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift +++ b/Tusker/Screens/Notifications/NotificationsCollectionViewController.swift @@ -700,14 +700,8 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate { UIAction(title: "Accept", image: UIImage(systemName: "checkmark.circle"), handler: { _ in cell.acceptButtonPressed() }), UIAction(title: "Reject", image: UIImage(systemName: "xmark.circle"), handler: { _ in cell.rejectButtonPressed() }), ] - let acceptRejectMenu: UIMenu - if #available(iOS 16.0, *) { - acceptRejectMenu = UIMenu(options: .displayInline, preferredElementSize: .medium, children: acceptRejectChildren) - } else { - acceptRejectMenu = UIMenu(options: .displayInline, children: acceptRejectChildren) - } return UIMenu(children: [ - acceptRejectMenu, + UIMenu(options: .displayInline, preferredElementSize: .medium, children: acceptRejectChildren), UIMenu(options: .displayInline, children: self.actionsForProfile(accountID: accountID, source: .view(cell))), ]) } diff --git a/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift index 17f34f94cd..32d2d018d2 100644 --- a/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift +++ b/Tusker/Screens/Onboarding/InstanceSelectorTableViewController.swift @@ -96,9 +96,7 @@ class InstanceSelectorTableViewController: UITableViewController { searchController.searchBar.placeholder = "Search or enter a URL" navigationItem.searchController = searchController navigationItem.hidesSearchBarWhenScrolling = false - if #available(iOS 16.0, *) { - navigationItem.preferredSearchBarPlacement = .stacked - } + navigationItem.preferredSearchBarPlacement = .stacked definesPresentationContext = true urlHandler = urlCheckerSubject diff --git a/Tusker/Screens/Preferences/About/AboutView.swift b/Tusker/Screens/Preferences/About/AboutView.swift index 8f87b236be..c3d0dcc522 100644 --- a/Tusker/Screens/Preferences/About/AboutView.swift +++ b/Tusker/Screens/Preferences/About/AboutView.swift @@ -91,14 +91,10 @@ struct AboutView: View { @ViewBuilder private var iconOrGame: some View { - if #available(iOS 16.0, *) { - FlipView { - appIcon - } back: { - TTTView() - } - } else { + FlipView { appIcon + } back: { + TTTView() } } diff --git a/Tusker/Screens/Preferences/Appearance/AppearancePrefsView.swift b/Tusker/Screens/Preferences/Appearance/AppearancePrefsView.swift index 35e8361890..88e2fd8a5d 100644 --- a/Tusker/Screens/Preferences/Appearance/AppearancePrefsView.swift +++ b/Tusker/Screens/Preferences/Appearance/AppearancePrefsView.swift @@ -27,14 +27,7 @@ struct AppearancePrefsView: View { private static let accentColorsAndImages: [(AccentColor, UIImage?)] = AccentColor.allCases.map { color in var image: UIImage? if let color = color.color { - if #available(iOS 16.0, *) { - image = UIImage(systemName: "circle.fill")!.withTintColor(color, renderingMode: .alwaysTemplate).withRenderingMode(.alwaysOriginal) - } else { - image = UIGraphicsImageRenderer(size: CGSize(width: 20, height: 20)).image { context in - color.setFill() - context.cgContext.fillEllipse(in: CGRect(x: 0, y: 0, width: 20, height: 20)) - } - } + image = UIImage(systemName: "circle.fill")!.withTintColor(color, renderingMode: .alwaysTemplate).withRenderingMode(.alwaysOriginal) } return (color, image) } diff --git a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift index c8437e2618..903395ce3e 100644 --- a/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift +++ b/Tusker/Screens/Preferences/Notifications/NotificationsPrefsView.swift @@ -36,12 +36,7 @@ struct NotificationsPrefsView: View { if #available(iOS 15.4, *) { Section { Button { - let str = if #available(iOS 16.0, *) { - UIApplication.openNotificationSettingsURLString - } else { - UIApplicationOpenNotificationSettingsURLString - } - if let url = URL(string: str) { + if let url = URL(string: UIApplication.openNotificationSettingsURLString) { UIApplication.shared.open(url) } } label: { diff --git a/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift b/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift index 98c68d6d81..9d6a7cb065 100644 --- a/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift +++ b/Tusker/Screens/Preferences/Notifications/PushInstanceSettingsView.swift @@ -34,7 +34,7 @@ struct PushInstanceSettingsView: View { HStack { PrefsAccountView(account: account) Spacer() - AsyncToggle("\(account.instanceURL.host!) notifications enabled", labelHidden: true, mode: $mode, onChange: updateNotificationsEnabled(enabled:)) + AsyncToggle("\(account.instanceURL.host!) notifications enabled", mode: $mode, onChange: updateNotificationsEnabled(enabled:)) .labelsHidden() } PushSubscriptionView(account: account, mastodonController: mastodonController, subscription: subscription, updateSubscription: updateSubscription) diff --git a/Tusker/Screens/Preferences/OppositeCollapseKeywordsView.swift b/Tusker/Screens/Preferences/OppositeCollapseKeywordsView.swift index 27e737461c..60a92547a3 100644 --- a/Tusker/Screens/Preferences/OppositeCollapseKeywordsView.swift +++ b/Tusker/Screens/Preferences/OppositeCollapseKeywordsView.swift @@ -43,21 +43,9 @@ struct OppositeCollapseKeywordsView: View { .listStyle(.grouped) .appGroupedListBackground(container: PreferencesNavigationController.self) } - #if !os(visionOS) - .onAppear(perform: updateAppearance) - #endif .navigationBarTitle(preferences.expandAllContentWarnings ? "Collapse Post CW Keywords" : "Expand Post CW Keywords") } - @available(iOS, obsoleted: 16.0) - private func updateAppearance() { - if #available(iOS 16.0, *) { - // no longer necessary - } else { - UIScrollView.appearance(whenContainedInInstancesOf: [PreferencesNavigationController.self]).keyboardDismissMode = .interactive - } - } - private func commitExisting(at index: Int) -> () -> Void { return { if keywords[index].value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { diff --git a/Tusker/Screens/Report/ReportAddStatusView.swift b/Tusker/Screens/Report/ReportAddStatusView.swift index e56cc76ba0..a148828eb3 100644 --- a/Tusker/Screens/Report/ReportAddStatusView.swift +++ b/Tusker/Screens/Report/ReportAddStatusView.swift @@ -69,34 +69,30 @@ private struct ScrollBackgroundModifier: ViewModifier { @Environment(\.colorScheme) private var colorScheme func body(content: Content) -> some View { - if #available(iOS 16.0, *) { - content - .scrollContentBackground(.hidden) - .background { - // otherwise the pureBlackDarkMode isn't propagated, for some reason? - // even though it is for ReportSelectRulesView?? - let traits: UITraitCollection = { - var t = UITraitCollection(userInterfaceStyle: colorScheme == .dark ? .dark : .light) - #if os(visionOS) + content + .scrollContentBackground(.hidden) + .background { + // otherwise the pureBlackDarkMode isn't propagated, for some reason? + // even though it is for ReportSelectRulesView?? + let traits: UITraitCollection = { + var t = UITraitCollection(userInterfaceStyle: colorScheme == .dark ? .dark : .light) + #if os(visionOS) + t = t.modifyingTraits({ mutableTraits in + mutableTraits.pureBlackDarkMode = true + }) + #else + if #available(iOS 17.0, *) { t = t.modifyingTraits({ mutableTraits in mutableTraits.pureBlackDarkMode = true }) - #else - if #available(iOS 17.0, *) { - t = t.modifyingTraits({ mutableTraits in - mutableTraits.pureBlackDarkMode = true - }) - } else { - t.obsoletePureBlackDarkMode = true - } - #endif - return t - }() - Color(uiColor: .appGroupedBackground.resolvedColor(with: traits)) - .edgesIgnoringSafeArea(.all) - } - } else { - content - } + } else { + t.obsoletePureBlackDarkMode = true + } + #endif + return t + }() + Color(uiColor: .appGroupedBackground.resolvedColor(with: traits)) + .edgesIgnoringSafeArea(.all) + } } } diff --git a/Tusker/Screens/Report/ReportSelectRulesView.swift b/Tusker/Screens/Report/ReportSelectRulesView.swift index 39e6510a81..4d7d062672 100644 --- a/Tusker/Screens/Report/ReportSelectRulesView.swift +++ b/Tusker/Screens/Report/ReportSelectRulesView.swift @@ -49,26 +49,12 @@ struct ReportSelectRulesView: View { } .appGroupedListRowBackground() } - .withAppBackgroundIfAvailable() + .scrollContentBackground(.hidden) + .background(Color.appGroupedBackground) .navigationTitle("Rules") } } -private extension View { - @available(iOS, obsoleted: 16.0) - @available(visionOS 1.0, *) - @ViewBuilder - func withAppBackgroundIfAvailable() -> some View { - if #available(iOS 16.0, *) { - self - .scrollContentBackground(.hidden) - .background(Color.appGroupedBackground) - } else { - self - } - } -} - //struct ReportSelectRulesView_Previews: PreviewProvider { // static var previews: some View { // ReportSelectRulesView() diff --git a/Tusker/Screens/Report/ReportView.swift b/Tusker/Screens/Report/ReportView.swift index d51fae1776..ec6d3fb798 100644 --- a/Tusker/Screens/Report/ReportView.swift +++ b/Tusker/Screens/Report/ReportView.swift @@ -27,18 +27,11 @@ struct ReportView: View { } var body: some View { - if #available(iOS 16.0, *) { - NavigationStack { - navigationViewContent - #if !os(visionOS) - .scrollDismissesKeyboard(.interactively) - #endif - } - } else { - NavigationView { - navigationViewContent - } - .navigationViewStyle(.stack) + NavigationStack { + navigationViewContent + #if !os(visionOS) + .scrollDismissesKeyboard(.interactively) + #endif } } diff --git a/Tusker/Screens/Search/MastodonSearchController.swift b/Tusker/Screens/Search/MastodonSearchController.swift index bc62764553..62efb009dd 100644 --- a/Tusker/Screens/Search/MastodonSearchController.swift +++ b/Tusker/Screens/Search/MastodonSearchController.swift @@ -39,9 +39,7 @@ class MastodonSearchController: UISearchController { searchResultsUpdater = searchResultsController automaticallyShowsSearchResultsController = false showsSearchResultsController = true - if #available(iOS 16.0, *) { - scopeBarActivation = .onSearchActivation - } + scopeBarActivation = .onSearchActivation searchBar.autocapitalizationType = .none searchBar.delegate = self @@ -78,12 +76,8 @@ class MastodonSearchController: UISearchController { if searchText != defaultLanguage, let match = languageRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) { let identifier = (searchText as NSString).substring(with: match.range(at: 1)) - if #available(iOS 16.0, *) { - if Locale.LanguageCode.isoLanguageCodes.contains(where: { $0.identifier == identifier }) { - langSuggestions.append("language:\(identifier)") - } - } else if searchText != "en" { - langSuggestions.append("language:\(searchText)") + if Locale.LanguageCode.isoLanguageCodes.contains(where: { $0.identifier == identifier }) { + langSuggestions.append("language:\(identifier)") } } suggestions.append((.language, langSuggestions)) diff --git a/Tusker/Screens/Utilities/EnhancedNavigationViewController.swift b/Tusker/Screens/Utilities/EnhancedNavigationViewController.swift index ee74cb1353..a2af712e30 100644 --- a/Tusker/Screens/Utilities/EnhancedNavigationViewController.swift +++ b/Tusker/Screens/Utilities/EnhancedNavigationViewController.swift @@ -22,8 +22,7 @@ class EnhancedNavigationViewController: UINavigationController { override var viewControllers: [UIViewController] { didSet { poppedViewControllers = [] - if #available(iOS 16.0, *), - useBrowserStyleNavigation { + if useBrowserStyleNavigation { // TODO: this for loop might not be necessary for vc in viewControllers { configureNavItem(vc.navigationItem) @@ -40,8 +39,7 @@ class EnhancedNavigationViewController: UINavigationController { self.interactivePushTransition = InteractivePushTransition(navigationController: self) #endif - if #available(iOS 16.0, *), - useBrowserStyleNavigation, + if useBrowserStyleNavigation, let topViewController { configureNavItem(topViewController.navigationItem) updateTopNavItemState() @@ -52,9 +50,7 @@ class EnhancedNavigationViewController: UINavigationController { let popped = performAfterAnimating(block: { super.popViewController(animated: animated) }, after: { - if #available(iOS 16.0, *) { - self.updateTopNavItemState() - } + self.updateTopNavItemState() }, animated: animated) if let popped { poppedViewControllers.insert(popped, at: 0) @@ -66,9 +62,7 @@ class EnhancedNavigationViewController: UINavigationController { let popped = performAfterAnimating(block: { super.popToRootViewController(animated: animated) }, after: { - if #available(iOS 16.0, *) { - self.updateTopNavItemState() - } + self.updateTopNavItemState() }, animated: animated) if let popped { poppedViewControllers = popped @@ -80,9 +74,7 @@ class EnhancedNavigationViewController: UINavigationController { let popped = performAfterAnimating(block: { super.popToViewController(viewController, animated: animated) }, after: { - if #available(iOS 16.0, *) { - self.updateTopNavItemState() - } + self.updateTopNavItemState() }, animated: animated) if let popped { poppedViewControllers.insert(contentsOf: popped, at: 0) @@ -97,15 +89,11 @@ class EnhancedNavigationViewController: UINavigationController { self.poppedViewControllers = [] } - if #available(iOS 16.0, *) { - configureNavItem(viewController.navigationItem) - } + configureNavItem(viewController.navigationItem) super.pushViewController(viewController, animated: animated) - if #available(iOS 16.0, *) { - updateTopNavItemState() - } + updateTopNavItemState() } func pushPoppedViewController() { @@ -135,9 +123,7 @@ class EnhancedNavigationViewController: UINavigationController { pushViewController(target, animated: true) }, after: { self.viewControllers.insert(contentsOf: toInsert, at: self.viewControllers.count - 1) - if #available(iOS 16.0, *) { - self.updateTopNavItemState() - } + self.updateTopNavItemState() }, animated: true) } diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 32d1cadd1d..5b9801cb1c 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -204,8 +204,7 @@ extension MenuActionProvider { }), ] - if #available(iOS 16.0, *), - includeStatusButtonActions { + if includeStatusButtonActions { let favorited = status.favourited // TODO: move this color into an asset catalog or something var favImage = UIImage(systemName: favorited ? "star.fill" : "star")! @@ -368,19 +367,11 @@ extension MenuActionProvider { addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID)) - if #available(iOS 16.0, *) { - let toggleableAndActions = toggleableSection + actionsSection - return [ - UIMenu(options: .displayInline, preferredElementSize: toggleableAndActions.count == 1 ? .large : .medium, children: toggleableAndActions), - UIMenu(options: .displayInline, children: shareSection), - ] - } else { - return [ - UIMenu(options: .displayInline, children: shareSection), - UIMenu(options: .displayInline, children: toggleableSection), - UIMenu(options: .displayInline, children: actionsSection), - ] - } + let toggleableAndActions = toggleableSection + actionsSection + return [ + UIMenu(options: .displayInline, preferredElementSize: toggleableAndActions.count == 1 ? .large : .medium, children: toggleableAndActions), + UIMenu(options: .displayInline, children: shareSection), + ] } func actionsForTrendingLink(card: Card, source: PopoverSource) -> [UIMenuElement] { diff --git a/Tusker/Shortcuts/UserActivityHandlingContext.swift b/Tusker/Shortcuts/UserActivityHandlingContext.swift index 5903bb8ef9..bfb627b38c 100644 --- a/Tusker/Shortcuts/UserActivityHandlingContext.swift +++ b/Tusker/Shortcuts/UserActivityHandlingContext.swift @@ -108,8 +108,7 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext { } func compose(editing draft: Draft) { - if #available(iOS 16.0, *), - UIDevice.current.userInterfaceIdiom == .phone { + if UIDevice.current.userInterfaceIdiom == .phone { self.root.compose(editing: draft, animated: false, isDucked: true, completion: nil) } else { DispatchQueue.main.async { @@ -123,8 +122,7 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext { precondition(state > .initial) navigation.run() #if !os(visionOS) - if #available(iOS 16.0, *), - let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) { + if let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) { self.root.compose(editing: duckedDraft, animated: false, isDucked: true, completion: nil) } #endif diff --git a/Tusker/TuskerNavigationDelegate.swift b/Tusker/TuskerNavigationDelegate.swift index ca1e462d53..18d903e57b 100644 --- a/Tusker/TuskerNavigationDelegate.swift +++ b/Tusker/TuskerNavigationDelegate.swift @@ -114,8 +114,7 @@ extension TuskerNavigationDelegate { #if os(visionOS) fatalError("unreachable") #else - if #available(iOS 16.0, *), - presentDuckable(compose, animated: animated, isDucked: isDucked, completion: completion) { + if presentDuckable(compose, animated: animated, isDucked: isDucked, completion: completion) { return } else { present(compose, animated: animated, completion: completion) diff --git a/Tusker/Views/AccountDisplayNameView.swift b/Tusker/Views/AccountDisplayNameView.swift index b089900ba0..2cda0c0f72 100644 --- a/Tusker/Views/AccountDisplayNameView.swift +++ b/Tusker/Views/AccountDisplayNameView.swift @@ -9,6 +9,7 @@ import SwiftUI import Pachyderm import WebURLFoundationExtras +import os private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -40,7 +41,7 @@ struct AccountDisplayNameView: View { guard !matches.isEmpty else { return } let emojiSize = self.emojiSize - let emojiImages = MultiThreadDictionary() + let emojiImages = OSAllocatedUnfairLock(initialState: [String: Image]()) let group = DispatchGroup() @@ -63,7 +64,9 @@ struct AccountDisplayNameView: View { image.draw(in: CGRect(origin: .zero, size: size)) } - emojiImages[emoji.shortcode] = Image(uiImage: resized) + emojiImages.withLock { + $0[emoji.shortcode] = Image(uiImage: resized) + } } if let request = request { emojiRequests.append(request) @@ -78,7 +81,7 @@ struct AccountDisplayNameView: View { // iterate backwards as to not alter the indices of earlier matches for match in matches.reversed() { let shortcode = (account.displayName as NSString).substring(with: match.range(at: 1)) - guard let image = emojiImages[shortcode] else { continue } + guard let image = emojiImages.withLock({ $0[shortcode] }) else { continue } let afterCurrentMatch = (account.displayName as NSString).substring(with: NSRange(location: match.range.upperBound, length: endIndex - match.range.upperBound)) diff --git a/Tusker/Views/Attachments/AttachmentView.swift b/Tusker/Views/Attachments/AttachmentView.swift index 081f994068..5617d3fc32 100644 --- a/Tusker/Views/Attachments/AttachmentView.swift +++ b/Tusker/Views/Attachments/AttachmentView.swift @@ -263,17 +263,7 @@ class AttachmentView: GIFImageView { let asset = AVURLAsset(url: attachment.url) let generator = AVAssetImageGenerator(asset: asset) generator.appliesPreferredTrackTransform = true - let image: CGImage? - #if os(visionOS) - image = try? await generator.image(at: .zero).image - #else - if #available(iOS 16.0, *) { - image = try? await generator.image(at: .zero).image - } else { - image = try? generator.copyCGImage(at: .zero, actualTime: nil) - } - #endif - guard let image, + guard let image = try? await generator.image(at: .zero).image, let prepared = await UIImage(cgImage: image).byPreparingForDisplay(), !Task.isCancelled else { return diff --git a/Tusker/Views/BaseEmojiLabel.swift b/Tusker/Views/BaseEmojiLabel.swift index e32aab9a40..92b0d6e4b6 100644 --- a/Tusker/Views/BaseEmojiLabel.swift +++ b/Tusker/Views/BaseEmojiLabel.swift @@ -9,6 +9,7 @@ import UIKit import Pachyderm import WebURLFoundationExtras +import os private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: []) @@ -56,7 +57,7 @@ extension BaseEmojiLabel { return imageSizeMatchingFontSize } - let emojiImages = MultiThreadDictionary() + let emojiImages = OSAllocatedUnfairLock(initialState: [String: UIImage]()) var foundEmojis = false let group = DispatchGroup() @@ -79,9 +80,11 @@ extension BaseEmojiLabel { // todo: consider caching these somewhere? the cache needs to take into account both the url and the font size, which may vary across labels, so can't just use ImageCache if let thumbnail = image.preparingThumbnail(of: emojiImageSize(image)), let cgImage = thumbnail.cgImage { - // the thumbnail API takes a pixel size and returns an image with scale 1, but we want the actual screen scale, so convert - // see FB12187798 - emojiImages[emoji.shortcode] = UIImage(cgImage: cgImage, scale: screenScale, orientation: .up) + emojiImages.withLock { + // the thumbnail API takes a pixel size and returns an image with scale 1, but we want the actual screen scale, so convert + // see FB12187798 + $0[emoji.shortcode] = UIImage(cgImage: cgImage, scale: screenScale, orientation: .up) + } } } else { // otherwise, perform the network request @@ -99,7 +102,9 @@ extension BaseEmojiLabel { group.leave() return } - emojiImages[emoji.shortcode] = transformedImage + emojiImages.withLock { + $0[emoji.shortcode] = transformedImage + } group.leave() } } diff --git a/Tusker/Views/ContentTextView.swift b/Tusker/Views/ContentTextView.swift index f6fc07fc3d..d2a2e50413 100644 --- a/Tusker/Views/ContentTextView.swift +++ b/Tusker/Views/ContentTextView.swift @@ -146,8 +146,7 @@ class ContentTextView: LinkTextView, BaseEmojiLabel { func getLinkAtPoint(_ point: CGPoint) -> (URL, NSRange)? { let locationInTextContainer = CGPoint(x: point.x - textContainerInset.left, y: point.y - textContainerInset.top) - if #available(iOS 16.0, *), - let textLayoutManager { + if let textLayoutManager { guard let fragment = textLayoutManager.textLayoutFragment(for: locationInTextContainer) else { return nil } @@ -305,8 +304,7 @@ extension ContentTextView: UIContextMenuInteractionDelegate { // Determine the line rects that the link takes up in the coordinate space of this view. var rects = [CGRect]() - if #available(iOS 16.0, *), - let textLayoutManager, + if let textLayoutManager, let contentManager = textLayoutManager.textContentManager { // convert from NSRange to NSTextRange // i have no idea under what circumstances any of these calls could fail diff --git a/Tusker/Views/CopyableLable.swift b/Tusker/Views/CopyableLabel.swift similarity index 73% rename from Tusker/Views/CopyableLable.swift rename to Tusker/Views/CopyableLabel.swift index 23d070e15e..4c1a2eb4b9 100644 --- a/Tusker/Views/CopyableLable.swift +++ b/Tusker/Views/CopyableLabel.swift @@ -1,5 +1,5 @@ // -// CopyableLable.swift +// CopyableLabel.swift // Tusker // // Created by Shadowfacts on 2/4/23. @@ -8,7 +8,7 @@ import UIKit -class CopyableLable: UILabel { +class CopyableLabel: UILabel { private var _editMenuInteraction: Any! @available(iOS 16.0, *) @@ -28,12 +28,10 @@ class CopyableLable: UILabel { } private func commonInit() { - if #available(iOS 16.0, *) { - editMenuInteraction = UIEditMenuInteraction(delegate: nil) - addInteraction(editMenuInteraction) - isUserInteractionEnabled = true - addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressed))) - } + editMenuInteraction = UIEditMenuInteraction(delegate: nil) + addInteraction(editMenuInteraction) + isUserInteractionEnabled = true + addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(longPressed))) } override func copy(_ sender: Any?) { diff --git a/Tusker/Views/Profile Header/ProfileHeaderView.xib b/Tusker/Views/Profile Header/ProfileHeaderView.xib index 1a5f1cfbe0..d0663427d6 100644 --- a/Tusker/Views/Profile Header/ProfileHeaderView.xib +++ b/Tusker/Views/Profile Header/ProfileHeaderView.xib @@ -1,9 +1,8 @@ - + - - + @@ -125,16 +124,16 @@ - + -