Raise min deployment target to iOS 16

This commit is contained in:
Shadowfacts 2024-09-12 10:30:58 -04:00
parent c99c397cf6
commit f4b51c06c1
66 changed files with 266 additions and 740 deletions

View File

@ -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
}
}

View File

@ -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)
]),
]
)

View File

@ -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<M: View, P: View>(@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)
}
}
}

View File

@ -214,44 +214,6 @@ fileprivate extension View {
self
}
}
@available(iOS, obsoleted: 16.0)
@ViewBuilder
func sheetOrPopover(isPresented: Binding<Bool>, @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<V: View>: 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, *)

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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"]),

View File

@ -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)
]),
]
)

View File

@ -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)
]),
]
)

View File

@ -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"]),

View File

@ -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)
]),
]
)

View File

@ -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)
]
),
]
)

View File

@ -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)
]),
]
)

View File

@ -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"]),

View File

@ -9,21 +9,14 @@ import SwiftUI
public struct AsyncPicker<V: Hashable, Content: View>: 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<V>, onChange: @escaping (V) async -> Bool, @ViewBuilder content: () -> Content) {
public init(_ titleKey: LocalizedStringKey, alignment: Alignment = .center, value: Binding<V>, 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<V: Hashable, Content: View>: 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 {

View File

@ -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<Mode>, onChange: @escaping (Bool) async -> Bool) {
public init(_ titleKey: LocalizedStringKey, mode: Binding<Mode>, 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

View File

@ -47,9 +47,7 @@ public struct MenuPicker<Value: Hashable>: 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
}

View File

@ -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)
]
)
]
)

View File

@ -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"]),

View File

@ -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 = "<group>"; };
D64B96832BC3893C002C8990 /* PushSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSubscriptionView.swift; sourceTree = "<group>"; };
D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = "<group>"; };
D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiThreadDictionary.swift; sourceTree = "<group>"; };
D651C5B32915B00400236EF6 /* ProfileFieldsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileFieldsView.swift; sourceTree = "<group>"; };
D6531DED246B81C9000F9538 /* GifvPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GifvPlayerView.swift; sourceTree = "<group>"; };
D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewSwipeActionProvider.swift; sourceTree = "<group>"; };
@ -778,7 +776,7 @@
D6D79F582A13293200AB2315 /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
D6D9498E298EB79400C59229 /* CopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLabel.swift; sourceTree = "<group>"; };
D6DD8FFC298495A8002AD3FD /* LogoutService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutService.swift; sourceTree = "<group>"; };
D6DD8FFE2984D327002AD3FD /* BookmarksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = "<group>"; };
D6DD996A2998611A0015C962 /* SuggestedProfilesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedProfilesViewController.swift; sourceTree = "<group>"; };
@ -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",

View File

@ -8,6 +8,7 @@
import Foundation
import CryptoKit
import os
struct DiskCacheTransformer<T> {
let toData: (T) throws -> Data
@ -21,7 +22,7 @@ class DiskCache<T> {
let defaultExpiry: CacheExpiry
let transformer: DiskCacheTransformer<T>
private var fileStates = MultiThreadDictionary<String, FileState>()
private var fileStates = OSAllocatedUnfairLock(initialState: [String: FileState]())
init(name: String, defaultExpiry: CacheExpiry, transformer: DiskCacheTransformer<T>, fileManager: FileManager = .default) throws {
self.defaultExpiry = defaultExpiry
@ -59,7 +60,9 @@ class DiskCache<T> {
}
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<T> {
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<T> {
}
guard date.timeIntervalSinceNow >= 0 else {
try fileManager.removeItem(atPath: path)
fileStates[key] = .doesNotExist
fileStates.withLock {
$0[key] = .doesNotExist
}
throw Error.expired
}

View File

@ -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)...]
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}

View File

@ -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<Key: Hashable & Sendable, Value: Sendable>: @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]