Compare commits
No commits in common. "b4bdf8b0dc818aecd63e61dfcdf1874bf85020fb" and "5cef76e4948d981fa4d8aaec93e7311de0cdcb9c" have entirely different histories.
b4bdf8b0dc
...
5cef76e494
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
import UniformTypeIdentifiers
|
|
||||||
|
|
||||||
class ActionViewController: UIViewController {
|
class ActionViewController: UIViewController {
|
||||||
|
|
||||||
|
@ -33,10 +32,10 @@ class ActionViewController: UIViewController {
|
||||||
private func findURLFromWebPage(completion: @escaping (URLComponents?) -> Void) {
|
private func findURLFromWebPage(completion: @escaping (URLComponents?) -> Void) {
|
||||||
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
||||||
for provider in item.attachments! {
|
for provider in item.attachments! {
|
||||||
guard provider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else {
|
guard provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
provider.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil) { (result, error) in
|
provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil) { (result, error) in
|
||||||
guard let result = result as? [String: Any],
|
guard let result = result as? [String: Any],
|
||||||
let jsResult = result[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any],
|
let jsResult = result[NSExtensionJavaScriptPreprocessingResultsKey] as? [String: Any],
|
||||||
let urlString = jsResult["activityPubURL"] as? String ?? jsResult["url"] as? String,
|
let urlString = jsResult["activityPubURL"] as? String ?? jsResult["url"] as? String,
|
||||||
|
@ -57,10 +56,10 @@ class ActionViewController: UIViewController {
|
||||||
private func findURLItem(completion: @escaping (URLComponents?) -> Void) {
|
private func findURLItem(completion: @escaping (URLComponents?) -> Void) {
|
||||||
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
for item in extensionContext!.inputItems as! [NSExtensionItem] {
|
||||||
for provider in item.attachments! {
|
for provider in item.attachments! {
|
||||||
guard provider.hasItemConformingToTypeIdentifier(UTType.url.identifier) else {
|
guard provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
provider.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { (result, error) in
|
provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil) { (result, error) in
|
||||||
guard let result = result as? URL,
|
guard let result = result as? URL,
|
||||||
let components = URLComponents(url: result, resolvingAgainstBaseURL: false) else {
|
let components = URLComponents(url: result, resolvingAgainstBaseURL: false) else {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
|
|
|
@ -136,7 +136,7 @@ class AttachmentRowController: ViewController {
|
||||||
.overlay {
|
.overlay {
|
||||||
thumbnailFocusedOverlay
|
thumbnailFocusedOverlay
|
||||||
}
|
}
|
||||||
.frame(width: thumbnailSize, height: thumbnailSize)
|
.frame(width: 80, height: 80)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
textEditorFocused = false
|
textEditorFocused = false
|
||||||
// if we just focus the attachment immediately, the text editor doesn't actually unfocus
|
// if we just focus the attachment immediately, the text editor doesn't actually unfocus
|
||||||
|
@ -162,7 +162,7 @@ class AttachmentRowController: ViewController {
|
||||||
|
|
||||||
switch controller.descriptionMode {
|
switch controller.descriptionMode {
|
||||||
case .allowEntry:
|
case .allowEntry:
|
||||||
InlineAttachmentDescriptionView(attachment: attachment, minHeight: thumbnailSize)
|
InlineAttachmentDescriptionView(attachment: attachment, minHeight: 80)
|
||||||
.matchedGeometrySource(id: AttachmentDescriptionTextViewID(attachment), presentationID: attachment.id)
|
.matchedGeometrySource(id: AttachmentDescriptionTextViewID(attachment), presentationID: attachment.id)
|
||||||
.focused($textEditorFocused)
|
.focused($textEditorFocused)
|
||||||
|
|
||||||
|
@ -177,27 +177,11 @@ class AttachmentRowController: ViewController {
|
||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
}
|
}
|
||||||
.onAppear(perform: controller.updateAttachmentDescriptionState)
|
.onAppear(perform: controller.updateAttachmentDescriptionState)
|
||||||
#if os(visionOS)
|
|
||||||
.onChange(of: textEditorFocused) {
|
|
||||||
if !textEditorFocused && controller.focusAttachmentOnTextEditorUnfocus {
|
|
||||||
controller.focusAttachment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.onChange(of: textEditorFocused) { newValue in
|
.onChange(of: textEditorFocused) { newValue in
|
||||||
if !newValue && controller.focusAttachmentOnTextEditorUnfocus {
|
if !newValue && controller.focusAttachmentOnTextEditorUnfocus {
|
||||||
controller.focusAttachment()
|
controller.focusAttachment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private var thumbnailSize: CGFloat {
|
|
||||||
#if os(visionOS)
|
|
||||||
120
|
|
||||||
#else
|
|
||||||
80
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -224,7 +208,6 @@ extension AttachmentRowController {
|
||||||
|
|
||||||
private extension View {
|
private extension View {
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func contextMenu<M: View, P: View>(@ViewBuilder menuItems: () -> M, @ViewBuilder previewIfAvailable preview: () -> P) -> some View {
|
func contextMenu<M: View, P: View>(@ViewBuilder menuItems: () -> M, @ViewBuilder previewIfAvailable preview: () -> P) -> some View {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
|
|
|
@ -40,13 +40,9 @@ class AttachmentThumbnailController: ViewController {
|
||||||
case .video, .gifv:
|
case .video, .gifv:
|
||||||
let asset = AVURLAsset(url: url)
|
let asset = AVURLAsset(url: url)
|
||||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
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) {
|
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||||
self.image = UIImage(cgImage: cgImage)
|
self.image = UIImage(cgImage: cgImage)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
case .audio, .unknown:
|
case .audio, .unknown:
|
||||||
break
|
break
|
||||||
|
@ -91,13 +87,9 @@ class AttachmentThumbnailController: ViewController {
|
||||||
if type.conforms(to: .movie) {
|
if type.conforms(to: .movie) {
|
||||||
let asset = AVURLAsset(url: url)
|
let asset = AVURLAsset(url: url)
|
||||||
let imageGenerator = AVAssetImageGenerator(asset: asset)
|
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) {
|
if let cgImage = try? imageGenerator.copyCGImage(at: .zero, actualTime: nil) {
|
||||||
self.image = UIImage(cgImage: cgImage)
|
self.image = UIImage(cgImage: cgImage)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
} else if let data = try? Data(contentsOf: url) {
|
} else if let data = try? Data(contentsOf: url) {
|
||||||
if type == .gif {
|
if type == .gif {
|
||||||
self.gifController = GIFController(gifData: data)
|
self.gifController = GIFController(gifData: data)
|
||||||
|
|
|
@ -131,9 +131,9 @@ class AttachmentsListController: ViewController {
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
attachmentsList
|
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
|
attachmentsList
|
||||||
|
|
||||||
if controller.parent.config.presentAssetPicker != nil {
|
if controller.parent.config.presentAssetPicker != nil {
|
||||||
addImageButton
|
addImageButton
|
||||||
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
||||||
|
@ -147,10 +147,6 @@ class AttachmentsListController: ViewController {
|
||||||
togglePollButton
|
togglePollButton
|
||||||
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
.buttonStyle(.bordered)
|
|
||||||
.labelStyle(AttachmentButtonLabelStyle())
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var attachmentsList: some View {
|
private var attachmentsList: some View {
|
||||||
|
@ -250,11 +246,3 @@ fileprivate struct SheetOrPopover<V: View>: ViewModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
fileprivate struct AttachmentButtonLabelStyle: LabelStyle {
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
|
||||||
DefaultLabelStyle().makeBody(configuration: configuration)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -275,9 +275,7 @@ public final class ComposeController: ViewController {
|
||||||
@OptionalObservedObject var poster: PostService?
|
@OptionalObservedObject var poster: PostService?
|
||||||
@EnvironmentObject var controller: ComposeController
|
@EnvironmentObject var controller: ComposeController
|
||||||
@EnvironmentObject var draft: Draft
|
@EnvironmentObject var draft: Draft
|
||||||
#if !os(visionOS)
|
|
||||||
@StateObject private var keyboardReader = KeyboardReader()
|
@StateObject private var keyboardReader = KeyboardReader()
|
||||||
#endif
|
|
||||||
@State private var globalFrameOutsideList = CGRect.zero
|
@State private var globalFrameOutsideList = CGRect.zero
|
||||||
|
|
||||||
init(poster: PostService?) {
|
init(poster: PostService?) {
|
||||||
|
@ -320,25 +318,16 @@ public final class ComposeController: ViewController {
|
||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
.animation(.default, value: controller.currentInput?.autocompleteState)
|
.animation(.default, value: controller.currentInput?.autocompleteState)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
ControllerView(controller: { controller.toolbarController })
|
ControllerView(controller: { controller.toolbarController })
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
|
||||||
// on iPadOS15, the toolbar ends up below the keyboard's toolbar without this
|
// on iPadOS15, the toolbar ends up below the keyboard's toolbar without this
|
||||||
.padding(.bottom, keyboardInset)
|
.padding(.bottom, keyboardInset)
|
||||||
#endif
|
|
||||||
.transition(.move(edge: .bottom))
|
.transition(.move(edge: .bottom))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) { cancelButton }
|
ToolbarItem(placement: .cancellationAction) { cancelButton }
|
||||||
ToolbarItem(placement: .confirmationAction) { postButton }
|
ToolbarItem(placement: .confirmationAction) { postButton }
|
||||||
#if os(visionOS)
|
|
||||||
ToolbarItem(placement: .bottomOrnament) {
|
|
||||||
ControllerView(controller: { controller.toolbarController })
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
.background(GeometryReader { proxy in
|
.background(GeometryReader { proxy in
|
||||||
Color.clear
|
Color.clear
|
||||||
|
@ -430,9 +419,7 @@ public final class ComposeController: ViewController {
|
||||||
.listRowBackground(config.backgroundColor)
|
.listRowBackground(config.backgroundColor)
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.listStyle(.plain)
|
||||||
#if !os(visionOS)
|
|
||||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||||
#endif
|
|
||||||
.disabled(controller.isPosting)
|
.disabled(controller.isPosting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +462,6 @@ public final class ComposeController: ViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
private var keyboardInset: CGFloat {
|
private var keyboardInset: CGFloat {
|
||||||
if #unavailable(iOS 16.0),
|
if #unavailable(iOS 16.0),
|
||||||
|
@ -486,7 +472,6 @@ public final class ComposeController: ViewController {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,15 +123,9 @@ class PollController: ViewController {
|
||||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||||
.foregroundColor(backgroundColor)
|
.foregroundColor(backgroundColor)
|
||||||
)
|
)
|
||||||
#if os(visionOS)
|
|
||||||
.onChange(of: controller.duration) {
|
|
||||||
poll.duration = controller.duration.timeInterval
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.onChange(of: controller.duration) { newValue in
|
.onChange(of: controller.duration) { newValue in
|
||||||
poll.duration = newValue.timeInterval
|
poll.duration = newValue.timeInterval
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var backgroundColor: Color {
|
private var backgroundColor: Color {
|
||||||
|
|
|
@ -45,26 +45,61 @@ class ToolbarController: ViewController {
|
||||||
@EnvironmentObject private var composeController: ComposeController
|
@EnvironmentObject private var composeController: ComposeController
|
||||||
@ScaledMetric(relativeTo: .body) private var imageSize: CGFloat = 22
|
@ScaledMetric(relativeTo: .body) private var imageSize: CGFloat = 22
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
@State private var minWidth: CGFloat?
|
@State private var minWidth: CGFloat?
|
||||||
@State private var realWidth: CGFloat?
|
@State private var realWidth: CGFloat?
|
||||||
#endif
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(visionOS)
|
|
||||||
buttons
|
|
||||||
#else
|
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
buttons
|
HStack(spacing: 0) {
|
||||||
.padding(.horizontal, 16)
|
cwButton
|
||||||
.frame(minWidth: minWidth)
|
|
||||||
.background(GeometryReader { proxy in
|
MenuPicker(selection: visibilityBinding, options: visibilityOptions, buttonStyle: .iconOnly)
|
||||||
Color.clear
|
#if !targetEnvironment(macCatalyst)
|
||||||
.preference(key: ToolbarWidthPrefKey.self, value: proxy.size.width)
|
// the button has a bunch of extra space by default, but combined with what we add it's too much
|
||||||
.onPreferenceChange(ToolbarWidthPrefKey.self) { width in
|
.padding(.horizontal, -8)
|
||||||
realWidth = width
|
#endif
|
||||||
}
|
.disabled(draft.editedStatusID != nil)
|
||||||
})
|
.disabled(composeController.mastodonController.instanceFeatures.localOnlyPostsVisibility && draft.localOnly)
|
||||||
|
|
||||||
|
if composeController.mastodonController.instanceFeatures.localOnlyPosts {
|
||||||
|
localOnlyPicker
|
||||||
|
#if targetEnvironment(macCatalyst)
|
||||||
|
.padding(.leading, 4)
|
||||||
|
#else
|
||||||
|
.padding(.horizontal, -8)
|
||||||
|
#endif
|
||||||
|
.disabled(draft.editedStatusID != nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentInput = composeController.currentInput,
|
||||||
|
currentInput.toolbarElements.contains(.emojiPicker) {
|
||||||
|
customEmojiButton
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentInput = composeController.currentInput,
|
||||||
|
currentInput.toolbarElements.contains(.formattingButtons),
|
||||||
|
composeController.config.contentType != .plain {
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
formatButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if #available(iOS 16.0, *),
|
||||||
|
composeController.mastodonController.instanceFeatures.createStatusWithLanguage {
|
||||||
|
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $composeController.hasChangedLanguageSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 16)
|
||||||
|
.frame(minWidth: minWidth)
|
||||||
|
.background(GeometryReader { proxy in
|
||||||
|
Color.clear
|
||||||
|
.preference(key: ToolbarWidthPrefKey.self, value: proxy.size.width)
|
||||||
|
.onPreferenceChange(ToolbarWidthPrefKey.self) { width in
|
||||||
|
realWidth = width
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0)
|
.scrollDisabledIfAvailable(realWidth ?? 0 <= minWidth ?? 0)
|
||||||
.frame(height: ToolbarController.height)
|
.frame(height: ToolbarController.height)
|
||||||
|
@ -81,52 +116,6 @@ class ToolbarController: ViewController {
|
||||||
minWidth = width
|
minWidth = width
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var buttons: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
cwButton
|
|
||||||
|
|
||||||
MenuPicker(selection: visibilityBinding, options: visibilityOptions, buttonStyle: .iconOnly)
|
|
||||||
#if !targetEnvironment(macCatalyst) && !os(visionOS)
|
|
||||||
// the button has a bunch of extra space by default, but combined with what we add it's too much
|
|
||||||
.padding(.horizontal, -8)
|
|
||||||
#endif
|
|
||||||
.disabled(draft.editedStatusID != nil)
|
|
||||||
.disabled(composeController.mastodonController.instanceFeatures.localOnlyPostsVisibility && draft.localOnly)
|
|
||||||
|
|
||||||
if composeController.mastodonController.instanceFeatures.localOnlyPosts {
|
|
||||||
localOnlyPicker
|
|
||||||
#if targetEnvironment(macCatalyst)
|
|
||||||
.padding(.leading, 4)
|
|
||||||
#elseif !os(visionOS)
|
|
||||||
.padding(.horizontal, -8)
|
|
||||||
#endif
|
|
||||||
.disabled(draft.editedStatusID != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentInput = composeController.currentInput,
|
|
||||||
currentInput.toolbarElements.contains(.emojiPicker) {
|
|
||||||
customEmojiButton
|
|
||||||
}
|
|
||||||
|
|
||||||
if let currentInput = composeController.currentInput,
|
|
||||||
currentInput.toolbarElements.contains(.formattingButtons),
|
|
||||||
composeController.config.contentType != .plain {
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
formatButtons
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
if #available(iOS 16.0, *),
|
|
||||||
composeController.mastodonController.instanceFeatures.createStatusWithLanguage {
|
|
||||||
LanguagePicker(draftLanguage: $draft.language, hasChangedSelection: $composeController.hasChangedLanguageSelection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cwButton: some View {
|
private var cwButton: some View {
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
// Created by Shadowfacts on 3/7/23.
|
// Created by Shadowfacts on 3/7/23.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
@ -39,5 +37,3 @@ class KeyboardReader: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import PencilKit
|
||||||
|
|
||||||
extension PKDrawing {
|
extension PKDrawing {
|
||||||
|
|
||||||
func imageInLightMode(from rect: CGRect, scale: CGFloat = 1) -> UIImage {
|
func imageInLightMode(from rect: CGRect, scale: CGFloat = UIScreen.main.scale) -> UIImage {
|
||||||
let lightTraitCollection = UITraitCollection(userInterfaceStyle: .light)
|
let lightTraitCollection = UITraitCollection(userInterfaceStyle: .light)
|
||||||
var drawingImage: UIImage!
|
var drawingImage: UIImage!
|
||||||
lightTraitCollection.performAsCurrent {
|
lightTraitCollection.performAsCurrent {
|
||||||
|
|
|
@ -8,11 +8,6 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
#if os(visionOS)
|
|
||||||
func scrollDisabledIfAvailable(_ disabled: Bool) -> some View {
|
|
||||||
self.scrollDisabled(disabled)
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func scrollDisabledIfAvailable(_ disabled: Bool) -> some View {
|
func scrollDisabledIfAvailable(_ disabled: Bool) -> some View {
|
||||||
|
@ -22,5 +17,4 @@ extension View {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,21 +22,13 @@ struct InlineAttachmentDescriptionView: View {
|
||||||
self.minHeight = minHeight
|
self.minHeight = minHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
private var placeholderOffset: CGSize {
|
|
||||||
#if os(visionOS)
|
|
||||||
CGSize(width: 8, height: 8)
|
|
||||||
#else
|
|
||||||
CGSize(width: 4, height: 8)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
if attachment.attachmentDescription.isEmpty {
|
if attachment.attachmentDescription.isEmpty {
|
||||||
placeholder
|
placeholder
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.offset(placeholderOffset)
|
.offset(x: 4, y: 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
WrappedTextView(
|
WrappedTextView(
|
||||||
|
@ -92,10 +84,6 @@ private struct WrappedTextView: UIViewRepresentable {
|
||||||
view.font = .preferredFont(forTextStyle: .body)
|
view.font = .preferredFont(forTextStyle: .body)
|
||||||
view.adjustsFontForContentSizeCategory = true
|
view.adjustsFontForContentSizeCategory = true
|
||||||
view.textContainer.lineBreakMode = .byWordWrapping
|
view.textContainer.lineBreakMode = .byWordWrapping
|
||||||
#if os(visionOS)
|
|
||||||
view.borderStyle = .roundedRect
|
|
||||||
view.textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4)
|
|
||||||
#endif
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,7 @@ struct EmojiTextField: UIViewRepresentable {
|
||||||
context.coordinator.maxLength = maxLength
|
context.coordinator.maxLength = maxLength
|
||||||
context.coordinator.focusNextView = focusNextView
|
context.coordinator.focusNextView = focusNextView
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
uiView.backgroundColor = colorScheme == .dark ? UIColor(controller.config.fillColor) : .secondarySystemBackground
|
uiView.backgroundColor = colorScheme == .dark ? UIColor(controller.config.fillColor) : .secondarySystemBackground
|
||||||
#endif
|
|
||||||
|
|
||||||
if becomeFirstResponder?.wrappedValue == true {
|
if becomeFirstResponder?.wrappedValue == true {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
|
@ -129,9 +129,7 @@ private struct LanguagePickerList: View {
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
.background(groupedBackgroundColor.edgesIgnoringSafeArea(.all))
|
.background(groupedBackgroundColor.edgesIgnoringSafeArea(.all))
|
||||||
.searchable(text: $query)
|
.searchable(text: $query)
|
||||||
#if !os(visionOS)
|
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.scrollDismissesKeyboard(.interactively)
|
||||||
#endif
|
|
||||||
.navigationTitle("Post Language")
|
.navigationTitle("Post Language")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -154,23 +152,13 @@ private struct LanguagePickerList: View {
|
||||||
.map { Lang(code: $0) }
|
.map { Lang(code: $0) }
|
||||||
.sorted { $0.name < $1.name }
|
.sorted { $0.name < $1.name }
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
.onChange(of: query, initial: true) {
|
|
||||||
filteredLangsChanged(query: query)
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.onChange(of: query) { newValue in
|
.onChange(of: query) { newValue in
|
||||||
filteredLangsChanged(query: newValue)
|
if newValue.isEmpty {
|
||||||
}
|
filteredLangs = nil
|
||||||
#endif
|
} else {
|
||||||
}
|
filteredLangs = langs.filter {
|
||||||
|
$0.name.localizedCaseInsensitiveContains(newValue) || $0.code.identifier.localizedCaseInsensitiveContains(newValue)
|
||||||
private func filteredLangsChanged(query: String) {
|
}
|
||||||
if query.isEmpty {
|
|
||||||
filteredLangs = nil
|
|
||||||
} else {
|
|
||||||
filteredLangs = langs.filter {
|
|
||||||
$0.name.localizedCaseInsensitiveContains(query) || $0.code.identifier.localizedCaseInsensitiveContains(query)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,41 +23,19 @@ struct MainTextView: View {
|
||||||
controller.config
|
controller.config
|
||||||
}
|
}
|
||||||
|
|
||||||
private var placeholderOffset: CGSize {
|
|
||||||
#if os(visionOS)
|
|
||||||
CGSize(width: 8, height: 8)
|
|
||||||
#else
|
|
||||||
CGSize(width: 4, height: 8)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private var textViewBackgroundColor: UIColor? {
|
|
||||||
#if os(visionOS)
|
|
||||||
nil
|
|
||||||
#else
|
|
||||||
colorScheme == .dark ? UIColor(config.fillColor) : .secondarySystemBackground
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .topLeading) {
|
ZStack(alignment: .topLeading) {
|
||||||
MainWrappedTextViewRepresentable(
|
colorScheme == .dark ? config.fillColor : Color(uiColor: .secondarySystemBackground)
|
||||||
text: $draft.text,
|
|
||||||
backgroundColor: textViewBackgroundColor,
|
|
||||||
becomeFirstResponder: $controller.mainComposeTextViewBecomeFirstResponder,
|
|
||||||
updateSelection: $updateSelection,
|
|
||||||
textDidChange: textDidChange
|
|
||||||
)
|
|
||||||
|
|
||||||
if draft.text.isEmpty {
|
if draft.text.isEmpty {
|
||||||
ControllerView(controller: { PlaceholderController() })
|
ControllerView(controller: { PlaceholderController() })
|
||||||
.font(.system(size: fontSize))
|
.font(.system(size: fontSize))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.offset(placeholderOffset)
|
.offset(x: 4, y: 8)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
.allowsHitTesting(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MainWrappedTextViewRepresentable(text: $draft.text, becomeFirstResponder: $controller.mainComposeTextViewBecomeFirstResponder, updateSelection: $updateSelection, textDidChange: textDidChange)
|
||||||
}
|
}
|
||||||
.frame(height: effectiveHeight)
|
.frame(height: effectiveHeight)
|
||||||
.onAppear(perform: becomeFirstResponderOnFirstAppearance)
|
.onAppear(perform: becomeFirstResponderOnFirstAppearance)
|
||||||
|
@ -84,7 +62,6 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
|
||||||
typealias UIViewType = UITextView
|
typealias UIViewType = UITextView
|
||||||
|
|
||||||
@Binding var text: String
|
@Binding var text: String
|
||||||
let backgroundColor: UIColor?
|
|
||||||
@Binding var becomeFirstResponder: Bool
|
@Binding var becomeFirstResponder: Bool
|
||||||
@Binding var updateSelection: ((UITextView) -> Void)?
|
@Binding var updateSelection: ((UITextView) -> Void)?
|
||||||
let textDidChange: (UITextView) -> Void
|
let textDidChange: (UITextView) -> Void
|
||||||
|
@ -97,16 +74,10 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
|
||||||
context.coordinator.textView = textView
|
context.coordinator.textView = textView
|
||||||
textView.delegate = context.coordinator
|
textView.delegate = context.coordinator
|
||||||
textView.isEditable = true
|
textView.isEditable = true
|
||||||
|
textView.backgroundColor = .clear
|
||||||
textView.font = UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 20))
|
textView.font = UIFontMetrics.default.scaledFont(for: .systemFont(ofSize: 20))
|
||||||
textView.adjustsFontForContentSizeCategory = true
|
textView.adjustsFontForContentSizeCategory = true
|
||||||
textView.textContainer.lineBreakMode = .byWordWrapping
|
textView.textContainer.lineBreakMode = .byWordWrapping
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
textView.borderStyle = .roundedRect
|
|
||||||
// yes, the X inset is 4 less than the placeholder offset
|
|
||||||
textView.textContainerInset = UIEdgeInsets(top: 8, left: 4, bottom: 8, right: 4)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return textView
|
return textView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +90,6 @@ fileprivate struct MainWrappedTextViewRepresentable: UIViewRepresentable {
|
||||||
uiView.isEditable = isEnabled
|
uiView.isEditable = isEnabled
|
||||||
uiView.keyboardType = controller.config.useTwitterKeyboard ? .twitter : .default
|
uiView.keyboardType = controller.config.useTwitterKeyboard ? .twitter : .default
|
||||||
|
|
||||||
uiView.backgroundColor = backgroundColor
|
|
||||||
|
|
||||||
context.coordinator.text = $text
|
context.coordinator.text = $text
|
||||||
|
|
||||||
if let updateSelection {
|
if let updateSelection {
|
||||||
|
|
|
@ -62,9 +62,7 @@ public class DuckableContainerViewController: UIViewController {
|
||||||
guard case .idle = state else {
|
guard case .idle = state else {
|
||||||
if animated,
|
if animated,
|
||||||
case .ducked(_, placeholder: let placeholder) = state {
|
case .ducked(_, placeholder: let placeholder) = state {
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
#endif
|
|
||||||
let origConstant = placeholder.topConstraint.constant
|
let origConstant = placeholder.topConstraint.constant
|
||||||
UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
|
UIView.animateKeyframes(withDuration: 0.4, delay: 0) {
|
||||||
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) {
|
||||||
|
|
|
@ -19,11 +19,7 @@ class ShareHostingController: UIHostingController<ShareHostingController.View> {
|
||||||
let image = UIImage(data: data) else {
|
let image = UIImage(data: data) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
let size: CGFloat = 50 * 2
|
|
||||||
#else
|
|
||||||
let size = 50 * UIScreen.main.scale
|
let size = 50 * UIScreen.main.scale
|
||||||
#endif
|
|
||||||
return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image
|
return await image.byPreparingThumbnail(ofSize: CGSize(width: size, height: size)) ?? image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
||||||
D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */; };
|
D6262C9A28D01C4B00390C1F /* LoadingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6262C9928D01C4B00390C1F /* LoadingTableViewCell.swift */; };
|
||||||
D627943223A5466600D38C68 /* SelectableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627943123A5466600D38C68 /* SelectableTableViewCell.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 */; };
|
D627944D23A9A03D00D38C68 /* ListTimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944C23A9A03D00D38C68 /* ListTimelineViewController.swift */; };
|
||||||
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627944E23A9C99800D38C68 /* EditListAccountsViewController.swift */; };
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||||
|
@ -101,7 +102,7 @@
|
||||||
D635237129B78A7D009ED5E7 /* TuskerComponents in Frameworks */ = {isa = PBXBuildFile; productRef = D635237029B78A7D009ED5E7 /* TuskerComponents */; };
|
D635237129B78A7D009ED5E7 /* TuskerComponents in Frameworks */ = {isa = PBXBuildFile; productRef = D635237029B78A7D009ED5E7 /* TuskerComponents */; };
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
||||||
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
|
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
|
||||||
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
|
D63CC702290EC0B8000E19DE /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = D63CC701290EC0B8000E19DE /* Sentry */; };
|
||||||
D63CC70C2910AADB000E19DE /* TuskerSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70B2910AADB000E19DE /* TuskerSceneDelegate.swift */; };
|
D63CC70C2910AADB000E19DE /* TuskerSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70B2910AADB000E19DE /* TuskerSceneDelegate.swift */; };
|
||||||
D63CC7102911F1E4000E19DE /* UIScrollView+Top.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC70F2911F1E4000E19DE /* UIScrollView+Top.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 */; };
|
D63CC7122911F57C000E19DE /* StatusBarTappableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63CC7112911F57C000E19DE /* StatusBarTappableViewController.swift */; };
|
||||||
|
@ -133,7 +134,7 @@
|
||||||
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
D6531DEE246B81C9000F9538 /* GifvAttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DED246B81C9000F9538 /* GifvAttachmentView.swift */; };
|
||||||
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
D6531DF0246B867E000F9538 /* GifvAttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6531DEF246B867E000F9538 /* GifvAttachmentViewController.swift */; };
|
||||||
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6538944214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift */; };
|
||||||
D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D6552366289870790048A653 /* ScreenCorners */; };
|
D6552367289870790048A653 /* ScreenCorners in Frameworks */ = {isa = PBXBuildFile; productRef = D6552366289870790048A653 /* ScreenCorners */; };
|
||||||
D659F35E2953A212002D944A /* TTTKit in Frameworks */ = {isa = PBXBuildFile; productRef = D659F35D2953A212002D944A /* TTTKit */; };
|
D659F35E2953A212002D944A /* TTTKit in Frameworks */ = {isa = PBXBuildFile; productRef = D659F35D2953A212002D944A /* TTTKit */; };
|
||||||
D659F36229541065002D944A /* TTTView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D659F36129541065002D944A /* TTTView.swift */; };
|
D659F36229541065002D944A /* TTTView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D659F36129541065002D944A /* TTTView.swift */; };
|
||||||
D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B532971F71D00DABDFB /* EditedReport.swift */; };
|
D65B4B542971F71D00DABDFB /* EditedReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B4B532971F71D00DABDFB /* EditedReport.swift */; };
|
||||||
|
@ -158,6 +159,7 @@
|
||||||
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
|
||||||
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
D66C900B28DAB7FD00217BF2 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */; };
|
||||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; };
|
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 */; };
|
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67895BF246870DE00D4CD9E /* LocalAccountAvatarView.swift */; };
|
||||||
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; };
|
D67B506D250B291200FAECFB /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67B506C250B291200FAECFB /* BlurHashDecode.swift */; };
|
||||||
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */; };
|
D67C1795266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */; };
|
||||||
|
@ -254,15 +256,13 @@
|
||||||
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366C2828444F00237D0E /* SavedInstance.swift */; };
|
D6B9366D2828445000237D0E /* SavedInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366C2828444F00237D0E /* SavedInstance.swift */; };
|
||||||
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366E2828452F00237D0E /* SavedHashtag.swift */; };
|
D6B9366F2828452F00237D0E /* SavedHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9366E2828452F00237D0E /* SavedHashtag.swift */; };
|
||||||
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */; };
|
D6B936712829F72900237D0E /* NSManagedObjectContext+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */; };
|
||||||
D6BC74842AFC3DF9000DD603 /* TrendingLinkCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC74832AFC3DF9000DD603 /* TrendingLinkCardView.swift */; };
|
|
||||||
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC74852AFC4772000DD603 /* SuggestedProfileCardView.swift */; };
|
|
||||||
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
D6BC9DB1232C61BC002CA326 /* NotificationsPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */; };
|
||||||
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
D6BC9DB3232D4C07002CA326 /* WellnessPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */; };
|
||||||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */; };
|
||||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BC9DD9232D8BE5002CA326 /* SearchResultsViewController.swift */; };
|
||||||
D6BD395929B64426005FFD2B /* ComposeUI in Frameworks */ = {isa = PBXBuildFile; productRef = D6BD395829B64426005FFD2B /* ComposeUI */; };
|
D6BD395929B64426005FFD2B /* ComposeUI in Frameworks */ = {isa = PBXBuildFile; productRef = D6BD395829B64426005FFD2B /* ComposeUI */; };
|
||||||
D6BD395B29B64441005FFD2B /* ComposeHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BD395A29B64441005FFD2B /* ComposeHostingController.swift */; };
|
D6BD395B29B64441005FFD2B /* ComposeHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BD395A29B64441005FFD2B /* ComposeHostingController.swift */; };
|
||||||
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, maccatalyst, ); productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
D6BEA245291A0EDE002F4D01 /* Duckable in Frameworks */ = {isa = PBXBuildFile; productRef = D6BEA244291A0EDE002F4D01 /* Duckable */; };
|
||||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BEA246291A0F2D002F4D01 /* Duckable+Root.swift */; };
|
||||||
D6C041C42AED77730094D32D /* EditListSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C041C32AED77730094D32D /* EditListSettingsService.swift */; };
|
D6C041C42AED77730094D32D /* EditListSettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C041C32AED77730094D32D /* EditListSettingsService.swift */; };
|
||||||
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
D6C1B2082545D1EC00DAAA66 /* StatusCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C1B2072545D1EC00DAAA66 /* StatusCardView.swift */; };
|
||||||
|
@ -321,7 +321,7 @@
|
||||||
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
D6E343AB265AAD6B00C4AA01 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AA265AAD6B00C4AA01 /* Media.xcassets */; };
|
||||||
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
D6E343AD265AAD6B00C4AA01 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E343AC265AAD6B00C4AA01 /* ActionViewController.swift */; };
|
||||||
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
D6E343B0265AAD6B00C4AA01 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6E343AE265AAD6B00C4AA01 /* MainInterface.storyboard */; };
|
||||||
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D6E343B4265AAD6B00C4AA01 /* OpenInTusker.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
|
D6E343BA265AAD8C00C4AA01 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = D6E343B9265AAD8C00C4AA01 /* Action.js */; };
|
||||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
||||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
|
||||||
|
@ -484,6 +484,7 @@
|
||||||
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
|
||||||
|
@ -558,6 +559,7 @@
|
||||||
D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineViewController.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D67C1794266D57D10070F250 /* FastAccountSwitcherIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FastAccountSwitcherIndicatorView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -654,8 +656,6 @@
|
||||||
D6B9366C2828444F00237D0E /* SavedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstance.swift; sourceTree = "<group>"; };
|
D6B9366C2828444F00237D0E /* SavedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedInstance.swift; sourceTree = "<group>"; };
|
||||||
D6B9366E2828452F00237D0E /* SavedHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtag.swift; sourceTree = "<group>"; };
|
D6B9366E2828452F00237D0E /* SavedHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavedHashtag.swift; sourceTree = "<group>"; };
|
||||||
D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Helpers.swift"; sourceTree = "<group>"; };
|
D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Helpers.swift"; sourceTree = "<group>"; };
|
||||||
D6BC74832AFC3DF9000DD603 /* TrendingLinkCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkCardView.swift; sourceTree = "<group>"; };
|
|
||||||
D6BC74852AFC4772000DD603 /* SuggestedProfileCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedProfileCardView.swift; sourceTree = "<group>"; };
|
|
||||||
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
D6BC9DB0232C61BC002CA326 /* NotificationsPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsPageViewController.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
D6BC9DB2232D4C07002CA326 /* WellnessPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellnessPrefsView.swift; sourceTree = "<group>"; };
|
||||||
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesPageViewController.swift; sourceTree = "<group>"; };
|
||||||
|
@ -904,10 +904,8 @@
|
||||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||||
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */,
|
D6E77D0A286D426E00D8B732 /* TrendingLinkCardCollectionViewCell.swift */,
|
||||||
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */,
|
D6E77D0C286E6B7300D8B732 /* TrendingLinkCardCollectionViewCell.xib */,
|
||||||
D6BC74832AFC3DF9000DD603 /* TrendingLinkCardView.swift */,
|
|
||||||
D601FA81297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.swift */,
|
D601FA81297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.swift */,
|
||||||
D601FA82297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib */,
|
D601FA82297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib */,
|
||||||
D6BC74852AFC4772000DD603 /* SuggestedProfileCardView.swift */,
|
|
||||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||||
|
@ -1238,6 +1236,7 @@
|
||||||
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */,
|
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */,
|
||||||
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */,
|
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */,
|
||||||
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */,
|
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */,
|
||||||
|
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */,
|
||||||
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */,
|
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */,
|
||||||
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */,
|
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */,
|
||||||
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
|
||||||
|
@ -1395,6 +1394,7 @@
|
||||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
||||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */,
|
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */,
|
||||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||||
|
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||||
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
D6A3BC872321F78000FD64D5 /* Account Cell */,
|
||||||
D67C57A721E2649B00C3118B /* Account Detail */,
|
D67C57A721E2649B00C3118B /* Account Detail */,
|
||||||
D6C7D27B22B6EBE200071952 /* Attachments */,
|
D6C7D27B22B6EBE200071952 /* Attachments */,
|
||||||
|
@ -1858,6 +1858,7 @@
|
||||||
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */,
|
||||||
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
D640D76922BAF5E6004FBE69 /* DomainBlocks.plist in Resources */,
|
||||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */,
|
||||||
|
D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */,
|
||||||
D601FA84297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib in Resources */,
|
D601FA84297EEC3F00A8E8B5 /* SuggestedProfileCardCollectionViewCell.xib in Resources */,
|
||||||
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */,
|
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */,
|
||||||
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
|
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
|
||||||
|
@ -1981,7 +1982,6 @@
|
||||||
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */,
|
||||||
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */,
|
||||||
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */,
|
||||||
D6BC74862AFC4772000DD603 /* SuggestedProfileCardView.swift in Sources */,
|
|
||||||
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */,
|
||||||
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
|
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
|
||||||
D61F75942936F0DA00C0B37F /* FollowedHashtag.swift in Sources */,
|
D61F75942936F0DA00C0B37F /* FollowedHashtag.swift in Sources */,
|
||||||
|
@ -2077,7 +2077,6 @@
|
||||||
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
D64AAE9726C88DC400FC57FB /* ToastConfiguration.swift in Sources */,
|
||||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */,
|
||||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */,
|
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */,
|
||||||
D6BC74842AFC3DF9000DD603 /* TrendingLinkCardView.swift in Sources */,
|
|
||||||
D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */,
|
D65B4B5E2973040D00DABDFB /* ReportAddStatusView.swift in Sources */,
|
||||||
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */,
|
||||||
D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */,
|
D6CA8CDE296387310050C433 /* SaveToPhotosActivity.swift in Sources */,
|
||||||
|
@ -2200,6 +2199,7 @@
|
||||||
D68A76DA29511CA6001DA1B3 /* AccountPreferences.swift in Sources */,
|
D68A76DA29511CA6001DA1B3 /* AccountPreferences.swift in Sources */,
|
||||||
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
D68015402401A6BA00D6103B /* ComposingPrefsView.swift in Sources */,
|
||||||
D6895DC428D65342006341DA /* ConfirmReblogStatusPreviewView.swift in Sources */,
|
D6895DC428D65342006341DA /* ConfirmReblogStatusPreviewView.swift in Sources */,
|
||||||
|
D67895BC24671E6D00D4CD9E /* PKDrawing+Render.swift in Sources */,
|
||||||
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */,
|
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */,
|
||||||
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */,
|
D6BEA247291A0F2D002F4D01 /* Duckable+Root.swift in Sources */,
|
||||||
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
04D14BB022B34A2800642648 /* GalleryViewController.swift in Sources */,
|
||||||
|
@ -2295,6 +2295,7 @@
|
||||||
};
|
};
|
||||||
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
|
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
|
platformFilter = ios;
|
||||||
target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
|
target = D6E343A7265AAD6B00C4AA01 /* OpenInTusker */;
|
||||||
targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
|
targetProxy = D6E343B2265AAD6B00C4AA01 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
@ -2416,11 +2417,10 @@
|
||||||
OTHER_CODE_SIGN_FLAGS = "";
|
OTHER_CODE_SIGN_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Dist;
|
name = Dist;
|
||||||
};
|
};
|
||||||
|
@ -2484,10 +2484,9 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Dist;
|
name = Dist;
|
||||||
};
|
};
|
||||||
|
@ -2512,11 +2511,10 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -2541,11 +2539,10 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -2570,11 +2567,10 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.ShareExtension";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,7";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
};
|
};
|
||||||
name = Dist;
|
name = Dist;
|
||||||
};
|
};
|
||||||
|
@ -2724,13 +2720,12 @@
|
||||||
OTHER_LDFLAGS = "";
|
OTHER_LDFLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -2755,11 +2750,10 @@
|
||||||
OTHER_CODE_SIGN_FLAGS = "";
|
OTHER_CODE_SIGN_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Tusker/Tusker-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
@ -2864,10 +2858,9 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
|
@ -2890,10 +2883,9 @@
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(BUNDLE_ID_PREFIX).Tusker.OpenInTusker";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator";
|
|
||||||
SUPPORTS_MACCATALYST = YES;
|
SUPPORTS_MACCATALYST = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2,6,7";
|
TARGETED_DEVICE_FAMILY = "1,2,6";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,11 +29,9 @@ class FavoriteService {
|
||||||
status.favourited.toggle()
|
status.favourited.toggle()
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
mastodonController.persistentContainer.statusSubject.send(status.id)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
let request = (status.favourited ? Status.favourite : Status.unfavourite)(status.id)
|
let request = (status.favourited ? Status.favourite : Status.unfavourite)(status.id)
|
||||||
do {
|
do {
|
||||||
|
@ -51,11 +49,9 @@ class FavoriteService {
|
||||||
}
|
}
|
||||||
presenter.showToast(configuration: config, animated: true)
|
presenter.showToast(configuration: config, animated: true)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@ import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import InstanceFeatures
|
import InstanceFeatures
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
|
|
||||||
private let oauthScopes = [Scope.read, .write, .follow]
|
private let oauthScopes = [Scope.read, .write, .follow]
|
||||||
|
@ -99,7 +97,6 @@ class MastodonController: ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
#if canImport(Sentry)
|
|
||||||
$instanceInfo
|
$instanceInfo
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.removeDuplicates(by: { $0.version == $1.version })
|
.removeDuplicates(by: { $0.version == $1.version })
|
||||||
|
@ -108,7 +105,6 @@ class MastodonController: ObservableObject {
|
||||||
setInstanceBreadcrumb(instance: instance, nodeInfo: nodeInfo)
|
setInstanceBreadcrumb(instance: instance, nodeInfo: nodeInfo)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
#endif
|
|
||||||
|
|
||||||
$instance
|
$instance
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
|
@ -616,7 +612,6 @@ class MastodonController: ObservableObject {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(Sentry)
|
|
||||||
private func setInstanceBreadcrumb(instance: InstanceInfo, nodeInfo: NodeInfo?) {
|
private func setInstanceBreadcrumb(instance: InstanceInfo, nodeInfo: NodeInfo?) {
|
||||||
let crumb = Breadcrumb(level: .info, category: "MastodonController")
|
let crumb = Breadcrumb(level: .info, category: "MastodonController")
|
||||||
crumb.data = [
|
crumb.data = [
|
||||||
|
@ -632,4 +627,3 @@ private func setInstanceBreadcrumb(instance: InstanceInfo, nodeInfo: NodeInfo?)
|
||||||
}
|
}
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
@ -80,11 +80,9 @@ class ReblogService {
|
||||||
status.reblogged.toggle()
|
status.reblogged.toggle()
|
||||||
mastodonController.persistentContainer.statusSubject.send(status.id)
|
mastodonController.persistentContainer.statusSubject.send(status.id)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
let request: Request<Status>
|
let request: Request<Status>
|
||||||
if status.reblogged {
|
if status.reblogged {
|
||||||
|
@ -106,11 +104,9 @@ class ReblogService {
|
||||||
}
|
}
|
||||||
presenter.showToast(configuration: config, animated: true)
|
presenter.showToast(configuration: config, animated: true)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import CoreData
|
import CoreData
|
||||||
import OSLog
|
import OSLog
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
import TuskerPreferences
|
import TuskerPreferences
|
||||||
|
@ -25,13 +23,9 @@ private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category:
|
||||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
#if canImport(Sentry)
|
|
||||||
configureSentry()
|
configureSentry()
|
||||||
#endif
|
|
||||||
#if !os(visionOS)
|
|
||||||
swizzleStatusBar()
|
swizzleStatusBar()
|
||||||
swizzlePresentationController()
|
swizzlePresentationController()
|
||||||
#endif
|
|
||||||
|
|
||||||
AppShortcutItem.createItems(for: application)
|
AppShortcutItem.createItems(for: application)
|
||||||
|
|
||||||
|
@ -62,9 +56,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
let oldPreferencesFile = documentsDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
|
let oldPreferencesFile = documentsDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
|
||||||
if FileManager.default.fileExists(atPath: oldPreferencesFile.path) {
|
if FileManager.default.fileExists(atPath: oldPreferencesFile.path) {
|
||||||
if case .failure(let error) = Preferences.migrate(from: oldPreferencesFile) {
|
if case .failure(let error) = Preferences.migrate(from: oldPreferencesFile) {
|
||||||
#if canImport(Sentry)
|
|
||||||
SentrySDK.capture(error: error)
|
SentrySDK.capture(error: error)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +70,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
for url in [oldDraftsFile, appGroupDraftsFile] where FileManager.default.fileExists(atPath: url.path) {
|
for url in [oldDraftsFile, appGroupDraftsFile] where FileManager.default.fileExists(atPath: url.path) {
|
||||||
DraftsPersistentContainer.shared.migrate(from: url) {
|
DraftsPersistentContainer.shared.migrate(from: url) {
|
||||||
if case .failure(let error) = $0 {
|
if case .failure(let error) = $0 {
|
||||||
#if canImport(Sentry)
|
|
||||||
SentrySDK.capture(error: error)
|
SentrySDK.capture(error: error)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +81,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(Sentry)
|
|
||||||
private func configureSentry() {
|
private func configureSentry() {
|
||||||
guard let dsn = Bundle.main.object(forInfoDictionaryKey: "SentryDSN") as? String,
|
guard let dsn = Bundle.main.object(forInfoDictionaryKey: "SentryDSN") as? String,
|
||||||
!dsn.isEmpty else {
|
!dsn.isEmpty else {
|
||||||
|
@ -131,9 +120,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
logger.info("Initialized Sentry with installation/user ID: \(id, privacy: .public)")
|
logger.info("Initialized Sentry with installation/user ID: \(id, privacy: .public)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
override func buildMenu(with builder: UIMenuBuilder) {
|
override func buildMenu(with builder: UIMenuBuilder) {
|
||||||
|
|
||||||
if builder.system == .main {
|
if builder.system == .main {
|
||||||
MenuController.buildMainMenu(builder: builder)
|
MenuController.buildMainMenu(builder: builder)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +162,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
private func swizzleStatusBar() {
|
private func swizzleStatusBar() {
|
||||||
let selector = Selector(("handleTapAction:"))
|
let selector = Selector(("handleTapAction:"))
|
||||||
var originalIMP: IMP?
|
var originalIMP: IMP?
|
||||||
|
@ -225,6 +213,5 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
Logging.general.error("Unable to swizzle presentation controller")
|
Logging.general.error("Unable to swizzle presentation controller")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,6 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
private let imageScale: CGFloat = 2
|
|
||||||
#else
|
|
||||||
private let imageScale = UIScreen.main.scale
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class ImageCache {
|
class ImageCache {
|
||||||
|
|
||||||
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24 * 7), desiredSize: CGSize(width: 50, height: 50))
|
static let avatars = ImageCache(name: "Avatars", memoryExpiry: .seconds(60 * 60), diskExpiry: .seconds(60 * 60 * 24 * 7), desiredSize: CGSize(width: 50, height: 50))
|
||||||
|
@ -32,7 +26,7 @@ class ImageCache {
|
||||||
|
|
||||||
init(name: String, memoryExpiry: CacheExpiry, diskExpiry: CacheExpiry? = nil, desiredSize: CGSize? = nil) {
|
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?
|
// todo: might not always want to use UIScreen.main for this, e.g. Catalyst?
|
||||||
let pixelSize = desiredSize?.applying(.init(scaleX: imageScale, y: imageScale))
|
let pixelSize = desiredSize?.applying(.init(scaleX: UIScreen.main.scale, y: UIScreen.main.scale))
|
||||||
self.desiredPixelSize = pixelSize
|
self.desiredPixelSize = pixelSize
|
||||||
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil, desiredPixelSize: pixelSize)
|
self.cache = ImageDataCache(name: name, memoryExpiry: memoryExpiry, diskExpiry: diskExpiry, storeOriginalDataInMemory: diskExpiry == nil, desiredPixelSize: pixelSize)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,7 @@ import CoreData
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
import OSLog
|
import OSLog
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
import CloudKit
|
import CloudKit
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
|
|
||||||
|
@ -201,7 +199,6 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
||||||
try context.save()
|
try context.save()
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
logger.error("Unable to save managed object context: \(String(describing: error), privacy: .public)")
|
logger.error("Unable to save managed object context: \(String(describing: error), privacy: .public)")
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .fatal, category: "PersistentStore")
|
let crumb = Breadcrumb(level: .fatal, category: "PersistentStore")
|
||||||
// note: NSDetailedErrorsKey == "NSDetailedErrorsKey" != "NSDetailedErrors"
|
// note: NSDetailedErrorsKey == "NSDetailedErrorsKey" != "NSDetailedErrors"
|
||||||
if let detailed = error.userInfo["NSDetailedErrors"] as? [NSError] {
|
if let detailed = error.userInfo["NSDetailedErrors"] as? [NSError] {
|
||||||
|
@ -220,7 +217,6 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
fatalError("Unable to save managed object context: \(String(describing: error))")
|
fatalError("Unable to save managed object context: \(String(describing: error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,6 @@ func fromTimelineKind(_ kind: String) -> Timeline {
|
||||||
|
|
||||||
// replace with Collection.trimmingPrefix
|
// replace with Collection.trimmingPrefix
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
private func trimmingPrefix(_ prefix: String, of str: String) -> Substring {
|
private func trimmingPrefix(_ prefix: String, of str: String) -> Substring {
|
||||||
return str[str.index(str.startIndex, offsetBy: prefix.count)...]
|
return str[str.index(str.startIndex, offsetBy: prefix.count)...]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// 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,22 +12,15 @@ import os
|
||||||
// once we target iOS 16, replace uses of this with OSAllocatedUnfairLock<[Key: Value]>
|
// once we target iOS 16, replace uses of this with OSAllocatedUnfairLock<[Key: Value]>
|
||||||
// to make the lock semantics more clear
|
// to make the lock semantics more clear
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
|
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]>
|
private let lock: any Lock<[Key: Value]>
|
||||||
#endif
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
#if !os(visionOS)
|
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
self.lock = OSAllocatedUnfairLock(initialState: [:])
|
self.lock = OSAllocatedUnfairLock(initialState: [:])
|
||||||
} else {
|
} else {
|
||||||
self.lock = UnfairLock(initialState: [:])
|
self.lock = UnfairLock(initialState: [:])
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript(key: Key) -> Value? {
|
subscript(key: Key) -> Value? {
|
||||||
|
@ -37,15 +30,9 @@ class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
#if os(visionOS)
|
|
||||||
lock.withLock { dict in
|
|
||||||
dict[key] = value
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
_ = lock.withLock { dict in
|
_ = lock.withLock { dict in
|
||||||
dict[key] = value
|
dict[key] = value
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +57,6 @@ class MultiThreadDictionary<Key: Hashable & Sendable, Value: Sendable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
// TODO: replace this only with OSAllocatedUnfairLock
|
// TODO: replace this only with OSAllocatedUnfairLock
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
fileprivate protocol Lock<State> {
|
fileprivate protocol Lock<State> {
|
||||||
|
@ -101,4 +87,3 @@ fileprivate class UnfairLock<State>: Lock {
|
||||||
return try body(&state)
|
return try body(&state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
|
@ -81,12 +81,10 @@ extension Color {
|
||||||
static let appFill = Color(uiColor: .appFill)
|
static let appFill = Color(uiColor: .appFill)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
@available(iOS, obsoleted: 17.0)
|
@available(iOS, obsoleted: 17.0)
|
||||||
private let traitsKey: String = ["Traits", "Defined", "client", "_"].reversed().joined()
|
private let traitsKey: String = ["Traits", "Defined", "client", "_"].reversed().joined()
|
||||||
@available(iOS, obsoleted: 17.0)
|
@available(iOS, obsoleted: 17.0)
|
||||||
private let key = "tusker_usePureBlackDarkMode"
|
private let key = "tusker_usePureBlackDarkMode"
|
||||||
#endif
|
|
||||||
|
|
||||||
@available(iOS 17.0, *)
|
@available(iOS 17.0, *)
|
||||||
private struct PureBlackDarkModeTrait: UITraitDefinition {
|
private struct PureBlackDarkModeTrait: UITraitDefinition {
|
||||||
|
@ -99,15 +97,10 @@ extension UITraitCollection {
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
return self[PureBlackDarkModeTrait.self]
|
return self[PureBlackDarkModeTrait.self]
|
||||||
} else {
|
} else {
|
||||||
#if os(visionOS)
|
|
||||||
return true // unreachable
|
|
||||||
#else
|
|
||||||
return obsoletePureBlackDarkMode
|
return obsoletePureBlackDarkMode
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
@available(iOS, obsoleted: 17.0)
|
@available(iOS, obsoleted: 17.0)
|
||||||
var obsoletePureBlackDarkMode: Bool {
|
var obsoletePureBlackDarkMode: Bool {
|
||||||
get {
|
get {
|
||||||
|
@ -120,18 +113,13 @@ extension UITraitCollection {
|
||||||
setValue(dict, forKey: traitsKey)
|
setValue(dict, forKey: traitsKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
convenience init(pureBlackDarkMode: Bool) {
|
convenience init(pureBlackDarkMode: Bool) {
|
||||||
if #available(iOS 17.0, visionOS 1.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
self.init(PureBlackDarkModeTrait.self, value: pureBlackDarkMode)
|
self.init(PureBlackDarkModeTrait.self, value: pureBlackDarkMode)
|
||||||
} else {
|
} else {
|
||||||
self.init()
|
self.init()
|
||||||
#if os(visionOS)
|
|
||||||
// unreachable
|
|
||||||
#else
|
|
||||||
self.obsoletePureBlackDarkMode = pureBlackDarkMode
|
self.obsoletePureBlackDarkMode = pureBlackDarkMode
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import MessageUI
|
import MessageUI
|
||||||
import CoreData
|
import CoreData
|
||||||
#if canImport(Duckable)
|
|
||||||
import Duckable
|
import Duckable
|
||||||
#endif
|
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
|
|
||||||
|
@ -254,9 +252,6 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
mastodonController.initialize()
|
mastodonController.initialize()
|
||||||
|
|
||||||
let split = MainSplitViewController(mastodonController: mastodonController)
|
let split = MainSplitViewController(mastodonController: mastodonController)
|
||||||
#if !canImport(Duckable)
|
|
||||||
return split
|
|
||||||
#else
|
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone,
|
if UIDevice.current.userInterfaceIdiom == .phone,
|
||||||
#available(iOS 16.0, *) {
|
#available(iOS 16.0, *) {
|
||||||
// TODO: maybe the duckable container should be outside the account switching container
|
// TODO: maybe the duckable container should be outside the account switching container
|
||||||
|
@ -264,7 +259,6 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
} else {
|
} else {
|
||||||
return split
|
return split
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOnboardingUI() -> UIViewController {
|
func createOnboardingUI() -> UIViewController {
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#if !os(visionOS)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
|
|
||||||
protocol TuskerSceneDelegate: UISceneDelegate {
|
protocol TuskerSceneDelegate: UISceneDelegate {
|
||||||
var window: UIWindow? { get }
|
var window: UIWindow? { get }
|
||||||
|
@ -34,9 +32,6 @@ extension TuskerSceneDelegate {
|
||||||
guard let window else { return }
|
guard let window else { return }
|
||||||
window.overrideUserInterfaceStyle = Preferences.shared.theme
|
window.overrideUserInterfaceStyle = Preferences.shared.theme
|
||||||
window.tintColor = Preferences.shared.accentColor.color
|
window.tintColor = Preferences.shared.accentColor.color
|
||||||
#if os(visionOS)
|
|
||||||
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
|
||||||
#else
|
|
||||||
if #available(iOS 17.0, *) {
|
if #available(iOS 17.0, *) {
|
||||||
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
window.traitOverrides.pureBlackDarkMode = Preferences.shared.pureBlackDarkMode
|
||||||
} else {
|
} else {
|
||||||
|
@ -50,6 +45,5 @@ extension TuskerSceneDelegate {
|
||||||
SentrySDK.capture(exception: exception)
|
SentrySDK.capture(exception: exception)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,7 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
||||||
|
|
||||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
#if !os(visionOS)
|
|
||||||
setNeedsStatusBarAppearanceUpdate()
|
setNeedsStatusBarAppearanceUpdate()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,15 +14,13 @@ import PhotosUI
|
||||||
import PencilKit
|
import PencilKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import CoreData
|
import CoreData
|
||||||
#if canImport(Duckable)
|
|
||||||
import Duckable
|
import Duckable
|
||||||
#endif
|
|
||||||
|
|
||||||
protocol ComposeHostingControllerDelegate: AnyObject {
|
protocol ComposeHostingControllerDelegate: AnyObject {
|
||||||
func dismissCompose(mode: DismissMode) -> Bool
|
func dismissCompose(mode: DismissMode) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComposeHostingController: UIHostingController<ComposeHostingController.View> {
|
class ComposeHostingController: UIHostingController<ComposeHostingController.View>, DuckableViewController {
|
||||||
|
|
||||||
weak var delegate: ComposeHostingControllerDelegate?
|
weak var delegate: ComposeHostingControllerDelegate?
|
||||||
|
|
||||||
|
@ -143,23 +141,8 @@ class ComposeHostingController: UIHostingController<ComposeHostingController.Vie
|
||||||
present(ComposeDrawingNavigationController(editing: drawing, delegate: self), animated: true)
|
present(ComposeDrawingNavigationController(editing: drawing, delegate: self), animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct View: SwiftUI.View {
|
// MARK: Duckable
|
||||||
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 {
|
func duckableViewControllerShouldDuck() -> DuckAttemptAction {
|
||||||
if controller.isPosting {
|
if controller.isPosting {
|
||||||
return .block
|
return .block
|
||||||
|
@ -181,8 +164,21 @@ extension ComposeHostingController: DuckableViewController {
|
||||||
func duckableViewControllerDidFinishAnimatingDuck() {
|
func duckableViewControllerDidFinishAnimatingDuck() {
|
||||||
controller.showToolbar = true
|
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 {
|
extension MastodonController: ComposeMastodonContext {
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|
|
@ -413,12 +413,6 @@ extension ConversationCollectionViewController: UICollectionViewDelegate {
|
||||||
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
func collectionView(_ collectionView: UICollectionView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ConversationCollectionViewController: UICollectionViewDragDelegate {
|
extension ConversationCollectionViewController: UICollectionViewDragDelegate {
|
||||||
|
|
|
@ -49,9 +49,7 @@ struct AddHashtagPinnedTimelineView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
list
|
list
|
||||||
#if !os(visionOS)
|
|
||||||
.appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self)
|
.appGroupedListBackground(container: AddHashtagPinnedTimelineRepresentable.UIViewControllerType.self)
|
||||||
#endif
|
|
||||||
.listStyle(.grouped)
|
.listStyle(.grouped)
|
||||||
.navigationTitle("Add Hashtag")
|
.navigationTitle("Add Hashtag")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
|
|
@ -148,9 +148,7 @@ struct EditFilterView: View {
|
||||||
.appGroupedListRowBackground()
|
.appGroupedListRowBackground()
|
||||||
}
|
}
|
||||||
.appGroupedListBackground(container: UIHostingController<CustomizeTimelinesList>.self)
|
.appGroupedListBackground(container: UIHostingController<CustomizeTimelinesList>.self)
|
||||||
#if !os(visionOS)
|
|
||||||
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
.scrollDismissesKeyboardInteractivelyIfAvailable()
|
||||||
#endif
|
|
||||||
.navigationTitle(create ? "Add Filter" : "Edit Filter")
|
.navigationTitle(create ? "Add Filter" : "Edit Filter")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
@ -171,21 +169,12 @@ struct EditFilterView: View {
|
||||||
}, message: { error in
|
}, message: { error in
|
||||||
Text(error.localizedDescription)
|
Text(error.localizedDescription)
|
||||||
})
|
})
|
||||||
#if os(visionOS)
|
|
||||||
.onChange(of: expiresIn) {
|
|
||||||
edited = true
|
|
||||||
if expires.wrappedValue {
|
|
||||||
filter.expiresIn = expiresIn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.onChange(of: expiresIn, perform: { newValue in
|
.onChange(of: expiresIn, perform: { newValue in
|
||||||
edited = true
|
edited = true
|
||||||
if expires.wrappedValue {
|
if expires.wrappedValue {
|
||||||
filter.expiresIn = newValue
|
filter.expiresIn = newValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
#endif
|
|
||||||
.onReceive(filter.objectWillChange, perform: { _ in
|
.onReceive(filter.objectWillChange, perform: { _ in
|
||||||
edited = true
|
edited = true
|
||||||
})
|
})
|
||||||
|
|
|
@ -111,10 +111,6 @@ struct PinnedTimelinesView: View {
|
||||||
Text("Pinned Timelines")
|
Text("Pinned Timelines")
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
|
.sheet(isPresented: $isShowingAddHashtagSheet, content: {
|
||||||
#if os(visionOS)
|
|
||||||
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
|
||||||
.edgesIgnoringSafeArea(.bottom)
|
|
||||||
#else
|
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
AddHashtagPinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||||
.edgesIgnoringSafeArea(.bottom)
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
@ -122,7 +118,6 @@ struct PinnedTimelinesView: View {
|
||||||
AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines)
|
AddHashtagPinnedTimelineRepresentable(pinnedTimelines: $pinnedTimelines)
|
||||||
.edgesIgnoringSafeArea(.bottom)
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
})
|
})
|
||||||
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
|
.sheet(isPresented: $isShowingAddInstanceSheet, content: {
|
||||||
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
AddInstancePinnedTimelineView(pinnedTimelines: $pinnedTimelines)
|
||||||
|
@ -133,19 +128,11 @@ struct PinnedTimelinesView: View {
|
||||||
pinnedTimelines = accountPreferences.pinnedTimelines
|
pinnedTimelines = accountPreferences.pinnedTimelines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
.onChange(of: pinnedTimelines) {
|
|
||||||
if accountPreferences.pinnedTimelines != pinnedTimelines {
|
|
||||||
accountPreferences.pinnedTimelines = pinnedTimelines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
.onChange(of: pinnedTimelines) { newValue in
|
.onChange(of: pinnedTimelines) { newValue in
|
||||||
if accountPreferences.pinnedTimelines != newValue {
|
if accountPreferences.pinnedTimelines != newValue {
|
||||||
accountPreferences.pinnedTimelines = newValue
|
accountPreferences.pinnedTimelines = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,13 +106,10 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded on visionOS because there is no light/dark mode
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
updateLayerColors()
|
updateLayerColors()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
|
@ -98,13 +98,10 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
||||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded on visionOS since there is no light/dark mode
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
updateLayerColors()
|
updateLayerColors()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private func updateLayerColors() {
|
private func updateLayerColors() {
|
||||||
if traitCollection.userInterfaceStyle == .dark {
|
if traitCollection.userInterfaceStyle == .dark {
|
||||||
|
@ -127,12 +124,10 @@ class SuggestedProfileCardCollectionViewCell: UICollectionViewCell {
|
||||||
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
|
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
|
||||||
toPresent = UINavigationController(rootViewController: host)
|
toPresent = UINavigationController(rootViewController: host)
|
||||||
toPresent.modalPresentationStyle = .pageSheet
|
toPresent.modalPresentationStyle = .pageSheet
|
||||||
#if !os(visionOS)
|
|
||||||
let sheetPresentationController = toPresent.sheetPresentationController!
|
let sheetPresentationController = toPresent.sheetPresentationController!
|
||||||
sheetPresentationController.detents = [
|
sheetPresentationController.detents = [
|
||||||
.medium()
|
.medium()
|
||||||
]
|
]
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
host.modalPresentationStyle = .popover
|
host.modalPresentationStyle = .popover
|
||||||
let popoverPresentationController = host.popoverPresentationController!
|
let popoverPresentationController = host.popoverPresentationController!
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
//
|
|
||||||
// SuggestedProfileCardView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 11/8/23.
|
|
||||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SuggestedProfileCardView: View {
|
|
||||||
let account: AccountMO
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
HeaderLayout {
|
|
||||||
AsyncImage(url: account.header) { image in
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
} placeholder: {
|
|
||||||
Rectangle().fill(.tertiary)
|
|
||||||
}
|
|
||||||
AsyncImage(url: account.avatar) { image in
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
|
||||||
} placeholder: {
|
|
||||||
Rectangle().fill(.tertiary)
|
|
||||||
}
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
AccountDisplayNameView(account: account, textStyle: .title, emojiSize: 24)
|
|
||||||
Text(verbatim: "@\(account.acct)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteTextView(note: account.note)
|
|
||||||
}
|
|
||||||
.glassBackgroundEffect(in: RoundedRectangle(cornerRadius: 12.5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct HeaderLayout: Layout {
|
|
||||||
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
|
|
||||||
let acceptedWidth = proposal.width ?? 200
|
|
||||||
let avatarSize = subviews[1].sizeThatFits(ProposedViewSize(width: 86, height: 86))
|
|
||||||
let accountInfoSize = subviews[2].sizeThatFits(ProposedViewSize(width: acceptedWidth - 8 - avatarSize.width, height: 43))
|
|
||||||
return CGSize(width: proposal.width ?? 200, height: 100 + 4 + accountInfoSize.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
|
|
||||||
let headerSize = subviews[0].sizeThatFits(ProposedViewSize(width: bounds.width, height: 100))
|
|
||||||
subviews[0].place(at: .zero, proposal: ProposedViewSize(headerSize))
|
|
||||||
let avatarSize = subviews[1].sizeThatFits(ProposedViewSize(width: 86, height: 86))
|
|
||||||
subviews[1].place(at: CGPoint(x: 8, y: headerSize.height), anchor: .leading, proposal: ProposedViewSize(avatarSize))
|
|
||||||
subviews[2].place(at: CGPoint(x: 8 + avatarSize.width + 8, y: headerSize.height + 4), proposal: ProposedViewSize(width: bounds.width - 8 - avatarSize.width - 8, height: 43))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct NoteTextView: UIViewRepresentable {
|
|
||||||
typealias UIViewType = ContentTextView
|
|
||||||
let note: String
|
|
||||||
func makeUIView(context: Context) -> ContentTextView {
|
|
||||||
let view = ContentTextView()
|
|
||||||
view.isUserInteractionEnabled = false
|
|
||||||
view.isScrollEnabled = true
|
|
||||||
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
func updateUIView(_ uiView: ContentTextView, context: Context) {
|
|
||||||
uiView.setBodyTextFromHTML(note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#Preview {
|
|
||||||
// SuggestedProfileCardView()
|
|
||||||
//}
|
|
||||||
#endif
|
|
|
@ -129,13 +129,10 @@ class TrendingLinkCardCollectionViewCell: UICollectionViewCell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded on visionOS because there is no light/dark mode
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
updateLayerColors()
|
updateLayerColors()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private func updateLayerColors() {
|
private func updateLayerColors() {
|
||||||
if traitCollection.userInterfaceStyle == .dark {
|
if traitCollection.userInterfaceStyle == .dark {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_0" orientation="portrait" appearance="light"/>
|
<device id="retina6_0" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
<capability name="collection view cell content view" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
@ -56,9 +56,9 @@
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cWo-9n-z42">
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cWo-9n-z42">
|
||||||
<rect key="frame" x="0.0" y="196.33333333333334" width="300" height="28.666666666666657"/>
|
<rect key="frame" x="0.0" y="196.66666666666666" width="300" height="28.333333333333343"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ktv-3s-cp9">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="ktv-3s-cp9">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="300" height="28.666666666666657"/>
|
<rect key="frame" x="0.0" y="0.0" width="300" height="28.333333333333343"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" showsExpansionTextWhenTruncated="YES" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho3-cU-IGi">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsLetterSpacingToFitWidth="YES" showsExpansionTextWhenTruncated="YES" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ho3-cU-IGi">
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
//
|
|
||||||
// TrendingLinkCardView.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfacts on 11/8/23.
|
|
||||||
// Copyright © 2023 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
import SwiftUI
|
|
||||||
import Pachyderm
|
|
||||||
import WebURLFoundationExtras
|
|
||||||
import HTMLStreamer
|
|
||||||
|
|
||||||
struct TrendingLinkCardView: View {
|
|
||||||
let card: Card
|
|
||||||
|
|
||||||
private var imageURL: URL? {
|
|
||||||
if let image = card.image {
|
|
||||||
URL(image)
|
|
||||||
} else {
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var descriptionText: String {
|
|
||||||
var converter = TextConverter(configuration: .init(insertNewlines: false))
|
|
||||||
return converter.convert(html: card.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
AsyncImage(url: imageURL, content: { image in
|
|
||||||
image
|
|
||||||
.resizable()
|
|
||||||
}, placeholder: {
|
|
||||||
Rectangle()
|
|
||||||
.fill(.tertiary)
|
|
||||||
})
|
|
||||||
.aspectRatio(4/3, contentMode: .fill)
|
|
||||||
.overlay(alignment: .bottom) {
|
|
||||||
Text(card.title.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
||||||
.font(.headline)
|
|
||||||
.lineLimit(2)
|
|
||||||
.padding(4)
|
|
||||||
.background(.regularMaterial)
|
|
||||||
.padding(-4)
|
|
||||||
}
|
|
||||||
.padding(-4)
|
|
||||||
|
|
||||||
Text(descriptionText)
|
|
||||||
.font(.callout)
|
|
||||||
.lineLimit(3, reservesSpace: true)
|
|
||||||
|
|
||||||
HStack(alignment: .bottom, spacing: 4) {
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
if let providerName = card.providerName {
|
|
||||||
Text(providerName.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
||||||
.font(.caption2)
|
|
||||||
.lineLimit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
let sorted = card.history!.sorted(by: { $0.day < $1.day })
|
|
||||||
let lastTwo = sorted[(sorted.count - 2)...]
|
|
||||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
|
||||||
let uses = lastTwo.map(\.uses).reduce(0, +)
|
|
||||||
// U+2009 THIN SPACE
|
|
||||||
Text("\(accounts.formatted())\u{2009}\(Image(systemName: "person")), \(uses.formatted())\u{2009}\(Image(systemName: "square.text.square"))")
|
|
||||||
.font(.caption2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let history = card.history {
|
|
||||||
CardHistoryView(history: history)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(4)
|
|
||||||
.glassBackgroundEffect(in: RoundedRectangle(cornerRadius: 12.5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct CardHistoryView: UIViewRepresentable {
|
|
||||||
typealias UIViewType = TrendHistoryView
|
|
||||||
let history: [History]
|
|
||||||
func makeUIView(context: Context) -> TrendHistoryView {
|
|
||||||
TrendHistoryView()
|
|
||||||
}
|
|
||||||
func updateUIView(_ uiView: TrendHistoryView, context: Context) {
|
|
||||||
uiView.setHistory(history)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#Preview {
|
|
||||||
// TrendingLinkCardView()
|
|
||||||
//}
|
|
||||||
#endif
|
|
|
@ -279,9 +279,7 @@ extension TrendingLinksViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
return UIContextMenuConfiguration {
|
return UIContextMenuConfiguration {
|
||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
return vc
|
return vc
|
||||||
} actionProvider: { _ in
|
} actionProvider: { _ in
|
||||||
UIMenu(children: self.actionsForTrendingLink(card: card, source: .view(cell)))
|
UIMenu(children: self.actionsForTrendingLink(card: card, source: .view(cell)))
|
||||||
|
|
|
@ -10,9 +10,6 @@ import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import Combine
|
import Combine
|
||||||
#if os(visionOS)
|
|
||||||
import SwiftUI
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class TrendsViewController: UIViewController, CollectionViewController {
|
class TrendsViewController: UIViewController, CollectionViewController {
|
||||||
|
|
||||||
|
@ -147,38 +144,18 @@ class TrendsViewController: UIViewController, CollectionViewController {
|
||||||
let trendingHashtagCell = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { (cell, indexPath, hashtag) in
|
let trendingHashtagCell = UICollectionView.CellRegistration<TrendingHashtagCollectionViewCell, Hashtag> { (cell, indexPath, hashtag) in
|
||||||
cell.updateUI(hashtag: hashtag)
|
cell.updateUI(hashtag: hashtag)
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
let trendingLinkCell = UICollectionView.CellRegistration<UICollectionViewCell, Card> { cell, indexPath, card in
|
|
||||||
cell.contentConfiguration = UIHostingConfiguration(content: {
|
|
||||||
TrendingLinkCardView(card: card)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
let trendingLinkCell = UICollectionView.CellRegistration<TrendingLinkCardCollectionViewCell, Card>(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { (cell, indexPath, card) in
|
let trendingLinkCell = UICollectionView.CellRegistration<TrendingLinkCardCollectionViewCell, Card>(cellNib: UINib(nibName: "TrendingLinkCardCollectionViewCell", bundle: .main)) { (cell, indexPath, card) in
|
||||||
cell.updateUI(card: card)
|
cell.updateUI(card: card)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
let statusCell = UICollectionView.CellRegistration<TrendingStatusCollectionViewCell, (String, CollapseState)> { [unowned self] cell, indexPath, item in
|
let statusCell = UICollectionView.CellRegistration<TrendingStatusCollectionViewCell, (String, CollapseState)> { [unowned self] cell, indexPath, item in
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
// TODO: filter trends
|
// TODO: filter trends
|
||||||
cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow, precomputedContent: nil)
|
cell.updateUI(statusID: item.0, state: item.1, filterResult: .allow, precomputedContent: nil)
|
||||||
}
|
}
|
||||||
#if os(visionOS)
|
|
||||||
let accountCell = UICollectionView.CellRegistration<UICollectionViewCell, (String, Suggestion.Source)> { [unowned self] cell, indexPath, item in
|
|
||||||
if let account = self.mastodonController.persistentContainer.account(for: item.0) {
|
|
||||||
cell.contentConfiguration = UIHostingConfiguration(content: {
|
|
||||||
SuggestedProfileCardView(account: account)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
cell.contentConfiguration = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
let accountCell = UICollectionView.CellRegistration<SuggestedProfileCardCollectionViewCell, (String, Suggestion.Source)>(cellNib: UINib(nibName: "SuggestedProfileCardCollectionViewCell", bundle: .main)) { [unowned self] cell, indexPath, item in
|
let accountCell = UICollectionView.CellRegistration<SuggestedProfileCardCollectionViewCell, (String, Suggestion.Source)>(cellNib: UINib(nibName: "SuggestedProfileCardCollectionViewCell", bundle: .main)) { [unowned self] cell, indexPath, item in
|
||||||
cell.delegate = self
|
cell.delegate = self
|
||||||
cell.updateUI(accountID: item.0, source: item.1)
|
cell.updateUI(accountID: item.0, source: item.1)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
let confirmLoadMoreCell = UICollectionView.CellRegistration<ConfirmLoadMoreCollectionViewCell, Bool> { [unowned self] cell, indexPath, isLoading in
|
let confirmLoadMoreCell = UICollectionView.CellRegistration<ConfirmLoadMoreCollectionViewCell, Bool> { [unowned self] cell, indexPath, isLoading in
|
||||||
cell.confirmLoadMore = self.confirmLoadMoreStatuses
|
cell.confirmLoadMore = self.confirmLoadMoreStatuses
|
||||||
cell.isLoading = isLoading
|
cell.isLoading = isLoading
|
||||||
|
@ -518,7 +495,6 @@ extension TrendsViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -542,9 +518,7 @@ extension TrendsViewController: UICollectionViewDelegate {
|
||||||
let cell = collectionView.cellForItem(at: indexPath)!
|
let cell = collectionView.cellForItem(at: indexPath)!
|
||||||
return UIContextMenuConfiguration {
|
return UIContextMenuConfiguration {
|
||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
return vc
|
return vc
|
||||||
} actionProvider: { _ in
|
} actionProvider: { _ in
|
||||||
UIMenu(children: self.actionsForTrendingLink(card: card, source: .view(cell)))
|
UIMenu(children: self.actionsForTrendingLink(card: card, source: .view(cell)))
|
||||||
|
@ -577,7 +551,7 @@ extension TrendsViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// implementing the highlightPreviewForItemAt method seems to prevent the old, single IndexPath variant of this method from being called on iOS 16
|
// 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, *)
|
@available(iOS 16.0, *)
|
||||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
guard indexPaths.count == 1 else {
|
guard indexPaths.count == 1 else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -607,12 +581,6 @@ extension TrendsViewController: UICollectionViewDelegate {
|
||||||
func collectionView(_ collectionView: UICollectionView, contextMenuConfiguration configuration: UIContextMenuConfiguration, dismissalPreviewForItemAt indexPath: IndexPath) -> UITargetedPreview? {
|
func collectionView(_ collectionView: UICollectionView, contextMenuConfiguration configuration: UIContextMenuConfiguration, dismissalPreviewForItemAt indexPath: IndexPath) -> UITargetedPreview? {
|
||||||
return self.collectionView(collectionView, contextMenuConfiguration: configuration, highlightPreviewForItemAt: indexPath)
|
return self.collectionView(collectionView, contextMenuConfiguration: configuration, highlightPreviewForItemAt: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TrendsViewController: UICollectionViewDragDelegate {
|
extension TrendsViewController: UICollectionViewDragDelegate {
|
||||||
|
|
|
@ -25,9 +25,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
private(set) var accountViews: [FastSwitchingAccountView] = []
|
private(set) var accountViews: [FastSwitchingAccountView] = []
|
||||||
private var lastSelectedAccountViewIndex: Int?
|
private var lastSelectedAccountViewIndex: Int?
|
||||||
#if !os(visionOS)
|
|
||||||
private var selectionChangedFeedbackGenerator: UISelectionFeedbackGenerator?
|
private var selectionChangedFeedbackGenerator: UISelectionFeedbackGenerator?
|
||||||
#endif
|
|
||||||
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
var itemOrientation: ItemOrientation = .iconsTrailing
|
var itemOrientation: ItemOrientation = .iconsTrailing
|
||||||
|
@ -117,9 +115,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastSelectedAccountViewIndex = nil
|
lastSelectedAccountViewIndex = nil
|
||||||
#if !os(visionOS)
|
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
#endif
|
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) {
|
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) {
|
||||||
self.view.alpha = 0
|
self.view.alpha = 0
|
||||||
|
@ -164,12 +160,10 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
private func switchAccount(newIndex: Int, hapticFeedback: Bool = true) {
|
private func switchAccount(newIndex: Int, hapticFeedback: Bool = true) {
|
||||||
if newIndex == 0 { // add account placeholder
|
if newIndex == 0 { // add account placeholder
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.selectionChanged()
|
||||||
}
|
}
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
#endif
|
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
||||||
|
@ -180,12 +174,10 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
let account = UserAccountsManager.shared.accounts[newIndex - 1]
|
let account = UserAccountsManager.shared.accounts[newIndex - 1]
|
||||||
|
|
||||||
if account.id != UserAccountsManager.shared.mostRecentAccountID {
|
if account.id != UserAccountsManager.shared.mostRecentAccountID {
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.selectionChanged()
|
||||||
}
|
}
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
#endif
|
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
||||||
|
@ -203,11 +195,9 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
@objc private func handleLongPress(_ recognizer: UIGestureRecognizer) {
|
@objc private func handleLongPress(_ recognizer: UIGestureRecognizer) {
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||||
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||||
selectionChangedFeedbackGenerator?.prepare()
|
selectionChangedFeedbackGenerator?.prepare()
|
||||||
#endif
|
|
||||||
|
|
||||||
show()
|
show()
|
||||||
|
|
||||||
|
@ -257,12 +247,10 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
}
|
}
|
||||||
lastSelectedAccountViewIndex = selectedAccountViewIndex
|
lastSelectedAccountViewIndex = selectedAccountViewIndex
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if hapticFeedback {
|
if hapticFeedback {
|
||||||
selectionChangedFeedbackGenerator?.selectionChanged()
|
selectionChangedFeedbackGenerator?.selectionChanged()
|
||||||
selectionChangedFeedbackGenerator?.prepare()
|
selectionChangedFeedbackGenerator?.prepare()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +273,6 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
accountsStack.bounds.contains(touch.location(in: accountsStack)) {
|
accountsStack.bounds.contains(touch.location(in: accountsStack)) {
|
||||||
handleGestureMoved(to: touch.location(in: view), hapticFeedback: false)
|
handleGestureMoved(to: touch.location(in: view), hapticFeedback: false)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
// don't trigger the haptic feedback immedaitely
|
// don't trigger the haptic feedback immedaitely
|
||||||
// if the user is merely tapping, not initiating a pan, we don't want to trigger a double-impact
|
// if the user is merely tapping, not initiating a pan, we don't want to trigger a double-impact
|
||||||
// if the tap ends very quickly, this will be cancelled
|
// if the tap ends very quickly, this will be cancelled
|
||||||
|
@ -297,7 +284,6 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
// 100ms determined experimentally to be fast enough that there's not a hugely-perceivable delay when beginning a pan gesture
|
// 100ms determined experimentally to be fast enough that there's not a hugely-perceivable delay when beginning a pan gesture
|
||||||
// and slow enough that it's longer than most reasonable-speed taps
|
// and slow enough that it's longer than most reasonable-speed taps
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100), execute: touchBeganFeedbackWorkItem!)
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100), execute: touchBeganFeedbackWorkItem!)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.touchesBegan(touches, with: event)
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
|
@ -244,10 +244,6 @@ fileprivate class GifvActivityItemSource: NSObject, UIActivityItemSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
func activityViewController(_ activityViewController: UIActivityViewController, thumbnailImageForActivityType activityType: UIActivity.ActivityType?, suggestedSize size: CGSize) -> UIImage? {
|
||||||
#if os(visionOS)
|
|
||||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
|
||||||
return nil
|
|
||||||
#else
|
|
||||||
let generator = AVAssetImageGenerator(asset: self.asset)
|
let generator = AVAssetImageGenerator(asset: self.asset)
|
||||||
generator.appliesPreferredTrackTransform = true
|
generator.appliesPreferredTrackTransform = true
|
||||||
if let image = try? generator.copyCGImage(at: .zero, actualTime: nil) {
|
if let image = try? generator.copyCGImage(at: .zero, actualTime: nil) {
|
||||||
|
@ -255,7 +251,6 @@ fileprivate class GifvActivityItemSource: NSObject, UIActivityItemSource {
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
|
||||||
|
|
|
@ -54,9 +54,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
||||||
|
|
||||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
#if !os(visionOS)
|
|
||||||
setNeedsStatusBarAppearanceUpdate()
|
setNeedsStatusBarAppearanceUpdate()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,7 @@ class LoadingLargeImageViewController: UIViewController, LargeImageAnimatableVie
|
||||||
|
|
||||||
var isInteractivelyAnimatingDismissal: Bool = false {
|
var isInteractivelyAnimatingDismissal: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
#if !os(visionOS)
|
|
||||||
setNeedsStatusBarAppearanceUpdate()
|
setNeedsStatusBarAppearanceUpdate()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#if canImport(ScreenCorners)
|
|
||||||
import ScreenCorners
|
import ScreenCorners
|
||||||
#endif
|
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
|
|
||||||
|
@ -80,15 +78,11 @@ class AccountSwitchingContainerViewController: UIViewController {
|
||||||
newRoot.view.transform = CGAffineTransform(translationX: 0, y: newInitialOffset).scaledBy(x: 0.9, y: 0.9)
|
newRoot.view.transform = CGAffineTransform(translationX: 0, y: newInitialOffset).scaledBy(x: 0.9, y: 0.9)
|
||||||
newRoot.view.layer.masksToBounds = true
|
newRoot.view.layer.masksToBounds = true
|
||||||
newRoot.view.layer.cornerCurve = .continuous
|
newRoot.view.layer.cornerCurve = .continuous
|
||||||
#if canImport(ScreenCorners)
|
|
||||||
newRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0
|
newRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0
|
||||||
#endif
|
|
||||||
|
|
||||||
oldRoot.view.layer.masksToBounds = true
|
oldRoot.view.layer.masksToBounds = true
|
||||||
oldRoot.view.layer.cornerCurve = .continuous
|
oldRoot.view.layer.cornerCurve = .continuous
|
||||||
#if canImport(ScreenCorners)
|
|
||||||
oldRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0
|
oldRoot.view.layer.cornerRadius = view.window?.screen.displayCornerRadius ?? 0
|
||||||
#endif
|
|
||||||
|
|
||||||
// only one edge is affected in each direction, i have no idea why
|
// only one edge is affected in each direction, i have no idea why
|
||||||
if direction == .upwards {
|
if direction == .upwards {
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#if canImport(Duckable)
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Duckable
|
import Duckable
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
|
@ -59,5 +57,3 @@ extension DuckableContainerViewController: AccountSwitchableViewController {
|
||||||
(child as? AccountSwitchableViewController)?.isFastAccountSwitcherActive ?? false
|
(child as? AccountSwitchableViewController)?.isFastAccountSwitcherActive ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
private var fastSwitcherIndicator: FastAccountSwitcherIndicatorView!
|
||||||
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
private var fastSwitcherConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
|
@available(iOS, obsoleted: 16.0)
|
||||||
|
private var draftToPresentOnAppear: Draft?
|
||||||
|
|
||||||
var selectedTab: Tab {
|
var selectedTab: Tab {
|
||||||
return Tab(rawValue: selectedIndex)!
|
return Tab(rawValue: selectedIndex)!
|
||||||
}
|
}
|
||||||
|
@ -76,12 +79,21 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
tabBar.isSpringLoaded = true
|
tabBar.isSpringLoaded = true
|
||||||
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
registerForTraitChanges([UITraitHorizontalSizeClass.self]) { (self: Self, previousTraitCollection) in
|
super.viewWillAppear(animated)
|
||||||
self.repositionFastSwitcherIndicator()
|
stateRestorationLogger.info("MainTabBarViewController: viewWillAppear, selectedIndex=\(self.selectedIndex, privacy: .public)")
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
stateRestorationLogger.info("MainTabBarViewController: viewDidAppear, selectedIndex=\(self.selectedIndex, privacy: .public)")
|
||||||
|
|
||||||
|
if let draftToPresentOnAppear {
|
||||||
|
self.draftToPresentOnAppear = nil
|
||||||
|
compose(editing: draftToPresentOnAppear, animated: true)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
|
@ -93,13 +105,11 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
repositionFastSwitcherIndicator()
|
repositionFastSwitcherIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
repositionFastSwitcherIndicator()
|
repositionFastSwitcherIndicator()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
func select(tab: Tab, dismissPresented: Bool) {
|
func select(tab: Tab, dismissPresented: Bool) {
|
||||||
if tab == .compose {
|
if tab == .compose {
|
||||||
|
@ -179,8 +189,7 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
compose(editing: nil)
|
compose(editing: nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if selectedIndex != NSNotFound,
|
if viewController == viewControllers![selectedIndex],
|
||||||
viewController == viewControllers![selectedIndex],
|
|
||||||
let nav = viewController as? UINavigationController,
|
let nav = viewController as? UINavigationController,
|
||||||
nav.viewControllers.count == 1,
|
nav.viewControllers.count == 1,
|
||||||
let scrollableVC = nav.viewControllers.first as? TabBarScrollableViewController {
|
let scrollableVC = nav.viewControllers.first as? TabBarScrollableViewController {
|
||||||
|
|
|
@ -235,9 +235,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
do {
|
do {
|
||||||
_ = try await mastodonController.run(request)
|
_ = try await mastodonController.run(request)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
#endif
|
|
||||||
self.actionButtonsStack.isHidden = true
|
self.actionButtonsStack.isHidden = true
|
||||||
self.addLabel(NSLocalizedString("Rejected", comment: "rejected follow request label"))
|
self.addLabel(NSLocalizedString("Rejected", comment: "rejected follow request label"))
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
|
@ -263,9 +261,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
do {
|
do {
|
||||||
_ = try await mastodonController.run(request)
|
_ = try await mastodonController.run(request)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
#endif
|
|
||||||
self.actionButtonsStack.isHidden = true
|
self.actionButtonsStack.isHidden = true
|
||||||
self.addLabel(NSLocalizedString("Accepted", comment: "accepted follow request label"))
|
self.addLabel(NSLocalizedString("Accepted", comment: "accepted follow request label"))
|
||||||
} catch let error as Client.Error {
|
} catch let error as Client.Error {
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
|
|
||||||
class NotificationsCollectionViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController {
|
class NotificationsCollectionViewController: UIViewController, TimelineLikeCollectionViewController, CollectionViewController {
|
||||||
|
|
||||||
|
@ -408,7 +406,6 @@ extension NotificationsCollectionViewController {
|
||||||
private func validateNotifications(_ notifications: [Pachyderm.Notification]) -> [Pachyderm.Notification] {
|
private func validateNotifications(_ notifications: [Pachyderm.Notification]) -> [Pachyderm.Notification] {
|
||||||
return notifications.compactMap { notif in
|
return notifications.compactMap { notif in
|
||||||
if notif.status == nil && (notif.kind == .mention || notif.kind == .reblog || notif.kind == .favourite || notif.kind == .status) {
|
if notif.status == nil && (notif.kind == .mention || notif.kind == .reblog || notif.kind == .favourite || notif.kind == .status) {
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .fatal, category: "notifications")
|
let crumb = Breadcrumb(level: .fatal, category: "notifications")
|
||||||
crumb.data = [
|
crumb.data = [
|
||||||
"id": notif.id,
|
"id": notif.id,
|
||||||
|
@ -417,7 +414,6 @@ extension NotificationsCollectionViewController {
|
||||||
"account": notif.account.id,
|
"account": notif.account.id,
|
||||||
]
|
]
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return notif
|
return notif
|
||||||
|
@ -672,12 +668,6 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate {
|
||||||
reconfigureVisibleCells()
|
reconfigureVisibleCells()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
||||||
|
|
|
@ -64,9 +64,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
||||||
navigationItem.scrollEdgeAppearance = appearance
|
navigationItem.scrollEdgeAppearance = appearance
|
||||||
|
|
||||||
tableView.backgroundColor = .appGroupedBackground
|
tableView.backgroundColor = .appGroupedBackground
|
||||||
#if !os(visionOS)
|
|
||||||
tableView.keyboardDismissMode = .interactive
|
tableView.keyboardDismissMode = .interactive
|
||||||
#endif
|
|
||||||
tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell)
|
tableView.register(UINib(nibName: "InstanceTableViewCell", bundle: .main), forCellReuseIdentifier: instanceCell)
|
||||||
tableView.rowHeight = UITableView.automaticDimension
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
tableView.estimatedRowHeight = 120
|
tableView.estimatedRowHeight = 120
|
||||||
|
|
|
@ -56,7 +56,6 @@ struct AppearancePrefsView : View {
|
||||||
|
|
||||||
private var themeSection: some View {
|
private var themeSection: some View {
|
||||||
Section {
|
Section {
|
||||||
#if !os(visionOS)
|
|
||||||
Picker(selection: $preferences.theme, label: Text("Theme")) {
|
Picker(selection: $preferences.theme, label: Text("Theme")) {
|
||||||
Text("Use System Theme").tag(UIUserInterfaceStyle.unspecified)
|
Text("Use System Theme").tag(UIUserInterfaceStyle.unspecified)
|
||||||
Text("Light").tag(UIUserInterfaceStyle.light)
|
Text("Light").tag(UIUserInterfaceStyle.light)
|
||||||
|
@ -69,7 +68,6 @@ struct AppearancePrefsView : View {
|
||||||
Text("Pure Black Dark Mode")
|
Text("Pure Black Dark Mode")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
Picker(selection: $preferences.accentColor, label: Text("Accent Color")) {
|
Picker(selection: $preferences.accentColor, label: Text("Accent Color")) {
|
||||||
ForEach(accentColorsAndImages, id: \.0.rawValue) { (color, image) in
|
ForEach(accentColorsAndImages, id: \.0.rawValue) { (color, image) in
|
||||||
|
@ -92,8 +90,7 @@ struct AppearancePrefsView : View {
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var interfaceSection: some View {
|
private var interfaceSection: some View {
|
||||||
let visionIdiom = UIUserInterfaceIdiom(rawValue: 6)
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
if [visionIdiom, .pad, .mac].contains(UIDevice.current.userInterfaceIdiom) {
|
|
||||||
Section(header: Text("Interface")) {
|
Section(header: Text("Interface")) {
|
||||||
WidescreenNavigationPrefsView()
|
WidescreenNavigationPrefsView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,13 +43,10 @@ struct OppositeCollapseKeywordsView: View {
|
||||||
.listStyle(.grouped)
|
.listStyle(.grouped)
|
||||||
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
|
||||||
.onAppear(perform: updateAppearance)
|
.onAppear(perform: updateAppearance)
|
||||||
#endif
|
|
||||||
.navigationBarTitle(preferences.expandAllContentWarnings ? "Collapse Post CW Keywords" : "Expand Post CW Keywords")
|
.navigationBarTitle(preferences.expandAllContentWarnings ? "Collapse Post CW Keywords" : "Expand Post CW Keywords")
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS, obsoleted: 16.0)
|
|
||||||
private func updateAppearance() {
|
private func updateAppearance() {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
// no longer necessary
|
// no longer necessary
|
||||||
|
|
|
@ -139,10 +139,6 @@ private struct TipRow: View {
|
||||||
@Binding var showConfetti: Bool
|
@Binding var showConfetti: Bool
|
||||||
@State private var error: TipJarView.Error?
|
@State private var error: TipJarView.Error?
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
@Environment(\.purchase) private var purchase
|
|
||||||
#endif
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
Text(product.displayName)
|
Text(product.displayName)
|
||||||
|
@ -179,11 +175,7 @@ private struct TipRow: View {
|
||||||
isPurchasing = true
|
isPurchasing = true
|
||||||
let result: Product.PurchaseResult
|
let result: Product.PurchaseResult
|
||||||
do {
|
do {
|
||||||
#if os(visionOS)
|
|
||||||
result = try await purchase(product)
|
|
||||||
#else
|
|
||||||
result = try await product.purchase()
|
result = try await product.purchase()
|
||||||
#endif
|
|
||||||
} catch {
|
} catch {
|
||||||
self.error = .purchasing(error)
|
self.error = .purchasing(error)
|
||||||
isPurchasing = false
|
isPurchasing = false
|
||||||
|
|
|
@ -631,12 +631,6 @@ extension ProfileStatusesViewController: UICollectionViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
if reconfigureVisibleItemsOnEndDecelerating {
|
if reconfigureVisibleItemsOnEndDecelerating {
|
||||||
reconfigureVisibleItemsOnEndDecelerating = false
|
reconfigureVisibleItemsOnEndDecelerating = false
|
||||||
|
|
|
@ -77,11 +77,6 @@ private struct ScrollBackgroundModifier: ViewModifier {
|
||||||
// even though it is for ReportSelectRulesView??
|
// even though it is for ReportSelectRulesView??
|
||||||
let traits: UITraitCollection = {
|
let traits: UITraitCollection = {
|
||||||
var t = UITraitCollection(userInterfaceStyle: colorScheme == .dark ? .dark : .light)
|
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, *) {
|
if #available(iOS 17.0, *) {
|
||||||
t = t.modifyingTraits({ mutableTraits in
|
t = t.modifyingTraits({ mutableTraits in
|
||||||
mutableTraits.pureBlackDarkMode = true
|
mutableTraits.pureBlackDarkMode = true
|
||||||
|
@ -89,7 +84,6 @@ private struct ScrollBackgroundModifier: ViewModifier {
|
||||||
} else {
|
} else {
|
||||||
t.obsoletePureBlackDarkMode = true
|
t.obsoletePureBlackDarkMode = true
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
return t
|
return t
|
||||||
}()
|
}()
|
||||||
Color(uiColor: .appGroupedBackground.resolvedColor(with: traits))
|
Color(uiColor: .appGroupedBackground.resolvedColor(with: traits))
|
||||||
|
|
|
@ -56,7 +56,6 @@ struct ReportSelectRulesView: View {
|
||||||
|
|
||||||
private extension View {
|
private extension View {
|
||||||
@available(iOS, obsoleted: 16.0)
|
@available(iOS, obsoleted: 16.0)
|
||||||
@available(visionOS 1.0, *)
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func withAppBackgroundIfAvailable() -> some View {
|
func withAppBackgroundIfAvailable() -> some View {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
|
|
|
@ -30,9 +30,7 @@ struct ReportView: View {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
navigationViewContent
|
navigationViewContent
|
||||||
#if !os(visionOS)
|
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.scrollDismissesKeyboard(.interactively)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
|
|
|
@ -106,9 +106,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
||||||
collectionView.dragDelegate = self
|
collectionView.dragDelegate = self
|
||||||
collectionView.allowsFocus = true
|
collectionView.allowsFocus = true
|
||||||
collectionView.backgroundColor = .appGroupedBackground
|
collectionView.backgroundColor = .appGroupedBackground
|
||||||
#if !os(visionOS)
|
|
||||||
collectionView.keyboardDismissMode = .interactive
|
collectionView.keyboardDismissMode = .interactive
|
||||||
#endif
|
|
||||||
|
|
||||||
dataSource = createDataSource()
|
dataSource = createDataSource()
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,12 +202,6 @@ extension StatusEditHistoryViewController: UICollectionViewDelegate {
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusEditHistoryViewController: TuskerNavigationDelegate {
|
extension StatusEditHistoryViewController: TuskerNavigationDelegate {
|
||||||
|
|
|
@ -13,23 +13,15 @@ class TimelineJumpButton: UIView {
|
||||||
var action: ((Mode) async -> Void)?
|
var action: ((Mode) async -> Void)?
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
#if os(visionOS)
|
|
||||||
CGSize(width: 44, height: 44)
|
|
||||||
#else
|
|
||||||
CGSize(width: UIView.noIntrinsicMetric, height: 44)
|
CGSize(width: UIView.noIntrinsicMetric, height: 44)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private let button: UIButton = {
|
private let button: UIButton = {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
#else
|
|
||||||
var config = UIButton.Configuration.plain()
|
var config = UIButton.Configuration.plain()
|
||||||
// We don't want a background for this button, even when accessibility button shapes are enabled, because it's in the navbar.
|
|
||||||
config.background.backgroundColor = .clear
|
|
||||||
#endif
|
|
||||||
config.image = UIImage(systemName: "arrow.up")
|
config.image = UIImage(systemName: "arrow.up")
|
||||||
config.contentInsets = .zero
|
config.contentInsets = .zero
|
||||||
|
// We don't want a background for this button, even when accessibility button shapes are enabled, because it's in the navbar.
|
||||||
|
config.background.backgroundColor = .clear
|
||||||
return UIButton(configuration: config)
|
return UIButton(configuration: config)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -109,7 +101,8 @@ class TimelineJumpButton: UIView {
|
||||||
}
|
}
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
|
||||||
var config = button.configuration!
|
var config = UIButton.Configuration.plain()
|
||||||
|
config.contentInsets = .zero
|
||||||
switch mode {
|
switch mode {
|
||||||
case .jump:
|
case .jump:
|
||||||
config.image = UIImage(systemName: "arrow.up")
|
config.image = UIImage(systemName: "arrow.up")
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
import Combine
|
import Combine
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
|
|
||||||
protocol TimelineViewControllerDelegate: AnyObject {
|
protocol TimelineViewControllerDelegate: AnyObject {
|
||||||
func timelineViewController(_ timelineViewController: TimelineViewController, willShowJumpToPresentToastWith animator: UIViewPropertyAnimator?)
|
func timelineViewController(_ timelineViewController: TimelineViewController, willShowJumpToPresentToastWith animator: UIViewPropertyAnimator?)
|
||||||
|
@ -387,7 +385,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
} catch {
|
} catch {
|
||||||
stateRestorationLogger.error("TimelineViewController: failed to update timeline marker: \(String(describing: error))")
|
stateRestorationLogger.error("TimelineViewController: failed to update timeline marker: \(String(describing: error))")
|
||||||
|
|
||||||
#if canImport(Sentry)
|
|
||||||
if let error = error as? Client.Error,
|
if let error = error as? Client.Error,
|
||||||
case .networkError(_) = error.type {
|
case .networkError(_) = error.type {
|
||||||
return
|
return
|
||||||
|
@ -395,7 +392,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
let event = Event(error: error)
|
let event = Event(error: error)
|
||||||
event.message = SentryMessage(formatted: "Failed to update timeline marker: \(String(describing: error))")
|
event.message = SentryMessage(formatted: "Failed to update timeline marker: \(String(describing: error))")
|
||||||
SentrySDK.capture(event: event)
|
SentrySDK.capture(event: event)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,11 +436,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreStateFromHandoff(statusIDs: [String], centerStatusID: String) async {
|
func restoreStateFromHandoff(statusIDs: [String], centerStatusID: String) async {
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .debug, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .debug, category: "TimelineViewController")
|
||||||
crumb.message = "Restoring state from handoff activity"
|
crumb.message = "Restoring state from handoff activity"
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
await controller.restoreInitial { @MainActor in
|
await controller.restoreInitial { @MainActor in
|
||||||
let position = TimelinePosition(context: mastodonController.persistentContainer.viewContext)
|
let position = TimelinePosition(context: mastodonController.persistentContainer.viewContext)
|
||||||
position.statusIDs = statusIDs
|
position.statusIDs = statusIDs
|
||||||
|
@ -504,7 +498,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
case .success(let status):
|
case .success(let status):
|
||||||
statuses.append(status)
|
statuses.append(status)
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .error, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .error, category: "TimelineViewController")
|
||||||
crumb.message = "Error loading status"
|
crumb.message = "Error loading status"
|
||||||
crumb.data = [
|
crumb.data = [
|
||||||
|
@ -512,18 +505,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
"id": id
|
"id": id
|
||||||
]
|
]
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext)
|
await mastodonController.persistentContainer.addAll(statuses: statuses, in: mastodonController.persistentContainer.viewContext)
|
||||||
|
|
||||||
// if an icloud sync completed in between starting to load the statuses and finishing, try to load again
|
// if an icloud sync completed in between starting to load the statuses and finishing, try to load again
|
||||||
if position.statusIDs != originalPositionStatusIDs {
|
if position.statusIDs != originalPositionStatusIDs {
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
||||||
crumb.message = "TimelinePosition statusIDs changed, retrying load"
|
crumb.message = "TimelinePosition statusIDs changed, retrying load"
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
return await loadStatusesToRestore(position: position)
|
return await loadStatusesToRestore(position: position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,14 +541,12 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
let centerStatusID = position.centerStatusID
|
let centerStatusID = position.centerStatusID
|
||||||
let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) }
|
let items = position.statusIDs.map { Item.status(id: $0, collapseState: .unknown, filterState: .unknown) }
|
||||||
snapshot.appendItems(items, toSection: .statuses)
|
snapshot.appendItems(items, toSection: .statuses)
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
||||||
crumb.message = "Restoring statuses"
|
crumb.message = "Restoring statuses"
|
||||||
crumb.data = [
|
crumb.data = [
|
||||||
"statusIDs": position.statusIDs
|
"statusIDs": position.statusIDs
|
||||||
]
|
]
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
await apply(snapshot, animatingDifferences: false)
|
await apply(snapshot, animatingDifferences: false)
|
||||||
if let centerStatusID,
|
if let centerStatusID,
|
||||||
let index = statusIDs.firstIndex(of: centerStatusID) {
|
let index = statusIDs.firstIndex(of: centerStatusID) {
|
||||||
|
@ -595,7 +583,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
} catch {
|
} catch {
|
||||||
stateRestorationLogger.error("TimelineViewController: failed to load from timeline marker: \(String(describing: error))")
|
stateRestorationLogger.error("TimelineViewController: failed to load from timeline marker: \(String(describing: error))")
|
||||||
|
|
||||||
#if canImport(Sentry)
|
|
||||||
if let error = error as? Client.Error,
|
if let error = error as? Client.Error,
|
||||||
case .networkError(_) = error.type {
|
case .networkError(_) = error.type {
|
||||||
return false
|
return false
|
||||||
|
@ -603,7 +590,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
let event = Event(error: error)
|
let event = Event(error: error)
|
||||||
event.message = SentryMessage(formatted: "Failed to load from timeline marker: \(String(describing: error))")
|
event.message = SentryMessage(formatted: "Failed to load from timeline marker: \(String(describing: error))")
|
||||||
SentrySDK.capture(event: event)
|
SentrySDK.capture(event: event)
|
||||||
#endif
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,11 +603,11 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
var count = 0
|
var count = 0
|
||||||
while count < 5 {
|
while count < 5 {
|
||||||
count += 1
|
count += 1
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .info, category: "TimelineViewController")
|
||||||
crumb.message = "scrollToItem, attempt=\(count)"
|
crumb.message = "scrollToItem, attempt=\(count)"
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
let origOffset = self.collectionView.contentOffset
|
let origOffset = self.collectionView.contentOffset
|
||||||
self.collectionView.layoutIfNeeded()
|
self.collectionView.layoutIfNeeded()
|
||||||
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
|
self.collectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: false)
|
||||||
|
@ -642,11 +628,9 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
|
private func filterResult(state: FilterState, statusID: String) -> (Filterer.Result, NSAttributedString?) {
|
||||||
let status = {
|
let status = {
|
||||||
guard let status = self.mastodonController.persistentContainer.status(for: statusID) else {
|
guard let status = self.mastodonController.persistentContainer.status(for: statusID) else {
|
||||||
#if canImport(Sentry)
|
|
||||||
let crumb = Breadcrumb(level: .fatal, category: "TimelineViewController")
|
let crumb = Breadcrumb(level: .fatal, category: "TimelineViewController")
|
||||||
crumb.message = "Looking up status \(statusID)"
|
crumb.message = "Looking up status \(statusID)"
|
||||||
SentrySDK.addBreadcrumb(crumb)
|
SentrySDK.addBreadcrumb(crumb)
|
||||||
#endif
|
|
||||||
preconditionFailure("Missing status for filtering")
|
preconditionFailure("Missing status for filtering")
|
||||||
}
|
}
|
||||||
// if the status is a reblog of another one, filter based on that one
|
// if the status is a reblog of another one, filter based on that one
|
||||||
|
@ -1366,12 +1350,6 @@ extension TimelineViewController: UICollectionViewDelegate {
|
||||||
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
MenuPreviewHelper.willPerformPreviewAction(animator: animator, presenter: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
|
|
||||||
return self.collectionView(collectionView, shouldSelectItemAt: indexPath)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
if isShowingTimelineDescription {
|
if isShowingTimelineDescription {
|
||||||
removeTimelineDescriptionCell()
|
removeTimelineDescriptionCell()
|
||||||
|
|
|
@ -149,9 +149,7 @@ class CustomAlertActionsView: UIControl {
|
||||||
private var separators: [UIView] = []
|
private var separators: [UIView] = []
|
||||||
private var separatorSizeConstraints: [NSLayoutConstraint] = []
|
private var separatorSizeConstraints: [NSLayoutConstraint] = []
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
private let generator = UISelectionFeedbackGenerator()
|
private let generator = UISelectionFeedbackGenerator()
|
||||||
#endif
|
|
||||||
private var currentSelectedActionIndex: Int?
|
private var currentSelectedActionIndex: Int?
|
||||||
|
|
||||||
init(config: CustomAlertController.Configuration, dismiss: @escaping () -> Void) {
|
init(config: CustomAlertController.Configuration, dismiss: @escaping () -> Void) {
|
||||||
|
@ -299,9 +297,7 @@ class CustomAlertActionsView: UIControl {
|
||||||
currentSelectedActionIndex = selectedButton?.offset
|
currentSelectedActionIndex = selectedButton?.offset
|
||||||
selectedButton?.element.backgroundColor = .secondarySystemFill
|
selectedButton?.element.backgroundColor = .secondarySystemFill
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
#endif
|
|
||||||
|
|
||||||
case .changed:
|
case .changed:
|
||||||
if selectedButton == nil && hitTest(recognizer.location(in: self), with: nil)?.tag == ViewTags.customAlertSeparator {
|
if selectedButton == nil && hitTest(recognizer.location(in: self), with: nil)?.tag == ViewTags.customAlertSeparator {
|
||||||
|
@ -312,17 +308,13 @@ class CustomAlertActionsView: UIControl {
|
||||||
if let currentSelectedActionIndex {
|
if let currentSelectedActionIndex {
|
||||||
actionButtons[currentSelectedActionIndex].backgroundColor = nil
|
actionButtons[currentSelectedActionIndex].backgroundColor = nil
|
||||||
}
|
}
|
||||||
#if !os(visionOS)
|
|
||||||
generator.selectionChanged()
|
generator.selectionChanged()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSelectedActionIndex = selectedButton?.offset
|
currentSelectedActionIndex = selectedButton?.offset
|
||||||
selectedButton?.element.backgroundColor = .secondarySystemFill
|
selectedButton?.element.backgroundColor = .secondarySystemFill
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
#endif
|
|
||||||
|
|
||||||
case .ended:
|
case .ended:
|
||||||
if let currentSelectedActionIndex {
|
if let currentSelectedActionIndex {
|
||||||
|
|
|
@ -15,9 +15,7 @@ class EnhancedNavigationViewController: UINavigationController {
|
||||||
var poppedViewControllers = [UIViewController]()
|
var poppedViewControllers = [UIViewController]()
|
||||||
var skipResetPoppedOnNextPush = false
|
var skipResetPoppedOnNextPush = false
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
private var interactivePushTransition: InteractivePushTransition!
|
private var interactivePushTransition: InteractivePushTransition!
|
||||||
#endif
|
|
||||||
|
|
||||||
override var viewControllers: [UIViewController] {
|
override var viewControllers: [UIViewController] {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -36,9 +34,7 @@ class EnhancedNavigationViewController: UINavigationController {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
self.interactivePushTransition = InteractivePushTransition(navigationController: self)
|
self.interactivePushTransition = InteractivePushTransition(navigationController: self)
|
||||||
#endif
|
|
||||||
|
|
||||||
if #available(iOS 16.0, *),
|
if #available(iOS 16.0, *),
|
||||||
useBrowserStyleNavigation,
|
useBrowserStyleNavigation,
|
||||||
|
@ -141,7 +137,6 @@ class EnhancedNavigationViewController: UINavigationController {
|
||||||
}, animated: true)
|
}, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
func onWillShow() {
|
func onWillShow() {
|
||||||
self.transitionCoordinator?.notifyWhenInteractionChanges({ (context) in
|
self.transitionCoordinator?.notifyWhenInteractionChanges({ (context) in
|
||||||
if context.isCancelled {
|
if context.isCancelled {
|
||||||
|
@ -159,7 +154,6 @@ class EnhancedNavigationViewController: UINavigationController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
@available(iOS 16.0, *)
|
@available(iOS 16.0, *)
|
||||||
private func configureNavItem(_ navItem: UINavigationItem) {
|
private func configureNavItem(_ navItem: UINavigationItem) {
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
|
|
||||||
/// Allows interactively moving forward through the navigation stack after popping
|
/// Allows interactively moving forward through the navigation stack after popping
|
||||||
/// Based on https://github.com/NSExceptional/TBInteractivePushTransition
|
/// Based on https://github.com/NSExceptional/TBInteractivePushTransition
|
||||||
class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
|
class InteractivePushTransition: UIPercentDrivenInteractiveTransition {
|
||||||
|
@ -143,5 +141,3 @@ extension InteractivePushTransition: UIViewControllerAnimatedTransitioning {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -7,9 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
#if canImport(Duckable)
|
|
||||||
import Duckable
|
import Duckable
|
||||||
#endif
|
|
||||||
import ComposeUI
|
import ComposeUI
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
@ -107,12 +105,10 @@ class StateRestorationUserActivityHandlingContext: UserActivityHandlingContext {
|
||||||
|
|
||||||
func finalize(activity: NSUserActivity) {
|
func finalize(activity: NSUserActivity) {
|
||||||
precondition(state > .initial)
|
precondition(state > .initial)
|
||||||
#if !os(visionOS)
|
|
||||||
if #available(iOS 16.0, *),
|
if #available(iOS 16.0, *),
|
||||||
let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) {
|
let duckedDraft = UserActivityManager.getDuckedDraft(from: activity) {
|
||||||
self.root.compose(editing: duckedDraft, animated: false, isDucked: true)
|
self.root.compose(editing: duckedDraft, animated: false, isDucked: true)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State: Comparable {
|
enum State: Comparable {
|
||||||
|
|
|
@ -51,9 +51,7 @@ extension TuskerNavigationDelegate {
|
||||||
let config = SFSafariViewController.Configuration()
|
let config = SFSafariViewController.Configuration()
|
||||||
config.entersReaderIfAvailable = Preferences.shared.inAppSafariAutomaticReaderMode
|
config.entersReaderIfAvailable = Preferences.shared.inAppSafariAutomaticReaderMode
|
||||||
let vc = SFSafariViewController(url: url, configuration: config)
|
let vc = SFSafariViewController(url: url, configuration: config)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
present(vc, animated: true)
|
present(vc, animated: true)
|
||||||
} else if UIApplication.shared.canOpenURL(url) {
|
} else if UIApplication.shared.canOpenURL(url) {
|
||||||
UIApplication.shared.open(url, options: [:])
|
UIApplication.shared.open(url, options: [:])
|
||||||
|
@ -94,28 +92,19 @@ extension TuskerNavigationDelegate {
|
||||||
|
|
||||||
func compose(editing draft: Draft?, animated: Bool = true, isDucked: Bool = false) {
|
func compose(editing draft: Draft?, animated: Bool = true, isDucked: Bool = false) {
|
||||||
let draft = draft ?? apiController.createDraft()
|
let draft = draft ?? apiController.createDraft()
|
||||||
let visionIdiom = UIUserInterfaceIdiom(rawValue: 6) // .vision is not available pre-iOS 17 :S
|
if UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac {
|
||||||
if [visionIdiom, .pad, .mac].contains(UIDevice.current.userInterfaceIdiom) {
|
|
||||||
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
let compose = UserActivityManager.editDraftActivity(id: draft.id, accountID: apiController.accountInfo!.id)
|
||||||
let options = UIWindowScene.ActivationRequestOptions()
|
let options = UIWindowScene.ActivationRequestOptions()
|
||||||
#if os(visionOS)
|
|
||||||
options.placement = .prominent()
|
|
||||||
#else
|
|
||||||
options.preferredPresentationStyle = .prominent
|
options.preferredPresentationStyle = .prominent
|
||||||
#endif
|
|
||||||
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: compose, options: options, errorHandler: nil)
|
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: compose, options: options, errorHandler: nil)
|
||||||
} else {
|
} else {
|
||||||
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
let compose = ComposeHostingController(draft: draft, mastodonController: apiController)
|
||||||
#if os(visionOS)
|
|
||||||
fatalError("unreachable")
|
|
||||||
#else
|
|
||||||
if #available(iOS 16.0, *),
|
if #available(iOS 16.0, *),
|
||||||
presentDuckable(compose, animated: animated, isDucked: isDucked) {
|
presentDuckable(compose, animated: animated, isDucked: isDucked) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
present(compose, animated: animated)
|
present(compose, animated: animated)
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -226,9 +226,6 @@ class AttachmentView: GIFImageView {
|
||||||
let asset = AVURLAsset(url: attachmentURL)
|
let asset = AVURLAsset(url: attachmentURL)
|
||||||
let generator = AVAssetImageGenerator(asset: asset)
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
generator.appliesPreferredTrackTransform = true
|
generator.appliesPreferredTrackTransform = true
|
||||||
#if os(visionOS)
|
|
||||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
|
||||||
#else
|
|
||||||
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
||||||
UIImage(cgImage: image).prepareForDisplay { [weak self] image in
|
UIImage(cgImage: image).prepareForDisplay { [weak self] image in
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
@ -237,7 +234,6 @@ class AttachmentView: GIFImageView {
|
||||||
self.displayImage()
|
self.displayImage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,15 +275,11 @@ class AttachmentView: GIFImageView {
|
||||||
AttachmentView.queue.async {
|
AttachmentView.queue.async {
|
||||||
let generator = AVAssetImageGenerator(asset: asset)
|
let generator = AVAssetImageGenerator(asset: asset)
|
||||||
generator.appliesPreferredTrackTransform = true
|
generator.appliesPreferredTrackTransform = true
|
||||||
#if os(visionOS)
|
|
||||||
#warning("Use async AVAssetImageGenerator.image(at:)")
|
|
||||||
#else
|
|
||||||
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
guard let image = try? generator.copyCGImage(at: .zero, actualTime: nil) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.source = .cgImage(attachmentURL, image)
|
self.source = .cgImage(attachmentURL, image)
|
||||||
self.displayImage()
|
self.displayImage()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let gifvView = GifvAttachmentView(asset: asset, gravity: .resizeAspectFill)
|
let gifvView = GifvAttachmentView(asset: asset, gravity: .resizeAspectFill)
|
||||||
|
|
|
@ -47,9 +47,6 @@ class GifvAttachmentView: UIView {
|
||||||
private static func createItem(asset: AVAsset) -> AVPlayerItem {
|
private static func createItem(asset: AVAsset) -> AVPlayerItem {
|
||||||
let item = AVPlayerItem(asset: asset)
|
let item = AVPlayerItem(asset: asset)
|
||||||
if Preferences.shared.grayscaleImages {
|
if Preferences.shared.grayscaleImages {
|
||||||
#if os(visionOS)
|
|
||||||
#warning("Use async AVVideoComposition CIFilter initializer")
|
|
||||||
#else
|
|
||||||
item.videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { (request) in
|
item.videoComposition = AVVideoComposition(asset: asset, applyingCIFiltersWithHandler: { (request) in
|
||||||
let filter = CIFilter(name: "CIColorMonochrome")!
|
let filter = CIFilter(name: "CIColorMonochrome")!
|
||||||
|
|
||||||
|
@ -59,7 +56,6 @@ class GifvAttachmentView: UIView {
|
||||||
|
|
||||||
request.finish(with: filter.outputImage!, context: nil)
|
request.finish(with: filter.outputImage!, context: nil)
|
||||||
})
|
})
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,6 @@ import Pachyderm
|
||||||
import WebURLFoundationExtras
|
import WebURLFoundationExtras
|
||||||
|
|
||||||
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
|
||||||
#if os(visionOS)
|
|
||||||
private let imageScale: CGFloat = 2
|
|
||||||
#else
|
|
||||||
private let imageScale = UIScreen.main.scale
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protocol BaseEmojiLabel: AnyObject {
|
protocol BaseEmojiLabel: AnyObject {
|
||||||
var emojiIdentifier: AnyHashable? { get set }
|
var emojiIdentifier: AnyHashable? { get set }
|
||||||
|
@ -50,7 +45,7 @@ extension BaseEmojiLabel {
|
||||||
func emojiImageSize(_ image: UIImage) -> CGSize {
|
func emojiImageSize(_ image: UIImage) -> CGSize {
|
||||||
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
var imageSizeMatchingFontSize = CGSize(width: image.size.width * (adjustedCapHeight / image.size.height), height: adjustedCapHeight)
|
||||||
var scale: CGFloat = 1.4
|
var scale: CGFloat = 1.4
|
||||||
scale *= imageScale
|
scale *= UIScreen.main.scale
|
||||||
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * scale, height: imageSizeMatchingFontSize.height * scale)
|
imageSizeMatchingFontSize = CGSize(width: imageSizeMatchingFontSize.width * scale, height: imageSizeMatchingFontSize.height * scale)
|
||||||
return imageSizeMatchingFontSize
|
return imageSizeMatchingFontSize
|
||||||
}
|
}
|
||||||
|
@ -80,7 +75,7 @@ extension BaseEmojiLabel {
|
||||||
let cgImage = thumbnail.cgImage {
|
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
|
// 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
|
// see FB12187798
|
||||||
emojiImages[emoji.shortcode] = UIImage(cgImage: cgImage, scale: imageScale, orientation: .up)
|
emojiImages[emoji.shortcode] = UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise, perform the network request
|
// otherwise, perform the network request
|
||||||
|
@ -93,7 +88,7 @@ extension BaseEmojiLabel {
|
||||||
}
|
}
|
||||||
image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in
|
image.prepareThumbnail(of: emojiImageSize(image)) { thumbnail in
|
||||||
guard let thumbnail = thumbnail?.cgImage,
|
guard let thumbnail = thumbnail?.cgImage,
|
||||||
case let rescaled = UIImage(cgImage: thumbnail, scale: imageScale, orientation: .up),
|
case let rescaled = UIImage(cgImage: thumbnail, scale: UIScreen.main.scale, orientation: .up),
|
||||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else {
|
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: URL(emoji.url)!, image: rescaled) else {
|
||||||
group.leave()
|
group.leave()
|
||||||
return
|
return
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15703"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="xP3-ps-Bp7" style="IBUITableViewCellStyleDefault" id="QwQ-aK-6Xu">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QwQ-aK-6Xu" id="whG-vX-EQq">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="xP3-ps-Bp7">
|
||||||
|
<rect key="frame" x="20" y="0.0" width="374" height="44"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||||
|
<nil key="textColor"/>
|
||||||
|
<nil key="highlightedColor"/>
|
||||||
|
</label>
|
||||||
|
</subviews>
|
||||||
|
</tableViewCellContentView>
|
||||||
|
<point key="canvasLocation" x="-113" y="20"/>
|
||||||
|
</tableViewCell>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -66,15 +66,9 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
textContainerInset = .zero
|
textContainerInset = .zero
|
||||||
textContainer.lineFragmentPadding = 0
|
textContainer.lineFragmentPadding = 0
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
linkTextAttributes = [
|
|
||||||
.foregroundColor: UIColor.link
|
|
||||||
]
|
|
||||||
#else
|
|
||||||
linkTextAttributes = [
|
linkTextAttributes = [
|
||||||
.foregroundColor: UIColor.tintColor
|
.foregroundColor: UIColor.tintColor
|
||||||
]
|
]
|
||||||
#endif
|
|
||||||
updateLinkUnderlineStyle()
|
updateLinkUnderlineStyle()
|
||||||
|
|
||||||
// the text view's builtin link interaction code is tied to isSelectable, so we need to use our own tap recognizer
|
// the text view's builtin link interaction code is tied to isSelectable, so we need to use our own tap recognizer
|
||||||
|
@ -209,9 +203,7 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController)
|
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController)
|
||||||
} else if url.scheme == "https" || url.scheme == "http" {
|
} else if url.scheme == "https" || url.scheme == "http" {
|
||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
return vc
|
return vc
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -28,9 +28,7 @@ class PollOptionsView: UIControl {
|
||||||
private let animationDuration: TimeInterval = 0.1
|
private let animationDuration: TimeInterval = 0.1
|
||||||
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
private let scaledTransform = CGAffineTransform(scaleX: 0.95, y: 0.95)
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
private let generator = UISelectionFeedbackGenerator()
|
private let generator = UISelectionFeedbackGenerator()
|
||||||
#endif
|
|
||||||
|
|
||||||
override var isEnabled: Bool {
|
override var isEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -137,10 +135,8 @@ class PollOptionsView: UIControl {
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
generator.selectionChanged()
|
generator.selectionChanged()
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
#endif
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -172,12 +168,10 @@ class PollOptionsView: UIControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if newIndex != nil {
|
if newIndex != nil {
|
||||||
generator.selectionChanged()
|
generator.selectionChanged()
|
||||||
generator.prepare()
|
generator.prepare()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -160,9 +160,7 @@ class StatusPollView: UIView, StatusContentView {
|
||||||
voteButton.isEnabled = false
|
voteButton.isEnabled = false
|
||||||
voteButton.disabledTitle = "Voted"
|
voteButton.disabledTitle = "Voted"
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
#endif
|
|
||||||
|
|
||||||
let request = Poll.vote(poll.id, choices: optionsView.checkedOptionIndices)
|
let request = Poll.vote(poll.id, choices: optionsView.checkedOptionIndices)
|
||||||
mastodonController.run(request) { (response) in
|
mastodonController.run(request) { (response) in
|
||||||
|
|
|
@ -48,11 +48,7 @@ class ProfileFieldValueView: UIView {
|
||||||
|
|
||||||
converted.enumerateAttribute(.link, in: converted.fullRange) { value, range, stop in
|
converted.enumerateAttribute(.link, in: converted.fullRange) { value, range, stop in
|
||||||
guard value != nil else { return }
|
guard value != nil else { return }
|
||||||
#if os(visionOS)
|
|
||||||
converted.addAttribute(.foregroundColor, value: UIColor.link, range: range)
|
|
||||||
#else
|
|
||||||
converted.addAttribute(.foregroundColor, value: UIColor.tintColor, range: range)
|
converted.addAttribute(.foregroundColor, value: UIColor.tintColor, range: range)
|
||||||
#endif
|
|
||||||
// the .link attribute in a UILabel always makes the color blue >.>
|
// the .link attribute in a UILabel always makes the color blue >.>
|
||||||
converted.removeAttribute(.link, range: range)
|
converted.removeAttribute(.link, range: range)
|
||||||
}
|
}
|
||||||
|
@ -152,12 +148,10 @@ class ProfileFieldValueView: UIView {
|
||||||
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
|
if traitCollection.horizontalSizeClass == .compact || traitCollection.verticalSizeClass == .compact {
|
||||||
toPresent = UINavigationController(rootViewController: host)
|
toPresent = UINavigationController(rootViewController: host)
|
||||||
toPresent.modalPresentationStyle = .pageSheet
|
toPresent.modalPresentationStyle = .pageSheet
|
||||||
#if !os(visionOS)
|
|
||||||
let sheetPresentationController = toPresent.sheetPresentationController!
|
let sheetPresentationController = toPresent.sheetPresentationController!
|
||||||
sheetPresentationController.detents = [
|
sheetPresentationController.detents = [
|
||||||
.medium()
|
.medium()
|
||||||
]
|
]
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
host.modalPresentationStyle = .popover
|
host.modalPresentationStyle = .popover
|
||||||
let popoverPresentationController = host.popoverPresentationController!
|
let popoverPresentationController = host.popoverPresentationController!
|
||||||
|
@ -188,9 +182,7 @@ extension ProfileFieldValueView: UIContextMenuInteractionDelegate, MenuActionPro
|
||||||
} else {
|
} else {
|
||||||
return UIContextMenuConfiguration {
|
return UIContextMenuConfiguration {
|
||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
return vc
|
return vc
|
||||||
} actionProvider: { _ in
|
} actionProvider: { _ in
|
||||||
UIMenu(children: self.actionsForURL(url, source: .view(self)))
|
UIMenu(children: self.actionsForURL(url, source: .view(self)))
|
||||||
|
|
|
@ -55,24 +55,14 @@ class ProfileFieldsView: UIView {
|
||||||
boundsObservation = observe(\.bounds, changeHandler: { [unowned self] _, _ in
|
boundsObservation = observe(\.bounds, changeHandler: { [unowned self] _, _ in
|
||||||
self.setNeedsUpdateConstraints()
|
self.setNeedsUpdateConstraints()
|
||||||
})
|
})
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
registerForTraitChanges([UITraitHorizontalSizeClass.self, UITraitPreferredContentSizeCategory.self]) { (self: Self, previousTraitCollection) in
|
|
||||||
if self.isUsingSingleColumn != self.needsSingleColumn {
|
|
||||||
self.configureFields()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
if isUsingSingleColumn != needsSingleColumn {
|
if isUsingSingleColumn != needsSingleColumn {
|
||||||
configureFields()
|
configureFields()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
func updateUI(account: AccountMO) {
|
func updateUI(account: AccountMO) {
|
||||||
isHidden = account.fields.isEmpty
|
isHidden = account.fields.isEmpty
|
||||||
|
|
|
@ -381,9 +381,7 @@ class ProfileHeaderView: UIView {
|
||||||
}
|
}
|
||||||
followButton.configuration!.showsActivityIndicator = true
|
followButton.configuration!.showsActivityIndicator = true
|
||||||
followButton.isEnabled = false
|
followButton.isEnabled = false
|
||||||
#if !os(visionOS)
|
|
||||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
#endif
|
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
let (relationship, _) = try await mastodonController.run(req)
|
let (relationship, _) = try await mastodonController.run(req)
|
||||||
|
|
|
@ -23,9 +23,7 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
private var selectedIndicatorViewAlignmentConstraints: [NSLayoutConstraint] = []
|
private var selectedIndicatorViewAlignmentConstraints: [NSLayoutConstraint] = []
|
||||||
private var changeSelectionPanRecognizer: UIGestureRecognizer!
|
private var changeSelectionPanRecognizer: UIGestureRecognizer!
|
||||||
private var selectedOptionAtStartOfPan: Value?
|
private var selectedOptionAtStartOfPan: Value?
|
||||||
#if !os(visionOS)
|
|
||||||
private lazy var selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
private lazy var selectionChangedFeedbackGenerator = UISelectionFeedbackGenerator()
|
||||||
#endif
|
|
||||||
|
|
||||||
override var intrinsicContentSize: CGSize {
|
override var intrinsicContentSize: CGSize {
|
||||||
let buttonWidths = optionsStack.arrangedSubviews.map(\.intrinsicContentSize.width).reduce(0, +)
|
let buttonWidths = optionsStack.arrangedSubviews.map(\.intrinsicContentSize.width).reduce(0, +)
|
||||||
|
@ -71,24 +69,18 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
selectedIndicatorView.heightAnchor.constraint(equalToConstant: 4),
|
selectedIndicatorView.heightAnchor.constraint(equalToConstant: 4),
|
||||||
selectedIndicatorView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
|
selectedIndicatorView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
registerForTraitChanges([UITraitPreferredContentSizeCategory.self], action: #selector(invalidateIntrinsicContentSize))
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
|
if traitCollection.preferredContentSizeCategory != previousTraitCollection?.preferredContentSizeCategory {
|
||||||
invalidateIntrinsicContentSize()
|
invalidateIntrinsicContentSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private func createOptionViews() {
|
private func createOptionViews() {
|
||||||
optionsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
optionsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
@ -115,11 +107,9 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if selectedOption != nil {
|
if selectedOption != nil {
|
||||||
selectionChangedFeedbackGenerator.selectionChanged()
|
selectionChangedFeedbackGenerator.selectionChanged()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
selectedOption = value
|
selectedOption = value
|
||||||
didSelectOption?(value)
|
didSelectOption?(value)
|
||||||
|
@ -180,15 +170,11 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
selectedOptionAtStartOfPan = selectedOption
|
selectedOptionAtStartOfPan = selectedOption
|
||||||
#if !os(visionOS)
|
|
||||||
selectionChangedFeedbackGenerator.prepare()
|
selectionChangedFeedbackGenerator.prepare()
|
||||||
#endif
|
|
||||||
|
|
||||||
case .changed:
|
case .changed:
|
||||||
if updateSelectionFor(location: horizontalLocationInStack) {
|
if updateSelectionFor(location: horizontalLocationInStack) {
|
||||||
#if !os(visionOS)
|
|
||||||
selectionChangedFeedbackGenerator.prepare()
|
selectionChangedFeedbackGenerator.prepare()
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case .ended:
|
case .ended:
|
||||||
|
@ -222,9 +208,7 @@ class ScrollingSegmentedControl<Value: Hashable>: UIScrollView, UIGestureRecogni
|
||||||
self.layoutIfNeeded()
|
self.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
animator.startAnimation()
|
animator.startAnimation()
|
||||||
#if !os(visionOS)
|
|
||||||
selectionChangedFeedbackGenerator.selectionChanged()
|
selectionChangedFeedbackGenerator.selectionChanged()
|
||||||
#endif
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -225,51 +225,27 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var replyButton = UIButton().configure {
|
private(set) lazy var replyButton = UIButton().configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "arrowshape.turn.up.left.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "arrowshape.turn.up.left.fill"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(replyPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(replyPressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "star.fill")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "repeat")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var moreButton = UIButton().configure {
|
private(set) lazy var moreButton = UIButton().configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "ellipsis")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
$0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.showsMenuAsPrimaryAction = true
|
$0.showsMenuAsPrimaryAction = true
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var actionButtons: [UIButton] {
|
private var actionButtons: [UIButton] {
|
||||||
|
@ -284,13 +260,9 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
]).configure {
|
]).configure {
|
||||||
$0.axis = .horizontal
|
$0.axis = .horizontal
|
||||||
$0.distribution = .fillEqually
|
$0.distribution = .fillEqually
|
||||||
#if os(visionOS)
|
|
||||||
$0.spacing = 8
|
|
||||||
#else
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
$0.heightAnchor.constraint(equalToConstant: 26),
|
$0.heightAnchor.constraint(equalToConstant: 26),
|
||||||
])
|
])
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private let accountDetailToContentWarningSpacer = UIView().configure {
|
private let accountDetailToContentWarningSpacer = UIView().configure {
|
||||||
|
@ -333,10 +305,8 @@ class ConversationMainStatusCollectionViewCell: UICollectionViewListCell, Status
|
||||||
contentContainer.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
contentContainer.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
||||||
firstSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
firstSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
||||||
secondSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
secondSeparator.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
||||||
|
actionsHStack.widthAnchor.constraint(equalTo: $0.widthAnchor),
|
||||||
])
|
])
|
||||||
#if !os(visionOS)
|
|
||||||
actionsHStack.widthAnchor.constraint(equalTo: $0.widthAnchor).isActive = true
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var prevThreadLinkView: UIView?
|
var prevThreadLinkView: UIView?
|
||||||
|
|
|
@ -69,11 +69,7 @@ class StatusCardView: UIView {
|
||||||
domainLabel.font = .preferredFont(forTextStyle: .caption2)
|
domainLabel.font = .preferredFont(forTextStyle: .caption2)
|
||||||
domainLabel.adjustsFontForContentSizeCategory = true
|
domainLabel.adjustsFontForContentSizeCategory = true
|
||||||
domainLabel.numberOfLines = 1
|
domainLabel.numberOfLines = 1
|
||||||
#if os(visionOS)
|
|
||||||
domainLabel.textColor = .link
|
|
||||||
#else
|
|
||||||
domainLabel.textColor = .tintColor
|
domainLabel.textColor = .tintColor
|
||||||
#endif
|
|
||||||
|
|
||||||
vStack = UIStackView(arrangedSubviews: [
|
vStack = UIStackView(arrangedSubviews: [
|
||||||
titleLabel,
|
titleLabel,
|
||||||
|
@ -141,13 +137,10 @@ class StatusCardView: UIView {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded on visionOS because there is no light/dark mode
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
updateBorderColor()
|
updateBorderColor()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
@ -248,9 +241,7 @@ extension StatusCardView: UIContextMenuInteractionDelegate {
|
||||||
|
|
||||||
return UIContextMenuConfiguration(identifier: nil) {
|
return UIContextMenuConfiguration(identifier: nil) {
|
||||||
let vc = SFSafariViewController(url: URL(card.url)!)
|
let vc = SFSafariViewController(url: URL(card.url)!)
|
||||||
#if !os(visionOS)
|
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
#endif
|
|
||||||
return vc
|
return vc
|
||||||
} actionProvider: { (_) in
|
} actionProvider: { (_) in
|
||||||
let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? []
|
let actions = self.actionProvider?.actionsForURL(URL(card.url)!, source: .view(self)) ?? []
|
||||||
|
|
|
@ -37,20 +37,8 @@ class StatusMetaIndicatorsView: UIView {
|
||||||
|
|
||||||
private func commonInit() {
|
private func commonInit() {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(configureImageViews), name: UIAccessibility.boldTextStatusDidChangeNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(configureImageViews), name: UIAccessibility.boldTextStatusDidChangeNotification, object: nil)
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
registerForTraitChanges([UITraitPreferredContentSizeCategory.self]) { (self: Self, previousTraitCollection) in
|
|
||||||
if self.isUsingSingleAxis != self.needsSingleAxis {
|
|
||||||
for image in self.images {
|
|
||||||
self.configureImageView(image)
|
|
||||||
}
|
|
||||||
self.placeImageViews(self.images)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
if isUsingSingleAxis != needsSingleAxis {
|
if isUsingSingleAxis != needsSingleAxis {
|
||||||
|
@ -60,7 +48,6 @@ class StatusMetaIndicatorsView: UIView {
|
||||||
placeImageViews(images)
|
placeImageViews(images)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
@objc private func configureImageViews() {
|
@objc private func configureImageViews() {
|
||||||
for image in images {
|
for image in images {
|
||||||
|
|
|
@ -219,17 +219,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
|
|
||||||
let pollView = StatusPollView()
|
let pollView = StatusPollView()
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
private lazy var actionsContainer = UIStackView(arrangedSubviews: [
|
|
||||||
replyButton,
|
|
||||||
favoriteButton,
|
|
||||||
reblogButton,
|
|
||||||
moreButton,
|
|
||||||
]).configure {
|
|
||||||
$0.axis = .horizontal
|
|
||||||
$0.spacing = 8
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
private var placeholderReplyButtonLeadingConstraint: NSLayoutConstraint!
|
private var placeholderReplyButtonLeadingConstraint: NSLayoutConstraint!
|
||||||
private lazy var actionsContainer = UIView().configure {
|
private lazy var actionsContainer = UIView().configure {
|
||||||
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
replyButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -264,54 +253,29 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
moreButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
moreButton.bottomAnchor.constraint(equalTo: $0.bottomAnchor),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
private(set) lazy var replyButton = UIButton().configure {
|
private(set) lazy var replyButton = UIButton().configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "arrowshape.turn.up.left.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "arrowshape.turn.up.left.fill"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(replyPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(replyPressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
private(set) lazy var favoriteButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "star.fill")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
$0.setImage(UIImage(systemName: "star.fill"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(favoritePressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
private(set) lazy var reblogButton = ToggleableButton(activeColor: UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1)).configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "repeat")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
$0.setImage(UIImage(systemName: "repeat"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
$0.addTarget(self, action: #selector(reblogPressed), for: .touchUpInside)
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) lazy var moreButton = UIButton().configure {
|
private(set) lazy var moreButton = UIButton().configure {
|
||||||
#if os(visionOS)
|
|
||||||
var config = UIButton.Configuration.borderedProminent()
|
|
||||||
config.image = UIImage(systemName: "ellipsis")
|
|
||||||
$0.configuration = config
|
|
||||||
#else
|
|
||||||
$0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
$0.setImage(UIImage(systemName: "ellipsis"), for: .normal)
|
||||||
$0.addInteraction(UIPointerInteraction(delegate: self))
|
|
||||||
#endif
|
|
||||||
$0.showsMenuAsPrimaryAction = true
|
$0.showsMenuAsPrimaryAction = true
|
||||||
|
$0.addInteraction(UIPointerInteraction(delegate: self))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var actionButtons: [UIButton] {
|
private var actionButtons: [UIButton] {
|
||||||
|
@ -357,9 +321,7 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
private var rebloggerID: String?
|
private var rebloggerID: String?
|
||||||
private var filterReason: String?
|
private var filterReason: String?
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
private var firstLayout = true
|
private var firstLayout = true
|
||||||
#endif
|
|
||||||
var isGrayscale = false
|
var isGrayscale = false
|
||||||
private var updateTimestampWorkItem: DispatchWorkItem?
|
private var updateTimestampWorkItem: DispatchWorkItem?
|
||||||
private var hasCreatedObservers = false
|
private var hasCreatedObservers = false
|
||||||
|
@ -392,23 +354,14 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
mainContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
mainContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
||||||
mainContainerBottomToActionsConstraint,
|
mainContainerBottomToActionsConstraint,
|
||||||
|
|
||||||
|
actionsContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
|
||||||
|
actionsContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
||||||
// yes, this is deliberately 6. 4 looks to cramped, 8 looks uneven
|
// yes, this is deliberately 6. 4 looks to cramped, 8 looks uneven
|
||||||
actionsContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6),
|
actionsContainer.bottomAnchor.constraint(equalTo: statusContainer.bottomAnchor, constant: -6),
|
||||||
|
|
||||||
metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: statusContainer.bottomAnchor, constant: -6),
|
metaIndicatorsView.bottomAnchor.constraint(lessThanOrEqualTo: statusContainer.bottomAnchor, constant: -6),
|
||||||
])
|
])
|
||||||
|
|
||||||
#if os(visionOS)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
actionsContainer.leadingAnchor.constraint(equalTo: contentVStack.leadingAnchor),
|
|
||||||
])
|
|
||||||
#else
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
actionsContainer.leadingAnchor.constraint(equalTo: statusContainer.leadingAnchor, constant: 16),
|
|
||||||
actionsContainer.trailingAnchor.constraint(equalTo: statusContainer.trailingAnchor, constant: -16),
|
|
||||||
])
|
|
||||||
#endif
|
|
||||||
|
|
||||||
updateActionsVisibility()
|
updateActionsVisibility()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||||
|
@ -421,7 +374,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
|
|
||||||
#if !os(visionOS)
|
|
||||||
if firstLayout {
|
if firstLayout {
|
||||||
firstLayout = false
|
firstLayout = false
|
||||||
|
|
||||||
|
@ -431,7 +383,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
||||||
placeholderReplyButtonLeadingConstraint.isActive = false
|
placeholderReplyButtonLeadingConstraint.isActive = false
|
||||||
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
replyButton.imageView!.leadingAnchor.constraint(equalTo: contentTextView.leadingAnchor).isActive = true
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateConfiguration(using state: UICellConfigurationState) {
|
override func updateConfiguration(using state: UICellConfigurationState) {
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import Pachyderm
|
import Pachyderm
|
||||||
#if canImport(Sentry)
|
|
||||||
import Sentry
|
import Sentry
|
||||||
#endif
|
|
||||||
import OSLog
|
import OSLog
|
||||||
@_spi(InstanceType) import InstanceFeatures
|
@_spi(InstanceType) import InstanceFeatures
|
||||||
|
|
||||||
|
@ -98,75 +96,70 @@ fileprivate extension Pachyderm.Client.Error {
|
||||||
private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError")
|
private let toastErrorLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ToastError")
|
||||||
|
|
||||||
private func captureError(_ error: Client.Error, in mastodonController: MastodonController, title: String) {
|
private func captureError(_ error: Client.Error, in mastodonController: MastodonController, title: String) {
|
||||||
var tags = [
|
let event = Event(error: error)
|
||||||
|
event.message = SentryMessage(formatted: "\(title): \(error)")
|
||||||
|
event.tags = [
|
||||||
"request_method": error.requestMethod.name,
|
"request_method": error.requestMethod.name,
|
||||||
"request_endpoint": error.requestEndpoint.description,
|
"request_endpoint": error.requestEndpoint.description,
|
||||||
]
|
]
|
||||||
var extra: [String: String]?
|
|
||||||
switch error.type {
|
switch error.type {
|
||||||
case .invalidRequest:
|
case .invalidRequest:
|
||||||
tags["error_type"] = "invalid_request"
|
event.tags!["error_type"] = "invalid_request"
|
||||||
case .invalidResponse:
|
case .invalidResponse:
|
||||||
tags["error_type"] = "invalid_response"
|
event.tags!["error_type"] = "invalid_response"
|
||||||
case .invalidModel(let error):
|
case .invalidModel(let error):
|
||||||
tags["error_type"] = "invalid_model"
|
event.tags!["error_type"] = "invalid_model"
|
||||||
extra = [
|
event.extra = [
|
||||||
"underlying_error": String(describing: error)
|
"underlying_error": String(describing: error)
|
||||||
]
|
]
|
||||||
case .mastodonError(let code, let error):
|
case .mastodonError(let code, let error):
|
||||||
tags["error_type"] = "mastodon_error"
|
event.tags!["error_type"] = "mastodon_error"
|
||||||
tags["response_code"] = "\(code)"
|
event.tags!["response_code"] = "\(code)"
|
||||||
extra = [
|
event.extra = [
|
||||||
"underlying_error": String(describing: error)
|
"underlying_error": String(describing: error)
|
||||||
]
|
]
|
||||||
case .unexpectedStatus(let code):
|
case .unexpectedStatus(let code):
|
||||||
tags["error_type"] = "unexpected_status"
|
event.tags!["error_type"] = "unexpected_status"
|
||||||
tags["response_code"] = "\(code)"
|
event.tags!["response_code"] = "\(code)"
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let code = tags["response_code"],
|
if let code = event.tags!["response_code"],
|
||||||
code == "401" || code == "403" || code == "404" || code == "422" || code == "500" || code == "502" || code == "503" {
|
code == "401" || code == "403" || code == "404" || code == "422" || code == "500" || code == "502" || code == "503" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch mastodonController.instanceFeatures.instanceType {
|
switch mastodonController.instanceFeatures.instanceType {
|
||||||
case .mastodon(let mastodonType, let mastodonVersion):
|
case .mastodon(let mastodonType, let mastodonVersion):
|
||||||
tags["instance_type"] = "mastodon"
|
event.tags!["instance_type"] = "mastodon"
|
||||||
tags["mastodon_version"] = mastodonVersion?.description ?? "unknown"
|
event.tags!["mastodon_version"] = mastodonVersion?.description ?? "unknown"
|
||||||
switch mastodonType {
|
switch mastodonType {
|
||||||
case .vanilla:
|
case .vanilla:
|
||||||
break
|
break
|
||||||
case .hometown(_):
|
case .hometown(_):
|
||||||
tags["mastodon_type"] = "hometown"
|
event.tags!["mastodon_type"] = "hometown"
|
||||||
case .glitch:
|
case .glitch:
|
||||||
tags["mastodon_type"] = "glitch"
|
event.tags!["mastodon_type"] = "glitch"
|
||||||
}
|
}
|
||||||
case .pleroma(let pleromaType):
|
case .pleroma(let pleromaType):
|
||||||
tags["instance_type"] = "pleroma"
|
event.tags!["instance_type"] = "pleroma"
|
||||||
switch pleromaType {
|
switch pleromaType {
|
||||||
case .vanilla(let version):
|
case .vanilla(let version):
|
||||||
tags["pleroma_version"] = version?.description ?? "unknown"
|
event.tags!["pleroma_version"] = version?.description ?? "unknown"
|
||||||
case .akkoma(let version):
|
case .akkoma(let version):
|
||||||
tags["pleroma_type"] = "akkoma"
|
event.tags!["pleroma_type"] = "akkoma"
|
||||||
tags["pleroma_version"] = version?.description ?? "unknown"
|
event.tags!["pleroma_version"] = version?.description ?? "unknown"
|
||||||
}
|
}
|
||||||
case .pixelfed:
|
case .pixelfed:
|
||||||
tags["instance_type"] = "pixelfed"
|
event.tags!["instance_type"] = "pixelfed"
|
||||||
case .gotosocial:
|
case .gotosocial:
|
||||||
tags["instance_type"] = "gotosocial"
|
event.tags!["instance_type"] = "gotosocial"
|
||||||
case .firefish(let calckeyVersion):
|
case .firefish(let calckeyVersion):
|
||||||
tags["instance_type"] = "firefish"
|
event.tags!["instance_type"] = "firefish"
|
||||||
if let calckeyVersion {
|
if let calckeyVersion {
|
||||||
tags["calckey_version"] = calckeyVersion
|
event.tags!["calckey_version"] = calckeyVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if canImport(Sentry)
|
|
||||||
let event = Event(error: error)
|
|
||||||
event.message = SentryMessage(formatted: "\(title): \(error)")
|
|
||||||
event.tags = tags
|
|
||||||
event.extra = extra
|
|
||||||
SentrySDK.capture(event: event)
|
SentrySDK.capture(event: event)
|
||||||
#endif
|
|
||||||
|
|
||||||
toastErrorLogger.error("\(title, privacy: .public): \(error), \(tags.debugDescription, privacy: .public)")
|
toastErrorLogger.error("\(title, privacy: .public): \(error), \(event.tags!.debugDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,7 @@ class ToggleableButton: UIButton {
|
||||||
|
|
||||||
var active: Bool {
|
var active: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
if var config = self.configuration {
|
tintColor = active ? activeColor : nil
|
||||||
config.baseForegroundColor = active ? activeColor : nil
|
|
||||||
self.configuration = config
|
|
||||||
} else {
|
|
||||||
tintColor = active ? activeColor : nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,11 @@ class TrendHistoryView: UIView {
|
||||||
createLayers()
|
createLayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unneeded on visionOS, since there is no dark/light mode
|
|
||||||
#if !os(visionOS)
|
|
||||||
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
|
||||||
super.traitCollectionDidChange(previousTraitCollection)
|
super.traitCollectionDidChange(previousTraitCollection)
|
||||||
|
|
||||||
createLayers()
|
createLayers()
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
func setHistory(_ history: [History]?) {
|
func setHistory(_ history: [History]?) {
|
||||||
if let history = history {
|
if let history = history {
|
||||||
|
|
Loading…
Reference in New Issue