Compare commits
15 Commits
cb47443649
...
99caaa0f28
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 99caaa0f28 | |
Shadowfacts | 0f70c9059e | |
Shadowfacts | 6d7074e71d | |
Shadowfacts | 13809b91d1 | |
Shadowfacts | 16f6dc84c9 | |
Shadowfacts | cdfb06f4a7 | |
Shadowfacts | 4e98e569eb | |
Shadowfacts | 6d3ffd7dd3 | |
Shadowfacts | ca7fe74a90 | |
Shadowfacts | 380f878d81 | |
Shadowfacts | 1c36312850 | |
Shadowfacts | de946be008 | |
Shadowfacts | b40d815274 | |
Shadowfacts | bc7500bde9 | |
Shadowfacts | 676e603ffc |
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,5 +1,20 @@
|
|||
# Changelog
|
||||
|
||||
## 2023.8 (107)
|
||||
Features/Improvements:
|
||||
- Style blockquotes in statuses
|
||||
- Use server language preference for search operator suggestions
|
||||
- Render IDN domains in the account switcher
|
||||
|
||||
Bugfixes:
|
||||
- Fix crash when showing trending hashtags with improper history data
|
||||
- Fix crash when uploading attachment w/o file extension
|
||||
- Fix status deletions not being handled properly in logged out views
|
||||
- Fix status history entries not having VoiceOver descriptions
|
||||
- Fix avatars in follow request notifications not being rounded
|
||||
- Fix potential crash if the app is dismissed while fast account switcher is animating
|
||||
- Fix error decoding certain statuses on Pixelfed
|
||||
|
||||
## 2023.8 (106)
|
||||
Bugfixes:
|
||||
- Fix being able to set post language to multiple/undefined
|
||||
|
|
|
@ -114,13 +114,9 @@ class PostService: ObservableObject {
|
|||
} catch let error as DraftAttachment.ExportError {
|
||||
throw Error.attachmentData(index: index, cause: error)
|
||||
}
|
||||
do {
|
||||
let uploaded = try await uploadAttachment(data: data, utType: utType, description: attachment.attachmentDescription)
|
||||
let uploaded = try await uploadAttachment(index: index, data: data, utType: utType, description: attachment.attachmentDescription)
|
||||
attachments.append(uploaded.id)
|
||||
currentStep += 1
|
||||
} catch let error as Client.Error {
|
||||
throw Error.attachmentUpload(index: index, cause: error)
|
||||
}
|
||||
}
|
||||
return attachments
|
||||
}
|
||||
|
@ -138,10 +134,21 @@ class PostService: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func uploadAttachment(data: Data, utType: UTType, description: String?) async throws -> Attachment {
|
||||
let formAttachment = FormAttachment(mimeType: utType.preferredMIMEType!, data: data, fileName: "file.\(utType.preferredFilenameExtension!)")
|
||||
private func uploadAttachment(index: Int, data: Data, utType: UTType, description: String?) async throws -> Attachment {
|
||||
guard let mimeType = utType.preferredMIMEType else {
|
||||
throw Error.attachmentMissingMimeType(index: index, type: utType)
|
||||
}
|
||||
var filename = "file"
|
||||
if let ext = utType.preferredFilenameExtension {
|
||||
filename.append(".\(ext)")
|
||||
}
|
||||
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: filename)
|
||||
let req = Client.upload(attachment: formAttachment, description: description)
|
||||
do {
|
||||
return try await mastodonController.run(req).0
|
||||
} catch let error as Client.Error {
|
||||
throw Error.attachmentUpload(index: index, cause: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func textForPosting() -> String {
|
||||
|
@ -170,6 +177,7 @@ class PostService: ObservableObject {
|
|||
|
||||
enum Error: Swift.Error, LocalizedError {
|
||||
case attachmentData(index: Int, cause: DraftAttachment.ExportError)
|
||||
case attachmentMissingMimeType(index: Int, type: UTType)
|
||||
case attachmentUpload(index: Int, cause: Client.Error)
|
||||
case posting(Client.Error)
|
||||
|
||||
|
@ -177,6 +185,8 @@ class PostService: ObservableObject {
|
|||
switch self {
|
||||
case let .attachmentData(index: index, cause: cause):
|
||||
return "Attachment \(index + 1): \(cause.localizedDescription)"
|
||||
case let .attachmentMissingMimeType(index: index, type: type):
|
||||
return "Attachment \(index + 1): unknown MIME type for \(type.identifier)"
|
||||
case let .attachmentUpload(index: index, cause: cause):
|
||||
return "Attachment \(index + 1): \(cause.localizedDescription)"
|
||||
case let .posting(error):
|
||||
|
|
|
@ -66,7 +66,8 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
|||
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)
|
||||
self.inReplyToAccountID = try container.decodeIfPresent(String.self, forKey: .inReplyToAccountID)
|
||||
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
||||
self.content = try container.decode(String.self, forKey: .content)
|
||||
// pixelfed statuses may have null content
|
||||
self.content = try container.decodeIfPresent(String.self, forKey: .content) ?? ""
|
||||
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
||||
self.emojis = try container.decodeIfPresent([Emoji].self, forKey: .emojis) ?? []
|
||||
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
||||
|
|
|
@ -2969,7 +2969,7 @@
|
|||
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
|
||||
requirement = {
|
||||
kind = upToNextMinorVersion;
|
||||
minimumVersion = 8.0.0;
|
||||
minimumVersion = 8.15.0;
|
||||
};
|
||||
};
|
||||
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */ = {
|
||||
|
|
|
@ -35,8 +35,7 @@ class DeleteStatusService {
|
|||
reblogIDs = reblogs.map(\.id)
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .statusDeleted, object: nil, userInfo: [
|
||||
"accountID": mastodonController.accountInfo!.id,
|
||||
NotificationCenter.default.post(name: .statusDeleted, object: mastodonController, userInfo: [
|
||||
"statusIDs": [status.id] + reblogIDs,
|
||||
])
|
||||
} catch {
|
||||
|
|
|
@ -36,11 +36,6 @@ class FetchStatusService {
|
|||
}
|
||||
|
||||
private func handleStatusNotFound() {
|
||||
// todo: what about when browsing on another instance?
|
||||
guard let accountID = mastodonController.accountInfo?.id else {
|
||||
return
|
||||
}
|
||||
|
||||
var reblogIDs = [String]()
|
||||
if let cached = mastodonController.persistentContainer.status(for: statusID) {
|
||||
let reblogsReq = StatusMO.fetchRequest()
|
||||
|
@ -50,8 +45,7 @@ class FetchStatusService {
|
|||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(name: .statusDeleted, object: nil, userInfo: [
|
||||
"accountID": accountID,
|
||||
NotificationCenter.default.post(name: .statusDeleted, object: mastodonController, userInfo: [
|
||||
"statusIDs": [statusID] + reblogIDs
|
||||
])
|
||||
}
|
||||
|
|
|
@ -86,6 +86,12 @@ struct HTMLConverter {
|
|||
}
|
||||
}
|
||||
|
||||
lazy var currentFont = if attributed.length == 0 {
|
||||
font
|
||||
} else {
|
||||
attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? font
|
||||
}
|
||||
|
||||
switch node.tagName() {
|
||||
case "br":
|
||||
// need to specify defaultFont here b/c otherwise it uses the default 12pt Helvetica which
|
||||
|
@ -102,20 +108,8 @@ struct HTMLConverter {
|
|||
case "p":
|
||||
attributed.append(NSAttributedString(string: "\n\n", attributes: [.font: font]))
|
||||
case "em", "i":
|
||||
let currentFont: UIFont
|
||||
if attributed.length == 0 {
|
||||
currentFont = font
|
||||
} else {
|
||||
currentFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? font
|
||||
}
|
||||
attributed.addAttribute(.font, value: currentFont.withTraits(.traitItalic)!, range: attributed.fullRange)
|
||||
case "strong", "b":
|
||||
let currentFont: UIFont
|
||||
if attributed.length == 0 {
|
||||
currentFont = font
|
||||
} else {
|
||||
currentFont = attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? font
|
||||
}
|
||||
attributed.addAttribute(.font, value: currentFont.withTraits(.traitBold)!, range: attributed.fullRange)
|
||||
case "del":
|
||||
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
||||
|
@ -124,6 +118,14 @@ struct HTMLConverter {
|
|||
case "pre":
|
||||
attributed.append(NSAttributedString(string: "\n\n"))
|
||||
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
||||
case "blockquote":
|
||||
let paragraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle
|
||||
paragraphStyle.headIndent = 32
|
||||
paragraphStyle.firstLineHeadIndent = 32
|
||||
attributed.addAttributes([
|
||||
.font: currentFont.withTraits(.traitItalic)!,
|
||||
.paragraphStyle: paragraphStyle,
|
||||
], range: attributed.fullRange)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
private let decoder = PropertyListDecoder()
|
||||
private let encoder = PropertyListEncoder()
|
||||
|
||||
@propertyWrapper
|
||||
public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
||||
public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
||||
|
||||
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
||||
private let fallback: Value
|
||||
|
@ -32,6 +33,7 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
|||
|
||||
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
|
||||
get {
|
||||
instance.performOnContext {
|
||||
var wrapper = instance[keyPath: storageKeyPath]
|
||||
if let value = wrapper.value {
|
||||
return value
|
||||
|
@ -56,7 +58,9 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set {
|
||||
instance.performOnContext {
|
||||
var wrapper = instance[keyPath: storageKeyPath]
|
||||
wrapper.value = newValue
|
||||
wrapper.skipClearingOnNextUpdate = true
|
||||
|
@ -65,6 +69,7 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
|||
instance[keyPath: wrapper.keyPath] = newData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func removeCachedValue() {
|
||||
value = nil
|
||||
|
@ -73,6 +78,16 @@ public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
|||
|
||||
}
|
||||
|
||||
extension NSManagedObject {
|
||||
fileprivate func performOnContext<V>(_ f: () -> V) -> V {
|
||||
if let managedObjectContext {
|
||||
managedObjectContext.performAndWait(f)
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LazilyDecoding {
|
||||
init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] {
|
||||
self.init(from: keyPath, fallback: [])
|
||||
|
|
|
@ -112,7 +112,7 @@ class ConversationViewController: UIViewController {
|
|||
appearance.configureWithDefaultBackground()
|
||||
navigationItem.scrollEdgeAppearance = appearance
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
private func updateVisibilityBarButtonItem() {
|
||||
|
@ -145,8 +145,6 @@ class ConversationViewController: UIViewController {
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String],
|
||||
case .localID(let mainStatusID) = mode else {
|
||||
return
|
||||
|
|
|
@ -102,7 +102,7 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -146,8 +146,6 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -166,7 +166,9 @@ class FastAccountSwitcherViewController: UIViewController {
|
|||
selectionChangedFeedbackGenerator = nil
|
||||
|
||||
hide() {
|
||||
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAddAccount()
|
||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
||||
sceneDelegate.showAddAccount()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let account = UserAccountsManager.shared.accounts[newIndex - 1]
|
||||
|
@ -178,7 +180,9 @@ class FastAccountSwitcherViewController: UIViewController {
|
|||
selectionChangedFeedbackGenerator = nil
|
||||
|
||||
hide() {
|
||||
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).activateAccount(account, animated: true)
|
||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
||||
sceneDelegate.activateAccount(account, animated: true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hide()
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import UserAccounts
|
||||
import WebURL
|
||||
|
||||
class FastSwitchingAccountView: UIView {
|
||||
|
||||
|
@ -126,7 +127,11 @@ class FastSwitchingAccountView: UIView {
|
|||
|
||||
private func setupAccount(account: UserAccountInfo) {
|
||||
usernameLabel.text = account.username
|
||||
if let domain = WebURL.Domain(account.instanceURL.host!) {
|
||||
instanceLabel.text = domain.render(.uncheckedUnicodeString)
|
||||
} else {
|
||||
instanceLabel.text = account.instanceURL.host!
|
||||
}
|
||||
let controller = MastodonController.getForAccount(account)
|
||||
controller.getOwnAccount { [weak self] (result) in
|
||||
guard let self = self,
|
||||
|
@ -140,7 +145,7 @@ class FastSwitchingAccountView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
accessibilityLabel = "\(account.username!)@\(account.instanceURL.host!)"
|
||||
accessibilityLabel = "\(account.username!)@\(instanceLabel.text!)"
|
||||
}
|
||||
|
||||
private func setupPlaceholder() {
|
||||
|
|
|
@ -107,7 +107,7 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
|
||||
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh \(predicateTitle)"))
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: mastodonController.persistentContainer.viewContext)
|
||||
}
|
||||
|
||||
|
@ -205,8 +205,6 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
|||
$0.contentMode = .scaleAspectFill
|
||||
$0.layer.masksToBounds = true
|
||||
$0.layer.cornerCurve = .continuous
|
||||
$0.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
||||
NSLayoutConstraint.activate([
|
||||
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
|
||||
])
|
||||
|
|
|
@ -121,7 +121,7 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
|||
self.reapplyFilters(actionsChanged: actionsChanged)
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
|
@ -257,8 +257,6 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import UserAccounts
|
||||
import WebURL
|
||||
|
||||
struct PreferencesView: View {
|
||||
let mastodonController: MastodonController
|
||||
|
@ -41,7 +42,12 @@ struct PreferencesView: View {
|
|||
VStack(alignment: .leading) {
|
||||
Text(verbatim: account.username)
|
||||
.foregroundColor(.primary)
|
||||
Text(verbatim: account.instanceURL.host!)
|
||||
let instance = if let domain = WebURL.Domain(account.instanceURL.host!) {
|
||||
domain.render(.uncheckedUnicodeString)
|
||||
} else {
|
||||
account.instanceURL.host!
|
||||
}
|
||||
Text(verbatim: instance)
|
||||
.font(.caption)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
|
|
|
@ -67,18 +67,25 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
(collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.trailingSwipeActions()
|
||||
}
|
||||
config.itemSeparatorHandler = { [unowned self] indexPath, sectionSeparatorConfiguration in
|
||||
guard let item = self.dataSource.itemIdentifier(for: indexPath) else {
|
||||
guard let item = self.dataSource.itemIdentifier(for: indexPath),
|
||||
let section = self.dataSource.sectionIdentifier(for: indexPath.section) else {
|
||||
return sectionSeparatorConfiguration
|
||||
}
|
||||
var config = sectionSeparatorConfiguration
|
||||
if item.hideSeparators {
|
||||
config.topSeparatorVisibility = .hidden
|
||||
config.bottomSeparatorVisibility = .hidden
|
||||
} else if section == .header {
|
||||
config.topSeparatorVisibility = .hidden
|
||||
config.bottomSeparatorInsets = .zero
|
||||
} else if indexPath.row == 0 && (section == .pinned || section == .entries) {
|
||||
// TODO: row == 0 isn't technically right, the top post could be filtered out
|
||||
config.topSeparatorInsets = .zero
|
||||
} else if case .status(id: _, collapseState: _, filterState: let filterState, pinned: _) = item,
|
||||
filterer.isKnownHide(state: filterState) {
|
||||
config.topSeparatorVisibility = .hidden
|
||||
config.bottomSeparatorVisibility = .hidden
|
||||
} else if case .status(_, _, _, _) = item {
|
||||
} else {
|
||||
config.topSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
||||
config.bottomSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
||||
}
|
||||
|
@ -88,6 +95,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
if case .header = dataSource.sectionIdentifier(for: sectionIndex) {
|
||||
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||
config.backgroundColor = .appBackground
|
||||
config.separatorConfiguration.bottomSeparatorInsets = .zero
|
||||
return .list(using: config, layoutEnvironment: environment)
|
||||
} else {
|
||||
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
|
||||
|
@ -148,7 +156,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
self.reapplyFilters(actionsChanged: actionsChanged)
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
|
@ -376,8 +384,6 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -65,12 +65,13 @@ class MastodonSearchController: UISearchController {
|
|||
searchText.isEmpty || $0.contains(searchText)
|
||||
}))
|
||||
|
||||
// TODO: use default language from preferences
|
||||
var langSuggestions = [String]()
|
||||
if searchText.isEmpty || "language:en".contains(searchText) {
|
||||
langSuggestions.append("language:en")
|
||||
let defaultLanguage = searchResultsController.mastodonController.accountPreferences.serverDefaultLanguage ?? "en"
|
||||
let languageToken = "language:\(defaultLanguage)"
|
||||
if searchText.isEmpty || languageToken.contains(searchText) {
|
||||
langSuggestions.append(languageToken)
|
||||
}
|
||||
if searchText != "en",
|
||||
if searchText != defaultLanguage,
|
||||
let match = languageRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) {
|
||||
let identifier = (searchText as NSString).substring(with: match.range(at: 1))
|
||||
if #available(iOS 16.0, *) {
|
||||
|
|
|
@ -120,7 +120,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
|||
|
||||
userActivity = UserActivityManager.searchActivity(query: nil, accountID: mastodonController.accountInfo!.id)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||
|
@ -309,8 +309,6 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ class StatusActionAccountListViewController: UIViewController {
|
|||
|
||||
view.backgroundColor = .appBackground
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -99,8 +99,6 @@ class StatusActionAccountListViewController: UIViewController {
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -91,8 +91,76 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
// todo: accessibility
|
||||
// MARK: Accessibility
|
||||
|
||||
override var isAccessibilityElement: Bool {
|
||||
get { true }
|
||||
set {}
|
||||
}
|
||||
|
||||
override var accessibilityAttributedLabel: NSAttributedString? {
|
||||
get {
|
||||
var str: AttributedString = ""
|
||||
if statusState.collapsed ?? false {
|
||||
if !edit.spoilerText.isEmpty {
|
||||
str += AttributedString(edit.spoilerText)
|
||||
str += ", "
|
||||
}
|
||||
str += "collapsed"
|
||||
} else {
|
||||
str += AttributedString(contentContainer.contentTextView.attributedText)
|
||||
|
||||
if edit.attachments.count > 0 {
|
||||
let includeDescriptions: Bool
|
||||
switch Preferences.shared.attachmentBlurMode {
|
||||
case .useStatusSetting:
|
||||
includeDescriptions = !Preferences.shared.blurMediaBehindContentWarning || edit.spoilerText.isEmpty
|
||||
case .always:
|
||||
includeDescriptions = true
|
||||
case .never:
|
||||
includeDescriptions = false
|
||||
}
|
||||
if includeDescriptions {
|
||||
if edit.attachments.count == 1 {
|
||||
let attachment = edit.attachments[0]
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment: \(desc)")
|
||||
} else {
|
||||
for (index, attachment) in edit.attachments.enumerated() {
|
||||
let desc = attachment.description?.isEmpty == false ? attachment.description! : "no description"
|
||||
str += AttributedString(", attachment \(index + 1): \(desc)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
str += AttributedString(", \(edit.attachments.count) attachment\(edit.attachments.count == 1 ? "" : "s")")
|
||||
}
|
||||
}
|
||||
if edit.poll != nil {
|
||||
str += ", poll"
|
||||
}
|
||||
}
|
||||
return NSAttributedString(str)
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
override var accessibilityHint: String? {
|
||||
get {
|
||||
if statusState.collapsed ?? false {
|
||||
return "Double tap to expand the post."
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
set {}
|
||||
}
|
||||
|
||||
override func accessibilityActivate() -> Bool {
|
||||
if statusState.collapsed ?? false {
|
||||
collapseButtonPressed()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: Configure UI
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
||||
|
||||
if userActivity != nil {
|
||||
userActivityNeedsUpdate
|
||||
|
@ -943,8 +943,6 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
|
||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||
guard let userInfo = notification.userInfo,
|
||||
let accountID = mastodonController.accountInfo?.id,
|
||||
userInfo["accountID"] as? String == accountID,
|
||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -207,10 +207,12 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
|||
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!)
|
||||
if let mention = getMention(for: url, text: text),
|
||||
let mastodonController {
|
||||
return ProfileViewController(accountID: mention.id, mastodonController: mastodonController)
|
||||
} else if let tag = getHashtag(for: url, text: text),
|
||||
let mastodonController {
|
||||
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController)
|
||||
} else if url.scheme == "https" || url.scheme == "http" {
|
||||
let vc = SFSafariViewController(url: url)
|
||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||
|
|
|
@ -67,7 +67,8 @@ class TrendingHashtagCollectionViewCell: UICollectionViewCell {
|
|||
historyView.setHistory(hashtag.history)
|
||||
historyView.isHidden = hashtag.history == nil || hashtag.history!.count < 2
|
||||
|
||||
if let history = hashtag.history {
|
||||
if let history = hashtag.history,
|
||||
history.count >= 2 {
|
||||
let sorted = history.sorted(by: { $0.day < $1.day })
|
||||
let lastTwo = sorted[(sorted.count - 2)...]
|
||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22155" 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="21678"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
||||
<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"/>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="vFa-g3-xIP" customClass="ProfileHeaderButton" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="358" y="142" width="48" height="48"/>
|
||||
<rect key="frame" x="358.5" y="142.5" width="47.5" height="47.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="vFa-g3-xIP" secondAttribute="height" multiplier="1:1" id="B01-24-GJj"/>
|
||||
</constraints>
|
||||
|
@ -54,7 +54,7 @@
|
|||
<buttonConfiguration key="configuration" style="plain" image="ellipsis" catalog="system"/>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cr8-p9-xkc" customClass="ProfileHeaderButton" customModule="Tusker" customModuleProvider="target">
|
||||
<rect key="frame" x="249" y="140" width="101" height="52"/>
|
||||
<rect key="frame" x="249.5" y="140.5" width="101" height="51.5"/>
|
||||
<state key="normal" title="Button"/>
|
||||
<buttonConfiguration key="configuration" style="plain" image="person.badge.plus" catalog="system" title="Follow" imagePadding="4"/>
|
||||
<connections>
|
||||
|
@ -123,13 +123,6 @@
|
|||
</imageView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5ja-fK-Fqz">
|
||||
<rect key="frame" x="16" y="861.5" width="398" height="0.5"/>
|
||||
<color key="backgroundColor" systemColor="separatorColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="0.5" id="VwS-gV-q8M"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
|
@ -139,11 +132,9 @@
|
|||
<constraint firstItem="wT9-2J-uSY" firstAttribute="centerY" secondItem="dgG-dR-lSv" secondAttribute="bottom" id="7gb-T3-Xe7"/>
|
||||
<constraint firstItem="vcl-Gl-kXl" firstAttribute="top" secondItem="dgG-dR-lSv" secondAttribute="bottom" constant="8" symbolic="YES" id="7ss-Mf-YYH"/>
|
||||
<constraint firstItem="vcl-Gl-kXl" firstAttribute="leading" secondItem="wT9-2J-uSY" secondAttribute="trailing" constant="8" id="8ho-WU-MxW"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="5ja-fK-Fqz" secondAttribute="bottom" id="9ZS-Ey-eKd"/>
|
||||
<constraint firstItem="jwU-EH-hmC" firstAttribute="top" secondItem="vcl-Gl-kXl" secondAttribute="bottom" id="9lx-Fn-M0U"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="u4P-3i-gEq" secondAttribute="bottom" id="9zc-N2-mfI"/>
|
||||
<constraint firstItem="vFa-g3-xIP" firstAttribute="bottom" secondItem="dgG-dR-lSv" secondAttribute="bottom" constant="-8" id="AXS-bG-20Q"/>
|
||||
<constraint firstAttribute="trailing" secondItem="5ja-fK-Fqz" secondAttribute="trailing" id="EMk-dp-yJV"/>
|
||||
<constraint firstItem="jwU-EH-hmC" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="TkY-oK-if4" secondAttribute="bottom" id="HBR-rg-RxO"/>
|
||||
<constraint firstItem="dgG-dR-lSv" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="VD1-yc-KSa"/>
|
||||
<constraint firstItem="wT9-2J-uSY" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="WNS-AR-ff2"/>
|
||||
|
@ -153,7 +144,6 @@
|
|||
<constraint firstItem="cr8-p9-xkc" firstAttribute="trailing" secondItem="vFa-g3-xIP" secondAttribute="leading" constant="-8" id="f1L-S8-l6H"/>
|
||||
<constraint firstItem="u4P-3i-gEq" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="hgl-UR-o3W"/>
|
||||
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="dgG-dR-lSv" secondAttribute="trailing" id="j0d-hY-815"/>
|
||||
<constraint firstItem="5ja-fK-Fqz" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="jPG-WM-9km"/>
|
||||
<constraint firstItem="jwU-EH-hmC" firstAttribute="leading" secondItem="wT9-2J-uSY" secondAttribute="trailing" constant="8" id="oGR-6M-gdd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="u4P-3i-gEq" secondAttribute="trailing" constant="16" id="ph6-NT-A02"/>
|
||||
<constraint firstItem="u4P-3i-gEq" firstAttribute="top" secondItem="wT9-2J-uSY" secondAttribute="bottom" priority="999" constant="8" id="tKQ-6d-Z55"/>
|
||||
|
@ -181,15 +171,12 @@
|
|||
<resources>
|
||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||
<image name="lock.fill" catalog="system" width="125" height="128"/>
|
||||
<image name="person.badge.plus" catalog="system" width="128" height="125"/>
|
||||
<image name="person.badge.plus" catalog="system" width="128" height="124"/>
|
||||
<systemColor name="labelColor">
|
||||
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</systemColor>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="separatorColor">
|
||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemBackgroundColor">
|
||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 2023.8
|
||||
CURRENT_PROJECT_VERSION = 106
|
||||
CURRENT_PROJECT_VERSION = 107
|
||||
CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION))
|
||||
|
||||
CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev
|
||||
|
|
Loading…
Reference in New Issue