Compare commits
No commits in common. "99caaa0f2846c23d98ffc0692e98e4673ae2b12f" and "cb474436496b1d7fb48db3bc32f4a523d8b9ac2e" have entirely different histories.
99caaa0f28
...
cb47443649
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,20 +1,5 @@
|
||||||
# Changelog
|
# 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)
|
## 2023.8 (106)
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
- Fix being able to set post language to multiple/undefined
|
- Fix being able to set post language to multiple/undefined
|
||||||
|
|
|
@ -114,9 +114,13 @@ class PostService: ObservableObject {
|
||||||
} catch let error as DraftAttachment.ExportError {
|
} catch let error as DraftAttachment.ExportError {
|
||||||
throw Error.attachmentData(index: index, cause: error)
|
throw Error.attachmentData(index: index, cause: error)
|
||||||
}
|
}
|
||||||
let uploaded = try await uploadAttachment(index: index, data: data, utType: utType, description: attachment.attachmentDescription)
|
do {
|
||||||
|
let uploaded = try await uploadAttachment(data: data, utType: utType, description: attachment.attachmentDescription)
|
||||||
attachments.append(uploaded.id)
|
attachments.append(uploaded.id)
|
||||||
currentStep += 1
|
currentStep += 1
|
||||||
|
} catch let error as Client.Error {
|
||||||
|
throw Error.attachmentUpload(index: index, cause: error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return attachments
|
return attachments
|
||||||
}
|
}
|
||||||
|
@ -134,21 +138,10 @@ class PostService: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadAttachment(index: Int, data: Data, utType: UTType, description: String?) async throws -> Attachment {
|
private func uploadAttachment(data: Data, utType: UTType, description: String?) async throws -> Attachment {
|
||||||
guard let mimeType = utType.preferredMIMEType else {
|
let formAttachment = FormAttachment(mimeType: utType.preferredMIMEType!, data: data, fileName: "file.\(utType.preferredFilenameExtension!)")
|
||||||
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)
|
let req = Client.upload(attachment: formAttachment, description: description)
|
||||||
do {
|
|
||||||
return try await mastodonController.run(req).0
|
return try await mastodonController.run(req).0
|
||||||
} catch let error as Client.Error {
|
|
||||||
throw Error.attachmentUpload(index: index, cause: error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func textForPosting() -> String {
|
private func textForPosting() -> String {
|
||||||
|
@ -177,7 +170,6 @@ class PostService: ObservableObject {
|
||||||
|
|
||||||
enum Error: Swift.Error, LocalizedError {
|
enum Error: Swift.Error, LocalizedError {
|
||||||
case attachmentData(index: Int, cause: DraftAttachment.ExportError)
|
case attachmentData(index: Int, cause: DraftAttachment.ExportError)
|
||||||
case attachmentMissingMimeType(index: Int, type: UTType)
|
|
||||||
case attachmentUpload(index: Int, cause: Client.Error)
|
case attachmentUpload(index: Int, cause: Client.Error)
|
||||||
case posting(Client.Error)
|
case posting(Client.Error)
|
||||||
|
|
||||||
|
@ -185,8 +177,6 @@ class PostService: ObservableObject {
|
||||||
switch self {
|
switch self {
|
||||||
case let .attachmentData(index: index, cause: cause):
|
case let .attachmentData(index: index, cause: cause):
|
||||||
return "Attachment \(index + 1): \(cause.localizedDescription)"
|
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):
|
case let .attachmentUpload(index: index, cause: cause):
|
||||||
return "Attachment \(index + 1): \(cause.localizedDescription)"
|
return "Attachment \(index + 1): \(cause.localizedDescription)"
|
||||||
case let .posting(error):
|
case let .posting(error):
|
||||||
|
|
|
@ -66,8 +66,7 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
||||||
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)
|
self.inReplyToID = try container.decodeIfPresent(String.self, forKey: .inReplyToID)
|
||||||
self.inReplyToAccountID = try container.decodeIfPresent(String.self, forKey: .inReplyToAccountID)
|
self.inReplyToAccountID = try container.decodeIfPresent(String.self, forKey: .inReplyToAccountID)
|
||||||
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
self.reblog = try container.decodeIfPresent(Status.self, forKey: .reblog)
|
||||||
// pixelfed statuses may have null content
|
self.content = try container.decode(String.self, forKey: .content)
|
||||||
self.content = try container.decodeIfPresent(String.self, forKey: .content) ?? ""
|
|
||||||
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
||||||
self.emojis = try container.decodeIfPresent([Emoji].self, forKey: .emojis) ?? []
|
self.emojis = try container.decodeIfPresent([Emoji].self, forKey: .emojis) ?? []
|
||||||
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
self.reblogsCount = try container.decode(Int.self, forKey: .reblogsCount)
|
||||||
|
|
|
@ -2969,7 +2969,7 @@
|
||||||
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
|
repositoryURL = "https://github.com/getsentry/sentry-cocoa.git";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = upToNextMinorVersion;
|
kind = upToNextMinorVersion;
|
||||||
minimumVersion = 8.15.0;
|
minimumVersion = 8.0.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */ = {
|
D6552365289870790048A653 /* XCRemoteSwiftPackageReference "ScreenCorners" */ = {
|
||||||
|
|
|
@ -35,7 +35,8 @@ class DeleteStatusService {
|
||||||
reblogIDs = reblogs.map(\.id)
|
reblogIDs = reblogs.map(\.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .statusDeleted, object: mastodonController, userInfo: [
|
NotificationCenter.default.post(name: .statusDeleted, object: nil, userInfo: [
|
||||||
|
"accountID": mastodonController.accountInfo!.id,
|
||||||
"statusIDs": [status.id] + reblogIDs,
|
"statusIDs": [status.id] + reblogIDs,
|
||||||
])
|
])
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -36,6 +36,11 @@ class FetchStatusService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleStatusNotFound() {
|
private func handleStatusNotFound() {
|
||||||
|
// todo: what about when browsing on another instance?
|
||||||
|
guard let accountID = mastodonController.accountInfo?.id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var reblogIDs = [String]()
|
var reblogIDs = [String]()
|
||||||
if let cached = mastodonController.persistentContainer.status(for: statusID) {
|
if let cached = mastodonController.persistentContainer.status(for: statusID) {
|
||||||
let reblogsReq = StatusMO.fetchRequest()
|
let reblogsReq = StatusMO.fetchRequest()
|
||||||
|
@ -45,7 +50,8 @@ class FetchStatusService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .statusDeleted, object: mastodonController, userInfo: [
|
NotificationCenter.default.post(name: .statusDeleted, object: nil, userInfo: [
|
||||||
|
"accountID": accountID,
|
||||||
"statusIDs": [statusID] + reblogIDs
|
"statusIDs": [statusID] + reblogIDs
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,12 +86,6 @@ struct HTMLConverter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var currentFont = if attributed.length == 0 {
|
|
||||||
font
|
|
||||||
} else {
|
|
||||||
attributed.attribute(.font, at: 0, effectiveRange: nil) as? UIFont ?? font
|
|
||||||
}
|
|
||||||
|
|
||||||
switch node.tagName() {
|
switch node.tagName() {
|
||||||
case "br":
|
case "br":
|
||||||
// need to specify defaultFont here b/c otherwise it uses the default 12pt Helvetica which
|
// need to specify defaultFont here b/c otherwise it uses the default 12pt Helvetica which
|
||||||
|
@ -108,8 +102,20 @@ struct HTMLConverter {
|
||||||
case "p":
|
case "p":
|
||||||
attributed.append(NSAttributedString(string: "\n\n", attributes: [.font: font]))
|
attributed.append(NSAttributedString(string: "\n\n", attributes: [.font: font]))
|
||||||
case "em", "i":
|
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)
|
attributed.addAttribute(.font, value: currentFont.withTraits(.traitItalic)!, range: attributed.fullRange)
|
||||||
case "strong", "b":
|
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)
|
attributed.addAttribute(.font, value: currentFont.withTraits(.traitBold)!, range: attributed.fullRange)
|
||||||
case "del":
|
case "del":
|
||||||
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
attributed.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: attributed.fullRange)
|
||||||
|
@ -118,14 +124,6 @@ struct HTMLConverter {
|
||||||
case "pre":
|
case "pre":
|
||||||
attributed.append(NSAttributedString(string: "\n\n"))
|
attributed.append(NSAttributedString(string: "\n\n"))
|
||||||
attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange)
|
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:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
|
||||||
|
|
||||||
private let decoder = PropertyListDecoder()
|
private let decoder = PropertyListDecoder()
|
||||||
private let encoder = PropertyListEncoder()
|
private let encoder = PropertyListEncoder()
|
||||||
|
|
||||||
@propertyWrapper
|
@propertyWrapper
|
||||||
public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
public struct LazilyDecoding<Enclosing: NSObject, Value: Codable> {
|
||||||
|
|
||||||
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
private let keyPath: ReferenceWritableKeyPath<Enclosing, Data?>
|
||||||
private let fallback: Value
|
private let fallback: Value
|
||||||
|
@ -33,7 +32,6 @@ public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
||||||
|
|
||||||
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
|
public static subscript(_enclosingInstance instance: Enclosing, wrapped wrappedKeyPath: ReferenceWritableKeyPath<Enclosing, Value>, storage storageKeyPath: ReferenceWritableKeyPath<Enclosing, Self>) -> Value {
|
||||||
get {
|
get {
|
||||||
instance.performOnContext {
|
|
||||||
var wrapper = instance[keyPath: storageKeyPath]
|
var wrapper = instance[keyPath: storageKeyPath]
|
||||||
if let value = wrapper.value {
|
if let value = wrapper.value {
|
||||||
return value
|
return value
|
||||||
|
@ -58,9 +56,7 @@ public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
set {
|
set {
|
||||||
instance.performOnContext {
|
|
||||||
var wrapper = instance[keyPath: storageKeyPath]
|
var wrapper = instance[keyPath: storageKeyPath]
|
||||||
wrapper.value = newValue
|
wrapper.value = newValue
|
||||||
wrapper.skipClearingOnNextUpdate = true
|
wrapper.skipClearingOnNextUpdate = true
|
||||||
|
@ -69,7 +65,6 @@ public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
||||||
instance[keyPath: wrapper.keyPath] = newData
|
instance[keyPath: wrapper.keyPath] = newData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mutating func removeCachedValue() {
|
mutating func removeCachedValue() {
|
||||||
value = nil
|
value = nil
|
||||||
|
@ -78,16 +73,6 @@ public struct LazilyDecoding<Enclosing: NSManagedObject, Value: Codable> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NSManagedObject {
|
|
||||||
fileprivate func performOnContext<V>(_ f: () -> V) -> V {
|
|
||||||
if let managedObjectContext {
|
|
||||||
managedObjectContext.performAndWait(f)
|
|
||||||
} else {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LazilyDecoding {
|
extension LazilyDecoding {
|
||||||
init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] {
|
init<T>(arrayFrom keyPath: ReferenceWritableKeyPath<Enclosing, Data?>) where Value == [T] {
|
||||||
self.init(from: keyPath, fallback: [])
|
self.init(from: keyPath, fallback: [])
|
||||||
|
|
|
@ -112,7 +112,7 @@ class ConversationViewController: UIViewController {
|
||||||
appearance.configureWithDefaultBackground()
|
appearance.configureWithDefaultBackground()
|
||||||
navigationItem.scrollEdgeAppearance = appearance
|
navigationItem.scrollEdgeAppearance = appearance
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibilityBarButtonItem() {
|
private func updateVisibilityBarButtonItem() {
|
||||||
|
@ -145,6 +145,8 @@ class ConversationViewController: UIViewController {
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String],
|
let statusIDs = userInfo["statusIDs"] as? [String],
|
||||||
case .localID(let mainStatusID) = mode else {
|
case .localID(let mainStatusID) = mode else {
|
||||||
return
|
return
|
||||||
|
|
|
@ -102,7 +102,7 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -146,6 +146,8 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,9 +166,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAddAccount()
|
||||||
sceneDelegate.showAddAccount()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let account = UserAccountsManager.shared.accounts[newIndex - 1]
|
let account = UserAccountsManager.shared.accounts[newIndex - 1]
|
||||||
|
@ -180,9 +178,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
if let sceneDelegate = self.view.window?.windowScene?.delegate as? MainSceneDelegate {
|
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).activateAccount(account, animated: true)
|
||||||
sceneDelegate.activateAccount(account, animated: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hide()
|
hide()
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import WebURL
|
|
||||||
|
|
||||||
class FastSwitchingAccountView: UIView {
|
class FastSwitchingAccountView: UIView {
|
||||||
|
|
||||||
|
@ -127,11 +126,7 @@ class FastSwitchingAccountView: UIView {
|
||||||
|
|
||||||
private func setupAccount(account: UserAccountInfo) {
|
private func setupAccount(account: UserAccountInfo) {
|
||||||
usernameLabel.text = account.username
|
usernameLabel.text = account.username
|
||||||
if let domain = WebURL.Domain(account.instanceURL.host!) {
|
|
||||||
instanceLabel.text = domain.render(.uncheckedUnicodeString)
|
|
||||||
} else {
|
|
||||||
instanceLabel.text = account.instanceURL.host!
|
instanceLabel.text = account.instanceURL.host!
|
||||||
}
|
|
||||||
let controller = MastodonController.getForAccount(account)
|
let controller = MastodonController.getForAccount(account)
|
||||||
controller.getOwnAccount { [weak self] (result) in
|
controller.getOwnAccount { [weak self] (result) in
|
||||||
guard let self = self,
|
guard let self = self,
|
||||||
|
@ -145,7 +140,7 @@ class FastSwitchingAccountView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accessibilityLabel = "\(account.username!)@\(instanceLabel.text!)"
|
accessibilityLabel = "\(account.username!)@\(account.instanceURL.host!)"
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupPlaceholder() {
|
private func setupPlaceholder() {
|
||||||
|
|
|
@ -107,7 +107,7 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
||||||
|
|
||||||
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh \(predicateTitle)"))
|
addKeyCommand(MenuController.refreshCommand(discoverabilityTitle: "Refresh \(predicateTitle)"))
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: mastodonController.persistentContainer.viewContext)
|
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: mastodonController.persistentContainer.viewContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,6 +205,8 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ class FollowRequestNotificationCollectionViewCell: UICollectionViewListCell {
|
||||||
$0.contentMode = .scaleAspectFill
|
$0.contentMode = .scaleAspectFill
|
||||||
$0.layer.masksToBounds = true
|
$0.layer.masksToBounds = true
|
||||||
$0.layer.cornerCurve = .continuous
|
$0.layer.cornerCurve = .continuous
|
||||||
$0.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadiusFraction * 30
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
|
$0.widthAnchor.constraint(equalTo: $0.heightAnchor),
|
||||||
])
|
])
|
||||||
|
|
|
@ -121,7 +121,7 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
||||||
self.reapplyFilters(actionsChanged: actionsChanged)
|
self.reapplyFilters(actionsChanged: actionsChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -257,6 +257,8 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UserAccounts
|
import UserAccounts
|
||||||
import WebURL
|
|
||||||
|
|
||||||
struct PreferencesView: View {
|
struct PreferencesView: View {
|
||||||
let mastodonController: MastodonController
|
let mastodonController: MastodonController
|
||||||
|
@ -42,12 +41,7 @@ struct PreferencesView: View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(verbatim: account.username)
|
Text(verbatim: account.username)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
let instance = if let domain = WebURL.Domain(account.instanceURL.host!) {
|
Text(verbatim: account.instanceURL.host!)
|
||||||
domain.render(.uncheckedUnicodeString)
|
|
||||||
} else {
|
|
||||||
account.instanceURL.host!
|
|
||||||
}
|
|
||||||
Text(verbatim: instance)
|
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.primary)
|
.foregroundColor(.primary)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,25 +67,18 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
(collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.trailingSwipeActions()
|
(collectionView.cellForItem(at: $0) as? TimelineStatusCollectionViewCell)?.trailingSwipeActions()
|
||||||
}
|
}
|
||||||
config.itemSeparatorHandler = { [unowned self] indexPath, sectionSeparatorConfiguration in
|
config.itemSeparatorHandler = { [unowned self] indexPath, sectionSeparatorConfiguration in
|
||||||
guard let item = self.dataSource.itemIdentifier(for: indexPath),
|
guard let item = self.dataSource.itemIdentifier(for: indexPath) else {
|
||||||
let section = self.dataSource.sectionIdentifier(for: indexPath.section) else {
|
|
||||||
return sectionSeparatorConfiguration
|
return sectionSeparatorConfiguration
|
||||||
}
|
}
|
||||||
var config = sectionSeparatorConfiguration
|
var config = sectionSeparatorConfiguration
|
||||||
if item.hideSeparators {
|
if item.hideSeparators {
|
||||||
config.topSeparatorVisibility = .hidden
|
config.topSeparatorVisibility = .hidden
|
||||||
config.bottomSeparatorVisibility = .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,
|
} else if case .status(id: _, collapseState: _, filterState: let filterState, pinned: _) = item,
|
||||||
filterer.isKnownHide(state: filterState) {
|
filterer.isKnownHide(state: filterState) {
|
||||||
config.topSeparatorVisibility = .hidden
|
config.topSeparatorVisibility = .hidden
|
||||||
config.bottomSeparatorVisibility = .hidden
|
config.bottomSeparatorVisibility = .hidden
|
||||||
} else {
|
} else if case .status(_, _, _, _) = item {
|
||||||
config.topSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
config.topSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
||||||
config.bottomSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
config.bottomSeparatorInsets = TimelineStatusCollectionViewCell.separatorInsets
|
||||||
}
|
}
|
||||||
|
@ -95,7 +88,6 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
if case .header = dataSource.sectionIdentifier(for: sectionIndex) {
|
if case .header = dataSource.sectionIdentifier(for: sectionIndex) {
|
||||||
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
var config = UICollectionLayoutListConfiguration(appearance: .plain)
|
||||||
config.backgroundColor = .appBackground
|
config.backgroundColor = .appBackground
|
||||||
config.separatorConfiguration.bottomSeparatorInsets = .zero
|
|
||||||
return .list(using: config, layoutEnvironment: environment)
|
return .list(using: config, layoutEnvironment: environment)
|
||||||
} else {
|
} else {
|
||||||
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
|
let section = NSCollectionLayoutSection.list(using: config, layoutEnvironment: environment)
|
||||||
|
@ -156,7 +148,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
self.reapplyFilters(actionsChanged: actionsChanged)
|
self.reapplyFilters(actionsChanged: actionsChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -384,6 +376,8 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,13 +65,12 @@ class MastodonSearchController: UISearchController {
|
||||||
searchText.isEmpty || $0.contains(searchText)
|
searchText.isEmpty || $0.contains(searchText)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// TODO: use default language from preferences
|
||||||
var langSuggestions = [String]()
|
var langSuggestions = [String]()
|
||||||
let defaultLanguage = searchResultsController.mastodonController.accountPreferences.serverDefaultLanguage ?? "en"
|
if searchText.isEmpty || "language:en".contains(searchText) {
|
||||||
let languageToken = "language:\(defaultLanguage)"
|
langSuggestions.append("language:en")
|
||||||
if searchText.isEmpty || languageToken.contains(searchText) {
|
|
||||||
langSuggestions.append(languageToken)
|
|
||||||
}
|
}
|
||||||
if searchText != defaultLanguage,
|
if searchText != "en",
|
||||||
let match = languageRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) {
|
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))
|
let identifier = (searchText as NSString).substring(with: match.range(at: 1))
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
|
|
|
@ -120,7 +120,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
||||||
|
|
||||||
userActivity = UserActivityManager.searchActivity(query: nil, accountID: mastodonController.accountInfo!.id)
|
userActivity = UserActivityManager.searchActivity(query: nil, accountID: mastodonController.accountInfo!.id)
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
private func createDataSource() -> UICollectionViewDiffableDataSource<Section, Item> {
|
||||||
|
@ -309,6 +309,8 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ class StatusActionAccountListViewController: UIViewController {
|
||||||
|
|
||||||
view.backgroundColor = .appBackground
|
view.backgroundColor = .appBackground
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
@ -99,6 +99,8 @@ class StatusActionAccountListViewController: UIViewController {
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,76 +91,8 @@ class StatusEditCollectionViewCell: UICollectionViewListCell {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Accessibility
|
// todo: 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
|
// MARK: Configure UI
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: mastodonController)
|
NotificationCenter.default.addObserver(self, selector: #selector(handleStatusDeleted), name: .statusDeleted, object: nil)
|
||||||
|
|
||||||
if userActivity != nil {
|
if userActivity != nil {
|
||||||
userActivityNeedsUpdate
|
userActivityNeedsUpdate
|
||||||
|
@ -943,6 +943,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
||||||
|
|
||||||
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
@objc private func handleStatusDeleted(_ notification: Foundation.Notification) {
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
|
let accountID = mastodonController.accountInfo?.id,
|
||||||
|
userInfo["accountID"] as? String == accountID,
|
||||||
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
let statusIDs = userInfo["statusIDs"] as? [String] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,12 +207,10 @@ class ContentTextView: LinkTextView, BaseEmojiLabel {
|
||||||
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)
|
let text = (self.text as NSString).substring(with: range)
|
||||||
|
|
||||||
if let mention = getMention(for: url, text: text),
|
if let mention = getMention(for: url, text: text) {
|
||||||
let mastodonController {
|
return ProfileViewController(accountID: mention.id, mastodonController: mastodonController!)
|
||||||
return ProfileViewController(accountID: mention.id, mastodonController: mastodonController)
|
} else if let tag = getHashtag(for: url, text: text) {
|
||||||
} else if let tag = getHashtag(for: url, text: text),
|
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController!)
|
||||||
let mastodonController {
|
|
||||||
return HashtagTimelineViewController(for: tag, mastodonController: mastodonController)
|
|
||||||
} else if url.scheme == "https" || url.scheme == "http" {
|
} else if url.scheme == "https" || url.scheme == "http" {
|
||||||
let vc = SFSafariViewController(url: url)
|
let vc = SFSafariViewController(url: url)
|
||||||
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
vc.preferredControlTintColor = Preferences.shared.accentColor.color
|
||||||
|
|
|
@ -67,8 +67,7 @@ class TrendingHashtagCollectionViewCell: UICollectionViewCell {
|
||||||
historyView.setHistory(hashtag.history)
|
historyView.setHistory(hashtag.history)
|
||||||
historyView.isHidden = hashtag.history == nil || hashtag.history!.count < 2
|
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 sorted = history.sorted(by: { $0.day < $1.day })
|
||||||
let lastTwo = sorted[(sorted.count - 2)...]
|
let lastTwo = sorted[(sorted.count - 2)...]
|
||||||
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
let accounts = lastTwo.map(\.accounts).reduce(0, +)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<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">
|
<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">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22131"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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">
|
<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.5" y="142.5" width="47.5" height="47.5"/>
|
<rect key="frame" x="358" y="142" width="48" height="48"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" secondItem="vFa-g3-xIP" secondAttribute="height" multiplier="1:1" id="B01-24-GJj"/>
|
<constraint firstAttribute="width" secondItem="vFa-g3-xIP" secondAttribute="height" multiplier="1:1" id="B01-24-GJj"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
<buttonConfiguration key="configuration" style="plain" image="ellipsis" catalog="system"/>
|
<buttonConfiguration key="configuration" style="plain" image="ellipsis" catalog="system"/>
|
||||||
</button>
|
</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">
|
<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.5" y="140.5" width="101" height="51.5"/>
|
<rect key="frame" x="249" y="140" width="101" height="52"/>
|
||||||
<state key="normal" title="Button"/>
|
<state key="normal" title="Button"/>
|
||||||
<buttonConfiguration key="configuration" style="plain" image="person.badge.plus" catalog="system" title="Follow" imagePadding="4"/>
|
<buttonConfiguration key="configuration" style="plain" image="person.badge.plus" catalog="system" title="Follow" imagePadding="4"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -123,6 +123,13 @@
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</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>
|
</subviews>
|
||||||
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||||
|
@ -132,9 +139,11 @@
|
||||||
<constraint firstItem="wT9-2J-uSY" firstAttribute="centerY" secondItem="dgG-dR-lSv" secondAttribute="bottom" id="7gb-T3-Xe7"/>
|
<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="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="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="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="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 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="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="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"/>
|
<constraint firstItem="wT9-2J-uSY" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="WNS-AR-ff2"/>
|
||||||
|
@ -144,6 +153,7 @@
|
||||||
<constraint firstItem="cr8-p9-xkc" firstAttribute="trailing" secondItem="vFa-g3-xIP" secondAttribute="leading" constant="-8" id="f1L-S8-l6H"/>
|
<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="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="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 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 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"/>
|
<constraint firstItem="u4P-3i-gEq" firstAttribute="top" secondItem="wT9-2J-uSY" secondAttribute="bottom" priority="999" constant="8" id="tKQ-6d-Z55"/>
|
||||||
|
@ -171,12 +181,15 @@
|
||||||
<resources>
|
<resources>
|
||||||
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
<image name="ellipsis" catalog="system" width="128" height="37"/>
|
||||||
<image name="lock.fill" catalog="system" width="125" height="128"/>
|
<image name="lock.fill" catalog="system" width="125" height="128"/>
|
||||||
<image name="person.badge.plus" catalog="system" width="128" height="124"/>
|
<image name="person.badge.plus" catalog="system" width="128" height="125"/>
|
||||||
<systemColor name="labelColor">
|
<systemColor name="labelColor">
|
||||||
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
<systemColor name="secondaryLabelColor">
|
<systemColor name="secondaryLabelColor">
|
||||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<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"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
<systemColor name="systemBackgroundColor">
|
<systemColor name="systemBackgroundColor">
|
||||||
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
// https://help.apple.com/xcode/#/dev745c5c974
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
MARKETING_VERSION = 2023.8
|
MARKETING_VERSION = 2023.8
|
||||||
CURRENT_PROJECT_VERSION = 107
|
CURRENT_PROJECT_VERSION = 106
|
||||||
CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION))
|
CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION))
|
||||||
|
|
||||||
CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev
|
CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev
|
||||||
|
|
Loading…
Reference in New Issue