Browse Source

Remove unused pre-iOS 14 code

async
Shadowfacts 8 months ago
parent
commit
669d55500a
  1. 12
      Tusker.xcodeproj/project.pbxproj
  2. 19
      Tusker/Controllers/MenuController.swift
  3. 21
      Tusker/Extensions/UIAccessibility.swift
  4. 6
      Tusker/MainSceneDelegate.swift
  5. 24
      Tusker/Screens/Compose/ComposeAttachmentRow.swift
  6. 18
      Tusker/Screens/Compose/ComposeAttachmentsList.swift
  7. 15
      Tusker/Screens/Compose/ComposeAutocompleteView.swift
  8. 6
      Tusker/Screens/Compose/ComposeDrawingViewController.swift
  9. 55
      Tusker/Screens/Compose/ComposeHostingController.swift
  10. 33
      Tusker/Screens/Compose/ComposeView.swift
  11. 20
      Tusker/Screens/Compose/MainComposeTextView.swift
  12. 2
      Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift
  13. 4
      Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift
  14. 2
      Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift
  15. 2
      Tusker/Screens/Main/AccountSwitchingContainerViewController.swift
  16. 6
      Tusker/Screens/Main/MainSidebarViewController.swift
  17. 6
      Tusker/Screens/Main/MainSplitViewController.swift
  18. 2
      Tusker/Screens/Preferences/AdvancedPrefsView.swift
  19. 2
      Tusker/Screens/Preferences/AppearancePrefsView.swift
  20. 2
      Tusker/Screens/Preferences/BehaviorPrefsView.swift
  21. 2
      Tusker/Screens/Preferences/ComposingPrefsView.swift
  22. 2
      Tusker/Screens/Preferences/MediaPrefsView.swift
  23. 13
      Tusker/Screens/Preferences/PreferencesView.swift
  24. 2
      Tusker/Screens/Preferences/SilentActionPrefs.swift
  25. 2
      Tusker/Screens/Preferences/WellnessPrefsView.swift
  26. 12
      Tusker/Screens/Profile/ProfileViewController.swift
  27. 4
      Tusker/Screens/Utilities/Previewing.swift
  28. 1
      Tusker/Screens/Utilities/TrackpadScrollGestureRecognizer.swift
  29. 32
      Tusker/TuskerNavigationDelegate.swift
  30. 13
      Tusker/Views/AccountDisplayNameLabel.swift
  31. 23
      Tusker/Views/ActivityIndicatorView.swift
  32. 59
      Tusker/Views/MaybeLazyStack.swift
  33. 19
      Tusker/Views/Profile Header/ProfileHeaderView.swift
  34. 8
      Tusker/Views/Profile Header/ProfileHeaderView.xib
  35. 10
      Tusker/Views/Status/BaseStatusTableViewCell.swift
  36. 25
      Tusker/Views/Status/TimelineStatusTableViewCell.swift
  37. 9
      TuskerUITests/ComposeTests.swift
  38. 54
      TuskerUITests/MyProfileTests.swift

12
Tusker.xcodeproj/project.pbxproj

@ -276,14 +276,12 @@
D6C82B5725C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */; };
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D862139E62700CB5196 /* LargeImageViewController.swift */; };
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C94D882139E6EC00CB5196 /* AttachmentView.swift */; };
D6C99FC724FACFAB005C74D3 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C99FC624FACFAB005C74D3 /* ActivityIndicatorView.swift */; };
D6C99FCB24FADC91005C74D3 /* MainComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C99FCA24FADC91005C74D3 /* MainComposeTextView.swift */; };
D6CA6A92249FAD8900AD45C1 /* AudioSessionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */; };
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */; };
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */; };
D6D3FDE024F41B8400FF50A5 /* ComposeContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D3FDDF24F41B8400FF50A5 /* ComposeContainerView.swift */; };
D6D3FDE224F46A8D00FF50A5 /* ComposeUIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D3FDE124F46A8D00FF50A5 /* ComposeUIState.swift */; };
D6D4CC91250D2C3100FCCF8D /* UIAccessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4CC90250D2C3100FCCF8D /* UIAccessibility.swift */; };
D6D4CC94250DB86A00FCCF8D /* ComposeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4CC93250DB86A00FCCF8D /* ComposeTests.swift */; };
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */; };
D6D4DDD7212518A200E1C4BB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD6212518A200E1C4BB /* Assets.xcassets */; };
@ -297,7 +295,6 @@
D6DFC6A0242C4CCC00ACC392 /* WeakArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */; };
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0DC8D216EDF1E00369478 /* Previewing.swift */; };
D6E4267725327FB400C02E1C /* ComposeAutocompleteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */; };
D6E426812532814100C02E1C /* MaybeLazyStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426802532814100C02E1C /* MaybeLazyStack.swift */; };
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
@ -640,14 +637,12 @@
D6C82B5525C5F3F20017F1E6 /* ExpandThreadTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExpandThreadTableViewCell.xib; sourceTree = "<group>"; };
D6C94D862139E62700CB5196 /* LargeImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeImageViewController.swift; sourceTree = "<group>"; };
D6C94D882139E6EC00CB5196 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
D6C99FC624FACFAB005C74D3 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = "<group>"; };
D6C99FCA24FADC91005C74D3 /* MainComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainComposeTextView.swift; sourceTree = "<group>"; };
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionHelper.swift; sourceTree = "<group>"; };
D6CA6A93249FADE700AD45C1 /* GalleryPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryPlayerViewController.swift; sourceTree = "<group>"; };
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ConditionalModifier.swift"; sourceTree = "<group>"; };
D6D3FDDF24F41B8400FF50A5 /* ComposeContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeContainerView.swift; sourceTree = "<group>"; };
D6D3FDE124F46A8D00FF50A5 /* ComposeUIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeUIState.swift; sourceTree = "<group>"; };
D6D4CC90250D2C3100FCCF8D /* UIAccessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAccessibility.swift; sourceTree = "<group>"; };
D6D4CC93250DB86A00FCCF8D /* ComposeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTests.swift; sourceTree = "<group>"; };
D6D4DDCC212518A000E1C4BB /* Tusker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Tusker.app; sourceTree = BUILT_PRODUCTS_DIR; };
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -667,7 +662,6 @@
D6DFC69F242C4CCC00ACC392 /* WeakArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakArray.swift; sourceTree = "<group>"; };
D6E0DC8D216EDF1E00369478 /* Previewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Previewing.swift; sourceTree = "<group>"; };
D6E4267625327FB400C02E1C /* ComposeAutocompleteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeAutocompleteView.swift; sourceTree = "<group>"; };
D6E426802532814100C02E1C /* MaybeLazyStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeLazyStack.swift; sourceTree = "<group>"; };
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcher.swift; sourceTree = "<group>"; };
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcherTests.swift; sourceTree = "<group>"; };
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = "<group>"; };
@ -1207,7 +1201,6 @@
D67895BB24671E6D00D4CD9E /* PKDrawing+Render.swift */,
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */,
D6D3F4C324FDB6B700EC4A6A /* View+ConditionalModifier.swift */,
D6D4CC90250D2C3100FCCF8D /* UIAccessibility.swift */,
D6620ACD2511A0ED00312CA0 /* StatusStateResolver.swift */,
D69693F32585941A00F4E116 /* UIWindowSceneDelegate+Close.swift */,
);
@ -1370,10 +1363,8 @@
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
D6C99FC624FACFAB005C74D3 /* ActivityIndicatorView.swift */,
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
D6B4A4FE2506B81A000C81C1 /* AccountDisplayNameLabel.swift */,
D6E426802532814100C02E1C /* MaybeLazyStack.swift */,
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */,
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */,
D67C57A721E2649B00C3118B /* Account Detail */,
@ -1910,7 +1901,6 @@
D6C7D27D22B6EBF800071952 /* AttachmentsContainerView.swift in Sources */,
D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */,
D6D3FDE224F46A8D00FF50A5 /* ComposeUIState.swift in Sources */,
D6C99FC724FACFAB005C74D3 /* ActivityIndicatorView.swift in Sources */,
D6B22A0F2560D52D004D82EF /* TabbedPageViewController.swift in Sources */,
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
0411610022B442870030A9B7 /* LoadingLargeImageViewController.swift in Sources */,
@ -1945,7 +1935,6 @@
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
D6E426812532814100C02E1C /* MaybeLazyStack.swift in Sources */,
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
D646C958213B367000269FB5 /* LargeImageShrinkAnimationController.swift in Sources */,
D6A3BC852321F6C100FD64D5 /* AccountListTableViewController.swift in Sources */,
@ -2007,7 +1996,6 @@
D6B053AE23BD322B00A066FA /* AssetPickerSheetContainerViewController.swift in Sources */,
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D6D4CC91250D2C3100FCCF8D /* UIAccessibility.swift in Sources */,
D627943E23A564D400D38C68 /* ExploreViewController.swift in Sources */,
D6B053AB23BD2F1400A066FA /* AssetCollectionViewCell.swift in Sources */,
D622757A24EE21D900B82A16 /* ComposeAttachmentRow.swift in Sources */,

19
Tusker/Controllers/MenuController.swift

@ -11,20 +11,13 @@ import UIKit
struct MenuController {
static let composeCommand: UIKeyCommand = {
let selector: Selector
if #available(iOS 14.0, *) {
selector = #selector(MainSplitViewController.presentCompose)
} else {
selector = #selector(MainTabBarViewController.presentCompose)
}
return UIKeyCommand(title: "Compose", action: selector, input: "n", modifierFlags: .command)
return UIKeyCommand(title: "Compose", action: #selector(MainSplitViewController.presentCompose), input: "n", modifierFlags: .command)
}()
static func refreshCommand(discoverabilityTitle: String?) -> UIKeyCommand {
return UIKeyCommand(title: "Refresh", action: #selector(RefreshableViewController.refresh), input: "r", modifierFlags: .command, discoverabilityTitle: discoverabilityTitle)
}
@available(iOS 14.0, *)
static func sidebarCommand(item: MainSidebarViewController.Item, command: String) -> UIKeyCommand {
let data: Any
if case let .tab(tab) = item {
@ -46,7 +39,6 @@ struct MenuController {
)
}
@available(iOS 14.0, *)
static let sidebarItemKeyCommands: [UIKeyCommand] = [
sidebarCommand(item: .tab(.timelines), command: "1"),
sidebarCommand(item: .tab(.notifications), command: "2"),
@ -92,25 +84,18 @@ struct MenuController {
}
private static func buildSidebarShortcuts() -> UIMenu {
let children: [UIMenuElement]
if #available(iOS 14.0, *) {
children = sidebarItemKeyCommands
} else {
children = []
}
return UIMenu(
title: "",
image: nil,
identifier: nil,
options: .displayInline,
children: children
children: sidebarItemKeyCommands
)
}
}
extension MenuController {
@available(iOS 14.0, *)
class SidebarItem: NSObject, NSCopying {
let item: MainSidebarViewController.Item

21
Tusker/Extensions/UIAccessibility.swift

@ -1,21 +0,0 @@
//
// UIAccessibility.swift
// Tusker
//
// Created by Shadowfacts on 9/12/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
extension UIAccessibility {
static var prefersCrossFadeTransitionsBackwardsCompat: Bool {
if #available(iOS 14.0, *) {
return prefersCrossFadeTransitions
} else {
return isReduceMotionEnabled
}
}
}

6
Tusker/MainSceneDelegate.swift

@ -176,11 +176,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
mastodonController.getOwnAccount()
mastodonController.getOwnInstance()
if #available(iOS 14.0, *) {
return MainSplitViewController(mastodonController: mastodonController)
} else {
return MainTabBarViewController(mastodonController: mastodonController)
}
return MainSplitViewController(mastodonController: mastodonController)
}
func createOnboardingUI() -> UIViewController {

24
Tusker/Screens/Compose/ComposeAttachmentRow.swift

@ -34,25 +34,11 @@ struct ComposeAttachmentRow: View {
.contextMenu {
if case .drawing(_) = attachment.data {
Button(action: self.editDrawing) {
if #available(iOS 14.0, *) {
Label("Edit Drawing", systemImage: "hand.draw")
} else {
HStack {
Text("Edit Drawing")
Image(systemName: "hand.draw")
}
}
Label("Edit Drawing", systemImage: "hand.draw")
}
} else if attachment.data.type == .image {
Button(action: self.recognizeText) {
if #available(iOS 14.0, *) {
Label("Recognize Text", systemImage: "doc.text.viewfinder")
} else {
HStack {
Text("Recognize Text")
Image(systemName: "doc.text.viewfinder")
}
}
Label("Recognize Text", systemImage: "doc.text.viewfinder")
}
}
}
@ -65,11 +51,7 @@ struct ComposeAttachmentRow: View {
.fontSize(17)
case .recognizingText:
if #available(iOS 14.0, *) {
ProgressView()
} else {
ActivityIndicatorView()
}
ProgressView()
}

18
Tusker/Screens/Compose/ComposeAttachmentsList.swift

@ -45,14 +45,7 @@ struct ComposeAttachmentsList: View {
}
Button(action: self.addAttachment) {
if #available(iOS 14.0, *) {
Label("Add photo or video", systemImage: addButtonImageName)
} else {
HStack {
Image(systemName: addButtonImageName)
Text("Add photo or video")
}
}
Label("Add photo or video", systemImage: addButtonImageName)
}
.disabled(!canAddAttachment)
.foregroundColor(.blue)
@ -61,14 +54,7 @@ struct ComposeAttachmentsList: View {
.listRowInsets(EdgeInsets(top: cellPadding / 2, leading: cellPadding / 2, bottom: cellPadding / 2, trailing: cellPadding / 2))
Button(action: self.createDrawing) {
if #available(iOS 14.0, *) {
Label("Draw something", systemImage: "hand.draw")
} else {
HStack(alignment: .lastTextBaseline) {
Image(systemName: "hand.draw")
Text("Draw something")
}
}
Label("Draw something", systemImage: "hand.draw")
}
.disabled(!canAddAttachment)
.foregroundColor(.blue)

15
Tusker/Screens/Compose/ComposeAutocompleteView.swift

@ -44,19 +44,6 @@ struct ComposeAutocompleteView: View {
}
}
fileprivate extension View {
@ViewBuilder
func iOS13OnlyPadding() -> some View {
// on iOS 13, if the scroll view content's height changes after the view is added to the hierarchy,
// it doesn't appear on screen until interactive keyboard dismissal is started and then cancelled :S
if #available(iOS 14.0, *) {
self
} else {
self.frame(height: 46)
}
}
}
struct ComposeAutocompleteMentionsView: View {
@EnvironmentObject private var mastodonController: MastodonController
@EnvironmentObject private var uiState: ComposeUIState
@ -104,7 +91,6 @@ struct ComposeAutocompleteMentionsView: View {
Spacer()
}
.padding(.horizontal, 8)
.iOS13OnlyPadding()
}
.onReceive(uiState.$autocompleteState.debounce(for: .milliseconds(250), scheduler: DispatchQueue.main), perform: queryChanged)
.onDisappear {
@ -333,7 +319,6 @@ struct ComposeAutocompleteHashtagsView: View {
Spacer()
}
.padding(.horizontal, 8)
.iOS13OnlyPadding()
}
.onReceive(uiState.$autocompleteState.debounce(for: .milliseconds(250), scheduler: DispatchQueue.main), perform: queryChanged)
.onDisappear {

6
Tusker/Screens/Compose/ComposeDrawingViewController.swift

@ -58,11 +58,7 @@ class ComposeDrawingViewController: UIViewController {
canvasView.drawing = initialDrawing
}
canvasView.delegate = self
if #available(iOS 14.0, *) {
canvasView.drawingPolicy = .anyInput
} else {
canvasView.allowsFingerDrawing = true
}
canvasView.drawingPolicy = .anyInput
canvasView.minimumZoomScale = 0.5
canvasView.maximumZoomScale = 2
canvasView.backgroundColor = .systemBackground

55
Tusker/Screens/Compose/ComposeHostingController.swift

@ -27,7 +27,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
private var cancellables = [AnyCancellable]()
private var keyboardHeight: CGFloat = 0
private var toolbarHeight: CGFloat = 44
private var mainToolbar: UIToolbar!
@ -115,13 +114,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.isAccessibilityElement = true
let visibilityAction: Selector?
if #available(iOS 14.0, *) {
visibilityAction = nil
} else {
visibilityAction = #selector(visibilityButtonPressed(_:))
}
let visibilityItem = UIBarButtonItem(image: UIImage(systemName: draft.visibility.imageName), style: .plain, target: self, action: visibilityAction)
let visibilityItem = UIBarButtonItem(image: UIImage(systemName: draft.visibility.imageName), style: .plain, target: nil, action: nil)
visibilityBarButtonItems.append(visibilityItem)
visibilityChanged(draft.visibility)
@ -135,7 +128,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
}
private func updateAdditionalSafeAreaInsets() {
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: toolbarHeight + keyboardHeight, right: 0)
additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 0, bottom: toolbarHeight, right: 0)
}
@objc private func keyboardWillShow(_ notification: Foundation.Notification) {
@ -147,19 +140,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
accessoryView.alpha = 1
accessoryView.isHidden = false
// on iOS 14, SwiftUI safe area automatically includes the keyboard
if #available(iOS 14.0, *) {
} else {
let userInfo = notification.userInfo!
let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
// temporarily reset add'l safe area insets so we can access the default inset
additionalSafeAreaInsets = .zero
// there are a few extra points that come from somewhere, it seems to be four
// and without it, the autocomplete suggestions are cut off :S
keyboardHeight = frame.height - view.safeAreaInsets.bottom - accessoryView.frame.height + 4
updateAdditionalSafeAreaInsets()
}
}
@objc private func keyboardWillHide(_ notification: Foundation.Notification) {
@ -192,13 +172,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
} completion: { (finished) in
accessoryView.alpha = 1
}
// on iOS 14, SwiftUI safe area automatically includes the keyboard
if #available(iOS 14.0, *) {
} else {
keyboardHeight = 0
updateAdditionalSafeAreaInsets()
}
}
@objc private func keyboardDidHide(_ notification: Foundation.Notification) {
@ -214,15 +187,13 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
item.image = UIImage(systemName: newVisibility.imageName)
item.image!.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), draft.visibility.displayName)
item.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), draft.visibility.displayName)
if #available(iOS 14.0, *) {
let elements = Status.Visibility.allCases.map { (visibility) -> UIMenuElement in
let state = visibility == newVisibility ? UIMenuElement.State.on : .off
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { (_) in
self.draft.visibility = visibility
}
let elements = Status.Visibility.allCases.map { (visibility) -> UIMenuElement in
let state = visibility == newVisibility ? UIMenuElement.State.on : .off
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { (_) in
self.draft.visibility = visibility
}
item.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: elements)
}
item.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: elements)
}
}
@ -255,18 +226,6 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
draft.contentWarningEnabled = !draft.contentWarningEnabled
}
@objc func visibilityButtonPressed(_ sender: UIBarButtonItem) {
// if #available(iOS 14.0, *) {
// } else {
let alertController = UIAlertController(currentVisibility: draft.visibility) { (visibility) in
guard let visibility = visibility else { return }
self.draft.visibility = visibility
}
alertController.popoverPresentationController?.barButtonItem = sender
present(alertController, animated: true)
// }
}
@objc func draftsButtonPresed() {
let draftsVC = DraftsTableViewController(account: mastodonController.accountInfo!, exclude: draft)
draftsVC.delegate = self

33
Tusker/Screens/Compose/ComposeView.swift

@ -44,14 +44,9 @@ struct ComposeView: View {
}
var body: some View {
// the pre-iOS 14 API does not result in the correct pointer interactions for nav bar buttons, see FB8595468
if #available(iOS 14.0, *) {
mostOfTheBody.toolbar {
ToolbarItem(placement: .cancellationAction) { cancelButton }
ToolbarItem(placement: .confirmationAction) { postButton }
}
} else {
mostOfTheBody.navigationBarItems(leading: cancelButton, trailing: postButton)
mostOfTheBody.toolbar {
ToolbarItem(placement: .cancellationAction) { cancelButton }
ToolbarItem(placement: .confirmationAction) { postButton }
}
}
@ -82,24 +77,14 @@ struct ComposeView: View {
@ViewBuilder
var autocompleteSuggestions: some View {
// on iOS 13, the transition causes SwiftUI to hang on the main thread when the view appears, so it's disabled
if #available(iOS 14.0, *) {
VStack(spacing: 0) {
Spacer()
if let state = uiState.autocompleteState {
ComposeAutocompleteView(autocompleteState: state)
}
}
.transition(.move(edge: .bottom))
.animation(.default)
} else {
VStack(spacing: 0) {
Spacer()
if let state = uiState.autocompleteState {
ComposeAutocompleteView(autocompleteState: state)
}
VStack(spacing: 0) {
Spacer()
if let state = uiState.autocompleteState {
ComposeAutocompleteView(autocompleteState: state)
}
}
.transition(.move(edge: .bottom))
.animation(.default)
}
func mainStack(outerMinY: CGFloat) -> some View {

20
Tusker/Screens/Compose/MainComposeTextView.swift

@ -69,13 +69,7 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
uiState.autocompleteHandler = context.coordinator
let visibilityAction: Selector?
if #available(iOS 14.0, *) {
visibilityAction = nil
} else {
visibilityAction = #selector(ComposeHostingController.visibilityButtonPressed(_:))
}
let visibilityButton = UIBarButtonItem(image: UIImage(systemName: visibility.imageName), style: .plain, target: nil, action: visibilityAction)
let visibilityButton = UIBarButtonItem(image: UIImage(systemName: visibility.imageName), style: .plain, target: nil, action: nil)
updateVisibilityMenu(visibilityButton)
let toolbar = UIToolbar()
toolbar.translatesAutoresizingMaskIntoConstraints = false
@ -131,15 +125,13 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
}
private func updateVisibilityMenu(_ visibilityButton: UIBarButtonItem) {
if #available(iOS 14.0, *) {
let elements = Status.Visibility.allCases.map { (visibility) -> UIMenuElement in
let state = visibility == self.visibility ? UIMenuElement.State.on : .off
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { (_) in
self.uiState.draft.visibility = visibility
}
let elements = Status.Visibility.allCases.map { (visibility) -> UIMenuElement in
let state = visibility == self.visibility ? UIMenuElement.State.on : .off
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { (_) in
self.uiState.draft.visibility = visibility
}
visibilityButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: elements)
}
visibilityButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: elements)
}
func updateUIView(_ uiView: UITextView, context: Context) {

2
Tusker/Screens/Fast Account Switcher/FastAccountSwitcherViewController.swift

@ -54,7 +54,7 @@ class FastAccountSwitcherViewController: UIViewController {
view.isHidden = false
if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat {
if UIAccessibility.prefersCrossFadeTransitions {
view.alpha = 0
UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) {
self.view.alpha = 1

4
Tusker/Screens/Large Image/Transitions/LargeImageExpandAnimationController.swift

@ -38,7 +38,7 @@ extension LargeImageAnimatableViewController {
class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat {
if UIAccessibility.prefersCrossFadeTransitions {
return 0.2
} else {
return 0.4
@ -51,7 +51,7 @@ class LargeImageExpandAnimationController: NSObject, UIViewControllerAnimatedTra
return
}
if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat {
if UIAccessibility.prefersCrossFadeTransitions {
animateCrossFadeTransition(using: transitionContext)
return
}

2
Tusker/Screens/Large Image/Transitions/LargeImageShrinkAnimationController.swift

@ -27,7 +27,7 @@ class LargeImageShrinkAnimationController: NSObject, UIViewControllerAnimatedTra
return
}
if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat && !transitionContext.isInteractive {
if UIAccessibility.prefersCrossFadeTransitions && !transitionContext.isInteractive {
animateCrossFadeTransition(using: transitionContext)
return
}

2
Tusker/Screens/Main/AccountSwitchingContainerViewController.swift

@ -37,7 +37,7 @@ class AccountSwitchingContainerViewController: UIViewController {
embedChild(newRoot)
if direction != .none {
if UIAccessibility.prefersCrossFadeTransitionsBackwardsCompat {
if UIAccessibility.prefersCrossFadeTransitions {
newRoot.view.alpha = 0
UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseInOut) {

6
Tusker/Screens/Main/MainSidebarViewController.swift

@ -9,13 +9,11 @@
import UIKit
import Pachyderm
@available(iOS 14.0, *)
protocol MainSidebarViewControllerDelegate: class {
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController)
func sidebar(_ sidebarViewController: MainSidebarViewController, didSelectItem item: MainSidebarViewController.Item)
}
@available(iOS 14.0, *)
class MainSidebarViewController: UIViewController {
private weak var mastodonController: MastodonController!
@ -252,7 +250,6 @@ class MainSidebarViewController: UIViewController {
}
@available(iOS 14.0, *)
extension MainSidebarViewController {
enum Section: Int, Hashable, CaseIterable {
case tabs
@ -362,7 +359,6 @@ fileprivate extension MainTabBarViewController.Tab {
}
}
@available(iOS 14.0, *)
extension MainSidebarViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
previouslySelectedItem = selectedItem
@ -397,7 +393,6 @@ extension MainSidebarViewController: UICollectionViewDelegate {
}
}
@available(iOS 14.0, *)
extension MainSidebarViewController: UICollectionViewDragDelegate {
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
guard let item = dataSource.itemIdentifier(for: indexPath),
@ -410,7 +405,6 @@ extension MainSidebarViewController: UICollectionViewDragDelegate {
}
}
@available(iOS 14.0, *)
extension MainSidebarViewController: InstanceTimelineViewControllerDelegate {
func didSaveInstance(url: URL) {
dismiss(animated: true) {

6
Tusker/Screens/Main/MainSplitViewController.swift

@ -8,7 +8,6 @@
import UIKit
@available(iOS 14.0, *)
class MainSplitViewController: UISplitViewController {
weak var mastodonController: MastodonController!
@ -103,7 +102,6 @@ class MainSplitViewController: UISplitViewController {
}
@available(iOS 14.0, *)
extension MainSplitViewController: UISplitViewControllerDelegate {
/// Transfer the navigation stack for a sidebar item to a destination navgiation controller.
/// - Parameter dropFirst: Remove the first view controller from the item's navigation stack before transferring.
@ -307,7 +305,6 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
}
}
@available(iOS 14.0, *)
extension MainSplitViewController: MainSidebarViewControllerDelegate {
func sidebarRequestPresentCompose(_ sidebarViewController: MainSidebarViewController) {
presentCompose()
@ -322,7 +319,6 @@ extension MainSplitViewController: MainSidebarViewControllerDelegate {
}
}
@available(iOS 14.0, *)
fileprivate extension MainSidebarViewController.Item {
func createRootViewController(_ mastodonController: MastodonController) -> UIViewController? {
switch self {
@ -344,7 +340,6 @@ fileprivate extension MainSidebarViewController.Item {
}
}
@available(iOS 14.0, *)
extension MainSplitViewController: TuskerRootViewController {
@objc func presentCompose() {
let vc = ComposeHostingController(mastodonController: mastodonController)
@ -381,7 +376,6 @@ extension MainSplitViewController: TuskerRootViewController {
}
}
@available(iOS 14.0, *)
extension MainSplitViewController: BackgroundableViewController {
func sceneDidEnterBackground() {
if traitCollection.horizontalSizeClass == .compact {

2
Tusker/Screens/Preferences/AdvancedPrefsView.swift

@ -17,7 +17,7 @@ struct AdvancedPrefsView : View {
automationSection
cachingSection
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Advanced"))
}

2
Tusker/Screens/Preferences/AppearancePrefsView.swift

@ -33,7 +33,7 @@ struct AppearancePrefsView : View {
accountsSection
postsSection
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Appearance"))
}

2
Tusker/Screens/Preferences/BehaviorPrefsView.swift

@ -16,7 +16,7 @@ struct BehaviorPrefsView: View {
linksSection
contentWarningsSection
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Behavior"))
}

2
Tusker/Screens/Preferences/ComposingPrefsView.swift

@ -17,7 +17,7 @@ struct ComposingPrefsView: View {
composingSection
replyingSection
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Composing")
}

2
Tusker/Screens/Preferences/MediaPrefsView.swift

@ -15,7 +15,7 @@ struct MediaPrefsView: View {
List {
viewingSection
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Media")
}

13
Tusker/Screens/Preferences/PreferencesView.swift

@ -89,7 +89,7 @@ struct PreferencesView: View {
}
}
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Preferences"), displayMode: .inline)
// }
}
@ -99,17 +99,6 @@ struct PreferencesView: View {
}
}
extension View {
@ViewBuilder
func insetOrGroupedListStyle() -> some View {
if #available(iOS 14.0, *) {
self.listStyle(InsetGroupedListStyle())
} else {
self.listStyle(GroupedListStyle())
}
}
}
#if DEBUG
struct PreferencesView_Previews : PreviewProvider {
static var previews: some View {

2
Tusker/Screens/Preferences/SilentActionPrefs.swift

@ -14,7 +14,7 @@ struct SilentActionPrefs : View {
List(Array(preferences.silentActions.keys), id: \.self) { source in
SilentActionPermissionCell(source: source)
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
// .navigationBarTitle("Silent Action Permissions")
// see FB6838291
}

2
Tusker/Screens/Preferences/WellnessPrefsView.swift

@ -17,7 +17,7 @@ struct WellnessPrefsView: View {
notificationsMode
grayscaleImages
}
.insetOrGroupedListStyle()
.listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Digital Wellness"))
}

12
Tusker/Screens/Profile/ProfileViewController.swift

@ -69,13 +69,11 @@ class ProfileViewController: UIPageViewController {
view.backgroundColor = .systemBackground
let composeButton = UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(composeMentioning))
if #available(iOS 14.0, *) {
composeButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
UIAction(title: "Direct Message", image: UIImage(systemName: Status.Visibility.direct.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
self.composeDirectMentioning()
})
])
}
composeButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
UIAction(title: "Direct Message", image: UIImage(systemName: Status.Visibility.direct.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
self.composeDirectMentioning()
})
])
navigationItem.rightBarButtonItem = composeButton
headerView = ProfileHeaderView.create()

4
Tusker/Screens/Utilities/Previewing.swift

@ -54,14 +54,14 @@ extension MenuPreviewProvider {
}),
]
if accountID != mastodonController.account.id,
#available(iOS 14.0, *) {
if accountID != mastodonController.account.id {
actionsSection.append(UIDeferredMenuElement({ (elementHandler) in
guard let mastodonController = self.mastodonController else {
elementHandler([])
return
}
let request = Client.getRelationships(accounts: [account.id])
// talk about callback hell :/
mastodonController.run(request) { [weak self] (response) in
if let self = self,
case let .success(results, _) = response,

1
Tusker/Screens/Utilities/TrackpadScrollGestureRecognizer.swift

@ -8,7 +8,6 @@
import UIKit
@available(iOS 13.4, *)
class TrackpadScrollGestureRecognizer: UIPanGestureRecognizer {
override init(target: Any?, action: Selector?) {

32
Tusker/TuskerNavigationDelegate.swift

@ -125,41 +125,13 @@ extension TuskerNavigationDelegate {
guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") }
guard let url = status.url else { fatalError("Missing url for status \(statusID)") }
// on iOS 14+, all these custom actions are in the context menu and don't need to be in the share sheet
if #available(iOS 14.0, *) {
return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil)
} else {
var customActivites: [UIActivity] = [
OpenInSafariActivity(),
(status.bookmarked ?? false) ? UnbookmarkStatusActivity() : BookmarkStatusActivity(),
status.muted ? UnmuteConversationActivity() : MuteConversationActivity(),
]
if apiController.account != nil, status.account.id == apiController.account.id {
let pinned = status.pinned ?? false
customActivites.insert(pinned ? UnpinStatusActivity() : PinStatusActivity(), at: 1)
}
let activityController = UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: customActivites)
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(navigator: self, url: url)
return activityController
}
return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil)
}
private func moreOptions(forAccount accountID: String) -> UIActivityViewController {
guard let account = apiController.persistentContainer.account(for: accountID) else { fatalError("Missing cached account \(accountID)") }
if #available(iOS 14.0, *) {
return UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: nil)
} else {
let customActivities: [UIActivity] = [
OpenInSafariActivity(),
]
let activityController = UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: customActivities)
activityController.completionWithItemsHandler = OpenInSafariActivity.completionHandler(navigator: self, url: account.url)
return activityController
}
return UIActivityViewController(activityItems: [account.url, AccountActivityItemSource(account)], applicationActivities: nil)
}
func showMoreOptions(forStatus statusID: String, sourceView: UIView?) {

13
Tusker/Views/AccountDisplayNameLabel.swift

@ -24,18 +24,11 @@ struct AccountDisplayNameLabel<Account: AccountProtocol>: View {
}
var body: some View {
if #available(iOS 14.0, *) {
text
.font(.system(size: CGFloat(fontSize), weight: .semibold))
.onAppear(perform: self.loadEmojis)
} else {
text
.font(.system(size: CGFloat(fontSize), weight: .semibold))
}
text
.font(.system(size: CGFloat(fontSize), weight: .semibold))
.onAppear(perform: self.loadEmojis)
}
// embedding Image inside Text is only available on iOS 14
@available(iOS 14.0, *)
private func loadEmojis() {
let fullRange = NSRange(account.displayName.startIndex..., in: account.displayName)
let matches = emojiRegex.matches(in: account.displayName, options: [], range: fullRange)

23
Tusker/Views/ActivityIndicatorView.swift

@ -1,23 +0,0 @@
//
// ActivityIndicatorView.swift
// Tusker
//
// Created by Shadowfacts on 8/29/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import SwiftUI
struct ActivityIndicatorView: UIViewRepresentable {
typealias UIViewType = UIActivityIndicatorView
func makeUIView(context: Context) -> UIActivityIndicatorView {
let view = UIActivityIndicatorView()
view.startAnimating()
return view
}
func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
}
}

59
Tusker/Views/MaybeLazyStack.swift

@ -1,59 +0,0 @@
//
// MaybeLazyStack.swift
// Tusker
//
// Created by Shadowfacts on 10/10/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import SwiftUI
struct MaybeLazyVStack<Content: View>: View {
private let alignment: HorizontalAlignment
private let spacing: CGFloat?
private let content: Content
init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) {
self.alignment = alignment
self.spacing = spacing
self.content = content()
}
@ViewBuilder
var body: some View {
if #available(iOS 14.0, *) {
LazyVStack(alignment: alignment, spacing: spacing) {
content
}
} else {
VStack(alignment: alignment, spacing: spacing) {
content
}
}
}
}
struct MaybeLazyHStack<Content: View>: View {
private let alignment: VerticalAlignment
private let spacing: CGFloat?
private let content: Content
init(alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) {
self.alignment = alignment
self.spacing = spacing
self.content = content()
}
@ViewBuilder
var body: some View {
if #available(iOS 14.0, *) {
LazyHStack(alignment: alignment, spacing: spacing) {
content
}
} else {
HStack(alignment: alignment, spacing: spacing) {
content
}
}
}
}

19
Tusker/Views/Profile Header/ProfileHeaderView.swift

@ -74,10 +74,8 @@ class ProfileHeaderView: UIView {
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
moreButton.addInteraction(UIPointerInteraction(delegate: self))
if #available(iOS 14.0, *) {
moreButton.showsMenuAsPrimaryAction = true
moreButton.isContextMenuInteractionEnabled = true
}
moreButton.showsMenuAsPrimaryAction = true
moreButton.isContextMenuInteractionEnabled = true
}
private func createObservers() {
@ -110,9 +108,7 @@ class ProfileHeaderView: UIView {
updateImages(account: account)
if #available(iOS 14.0, *) {
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForProfile(accountID: accountID, sourceView: moreButton))
}
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForProfile(accountID: accountID, sourceView: moreButton))
noteTextView.navigationDelegate = delegate
noteTextView.setTextFromHtml(account.note)
@ -224,14 +220,6 @@ class ProfileHeaderView: UIView {
// MARK: Interaction
@IBAction func morePressed(_ sender: Any) {
guard #available(iOS 14.0, *) else {
// can't use TuskerNavigationDelegate method, because it doesn't know about the (un)follow activity
delegate?.profileHeader(self, showMoreOptionsFor: accountID, sourceView: moreButton)
return
}
}
@objc func avatarPressed() {
guard let account = mastodonController.persistentContainer.account(for: accountID) else {
return
@ -253,7 +241,6 @@ class ProfileHeaderView: UIView {
}
@available(iOS 13.4, *)
extension ProfileHeaderView: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
let preview = UITargetedPreview(view: moreButton)

8
Tusker/Views/Profile Header/ProfileHeaderView.xib

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18121" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17124"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18091"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -58,9 +57,6 @@
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="image" keyPath="image" value="ellipsis" catalog="system"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="morePressed:" destination="iN0-l3-epB" eventType="touchUpInside" id="Td6-rw-Xvr"/>
</connections>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="u4P-3i-gEq">
<rect key="frame" x="16" y="262" width="398" height="600"/>

10
Tusker/Views/Status/BaseStatusTableViewCell.swift

@ -96,9 +96,7 @@ class BaseStatusTableViewCell: UITableViewCell {
accessibilityElements = [displayNameLabel!, contentWarningLabel!, collapseButton!, contentTextView!, attachmentsView!]
attachmentsView.isAccessibilityElement = true
if #available(iOS 14.0, *) {
moreButton.showsMenuAsPrimaryAction = true
}
moreButton.showsMenuAsPrimaryAction = true
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
@ -213,10 +211,8 @@ class BaseStatusTableViewCell: UITableViewCell {
reblogButton.accessibilityLabel = NSLocalizedString("Reblog", comment: "reblog button accessibility label")
}
if #available(iOS 14.0, *) {
// keep menu in sync with changed states e.g. bookmarked, muted
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForStatus(status, sourceView: moreButton))
}
// keep menu in sync with changed states e.g. bookmarked, muted
moreButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: actionsForStatus(status, sourceView: moreButton))
}
func updateUI(account: AccountMO) {

25
Tusker/Views/Status/TimelineStatusTableViewCell.swift

@ -266,28 +266,17 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
}
func trailingSwipeActionsConfiguration() -> UISwipeActionsConfiguration? {
let moreTitle: String
let moreImage: UIImage
// on iOS 14+, more actions are in the context menu so display this as 'Share'
if #available(iOS 14.0, *) {
moreTitle = "Share"
// Bold to more closely match the other action symbols
let config = UIImage.SymbolConfiguration(weight: .bold)
moreImage = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
} else {
moreTitle = "More"
moreImage = UIImage(systemName: "ellipsis.circle.fill")!
}
let more = UIContextualAction(style: .normal, title: moreTitle) { (action, view, completion) in
let share = UIContextualAction(style: .normal, title: "Share") { (action, view, completion) in
completion(true)
self.delegate?.showMoreOptions(forStatus: self.statusID, sourceView: self)
}
more.image = moreImage
more.backgroundColor = .lightGray
// Bold to more closely match the other action symbols
let config = UIImage.SymbolConfiguration(weight: .bold)
share.image = UIImage(systemName: "square.and.arrow.up")!.applyingSymbolConfiguration(config)!
share.backgroundColor = .lightGray
guard mastodonController.loggedIn else {
return UISwipeActionsConfiguration(actions: [more])
return UISwipeActionsConfiguration(actions: [share])
}
let reply = UIContextualAction(style: .normal, title: "Reply") { (action, view, completion) in
@ -297,7 +286,7 @@ extension TimelineStatusTableViewCell: TableViewSwipeActionProvider {
reply.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
reply.backgroundColor = tintColor
return UISwipeActionsConfiguration(actions: [reply, more])
return UISwipeActionsConfiguration(actions: [reply, share])
}
}

9
TuskerUITests/ComposeTests.swift

@ -82,13 +82,8 @@ class ComposeTests: TuskerUITests {
XCTAssertTrue(app.staticTexts["500 characters remaining"].exists)
cwField.tap()
let str: String
// on iOS 14, the first type text is typed into a SwiftUI TextField, the 2nd character is inexplicably dropped >.<
if #available(iOS 14.0, *) {
str = "fooo"
} else {
str = "foo"
}
// on iOS 14, the first type text is typed into a SwiftUI TextField by a test has the 1st character is inexplicably dropped >.<
let str = "fooo"
cwField.typeText(str)
XCTAssertTrue(app.staticTexts["497 characters remaining"].exists)
// CharacterCounter is not used => '@' is counted

54
TuskerUITests/MyProfileTests.swift

@ -36,38 +36,28 @@ class MyProfileTests: TuskerUITests {
XCTAssertTrue(app.buttons["More Actions"].exists)
app.buttons["More Actions"].tap()
if #available(iOS 14.0, *) {
XCTAssertTrue(app.buttons["Open in Safari"].exists)
XCTAssertTrue(app.buttons["Share..."].exists)
XCTAssertTrue(app.buttons["Send Message"].exists)
app.buttons["Open in Safari"].tap()
XCTAssertTrue(app.otherElements["TopBrowserBar"].exists)
app.buttons["Done"].tap()
XCTAssertFalse(app.otherElements["TopBrowserBar"].exists)
app.buttons["More Actions"].tap()
app.buttons["Share..."].tap()
let activityListView = app.otherElements["ActivityListView"]
XCTAssertTrue(activityListView.waitForExistence(timeout: 0.2))
activityListView.buttons["Close"].tap()
XCTAssertFalse(activityListView.exists)
app.buttons["More Actions"].tap()
app.buttons["Send Message"].tap()
XCTAssertTrue(app.staticTexts["Compose"].exists)
XCTAssertTrue(app.buttons["Cancel"].exists)
XCTAssertTrue(app.buttons["Post"].exists)
app.buttons["Cancel"].tap()
} else {
// first tap doesn't trigger share sheet for some reason
app.buttons["More Actions"].tap()
let activityListView = app.otherElements["ActivityListView"]
XCTAssertTrue(activityListView.waitForExistence(timeout: 0.2))
activityListView.buttons["Close"].tap()
XCTAssertFalse(activityListView.exists)
// can't test individual actions :/
}
XCTAssertTrue(app.buttons["Open in Safari"].exists)
XCTAssertTrue(app.buttons["Share..."].exists)
XCTAssertTrue(app.buttons["Send Message"].exists)
app.buttons["Open in Safari"].tap()
XCTAssertTrue(app.otherElements["TopBrowserBar"].exists)
app.buttons["Done"].tap()
XCTAssertFalse(app.otherElements["TopBrowserBar"].exists)
app.buttons["More Actions"].tap()
app.buttons["Share..."].tap()
let activityListView = app.otherElements["ActivityListView"]
XCTAssertTrue(activityListView.waitForExistence(timeout: 0.2))
activityListView.buttons["Close"].tap()
XCTAssertFalse(activityListView.exists)
app.buttons["More Actions"].tap()
app.buttons["Send Message"].tap()
XCTAssertTrue(app.staticTexts["Compose"].exists)
XCTAssertTrue(app.buttons["Cancel"].exists)
XCTAssertTrue(app.buttons["Post"].exists)
app.buttons["Cancel"].tap()