Compare commits

..

5 Commits

Author SHA1 Message Date
Shadowfacts 157c8629a9 Add underline links preference
Closes #397
2023-10-24 16:02:03 -04:00
Shadowfacts bde21fbc6c Fix crash due to prematurely pruned statuses being fetched
If the app hasn't launched in long enough, we may be displaying old statuses as a result of state restoration. If the user leaves the app, those statuses can't get pruned, because the user may return. We need to make sure the lastFetchedAt date is current, since awakeFromFetch won't be called until the object is faulted in (which wasn't happening immediately during state restoration).
2023-10-24 15:50:58 -04:00
Shadowfacts 74820e8922 Underline links when button shapes accessibility setting is on 2023-10-24 15:50:58 -04:00
Shadowfacts f7a9075b77 Fix timeline jump button having background when button shapes accessibility setting is on 2023-10-24 15:50:58 -04:00
Shadowfacts 4af56e48bf Clean up TimelineLikeCollectionViewController.apply(_:animatingDifferences:) 2023-10-24 14:56:39 -04:00
8 changed files with 55 additions and 7 deletions

View File

@ -61,6 +61,7 @@ public final class Preferences: Codable, ObservableObject {
self.leadingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .leadingStatusSwipeActions) ?? leadingStatusSwipeActions
self.trailingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .trailingStatusSwipeActions) ?? trailingStatusSwipeActions
self.widescreenNavigationMode = try container.decodeIfPresent(WidescreenNavigationMode.self, forKey: .widescreenNavigationMode) ?? Self.defaultWidescreenNavigationMode
self.underlineTextLinks = try container.decodeIfPresent(Bool.self, forKey: .underlineTextLinks) ?? false
self.defaultPostVisibility = try container.decode(Visibility.self, forKey: .defaultPostVisibility)
self.defaultReplyVisibility = try container.decodeIfPresent(ReplyVisibility.self, forKey: .defaultReplyVisibility) ?? .sameAsPost
@ -121,6 +122,7 @@ public final class Preferences: Codable, ObservableObject {
try container.encode(leadingStatusSwipeActions, forKey: .leadingStatusSwipeActions)
try container.encode(trailingStatusSwipeActions, forKey: .trailingStatusSwipeActions)
try container.encode(widescreenNavigationMode, forKey: .widescreenNavigationMode)
try container.encode(underlineTextLinks, forKey: .underlineTextLinks)
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
try container.encode(defaultReplyVisibility, forKey: .defaultReplyVisibility)
@ -175,6 +177,7 @@ public final class Preferences: Codable, ObservableObject {
@Published public var trailingStatusSwipeActions: [StatusSwipeAction] = [.reply, .share]
private static var defaultWidescreenNavigationMode = WidescreenNavigationMode.splitScreen
@Published public var widescreenNavigationMode = Preferences.defaultWidescreenNavigationMode
@Published public var underlineTextLinks = false
// MARK: Composing
@Published public var defaultPostVisibility = Visibility.public
@ -245,6 +248,7 @@ public final class Preferences: Codable, ObservableObject {
case leadingStatusSwipeActions
case trailingStatusSwipeActions
case widescreenNavigationMode
case underlineTextLinks
case defaultPostVisibility
case defaultReplyVisibility

View File

@ -59,10 +59,15 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
super.awakeFromFetch()
managedObjectContext?.perform {
self.lastFetchedAt = Date()
self.touch()
}
}
/// Update the `lastFetchedAt` date so this object isn't pruned early.
func touch() {
lastFetchedAt = Date()
}
}
extension AccountMO {

View File

@ -89,10 +89,15 @@ public final class StatusMO: NSManagedObject, StatusProtocol {
super.awakeFromFetch()
managedObjectContext?.perform {
self.lastFetchedAt = Date()
self.touch()
}
}
/// Update the `lastFetchedAt` date so this object isn't pruned early.
func touch() {
lastFetchedAt = Date()
}
}
extension StatusMO {

View File

@ -124,6 +124,9 @@ struct AppearancePrefsView : View {
Toggle(isOn: $preferences.showLinkPreviews) {
Text("Show Link Previews")
}
Toggle(isOn: $preferences.underlineTextLinks) {
Text("Underline Links")
}
NavigationLink("Leading Swipe Actions") {
SwipeActionsPrefsView(selection: $preferences.leadingStatusSwipeActions)
.edgesIgnoringSafeArea(.all)

View File

@ -20,6 +20,8 @@ class TimelineJumpButton: UIView {
var config = UIButton.Configuration.plain()
config.image = UIImage(systemName: "arrow.up")
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)
}()

View File

@ -440,7 +440,15 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
private func loadStatusesToRestore(position: TimelinePosition) async -> Bool {
let originalPositionStatusIDs = position.statusIDs
let unloaded = position.statusIDs.filter({ mastodonController.persistentContainer.status(for: $0) == nil })
var unloaded = [String]()
for id in position.statusIDs {
if let status = mastodonController.persistentContainer.status(for: id) {
// touch the status so that, even if it's old, it doesn't get pruned when we go into the background
status.touch()
} else {
unloaded.append(id)
}
}
guard !unloaded.isEmpty else {
return true
}

View File

@ -211,11 +211,9 @@ extension TimelineLikeCollectionViewController {
extension TimelineLikeCollectionViewController {
// apply(_:animatingDifferences:) is marked as nonisolated, so just awaiting it doesn't dispatch to the main thread, unlike other async @MainActor methods
// but we always want to update the data source on the main thread for consistency, so this method does that
@MainActor
func apply(_ snapshot: NSDiffableDataSourceSnapshot<Section, Item>, animatingDifferences: Bool) async {
let task = Task { @MainActor in
self.dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
await task.value
await self.dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
}
@MainActor

View File

@ -12,6 +12,7 @@ import Pachyderm
import SafariServices
import WebURL
import WebURLFoundationExtras
import Combine
private let emojiRegex = try! NSRegularExpression(pattern: ":(\\w+):", options: [])
private let dataDetectorsScheme = "x-apple-data-detectors"
@ -52,6 +53,8 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
// The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing.
private weak var currentTargetedPreview: UITargetedPreview?
private var underlineTextLinksCancellable: AnyCancellable?
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
@ -78,10 +81,30 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
linkTextAttributes = [
.foregroundColor: UIColor.tintColor
]
updateLinkUnderlineStyle()
// the text view's builtin link interaction code is tied to isSelectable, so we need to use our own tap recognizer
let recognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped(_:)))
addGestureRecognizer(recognizer)
NotificationCenter.default.addObserver(self, selector: #selector(_updateLinkUnderlineStyle), name: UIAccessibility.buttonShapesEnabledStatusDidChangeNotification, object: nil)
underlineTextLinksCancellable =
Preferences.shared.$underlineTextLinks
.sink { [unowned self] in
self.updateLinkUnderlineStyle(preference: $0)
}
}
@objc private func _updateLinkUnderlineStyle() {
updateLinkUnderlineStyle()
}
private func updateLinkUnderlineStyle(preference: Bool = Preferences.shared.underlineTextLinks) {
if UIAccessibility.buttonShapesEnabled || preference {
linkTextAttributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
} else {
linkTextAttributes.removeValue(forKey: .underlineStyle)
}
}
// MARK: - Emojis