Compile for visionOS
This commit is contained in:
parent
c4bf5d406d
commit
e4c22a0205
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import MobileCoreServices
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class ActionViewController: UIViewController {
|
||||
|
||||
|
@ -32,10 +33,10 @@ class ActionViewController: UIViewController {
|
|||
private func findURLFromWebPage(completion: @escaping (URLComponents?) -> Void) {
|
||||
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
||||
for provider in item.attachments! {
|
||||
guard provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) else {
|
||||
guard provider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
|
||||
continue
|
||||
}
|
||||
provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil) { (result, error) in
|
||||
provider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil) { (result, error) in
|
||||
guard let result = result as? [String: Any],
|
||||
let jsResult = result[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any],
|
||||
let urlString = jsResult["activityPubURL"] as? String ?? jsResult["url"] as? String,
|
||||
|
@ -56,10 +57,10 @@ class ActionViewController: UIViewController {
|
|||
private func findURLItem(completion: @escaping (URLComponents?) -> Void) {
|
||||
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
||||
for provider in item.attachments! {
|
||||
guard provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) else {
|
||||
guard provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) else {
|
||||
continue
|
||||
}
|
||||
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (result, error) in
|
||||
provider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { (result, error) in
|
||||
guard let result = result as? URL,
|
||||
let components = URLComponents(url: result, resolvingAgainstBaseURL: false) else {
|
||||
completion(nil)
|
||||
|
|
|
@ -177,11 +177,19 @@ class AttachmentRowController: ViewController {
|
|||
Text(error.localizedDescription)
|
||||
}
|
||||
.onAppear(perform: controller.updateAttachmentDescriptionState)
|
||||
#if os(visionOS)
|
||||
.onChange(of: textEditorFocused) {
|
||||
if !textEditorFocused && controller.focusAttachmentOnTextEditorUnfocus {
|
||||
controller.focusAttachment()
|
||||
}
|
||||
}
|
||||
#else
|
||||
.onChange(of: textEditorFocused) { newValue in
|
||||
if !newValue && controller.focusAttachmentOnTextEditorUnfocus {
|
||||
controller.focusAttachment()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -208,6 +216,7 @@ extension AttachmentRowController {
|
|||
|
||||
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, *) {
|
||||
|
|
|
@ -40,9 +40,13 @@ class AttachmentThumbnailController: ViewController {
|
|||
case .video, .gifv:
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
#if os(visionOS)
|
||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
||||
#else
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
self.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
#endif
|
||||
|
||||
case .audio, .unknown:
|
||||
break
|
||||
|
@ -87,9 +91,13 @@ class AttachmentThumbnailController: ViewController {
|
|||
if type.conforms(to: .movie) {
|
||||
let asset = AVURLAsset(url: url)
|
||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
||||
#if os(visionOS)
|
||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
||||
#else
|
||||
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||
self.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
#endif
|
||||
} else if let data = try? Data(contentsOf: url) {
|
||||
if type == .gif {
|
||||
self.gifController = GIFController(gifData: data)
|
||||
|
|
|
@ -270,7 +270,9 @@ public final class ComposeController: ViewController {
|
|||
@OptionalObservedObject var poster: PostService?
|
||||
@EnvironmentObject var controller: ComposeController
|
||||
@EnvironmentObject var draft: Draft
|
||||
#if !os(visionOS)
|
||||
@StateObject private var keyboardReader = KeyboardReader()
|
||||
#endif
|
||||
@State private var globalFrameOutsideList = CGRect.zero
|
||||
|
||||
init(poster: PostService?) {
|
||||
|
@ -315,8 +317,10 @@ public final class ComposeController: ViewController {
|
|||
|
||||
ControllerView(controller: { controller.toolbarController })
|
||||
}
|
||||
#if !os(visionOS)
|
||||
// on iPadOS15, the toolbar ends up below the keyboard's toolbar without this
|
||||
.padding(.bottom, keyboardInset)
|
||||
#endif
|
||||
.transition(.move(edge: .bottom))
|
||||
}
|
||||
}
|
||||
|
@ -414,7 +418,9 @@ public final class ComposeController: ViewController {
|
|||
.listRowBackground(config.backgroundColor)
|
||||
}
|
||||
.listStyle(.plain)
|
||||
#if !os(visionOS)
|
||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||
#endif
|
||||
.disabled(controller.isPosting)
|
||||
}
|
||||
|
||||
|
@ -457,6 +463,7 @@ public final class ComposeController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
private var keyboardInset: CGFloat {
|
||||
if #unavailable(iOS 16.0),
|
||||
|
@ -467,6 +474,7 @@ public final class ComposeController: ViewController {
|
|||
return 0
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -123,9 +123,15 @@ class PollController: ViewController {
|
|||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.foregroundColor(backgroundColor)
|
||||
)
|
||||
#if os(visionOS)
|
||||
.onChange(of: controller.duration) {
|
||||
poll.duration = controller.duration.timeInterval
|
||||
}
|
||||
#else
|
||||
.onChange(of: controller.duration) { newValue in
|
||||
poll.duration = newValue.timeInterval
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private var backgroundColor: Color {
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// Created by Shadowfacts on 3/7/23.
|
||||
//
|
||||
|
||||
#if !os(visionOS)
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
|
@ -37,3 +39,5 @@ class KeyboardReader: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -11,7 +11,7 @@ import PencilKit
|
|||
|
||||
extension PKDrawing {
|
||||
|
||||
func imageInLightMode(from rect: CGRect, scale: CGFloat = UIScreen.main.scale) -> UIImage {
|
||||
func imageInLightMode(from rect: CGRect, scale: CGFloat = 1) -> UIImage {
|
||||
let lightTraitCollection = UITraitCollection(userInterfaceStyle: .light)
|
||||
var drawingImage: UIImage!
|
||||
lightTraitCollection.performAsCurrent {
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
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 {
|
||||
|
@ -17,4 +22,5 @@ extension View {
|
|||
self
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -124,7 +124,9 @@ private struct LanguagePickerList: View {
|
|||
.scrollContentBackground(.hidden)
|
||||
.background(groupedBackgroundColor.edgesIgnoringSafeArea(.all))
|
||||
.searchable(text: $query)
|
||||
#if !os(visionOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
.navigationTitle("Post Language")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
@ -145,13 +147,23 @@ private struct LanguagePickerList: View {
|
|||
.map { Lang(code: $0) }
|
||||
.sorted { $0.name < $1.name }
|
||||
}
|
||||
#if os(visionOS)
|
||||
.onChange(of: query, initial: true) {
|
||||
filteredLangsChanged(query: query)
|
||||
}
|
||||
#else
|
||||
.onChange(of: query) { newValue in
|
||||
if newValue.isEmpty {
|
||||
filteredLangs = nil
|
||||
} else {
|
||||
filteredLangs = langs.filter {
|
||||
$0.name.localizedCaseInsensitiveContains(newValue) || $0.code.identifier.localizedCaseInsensitiveContains(newValue)
|
||||
}
|
||||
filteredLangsChanged(query: newValue)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func filteredLangsChanged(query: String) {
|
||||
if query.isEmpty {
|
||||
filteredLangs = nil
|
||||
} else {
|
||||
filteredLangs = langs.filter {
|
||||
$0.name.localizedCaseInsensitiveContains(query) || $0.code.identifier.localizedCaseInsensitiveContains(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,9 @@ public class DuckableContainerViewController: UIViewController {
|
|||
guard case .idle = state else {
|
||||
if animated,
|
||||
case .ducked(_, placeholder: let placeholder) = state {
|
||||
#if !os(visionOS)
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
#endif
|
||||
let origConstant = placeholder.topConstraint.constant
|
||||
UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
|
||||
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
||||
|
|
|
@ -19,7 +19,11 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
|
|||
let image = UIImage(data: data) else {
|
||||
return nil
|
||||
}
|
||||
#if os(visionOS)
|
||||
let size: CGFloat = 50 * 2
|
||||
#else
|
||||
let size = 50 * UIScreen.main.scale
|
||||
#endif
|
||||
return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image
|
||||
}
|
||||
|
||||
|
|
|
@ -85,7 +85,6 @@
|
|||
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
||||
D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */; };
|
||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.swift */; };
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */; };
|
||||
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||
|
@ -102,7 +101,7 @@
|
|||
D635237129B78A7D009ED5E7 /* TuskerComponents in Frameworks */ = {isa = PBXBuildFile; productRef = D635237029B78A7D009ED5E7 /* TuskerComponents */; };
|
||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
||||
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
|
||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
|
||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
|
||||
D63CC70C2910AADB000E19DE /* TuskerSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70B2910AADB000E19DE /* TuskerSceneDelegate.swift */; };
|
||||
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70F2911F1E4000E19DE /* UIScrollView+Top.swift */; };
|
||||
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */; };
|
||||
|
@ -134,7 +133,7 @@
|
|||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; productRef = D6552366289870790048A653 /* ScreenCorners */; };
|
||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D6552366289870790048A653 /* ScreenCorners */; };
|
||||
D659F35E2953A212002D944A /* TTTKit in Frameworks */ = {isa = PBXBuildFile; productRef = D659F35D2953A212002D944A /* TTTKit */; };
|
||||
D659F36229541065002D944A /* TTTView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D659F36129541065002D944A /* TTTView.swift */; };
|
||||
D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B532971F71D00DABDFB /* EditedReport.swift */; };
|
||||
|
@ -159,7 +158,6 @@
|
|||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
||||
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; };
|
||||
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */; };
|
||||
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */; };
|
||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; };
|
||||
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */; };
|
||||
|
@ -265,7 +263,7 @@
|
|||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||
D6BD395929B64426005FFD2B /* ComposeUI in Frameworks */ = {isa = PBXBuildFile; productRef = D6BD395829B64426005FFD2B /* ComposeUI */; };
|
||||
D6BD395B29B64441005FFD2B /* ComposeHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BD395A29B64441005FFD2B /* ComposeHostingController.swift */; };
|
||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||
D6C3F4F5298ED0890009FCFF /* LocalPredicateStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F4F4298ED0890009FCFF /* LocalPredicateStatusesViewController.swift */; };
|
||||
|
@ -324,7 +322,7 @@
|
|||
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
||||
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
||||
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
||||
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
|
||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
||||
|
@ -487,7 +485,6 @@
|
|||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BasicTableViewCell.xib; sourceTree = "<group>"; };
|
||||
D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditListAccountsViewController.swift; sourceTree = "<group>"; };
|
||||
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||
|
@ -562,7 +559,6 @@
|
|||
D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
|
||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tusker-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D674A50727F910F300BA03AC /* Pachyderm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Pachyderm; path = Packages/Pachyderm; sourceTree = "<group>"; };
|
||||
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PKDrawing+Render.swift"; sourceTree = "<group>"; };
|
||||
D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAccountAvatarView.swift; sourceTree = "<group>"; };
|
||||
D67B506C250B291200FAECFB /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherIndicatorView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1242,7 +1238,6 @@
|
|||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */,
|
||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */,
|
||||
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */,
|
||||
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */,
|
||||
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */,
|
||||
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */,
|
||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
||||
|
@ -1402,7 +1397,6 @@
|
|||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */,
|
||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||
|
@ -1868,7 +1862,6 @@
|
|||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||
D601FA84297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib in Resources */,
|
||||
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */,
|
||||
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
|
||||
|
@ -2210,7 +2203,6 @@
|
|||
D68A76DA29511CA6001DA1B3 /* AccountPreferences.swift in Sources */,
|
||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
||||
D6895DC428D65342006341DA /* ConfirmReblogStatusPreviewView.swift in Sources */,
|
||||
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
||||
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */,
|
||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */,
|
||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||
|
@ -2307,7 +2299,6 @@
|
|||
};
|
||||
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
platformFilter = ios;
|
||||
target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
|
||||
targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
|
||||
};
|
||||
|
@ -2429,10 +2420,11 @@
|
|||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Dist;
|
||||
};
|
||||
|
@ -2495,9 +2487,10 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Dist;
|
||||
};
|
||||
|
@ -2522,10 +2515,11 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -2550,10 +2544,11 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -2578,10 +2573,11 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
||||
};
|
||||
name = Dist;
|
||||
};
|
||||
|
@ -2731,12 +2727,13 @@
|
|||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -2761,10 +2758,11 @@
|
|||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -2867,9 +2865,10 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -2892,9 +2891,10 @@
|
|||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
|
|
@ -29,9 +29,11 @@ class FavoriteService {
|
|||
status.favourited.toggle()
|
||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
||||
|
||||
#if !os(visionOS)
|
||||
if hapticFeedback {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
}
|
||||
#endif
|
||||
|
||||
let request = (status.favourited ? Status.favourite : Status.unfavourite)(status.id)
|
||||
do {
|
||||
|
@ -49,9 +51,11 @@ class FavoriteService {
|
|||
}
|
||||
presenter.showToast(configuration: config, animated: true)
|
||||
|
||||
#if !os(visionOS)
|
||||
if hapticFeedback {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ import Pachyderm
|
|||
import Combine
|
||||
import UserAccounts
|
||||
import InstanceFeatures
|
||||
#if canImport(Sentry)
|
||||
import Sentry
|
||||
#endif
|
||||
import ComposeUI
|
||||
|
||||
private let oauthScopes = [Scope.read, .write, .follow]
|
||||
|
@ -96,6 +98,7 @@ class MastodonController: ObservableObject {
|
|||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
#if canImport(Sentry)
|
||||
$instanceInfo
|
||||
.compactMap { $0 }
|
||||
.removeDuplicates(by: { $0.version == $1.version })
|
||||
|
@ -104,6 +107,7 @@ class MastodonController: ObservableObject {
|
|||
setInstanceBreadcrumb(instance: instance, nodeInfo: nodeInfo)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
#endif
|
||||
|
||||
$instance
|
||||
.compactMap { $0 }
|
||||
|
@ -579,6 +583,7 @@ class MastodonController: ObservableObject {
|
|||
|
||||
}
|
||||
|
||||
#if canImport(Sentry)
|
||||
private func setInstanceBreadcrumb(instance: InstanceInfo, nodeInfo: NodeInfo?) {
|
||||
let crumb = Breadcrumb(level: .info, category: "MastodonController")
|
||||
crumb.data = [
|
||||
|
@ -594,3 +599,4 @@ private func setInstanceBreadcrumb(instance: InstanceInfo, nodeInfo: NodeInfo?)
|
|||
}
|
||||
SentrySDK.addBreadcrumb(crumb)
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -80,9 +80,11 @@ class ReblogService {
|
|||
status.reblogged.toggle()
|
||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
||||
|
||||
#if !os(visionOS)
|
||||
if hapticFeedback {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
}
|
||||
#endif
|
||||
|
||||
let request: Request<Status>
|
||||
if status.reblogged {
|
||||
|
@ -104,9 +106,11 @@ class ReblogService {
|
|||
}
|
||||
presenter.showToast(configuration: config, animated: true)
|
||||
|
||||
#if !os(visionOS)
|
||||
if hapticFeedback {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
import UIKit
|
||||
import CoreData
|
||||
import OSLog
|
||||
#if canImport(Sentry)
|
||||
import Sentry
|
||||
#endif
|
||||
import UserAccounts
|
||||
import ComposeUI
|
||||
import TuskerPreferences
|
||||
|
@ -23,9 +25,13 @@ private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category:
|
|||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
#if canImport(Sentry)
|
||||
configureSentry()
|
||||
#endif
|
||||
#if !os(visionOS)
|
||||
swizzleStatusBar()
|
||||
swizzlePresentationController()
|
||||
#endif
|
||||
|
||||
AppShortcutItem.createItems(for: application)
|
||||
|
||||
|
@ -56,7 +62,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
let oldPreferencesFile = documentsDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
|
||||
if FileManager.default.fileExists(atPath: oldPreferencesFile.path) {
|
||||
if case .failure(let error) = Preferences.migrate(from: oldPreferencesFile) {
|
||||
#if canImport(Sentry)
|
||||
SentrySDK.capture(error: error)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,7 +78,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
for url in [oldDraftsFile, appGroupDraftsFile] where FileManager.default.fileExists(atPath: url.path) {
|
||||
DraftsPersistentContainer.shared.migrate(from: url) {
|
||||
if case .failure(let error) = $0 {
|
||||
#if canImport(Sentry)
|
||||
SentrySDK.capture(error: error)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +91,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
return true
|
||||
}
|
||||
|
||||
#if canImport(Sentry)
|
||||
private func configureSentry() {
|
||||
guard let dsn = Bundle.main.object(forInfoDictionaryKey: "SentryDSN") as? String,
|
||||
!dsn.isEmpty else {
|
||||
|
@ -120,9 +131,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
logger.info("Initialized Sentry with installation/user ID: \(id, privacy: .public)")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
override func buildMenu(with builder: UIMenuBuilder) {
|
||||
|
||||
if builder.system == .main {
|
||||
MenuController.buildMainMenu(builder: builder)
|
||||
}
|
||||
|
@ -169,6 +180,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
UIApplication.shared.requestSceneSessionDestruction(scene.session, options: nil)
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
private func swizzleStatusBar() {
|
||||
let selector = Selector(("handleTapAction:"))
|
||||
var originalIMP: IMP?
|
||||
|
@ -220,5 +232,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
Logging.general.error("Unable to swizzle presentation controller")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
#if os(visionOS)
|
||||
private let imageScale: CGFloat = 2
|
||||
#else
|
||||
private let imageScale = UIScreen.main.scale
|
||||
#endif
|
||||
|
||||
class ImageCache {
|
||||
|
||||
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24 * 7), desiredSize: CGSize(width: 50, height: 50))
|
||||
|
@ -26,7 +32,7 @@ class ImageCache {
|
|||
|
||||
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry? = nil, desiredSize: CGSize? = nil) {
|
||||
// todo: might not always want to use UIScreen.main for this, e.g. Catalyst?
|
||||
let pixelSize = desiredSize?.applying(.init(scaleX: UIScreen.main.scale, y: UIScreen.main.scale))
|
||||
let pixelSize = desiredSize?.applying(.init(scaleX: imageScale, y: imageScale))
|
||||
self.desiredPixelSize = pixelSize
|
||||
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil, desiredPixelSize: pixelSize)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ import CoreData
|
|||
import Pachyderm
|
||||
import Combine
|
||||
import OSLog
|
||||
#if canImport(Sentry)
|
||||
import Sentry
|
||||
#endif
|
||||
import CloudKit
|
||||
import UserAccounts
|
||||
|
||||
|
@ -199,6 +201,7 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
|||
try context.save()
|
||||
} catch let error as NSError {
|
||||
logger.error("Unable to save managed object context: \(String(describing: error), privacy: .public)")
|
||||
#if canImport(Sentry)
|
||||
let crumb = Breadcrumb(level: .fatal, category: "PersistentStore")
|
||||
// note: NSDetailedErrorsKey == "NSDetailedErrorsKey" != "NSDetailedErrors"
|
||||
if let detailed = error.userInfo["NSDetailedErrors"] as? [NSError] {
|
||||
|
@ -217,6 +220,7 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
|||
]
|
||||
}
|
||||
SentrySDK.addBreadcrumb(crumb)
|
||||
#endif
|
||||
fatalError("Unable to save managed object context: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@ func fromTimelineKind(_ kind: String) -> Timeline {
|
|||
|
||||
// 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)...]
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// PKDrawing+Render.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 5/9/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import PencilKit
|
||||
|
||||
extension PKDrawing {
|
||||
|
||||
func imageInLightMode(from rect: CGRect, scale: CGFloat = UIScreen.main.scale) -> UIImage {
|
||||
let lightTraitCollection = UITraitCollection(userInterfaceStyle: .light)
|
||||
var drawingImage: UIImage!
|
||||
lightTraitCollection.performAsCurrent {
|
||||
drawingImage = self.image(from: rect, scale: scale)
|
||||
}
|
||||
|
||||
let imageRect = CGRect(origin: .zero, size: rect.size)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.opaque = false
|
||||
format.scale = scale
|
||||
let renderer = UIGraphicsImageRenderer(size: rect.size, format: format)
|
||||
return renderer.image { (context) in
|
||||
UIColor.white.setFill()
|
||||
context.fill(imageRect)
|
||||
drawingImage.draw(in: imageRect)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -12,15 +12,22 @@ 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, *)
|
||||
class MultiThreadDictionary<Key: Hashable & Sendable, Value: 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? {
|
||||
|
@ -30,9 +37,15 @@ class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
|
|||
}
|
||||
}
|
||||
set(value) {
|
||||
#if os(visionOS)
|
||||
lock.withLock { dict in
|
||||
dict[key] = value
|
||||
}
|
||||
#else
|
||||
_ = lock.withLock { dict in
|
||||
dict[key] = value
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +70,7 @@ class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
|
|||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
// TODO: replace this only with OSAllocatedUnfairLock
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
fileprivate protocol Lock<State> {
|
||||
|
@ -87,3 +101,4 @@ fileprivate class UnfairLock<State>: Lock {
|
|||
return try body(&state)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -81,10 +81,12 @@ extension Color {
|
|||
static let appFill = Color(uiColor: .appFill)
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
@available(iOS, obsoleted: 17.0)
|
||||
private let traitsKey: String = ["Traits", "Defined", "client", "_"].reversed().joined()
|
||||
@available(iOS, obsoleted: 17.0)
|
||||
private let key = "tusker_usePureBlackDarkMode"
|
||||
#endif
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
private struct PureBlackDarkModeTrait: UITraitDefinition {
|
||||
|
@ -97,10 +99,15 @@ extension UITraitCollection {
|
|||
if #available(iOS 17.0, *) {
|
||||
return self[PureBlackDarkModeTrait.self]
|
||||
} else {
|
||||
#if os(visionOS)
|
||||
return true // unreachable
|
||||
#else
|
||||
return obsoletePureBlackDarkMode
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if !os(visionOS)
|
||||
@available(iOS, obsoleted: 17.0)
|
||||
var obsoletePureBlackDarkMode: Bool {
|
||||
get {
|
||||
|
@ -113,13 +120,18 @@ extension UITraitCollection {
|
|||
setValue(dict, forKey: traitsKey)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
convenience init(pureBlackDarkMode: Bool) {
|
||||
if #available(iOS 17.0, *) {
|
||||
if #available(iOS 17.0, visionOS 1.0, *) {
|
||||
self.init(PureBlackDarkModeTrait.self, value: pureBlackDarkMode)
|
||||
} else {
|
||||
self.init()
|
||||
#if os(visionOS)
|
||||
// unreachable
|
||||
#else
|
||||
self.obsoletePureBlackDarkMode = pureBlackDarkMode
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ import UIKit
|
|||
import Pachyderm
|
||||
import MessageUI
|
||||
import CoreData
|
||||
#if canImport(Duckable)
|
||||
import Duckable
|
||||
#endif
|
||||
import UserAccounts
|
||||
import ComposeUI
|
||||
|
||||
|
@ -245,6 +247,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
|||
mastodonController.initialize()
|
||||
|
||||
let split = MainSplitViewController(mastodonController: mastodonController)
|
||||
#if !canImport(Duckable)
|
||||
return split
|
||||
#else
|
||||
if UIDevice.current.userInterfaceIdiom == .phone,
|
||||
#available(iOS 16.0, *) {
|
||||
// TODO: maybe the duckable container should be outside the account switching container
|
||||
|
@ -252,6 +257,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
|||
} else {
|
||||
return split
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func createOnboardingUI() -> UIViewController {
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
#if !os(visionOS)
|
||||
import Sentry
|
||||
#endif
|
||||
|
||||
protocol TuskerSceneDelegate: UISceneDelegate {
|
||||
var window: UIWindow? { get }
|
||||
|
@ -32,6 +34,9 @@ extension TuskerSceneDelegate {
|
|||
guard let window else { return }
|
||||
window.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||
window.tintColor = Preferences.shared.accentColor.color
|
||||
#if os(visionOS)
|
||||
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
||||
#else
|
||||
if #available(iOS 17.0, *) {
|
||||
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
||||
} else {
|
||||
|
@ -45,5 +50,6 @@ extension TuskerSceneDelegate {
|
|||
SentrySDK.capture(exception: exception)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,9 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
|
||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||
didSet {
|
||||
#if !os(visionOS)
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,13 +14,15 @@ import PhotosUI
|
|||
import PencilKit
|
||||
import Pachyderm
|
||||
import CoreData
|
||||
#if canImport(Duckable)
|
||||
import Duckable
|
||||
#endif
|
||||
|
||||
protocol ComposeHostingControllerDelegate: AnyObject {
|
||||
func dismissCompose(mode: DismissMode) -> Bool
|
||||
}
|
||||
|
||||
class ComposeHostingController: UIHostingController<ComposeHostingController.View>, DuckableViewController {
|
||||
class ComposeHostingController: UIHostingController<ComposeHostingController.View> {
|
||||
|
||||
weak var delegate: ComposeHostingControllerDelegate?
|
||||
|
||||
|
@ -141,8 +143,23 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
|||
present(ComposeDrawingNavigationController(editing: drawing, delegate: self), animated: true)
|
||||
}
|
||||
|
||||
// MARK: Duckable
|
||||
|
||||
struct View: SwiftUI.View {
|
||||
let mastodonController: MastodonController
|
||||
let controller: ComposeController
|
||||
|
||||
var body: some SwiftUI.View {
|
||||
ControllerView(controller: { controller })
|
||||
.task {
|
||||
if let account = try? await mastodonController.getOwnAccount() {
|
||||
controller.currentAccount = account
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(Duckable)
|
||||
extension ComposeHostingController: DuckableViewController {
|
||||
func duckableViewControllerShouldDuck() -> DuckAttemptAction {
|
||||
if controller.isPosting {
|
||||
return .block
|
||||
|
@ -164,21 +181,8 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
|||
func duckableViewControllerDidFinishAnimatingDuck() {
|
||||
controller.showToolbar = true
|
||||
}
|
||||
|
||||
struct View: SwiftUI.View {
|
||||
let mastodonController: MastodonController
|
||||
let controller: ComposeController
|
||||
|
||||
var body: some SwiftUI.View {
|
||||
ControllerView(controller: { controller })
|
||||
.task {
|
||||
if let account = try? await mastodonController.getOwnAccount() {
|
||||
controller.currentAccount = account
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extension MastodonController: ComposeMastodonContext {
|
||||
@MainActor
|
||||
|
|
|
@ -49,7 +49,9 @@ 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)
|
||||
|
|
|
@ -148,7 +148,9 @@ struct EditFilterView: View {
|
|||
.appGroupedListRowBackground()
|
||||
}
|
||||
.appGroupedListBackground(container: UIHostingController<CustomizeTimelinesList>.self)
|
||||
#if !os(visionOS)
|
||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||
#endif
|
||||
.navigationTitle(create ? "Add Filter" : "Edit Filter")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
|
@ -169,12 +171,21 @@ struct EditFilterView: View {
|
|||
}, message: { error in
|
||||
Text(error.localizedDescription)
|
||||
})
|
||||
#if os(visionOS)
|
||||
.onChange(of: expiresIn) {
|
||||
edited = true
|
||||
if expires.wrappedValue {
|
||||
filter.expiresIn = expiresIn
|
||||
}
|
||||
}
|
||||
#else
|
||||
.onChange(of: expiresIn, perform: { newValue in
|
||||
edited = true
|
||||
if expires.wrappedValue {
|
||||
filter.expiresIn = newValue
|
||||
}
|
||||
})
|
||||
#endif
|
||||
.onReceive(filter.objectWillChange, perform: { _ in
|
||||
edited = true
|
||||
})
|
||||
|
|
|
@ -111,6 +111,10 @@ struct PinnedTimelinesView: View {
|
|||
Text("Pinned Timelines")
|
||||
}
|
||||
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
|
||||
#if os(visionOS)
|
||||
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
#else
|
||||
if #available(iOS 16.0, *) {
|
||||
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
|
@ -118,6 +122,7 @@ struct PinnedTimelinesView: View {
|
|||
AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines)
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
}
|
||||
#endif
|
||||
})
|
||||
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
|
||||
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||
|
@ -128,11 +133,19 @@ struct PinnedTimelinesView: View {
|
|||
pinnedTimelines = accountPreferences.pinnedTimelines
|
||||
}
|
||||
}
|
||||
#if os(visionOS)
|
||||
.onChange(of: pinnedTimelines) {
|
||||
if accountPreferences.pinnedTimelines != pinnedTimelines {
|
||||
accountPreferences.pinnedTimelines = pinnedTimelines
|
||||
}
|
||||
}
|
||||
#else
|
||||
.onChange(of: pinnedTimelines) { newValue in
|
||||
if accountPreferences.pinnedTimelines != newValue {
|
||||
accountPreferences.pinnedTimelines = newValue
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,10 +108,13 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
// Unneeded on visionOS because there is no light/dark mode
|
||||
#if !os(visionOS)
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateLayerColors()
|
||||
}
|
||||
#endif
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
|
|
@ -100,10 +100,13 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
|||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
}
|
||||
|
||||
// Unneeded on visionOS since there is no light/dark mode
|
||||
#if !os(visionOS)
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateLayerColors()
|
||||
}
|
||||
#endif
|
||||
|
||||
private func updateLayerColors() {
|
||||
if traitCollection.userInterfaceStyle == .dark {
|
||||
|
@ -126,10 +129,12 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
|||
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
|
||||
toPresent = UINavigationController(rootViewController: host)
|
||||
toPresent.modalPresentationStyle = .pageSheet
|
||||
#if !os(visionOS)
|
||||
let sheetPresentationController = toPresent.sheetPresentationController!
|
||||
sheetPresentationController.detents = [
|
||||
.medium()
|
||||
]
|
||||
#endif
|
||||
} else {
|
||||
host.modalPresentationStyle = .popover
|
||||
let popoverPresentationController = host.popoverPresentationController!
|
||||
|
|
|
@ -129,10 +129,13 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
// Unneeded on visionOS because there is no light/dark mode
|
||||
#if !os(visionOS)
|
||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||
super.traitCollectionDidChange(previousTraitCollection)
|
||||
updateLayerColors()
|
||||
}
|
||||
#endif
|
||||
|
||||
private func updateLayerColors() {
|
||||
if traitCollection.userInterfaceStyle == |