Compare commits
7 Commits
13d649bace
...
a79b3cfd70
Author | SHA1 | Date |
---|---|---|
Shadowfacts | a79b3cfd70 | |
Shadowfacts | 9a35f96c75 | |
Shadowfacts | 60767c6a7e | |
Shadowfacts | 57668886b2 | |
Shadowfacts | ffb5c76f7c | |
Shadowfacts | 00e8dd6345 | |
Shadowfacts | 7904462920 |
|
@ -257,8 +257,8 @@ private func setInstanceBreadcrumb(instance: Instance, nodeInfo: NodeInfo?) {
|
|||
]
|
||||
if let nodeInfo {
|
||||
crumb.data!["nodeInfo"] = [
|
||||
"version": nodeInfo.version,
|
||||
"software": nodeInfo.software,
|
||||
"software": nodeInfo.software.name,
|
||||
"version": nodeInfo.software.version,
|
||||
]
|
||||
}
|
||||
SentrySDK.addBreadcrumb(crumb: crumb)
|
||||
|
|
|
@ -52,7 +52,11 @@ class Preferences: Codable, ObservableObject {
|
|||
self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
|
||||
self.useTwitterKeyboard = try container.decodeIfPresent(Bool.self, forKey: .useTwitterKeyboard) ?? false
|
||||
|
||||
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
|
||||
if let blurAllMedia = try? container.decodeIfPresent(Bool.self, forKey: .blurAllMedia) {
|
||||
self.attachmentBlurMode = blurAllMedia ? .always : .useStatusSetting
|
||||
} else {
|
||||
self.attachmentBlurMode = try container.decode(AttachmentBlurMode.self, forKey: .attachmentBlurMode)
|
||||
}
|
||||
self.blurMediaBehindContentWarning = try container.decodeIfPresent(Bool.self, forKey: .blurMediaBehindContentWarning) ?? true
|
||||
self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
|
||||
|
||||
|
@ -95,7 +99,7 @@ class Preferences: Codable, ObservableObject {
|
|||
try container.encode(mentionReblogger, forKey: .mentionReblogger)
|
||||
try container.encode(useTwitterKeyboard, forKey: .useTwitterKeyboard)
|
||||
|
||||
try container.encode(blurAllMedia, forKey: .blurAllMedia)
|
||||
try container.encode(attachmentBlurMode, forKey: .attachmentBlurMode)
|
||||
try container.encode(blurMediaBehindContentWarning, forKey: .blurMediaBehindContentWarning)
|
||||
try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
|
||||
|
||||
|
@ -140,10 +144,12 @@ class Preferences: Codable, ObservableObject {
|
|||
@Published var useTwitterKeyboard = false
|
||||
|
||||
// MARK: Media
|
||||
@Published var blurAllMedia = false {
|
||||
@Published var attachmentBlurMode = AttachmentBlurMode.useStatusSetting {
|
||||
didSet {
|
||||
if blurAllMedia {
|
||||
if attachmentBlurMode == .always {
|
||||
blurMediaBehindContentWarning = true
|
||||
} else if attachmentBlurMode == .never {
|
||||
blurMediaBehindContentWarning = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,7 +197,8 @@ class Preferences: Codable, ObservableObject {
|
|||
case mentionReblogger
|
||||
case useTwitterKeyboard
|
||||
|
||||
case blurAllMedia
|
||||
case blurAllMedia // only used for migration
|
||||
case attachmentBlurMode
|
||||
case blurMediaBehindContentWarning
|
||||
case automaticallyPlayGifs
|
||||
|
||||
|
@ -254,4 +261,23 @@ extension Preferences {
|
|||
}
|
||||
}
|
||||
|
||||
extension Preferences {
|
||||
enum AttachmentBlurMode: Codable, Hashable, CaseIterable {
|
||||
case useStatusSetting
|
||||
case always
|
||||
case never
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .useStatusSetting:
|
||||
return "Default"
|
||||
case .always:
|
||||
return "Always"
|
||||
case .never:
|
||||
return "Never"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIUserInterfaceStyle: Codable {}
|
||||
|
|
|
@ -137,6 +137,11 @@ class GalleryViewController: UIPageViewController, UIPageViewControllerDataSourc
|
|||
}
|
||||
}
|
||||
|
||||
override func accessibilityPerformEscape() -> Bool {
|
||||
dismiss(animated: true)
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Page View Controller Data Source
|
||||
|
||||
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
|
||||
|
|
|
@ -123,4 +123,23 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Accessibility
|
||||
|
||||
override var isAccessibilityElement: Bool {
|
||||
get { true }
|
||||
set {}
|
||||
}
|
||||
|
||||
override var accessibilityAttributedLabel: NSAttributedString? {
|
||||
get {
|
||||
guard let account else {
|
||||
return nil
|
||||
}
|
||||
let s = NSMutableAttributedString(string: "\(account.displayName), ")
|
||||
s.append(noteTextView.attributedText)
|
||||
return s
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,8 +34,9 @@ class ProfileDirectoryViewController: UIViewController {
|
|||
|
||||
title = NSLocalizedString("Profile Directory", comment: "profile directory title")
|
||||
|
||||
// todo: it would be nice if there were a better "filter" icon
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "scope"), menu: nil)
|
||||
let filterItem = UIBarButtonItem(image: UIImage(systemName: "line.3.horizontal.decrease.circle"), menu: nil)
|
||||
filterItem.accessibilityLabel = "Filter"
|
||||
navigationItem.rightBarButtonItem = filterItem
|
||||
updateFilterMenu()
|
||||
|
||||
let layout = UICollectionViewCompositionalLayout(sectionProvider: { (sectionIndex, layoutEnvironment) in
|
||||
|
|
|
@ -21,6 +21,7 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
@IBOutlet weak var descriptionLabel: UILabel!
|
||||
|
||||
private var shareContainer: UIView!
|
||||
private var closeContainer: UIView!
|
||||
private var shareImage: UIImageView!
|
||||
private var shareButtonTopConstraint: NSLayoutConstraint!
|
||||
private var shareButtonLeadingConstraint: NSLayoutConstraint!
|
||||
|
@ -116,6 +117,12 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
view.addGestureRecognizer(doubleTap)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
|
||||
accessibilityElements = [
|
||||
topControlsView!,
|
||||
contentView,
|
||||
bottomControlsView!,
|
||||
]
|
||||
}
|
||||
|
||||
private func setupContentView() {
|
||||
|
@ -135,6 +142,9 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
|
||||
private func setupControls() {
|
||||
shareContainer = UIView()
|
||||
shareContainer.isAccessibilityElement = true
|
||||
shareContainer.accessibilityTraits = .button
|
||||
shareContainer.accessibilityLabel = "Share"
|
||||
shareContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(sharePressed)))
|
||||
shareContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
topControlsView.addSubview(shareContainer)
|
||||
|
@ -161,7 +171,10 @@ class LargeImageViewController: UIViewController, UIScrollViewDelegate, LargeIma
|
|||
shareImage.heightAnchor.constraint(equalToConstant: 24),
|
||||
])
|
||||
|
||||
let closeContainer = UIView()
|
||||
closeContainer = UIView()
|
||||
closeContainer.isAccessibilityElement = true
|
||||
closeContainer.accessibilityTraits = .button
|
||||
closeContainer.accessibilityLabel = "Close"
|
||||
closeContainer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(closeButtonPressed)))
|
||||
closeContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
topControlsView.addSubview(closeContainer)
|
||||
|
|
|
@ -21,14 +21,18 @@ struct MediaPrefsView: View {
|
|||
|
||||
var viewingSection: some View {
|
||||
Section(header: Text("Viewing")) {
|
||||
Toggle(isOn: $preferences.blurAllMedia) {
|
||||
Text("Blur All Media")
|
||||
Picker(selection: $preferences.attachmentBlurMode) {
|
||||
ForEach(Preferences.AttachmentBlurMode.allCases, id: \.self) { mode in
|
||||
Text(mode.displayName).tag(mode)
|
||||
}
|
||||
} label: {
|
||||
Text("Blur Media")
|
||||
}
|
||||
|
||||
Toggle(isOn: $preferences.blurMediaBehindContentWarning) {
|
||||
Text("Blur Media Behind Content Warning")
|
||||
}
|
||||
.disabled(preferences.blurAllMedia)
|
||||
.disabled(preferences.attachmentBlurMode != .useStatusSetting)
|
||||
|
||||
Toggle(isOn: $preferences.automaticallyPlayGifs) {
|
||||
Text("Automatically Play GIFs")
|
||||
|
|
|
@ -345,7 +345,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
|
||||
private func removeTimelineDescriptionCell() {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteSections([.header])
|
||||
snapshot.deleteItems([.publicTimelineDescription])
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
isShowingTimelineDescription = false
|
||||
}
|
||||
|
|
|
@ -173,15 +173,17 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
|
||||
// MARK: - Navigation
|
||||
|
||||
func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController {
|
||||
func getViewController(forLink url: URL, inRange range: NSRange) -> UIViewController? {
|
||||
let text = (self.text as NSString).substring(with: range)
|
||||
|
||||
if let mention = getMention(for: url, text: text) {
|
||||
return ProfileViewController(accountID: mention.id, mastodonController: mastodonController!)
|
||||
} else if let tag = getHashtag(for: url, text: text) {
|
||||
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController!)
|
||||
} else {
|
||||
} else if url.scheme == "https" || url.scheme == "http" {
|
||||
return SFSafariViewController(url: url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -231,16 +231,17 @@ class BaseStatusTableViewCell: UITableViewCell {
|
|||
|
||||
func updateUIForPreferences(account: AccountMO, status: StatusMO) {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
if Preferences.shared.blurAllMedia {
|
||||
attachmentsView.contentHidden = true
|
||||
} else if status.sensitive {
|
||||
if !Preferences.shared.blurMediaBehindContentWarning && !status.spoilerText.isEmpty {
|
||||
attachmentsView.contentHidden = false
|
||||
} else {
|
||||
attachmentsView.contentHidden = true
|
||||
}
|
||||
} else {
|
||||
switch Preferences.shared.attachmentBlurMode {
|
||||
case .never:
|
||||
attachmentsView.contentHidden = false
|
||||
case .always:
|
||||
attachmentsView.contentHidden = true
|
||||
default:
|
||||
if status.sensitive {
|
||||
attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
||||
} else {
|
||||
attachmentsView.contentHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
updateStatusIconsForPreferences(status)
|
||||
|
|
|
@ -148,16 +148,17 @@ extension StatusCollectionViewCell {
|
|||
|
||||
func baseUpdateUIForPreferences(status: StatusMO) {
|
||||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * Self.avatarImageViewSize
|
||||
if Preferences.shared.blurAllMedia {
|
||||
contentContainer.attachmentsView.contentHidden = true
|
||||
} else if status.sensitive {
|
||||
if !Preferences.shared.blurMediaBehindContentWarning && !status.spoilerText.isEmpty {
|
||||
contentContainer.attachmentsView.contentHidden = false
|
||||
} else {
|
||||
contentContainer.attachmentsView.contentHidden = true
|
||||
}
|
||||
} else {
|
||||
switch Preferences.shared.attachmentBlurMode {
|
||||
case .never:
|
||||
contentContainer.attachmentsView.contentHidden = false
|
||||
case .always:
|
||||
contentContainer.attachmentsView.contentHidden = true
|
||||
default:
|
||||
if status.sensitive {
|
||||
contentContainer.attachmentsView.contentHidden = status.spoilerText.isEmpty || Preferences.shared.blurMediaBehindContentWarning
|
||||
} else {
|
||||
contentContainer.attachmentsView.contentHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
let reblogButtonImage: UIImage
|
||||
|
|
|
@ -369,7 +369,13 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||
return nil
|
||||
}
|
||||
var str = AttributedString("\(status.account.displayOrUserName), ")
|
||||
var str: AttributedString = ""
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
str += AttributedString("Reblogged by \(reblogger.displayOrUserName): ")
|
||||
}
|
||||
str += AttributedString(status.account.displayOrUserName)
|
||||
str += ", "
|
||||
if statusState.collapsed ?? false {
|
||||
if !status.spoilerText.isEmpty {
|
||||
str += AttributedString(status.spoilerText)
|
||||
|
@ -378,15 +384,24 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
str += "collapsed"
|
||||
} else {
|
||||
str += AttributedString(contentTextView.attributedText)
|
||||
|
||||
if status.attachments.count > 0 {
|
||||
if status.attachments.count == 1 {
|
||||
let attachment = status.attachments[0]
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment: \(desc)")
|
||||
} else {
|
||||
for (index, attachment) in status.attachments.enumerated() {
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment \(index + 1): \(desc)")
|
||||
}
|
||||
}
|
||||
}
|
||||
if status.poll != nil {
|
||||
str += ", poll"
|
||||
}
|
||||
}
|
||||
|
||||
if status.attachments.count > 0 {
|
||||
// TODO: localize me
|
||||
str += AttributedString(", \(status.attachments.count) attachment\(status.attachments.count > 1 ? "s" : "")")
|
||||
}
|
||||
if status.poll != nil {
|
||||
str += ", poll"
|
||||
}
|
||||
str += AttributedString(", \(status.createdAt.formatted(.relative(presentation: .numeric)))")
|
||||
if status.visibility < .unlisted {
|
||||
str += AttributedString(", \(status.visibility.displayName)")
|
||||
|
@ -394,10 +409,6 @@ class TimelineStatusCollectionViewCell: UICollectionViewListCell, StatusCollecti
|
|||
if status.localOnly {
|
||||
str += ", local"
|
||||
}
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
str += AttributedString(", reblogged by \(reblogger.displayOrUserName)")
|
||||
}
|
||||
return NSAttributedString(str)
|
||||
}
|
||||
set {}
|
||||
|
|
|
@ -254,7 +254,13 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
guard let status = mastodonController.persistentContainer.status(for: statusID) else {
|
||||
return nil
|
||||
}
|
||||
var str = AttributedString("\(status.account.displayOrUserName), ")
|
||||
var str: AttributedString = ""
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
str += AttributedString("Reblogged by \(reblogger.displayOrUserName): ")
|
||||
}
|
||||
str += AttributedString(status.account.displayOrUserName)
|
||||
str += ", "
|
||||
if statusState.collapsed ?? false {
|
||||
if !status.spoilerText.isEmpty {
|
||||
str += AttributedString(status.spoilerText)
|
||||
|
@ -263,15 +269,24 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
str += "collapsed"
|
||||
} else {
|
||||
str += AttributedString(contentTextView.attributedText)
|
||||
|
||||
if status.attachments.count > 0 {
|
||||
if status.attachments.count == 1 {
|
||||
let attachment = status.attachments[0]
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment: \(desc)")
|
||||
} else {
|
||||
for (index, attachment) in status.attachments.enumerated() {
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment \(index + 1): \(desc)")
|
||||
}
|
||||
}
|
||||
}
|
||||
if status.poll != nil {
|
||||
str += ", poll"
|
||||
}
|
||||
}
|
||||
|
||||
if status.attachments.count > 0 {
|
||||
// TODO: localize me
|
||||
str += AttributedString(", \(status.attachments.count) attachment\(status.attachments.count > 1 ? "s" : "")")
|
||||
}
|
||||
if status.poll != nil {
|
||||
str += ", poll"
|
||||
}
|
||||
str += AttributedString(", \(status.createdAt.formatted(.relative(presentation: .numeric)))")
|
||||
if status.visibility < .unlisted {
|
||||
str += AttributedString(", \(status.visibility.displayName)")
|
||||
|
@ -279,10 +294,6 @@ class TimelineStatusTableViewCell: BaseStatusTableViewCell {
|
|||
if status.localOnly {
|
||||
str += ", local"
|
||||
}
|
||||
if let rebloggerID,
|
||||
let reblogger = mastodonController.persistentContainer.account(for: rebloggerID) {
|
||||
str += AttributedString(", reblogged by \(reblogger.displayOrUserName)")
|
||||
}
|
||||
return NSAttributedString(str)
|
||||
}
|
||||
set {}
|
||||
|
|
Loading…
Reference in New Issue