Compare commits

..

3 Commits

5 changed files with 74 additions and 58 deletions

View File

@ -16,6 +16,12 @@ extension UIBezierPath {
/// and draws a line around the outer borders of the combined shape. /// and draws a line around the outer borders of the combined shape.
convenience init(wrappingAround rects: [CGRect]) { convenience init(wrappingAround rects: [CGRect]) {
precondition(rects.count > 0) precondition(rects.count > 0)
if rects.count == 1 {
self.init(rect: rects.first!)
return
}
let rects = rects.sorted { $0.minY < $1.minY } let rects = rects.sorted { $0.minY < $1.minY }
self.init() self.init()

View File

@ -30,7 +30,20 @@ class LocalData: ObservableObject {
] ]
} }
} else { } else {
defaults = UserDefaults() defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
tryMigrateOldDefaults()
}
}
// TODO: remove me before public beta
private func tryMigrateOldDefaults() {
let old = UserDefaults()
if let accounts = old.array(forKey: accountsKey) as? [[String: String]],
let mostRecentAccount = old.string(forKey: mostRecentAccountKey) {
defaults.setValue(accounts, forKey: accountsKey)
defaults.setValue(mostRecentAccount, forKey: mostRecentAccountKey)
old.removeObject(forKey: accountsKey)
old.removeObject(forKey: mostRecentAccountKey)
} }
} }

View File

@ -7,7 +7,6 @@
// //
import UIKit import UIKit
import SwiftUI
class MyProfileTableViewController: ProfileTableViewController { class MyProfileTableViewController: ProfileTableViewController {
@ -32,14 +31,18 @@ class MyProfileTableViewController: ProfileTableViewController {
} }
}) })
} }
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preferences", style: .plain, target: self, action: #selector(preferencesPressed))
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preferences", style: .plain, target: self, action: #selector(preferencesPressed))
}
@objc func preferencesPressed() { @objc func preferencesPressed() {
present(PreferencesNavigationController(mastodonController: mastodonController), animated: true) present(PreferencesNavigationController(mastodonController: mastodonController), animated: true)
} }

View File

@ -4,6 +4,10 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.space.vaccor.Tusker</string>
</array>
<key>com.apple.security.device.audio-input</key> <key>com.apple.security.device.audio-input</key>
<true/> <true/>
<key>com.apple.security.device.camera</key> <key>com.apple.security.device.camera</key>

View File

@ -22,6 +22,11 @@ class ContentTextView: LinkTextView {
var defaultFont: UIFont = .systemFont(ofSize: 17) var defaultFont: UIFont = .systemFont(ofSize: 17)
var defaultColor: UIColor = .label var defaultColor: UIColor = .label
// The link range currently being previewed
private var currentPreviewedLinkRange: NSRange?
// The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing.
private weak var currentTargetedPreview: UITargetedPreview?
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
@ -256,11 +261,8 @@ extension ContentTextView: MenuPreviewProvider {
extension ContentTextView: UIContextMenuInteractionDelegate { extension ContentTextView: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
if let (link, range) = getLinkAtPoint(location) { if let (link, range) = getLinkAtPoint(location) {
// Determine the line rects that the link takes up in the coordinate space of this view // Store the previewed link range for use in the previewForHighlighting method
var rects = [CGRect]() currentPreviewedLinkRange = range
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
rects.append(rect)
}
let preview: UIContextMenuContentPreviewProvider = { let preview: UIContextMenuContentPreviewProvider = {
self.getViewController(forLink: link, inRange: range) self.getViewController(forLink: link, inRange: range)
@ -278,66 +280,53 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions) return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
} }
// Use a custom UIContentMenuConfiguration subclass to pass the text line rect information return UIContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
// to the `contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)` method.
let configuration = ContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
configuration.textLineRects = rects
return configuration
} else { } else {
currentPreviewedLinkRange = nil
return nil return nil
} }
} }
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
// If there isn't custom text line rect data, use the default system-generated preview. // If there isn't a link range, use the default system-generated preview.
guard let config = configuration as? ContextMenuConfiguration, guard let range = currentPreviewedLinkRange else {
let rects = config.textLineRects, return nil
rects.count > 0 else { }
currentPreviewedLinkRange = nil
// Determine the line rects that the link takes up in the coordinate space of this view.
var rects = [CGRect]()
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
rects.append(rect)
}
// Try to create a snapshot view of this view to disply as the preview.
// If a snapshot view cannot be created, we bail and use the system-provided preview.
guard let snapshot = self.snapshotView(afterScreenUpdates: false) else {
return nil return nil
} }
// Mask the snapshot layer to only show the text of the link, and nothing else.
// By default, the system-applied mask is too wide and other content may seep in.
let path = UIBezierPath(wrappingAround: rects)
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
snapshot.layer.mask = maskLayer
// The preview parameters describe how the preview view is shown inside the preview.
let parameters = UIPreviewParameters(textLineRects: rects as [NSValue])
// Calculate the smallest rect enclosing all of the text line rects, in the coordinate space of this view. // Calculate the smallest rect enclosing all of the text line rects, in the coordinate space of this view.
var minX: CGFloat = .greatestFiniteMagnitude, maxX: CGFloat = .leastNonzeroMagnitude, minY: CGFloat = .greatestFiniteMagnitude, maxY: CGFloat = .leastNonzeroMagnitude var minX: CGFloat = .greatestFiniteMagnitude, maxX: CGFloat = -.greatestFiniteMagnitude, minY: CGFloat = .greatestFiniteMagnitude, maxY: CGFloat = -.greatestFiniteMagnitude
for rect in rects { for rect in rects {
minX = min(rect.minX, minX) minX = min(rect.minX, minX)
maxX = max(rect.maxX, maxX) maxX = max(rect.maxX, maxX)
minY = min(rect.minY, minY) minY = min(rect.minY, minY)
maxY = max(rect.maxY, maxY) maxY = max(rect.maxY, maxY)
} }
let rectEnclosingTextLineRects = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
// Try to create a snapshot view of this view that only shows the minimum
// rectangle necessary to fully display the link text (reduces the likelihood that
// other text will be displayed alongside it).
// If a snapshot view cannot be created, we bail and use the system-provided preview.
guard let snapshot = self.resizableSnapshotView(from: rectEnclosingTextLineRects, afterScreenUpdates: false, withCapInsets: .zero) else {
return nil
}
// Convert the textLineRects from the context menu configuration to be in the
// coordinate space of the snapshot view. The snapshot view is created from
// rectEnclosingTextLineRects, which means that, while its size is the same as the
// enclosing rect, its coordinate space is relative to this text views by rectEnclosingTextLineRects.origin.
// Since the text line rects passed to UIPreviewParameters need to be in the coordinate space of
// the preview view, we subtract the origin position from each rect to convert to the snapshot view's
// coordinate space.
let rectsInCoordinateSpaceOfEnclosingRect = rects.map {
$0.offsetBy(dx: -rectEnclosingTextLineRects.minX, dy: -rectEnclosingTextLineRects.minY)
}
// The preview parameters describe how the preview view is shown inside the prev.
let parameters = UIPreviewParameters(textLineRects: rectsInCoordinateSpaceOfEnclosingRect as [NSValue])
// Mask the snapshot layer to only show the text of the link, and nothing else.
// By default, the system-applied mask is too wide and other content may seep in.
let path = UIBezierPath(wrappingAround: rectsInCoordinateSpaceOfEnclosingRect)
let maskLayer = CAShapeLayer()
maskLayer.path = path.cgPath
snapshot.layer.mask = maskLayer
// The center point of the the minimum enclosing rect in our coordinate space is the point where the // The center point of the the minimum enclosing rect in our coordinate space is the point where the
// center of the preview should be, since that's also in this view's coordinate space. // center of the preview should be, since that's also in this view's coordinate space.
let rectsCenter = CGPoint(x: rectEnclosingTextLineRects.midX, y: rectEnclosingTextLineRects.midY) let rectsCenter = CGPoint(x: (minX + maxX) / 2, y: (minY + maxY) / 2)
// The preview target describes how the preview is positioned. // The preview target describes how the preview is positioned.
let target = UIPreviewTarget(container: self, center: rectsCenter) let target = UIPreviewTarget(container: self, center: rectsCenter)
@ -347,7 +336,14 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
let snapshotContainer = UIView(frame: snapshot.bounds) let snapshotContainer = UIView(frame: snapshot.bounds)
snapshotContainer.addSubview(snapshot) snapshotContainer.addSubview(snapshot)
return UITargetedPreview(view: snapshotContainer, parameters: parameters, target: target) let preview = UITargetedPreview(view: snapshotContainer, parameters: parameters, target: target)
currentTargetedPreview = preview
return preview
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
// Use the same preview for dismissing as was used for highlighting, so that the link animates back to the original position.
return currentTargetedPreview
} }
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
@ -358,10 +354,4 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
} }
} }
} }
/// Used to pass text line rect data between `contextMenuInteraction(_:configurationForMenuAtLocation:)` and `contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)`
fileprivate class ContextMenuConfiguration: UIContextMenuConfiguration {
/// The line rects of the source of this context menu configuration in the coordinate space of the preview target view.
var textLineRects: [CGRect]?
}
} }