Compare commits
No commits in common. "ca764811ed9c5e99f0bd7599141f98f7e3743836" and "798e0c0cf12c5c7ae5363512de30d9d2450679b6" have entirely different histories.
ca764811ed
...
798e0c0cf1
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,16 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 2024.2 (122)
|
||||
Features/Improvements:
|
||||
- Show instance announcements in Notifications
|
||||
- Pleroma/Akkoma: Display emoji reactions in Notifications
|
||||
- Pleroma/Akkoma: Add push notifications for emoji reactions
|
||||
|
||||
Bugfixes:
|
||||
- Fix issue fetching server info on some instances
|
||||
- Fix Preferences background color not updating after changing Pure Black Dark Mode
|
||||
- Fix push subscription settings background using incorrect color with Pure Black Dark Mode off
|
||||
|
||||
## 2024.2 (121)
|
||||
This build introduces a new multi-column navigation mode on iPad. You can revert to the old mode under Preferences -> Appearance.
|
||||
|
||||
|
|
|
@ -109,12 +109,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||
kindStr = "📊 Poll finished"
|
||||
case .update:
|
||||
kindStr = "✏️ Edited"
|
||||
case .emojiReaction:
|
||||
if let emoji = notification.emoji {
|
||||
kindStr = "\(emoji) Reacted"
|
||||
} else {
|
||||
kindStr = nil
|
||||
}
|
||||
default:
|
||||
kindStr = nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import SwiftUI
|
||||
import Pachyderm
|
||||
import Combine
|
||||
import TuskerComponents
|
||||
|
||||
class AutocompleteEmojisController: ViewController {
|
||||
unowned let composeController: ComposeController
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import SwiftUI
|
||||
import Combine
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
|
||||
class AutocompleteHashtagsController: ViewController {
|
||||
unowned let composeController: ComposeController
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// FuzzyMatcher.swift
|
||||
// TuskerComponents
|
||||
// ComposeUI
|
||||
//
|
||||
// Created by Shadowfacts on 10/10/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct FuzzyMatcher {
|
||||
struct FuzzyMatcher {
|
||||
|
||||
private init() {}
|
||||
|
||||
|
@ -21,7 +21,7 @@ public struct FuzzyMatcher {
|
|||
/// +2 points for every char in `pattern` that occurs in `str` sequentially
|
||||
/// -2 points for every char in `pattern` that does not occur in `str` sequentially
|
||||
/// -1 point for every char in `str` skipped between matching chars from the `pattern`
|
||||
public static func match(pattern: String, str: String) -> (matched: Bool, score: Int) {
|
||||
static func match(pattern: String, str: String) -> (matched: Bool, score: Int) {
|
||||
let pattern = pattern.lowercased()
|
||||
let str = str.lowercased()
|
||||
|
|
@ -209,14 +209,6 @@ public final class InstanceFeatures: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
public var instanceAnnouncements: Bool {
|
||||
hasMastodonVersion(3, 1, 0)
|
||||
}
|
||||
|
||||
public var emojiReactionNotifications: Bool {
|
||||
instanceType.isPleroma
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
//
|
||||
// Announcement.swift
|
||||
// Pachyderm
|
||||
//
|
||||
// Created by Shadowfacts on 4/16/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Announcement: Decodable, Sendable, Hashable, Identifiable {
|
||||
public let id: String
|
||||
public let content: String
|
||||
public let startsAt: Date?
|
||||
public let endsAt: Date?
|
||||
public let allDay: Bool
|
||||
public let publishedAt: Date
|
||||
public let updatedAt: Date
|
||||
public let read: Bool?
|
||||
public let mentions: [Account]
|
||||
public let statuses: [Status]
|
||||
public let tags: [Hashtag]
|
||||
public let emojis: [Emoji]
|
||||
public var reactions: [Reaction]
|
||||
|
||||
public static func all() -> Request<[Announcement]> {
|
||||
return Request(method: .get, path: "/api/v1/announcements")
|
||||
}
|
||||
|
||||
public static func dismiss(id: String) -> Request<Empty> {
|
||||
return Request(method: .post, path: "/api/v1/announcements/\(id)/dismiss")
|
||||
}
|
||||
|
||||
public static func react(id: String, name: String) -> Request<Empty> {
|
||||
return Request(method: .put, path: "/api/v1/announcements/\(id)/reactions/\(name)")
|
||||
}
|
||||
|
||||
public static func unreact(id: String, name: String) -> Request<Empty> {
|
||||
return Request(method: .delete, path: "/api/v1/announcements/\(id)/reactions/\(name)")
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case content
|
||||
case startsAt = "starts_at"
|
||||
case endsAt = "ends_at"
|
||||
case allDay = "all_day"
|
||||
case publishedAt = "published_at"
|
||||
case updatedAt = "updated_at"
|
||||
case read
|
||||
case mentions
|
||||
case statuses
|
||||
case tags
|
||||
case emojis
|
||||
case reactions
|
||||
}
|
||||
}
|
||||
|
||||
extension Announcement {
|
||||
public struct Account: Decodable, Sendable, Hashable {
|
||||
public let id: String
|
||||
public let username: String
|
||||
public let url: WebURL
|
||||
public let acct: String
|
||||
}
|
||||
}
|
||||
|
||||
extension Announcement {
|
||||
public struct Status: Decodable, Sendable, Hashable {
|
||||
public let id: String
|
||||
public let url: WebURL
|
||||
}
|
||||
}
|
||||
|
||||
extension Announcement {
|
||||
public struct Reaction: Decodable, Sendable, Hashable {
|
||||
public let name: String
|
||||
public var count: Int
|
||||
public var me: Bool?
|
||||
public let url: URL?
|
||||
public let staticURL: URL?
|
||||
|
||||
public init(name: String, count: Int, me: Bool?, url: URL?, staticURL: URL?) {
|
||||
self.name = name
|
||||
self.count = count
|
||||
self.me = me
|
||||
self.url = url
|
||||
self.staticURL = staticURL
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case count
|
||||
case me
|
||||
case url
|
||||
case staticURL = "static_url"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,13 +43,8 @@ extension Emoji: CustomDebugStringConvertible {
|
|||
}
|
||||
}
|
||||
|
||||
extension Emoji: Equatable, Hashable {
|
||||
extension Emoji: Equatable {
|
||||
public static func ==(lhs: Emoji, rhs: Emoji) -> Bool {
|
||||
return lhs.shortcode == rhs.shortcode && lhs.url == rhs.url
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(shortcode)
|
||||
hasher.combine(url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ extension InstanceV2 {
|
|||
public struct Thumbnail: Decodable, Sendable {
|
||||
public let url: String
|
||||
public let blurhash: String?
|
||||
public let versions: ThumbnailVersions?
|
||||
public let versions: ThumbnailVersions
|
||||
}
|
||||
|
||||
public struct ThumbnailVersions: Decodable, Sendable {
|
||||
|
@ -120,6 +120,6 @@ extension InstanceV2 {
|
|||
extension InstanceV2 {
|
||||
public struct Contact: Decodable, Sendable {
|
||||
public let email: String
|
||||
public let account: Account?
|
||||
public let account: Account
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct Notification: Decodable, Sendable {
|
||||
public let id: String
|
||||
|
@ -15,10 +14,6 @@ public struct Notification: Decodable, Sendable {
|
|||
public let createdAt: Date
|
||||
public let account: Account
|
||||
public let status: Status?
|
||||
// Only present for pleroma emoji reactions
|
||||
// Either an emoji or :shortcode: (for akkoma custom emoji reactions)
|
||||
public let emoji: String?
|
||||
public let emojiURL: WebURL?
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
@ -32,8 +27,6 @@ public struct Notification: Decodable, Sendable {
|
|||
self.createdAt = try container.decode(Date.self, forKey: .createdAt)
|
||||
self.account = try container.decode(Account.self, forKey: .account)
|
||||
self.status = try container.decodeIfPresent(Status.self, forKey: .status)
|
||||
self.emoji = try container.decodeIfPresent(String.self, forKey: .emoji)
|
||||
self.emojiURL = try container.decodeIfPresent(WebURL.self, forKey: .emojiURL)
|
||||
}
|
||||
|
||||
public static func dismiss(id notificationID: String) -> Request<Empty> {
|
||||
|
@ -46,8 +39,6 @@ public struct Notification: Decodable, Sendable {
|
|||
case createdAt = "created_at"
|
||||
case account
|
||||
case status
|
||||
case emoji
|
||||
case emojiURL = "emoji_url"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +52,6 @@ extension Notification {
|
|||
case poll
|
||||
case update
|
||||
case status
|
||||
case emojiReaction = "pleroma:emoji_reaction"
|
||||
case unknown
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ public struct PushSubscription: Decodable, Sendable {
|
|||
"data[alerts][favourite]" => alerts.favourite,
|
||||
"data[alerts][poll]" => alerts.poll,
|
||||
"data[alerts][update]" => alerts.update,
|
||||
"data[alerts][pleroma:emoji_reaction]" => alerts.emojiReaction,
|
||||
"data[policy]" => policy.rawValue,
|
||||
]))
|
||||
}
|
||||
|
@ -59,7 +58,6 @@ public struct PushSubscription: Decodable, Sendable {
|
|||
"data[alerts][favourite]" => alerts.favourite,
|
||||
"data[alerts][poll]" => alerts.poll,
|
||||
"data[alerts][update]" => alerts.update,
|
||||
"data[alerts][pleroma:emoji_reaction]" => alerts.emojiReaction,
|
||||
"data[policy]" => policy.rawValue,
|
||||
]))
|
||||
}
|
||||
|
@ -87,19 +85,8 @@ extension PushSubscription {
|
|||
public let favourite: Bool
|
||||
public let poll: Bool
|
||||
public let update: Bool
|
||||
public let emojiReaction: Bool
|
||||
|
||||
public init(
|
||||
mention: Bool,
|
||||
status: Bool,
|
||||
reblog: Bool,
|
||||
follow: Bool,
|
||||
followRequest: Bool,
|
||||
favourite: Bool,
|
||||
poll: Bool,
|
||||
update: Bool,
|
||||
emojiReaction: Bool
|
||||
) {
|
||||
public init(mention: Bool, status: Bool, reblog: Bool, follow: Bool, followRequest: Bool, favourite: Bool, poll: Bool, update: Bool) {
|
||||
self.mention = mention
|
||||
self.status = status
|
||||
self.reblog = reblog
|
||||
|
@ -108,7 +95,6 @@ extension PushSubscription {
|
|||
self.favourite = favourite
|
||||
self.poll = poll
|
||||
self.update = update
|
||||
self.emojiReaction = emojiReaction
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
|
@ -124,8 +110,6 @@ extension PushSubscription {
|
|||
self.poll = try container.decode(Bool.self, forKey: PushSubscription.Alerts.CodingKeys.poll)
|
||||
// update added in mastodon 3.5.0
|
||||
self.update = try container.decodeIfPresent(Bool.self, forKey: PushSubscription.Alerts.CodingKeys.update) ?? false
|
||||
// pleroma/akkoma only
|
||||
self.emojiReaction = try container.decodeIfPresent(Bool.self, forKey: .emojiReaction) ?? false
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
|
@ -137,7 +121,6 @@ extension PushSubscription {
|
|||
case favourite
|
||||
case poll
|
||||
case update
|
||||
case emojiReaction = "pleroma:emoji_reaction"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,12 +124,6 @@ public final class Status: StatusProtocol, Decodable, Sendable {
|
|||
return request
|
||||
}
|
||||
|
||||
public static func getReactions(_ statusID: String, emoji: String, range: RequestRange = .default) -> Request<[Account]> {
|
||||
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(statusID)/reactions/\(emoji)")
|
||||
request.range = range
|
||||
return request
|
||||
}
|
||||
|
||||
public static func delete(_ statusID: String) -> Request<Empty> {
|
||||
return Request<Empty>(method: .delete, path: "/api/v1/statuses/\(statusID)")
|
||||
}
|
||||
|
|
|
@ -7,18 +7,17 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import WebURL
|
||||
|
||||
public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
||||
public private(set) var notifications: [Notification]
|
||||
public let id: String
|
||||
public let kind: Kind
|
||||
public let kind: Notification.Kind
|
||||
|
||||
public init?(notifications: [Notification], kind: Kind) {
|
||||
public init?(notifications: [Notification]) {
|
||||
guard !notifications.isEmpty else { return nil }
|
||||
self.notifications = notifications
|
||||
self.id = notifications.first!.id
|
||||
self.kind = kind
|
||||
self.kind = notifications.first!.kind
|
||||
}
|
||||
|
||||
public static func ==(lhs: NotificationGroup, rhs: NotificationGroup) -> Bool {
|
||||
|
@ -45,61 +44,30 @@ public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
|||
notifications.append(contentsOf: group.notifications)
|
||||
}
|
||||
|
||||
private static func groupKind(for notification: Notification) -> Kind {
|
||||
switch notification.kind {
|
||||
case .mention:
|
||||
return .mention
|
||||
case .reblog:
|
||||
return .reblog
|
||||
case .favourite:
|
||||
return .favourite
|
||||
case .follow:
|
||||
return .follow
|
||||
case .followRequest:
|
||||
return .followRequest
|
||||
case .poll:
|
||||
return .poll
|
||||
case .update:
|
||||
return .update
|
||||
case .status:
|
||||
return .status
|
||||
case .emojiReaction:
|
||||
if let emoji = notification.emoji {
|
||||
return .emojiReaction(emoji, notification.emojiURL)
|
||||
} else {
|
||||
return .unknown
|
||||
}
|
||||
case .unknown:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
public static func createGroups(notifications: [Notification], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {
|
||||
var groups = [NotificationGroup]()
|
||||
for notification in notifications {
|
||||
let groupKind = groupKind(for: notification)
|
||||
|
||||
if allowedTypes.contains(notification.kind) {
|
||||
if let lastGroup = groups.last, canMerge(notification: notification, kind: groupKind, into: lastGroup) {
|
||||
if let lastGroup = groups.last, canMerge(notification: notification, into: lastGroup) {
|
||||
groups[groups.count - 1].append(notification)
|
||||
continue
|
||||
} else if groups.count >= 2 {
|
||||
let secondToLastGroup = groups[groups.count - 2]
|
||||
if allowedTypes.contains(notification.kind), canMerge(notification: notification, kind: groupKind, into: secondToLastGroup) {
|
||||
if allowedTypes.contains(groups[groups.count - 1].kind), canMerge(notification: notification, into: secondToLastGroup) {
|
||||
groups[groups.count - 2].append(notification)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
groups.append(NotificationGroup(notifications: [notification], kind: groupKind)!)
|
||||
groups.append(NotificationGroup(notifications: [notification])!)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
private static func canMerge(notification: Notification, kind: Kind, into group: NotificationGroup) -> Bool {
|
||||
return kind == group.kind && notification.status?.id == group.notifications.first!.status?.id
|
||||
private static func canMerge(notification: Notification, into group: NotificationGroup) -> Bool {
|
||||
return notification.kind == group.kind && notification.status?.id == group.notifications.first!.status?.id
|
||||
}
|
||||
|
||||
public static func mergeGroups(first: [NotificationGroup], second: [NotificationGroup], only allowedTypes: [Notification.Kind]) -> [NotificationGroup] {
|
||||
|
@ -114,21 +82,21 @@ public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
|||
var second = second
|
||||
merged.reserveCapacity(second.count)
|
||||
while let firstGroupFromSecond = second.first,
|
||||
allowedTypes.contains(firstGroupFromSecond.kind.notificationKind) {
|
||||
allowedTypes.contains(firstGroupFromSecond.kind) {
|
||||
|
||||
second.removeFirst()
|
||||
|
||||
guard let lastGroup = merged.last,
|
||||
allowedTypes.contains(lastGroup.kind.notificationKind) else {
|
||||
allowedTypes.contains(lastGroup.kind) else {
|
||||
merged.append(firstGroupFromSecond)
|
||||
break
|
||||
}
|
||||
|
||||
if canMerge(notification: firstGroupFromSecond.notifications.first!, kind: firstGroupFromSecond.kind, into: lastGroup) {
|
||||
if canMerge(notification: firstGroupFromSecond.notifications.first!, into: lastGroup) {
|
||||
merged[merged.count - 1].append(group: firstGroupFromSecond)
|
||||
} else if merged.count >= 2 {
|
||||
let secondToLastGroup = merged[merged.count - 2]
|
||||
if allowedTypes.contains(secondToLastGroup.kind.notificationKind), canMerge(notification: firstGroupFromSecond.notifications.first!, kind: firstGroupFromSecond.kind, into: secondToLastGroup) {
|
||||
if allowedTypes.contains(secondToLastGroup.kind), canMerge(notification: firstGroupFromSecond.notifications.first!, into: secondToLastGroup) {
|
||||
merged[merged.count - 2].append(group: firstGroupFromSecond)
|
||||
} else {
|
||||
merged.append(firstGroupFromSecond)
|
||||
|
@ -141,42 +109,4 @@ public struct NotificationGroup: Identifiable, Hashable, Sendable {
|
|||
return merged
|
||||
}
|
||||
|
||||
public enum Kind: Sendable, Equatable {
|
||||
case mention
|
||||
case reblog
|
||||
case favourite
|
||||
case follow
|
||||
case followRequest
|
||||
case poll
|
||||
case update
|
||||
case status
|
||||
case emojiReaction(String, WebURL?)
|
||||
case unknown
|
||||
|
||||
var notificationKind: Notification.Kind {
|
||||
switch self {
|
||||
case .mention:
|
||||
.mention
|
||||
case .reblog:
|
||||
.reblog
|
||||
case .favourite:
|
||||
.favourite
|
||||
case .follow:
|
||||
.follow
|
||||
case .followRequest:
|
||||
.followRequest
|
||||
case .poll:
|
||||
.poll
|
||||
case .update:
|
||||
.update
|
||||
case .status:
|
||||
.status
|
||||
case .emojiReaction(_, _):
|
||||
.emojiReaction
|
||||
case .unknown:
|
||||
.unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -70,7 +70,6 @@ public struct PushSubscription {
|
|||
public static let favorite = Alerts(rawValue: 1 << 5)
|
||||
public static let poll = Alerts(rawValue: 1 << 6)
|
||||
public static let update = Alerts(rawValue: 1 << 7)
|
||||
public static let emojiReaction = Alerts(rawValue: 1 << 8)
|
||||
|
||||
public let rawValue: Int
|
||||
|
||||
|
|
|
@ -74,9 +74,4 @@ extension PreferenceStore {
|
|||
public func hasFeatureFlag(_ flag: FeatureFlag) -> Bool {
|
||||
enabledFeatureFlags.contains(flag)
|
||||
}
|
||||
|
||||
|
||||
public func getValue<Key: PreferenceKey>(preferenceKeyPath: KeyPath<PreferenceStore, PreferencePublisher<Key>>) -> Key.Value {
|
||||
self[keyPath: preferenceKeyPath].preference.wrappedValue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,12 +227,6 @@
|
|||
D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */; };
|
||||
D6969E9E240C81B9002843CE /* NSTextAttachment+Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */; };
|
||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
|
||||
D698F4672BD079800054DB14 /* AnnouncementsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F4662BD079800054DB14 /* AnnouncementsHostingController.swift */; };
|
||||
D698F4692BD0799F0054DB14 /* AnnouncementsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F4682BD0799F0054DB14 /* AnnouncementsView.swift */; };
|
||||
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46A2BD079F00054DB14 /* AnnouncementListRow.swift */; };
|
||||
D698F46D2BD0B8310054DB14 /* AnnouncementsCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */; };
|
||||
D698F46F2BD0B8DF0054DB14 /* AddReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */; };
|
||||
D698F4712BD0CBAA0054DB14 /* AnnouncementContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */; };
|
||||
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; };
|
||||
D6A3A380295515550036B6EF /* ProfileHeaderButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */; };
|
||||
D6A3A3822956123A0036B6EF /* TimelinePosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3A3812956123A0036B6EF /* TimelinePosition.swift */; };
|
||||
|
@ -297,7 +291,6 @@
|
|||
D6C3F5192991F5D60009FCFF /* TimelineJumpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C3F5182991F5D60009FCFF /* TimelineJumpButton.swift */; };
|
||||
D6C4532D2BCB86AC00E26A0E /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532C2BCB86AC00E26A0E /* AppearancePrefsView.swift */; };
|
||||
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C4532E2BCB873400E26A0E /* MockStatusView.swift */; };
|
||||
D6C453372BCE1CEF00E26A0E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = D6C4532A2BCAD7F900E26A0E /* PrivacyInfo.xcprivacy */; };
|
||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693EE216192C2007D6A6D /* TuskerNavigationDelegate.swift */; };
|
||||
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FB2162FE6F007D6A6D /* LoadingViewController.swift */; };
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C693FD2162FEEA007D6A6D /* UIViewController+Children.swift */; };
|
||||
|
@ -656,12 +649,6 @@
|
|||
D69693F925859A8000F4E116 /* ComposeSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextAttachment+Emoji.swift"; sourceTree = "<group>"; };
|
||||
D6969E9F240C8384002843CE /* EmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLabel.swift; sourceTree = "<group>"; };
|
||||
D698F4662BD079800054DB14 /* AnnouncementsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsHostingController.swift; sourceTree = "<group>"; };
|
||||
D698F4682BD0799F0054DB14 /* AnnouncementsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsView.swift; sourceTree = "<group>"; };
|
||||
D698F46A2BD079F00054DB14 /* AnnouncementListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementListRow.swift; sourceTree = "<group>"; };
|
||||
D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementsCollection.swift; sourceTree = "<group>"; };
|
||||
D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddReactionView.swift; sourceTree = "<group>"; };
|
||||
D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnouncementContentTextView.swift; sourceTree = "<group>"; };
|
||||
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionsView.swift; sourceTree = "<group>"; };
|
||||
D6A3A37F295515550036B6EF /* ProfileHeaderButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileHeaderButton.swift; sourceTree = "<group>"; };
|
||||
D6A3A3812956123A0036B6EF /* TimelinePosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePosition.swift; sourceTree = "<group>"; };
|
||||
|
@ -1067,7 +1054,6 @@
|
|||
children = (
|
||||
D65B4B89297879DE00DABDFB /* Account Follows */,
|
||||
D6A3BC822321F69400FD64D5 /* Account List */,
|
||||
D698F4472BCEE2320054DB14 /* Announcements */,
|
||||
D641C787213DD862004B4513 /* Compose */,
|
||||
D641C785213DD83B004B4513 /* Conversation */,
|
||||
D6F2E960249E772F005846BB /* Crash Reporter */,
|
||||
|
@ -1363,19 +1349,6 @@
|
|||
path = About;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D698F4472BCEE2320054DB14 /* Announcements */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D698F4662BD079800054DB14 /* AnnouncementsHostingController.swift */,
|
||||
D698F46C2BD0B8310054DB14 /* AnnouncementsCollection.swift */,
|
||||
D698F4682BD0799F0054DB14 /* AnnouncementsView.swift */,
|
||||
D698F46A2BD079F00054DB14 /* AnnouncementListRow.swift */,
|
||||
D698F4702BD0CBAA0054DB14 /* AnnouncementContentTextView.swift */,
|
||||
D698F46E2BD0B8DF0054DB14 /* AddReactionView.swift */,
|
||||
);
|
||||
path = Announcements;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D6A3BC822321F69400FD64D5 /* Account List */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1981,7 +1954,6 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6C453372BCE1CEF00E26A0E /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -2157,7 +2129,6 @@
|
|||
D6934F3C2BAA0F80002B1C8D /* VideoGalleryContentViewController.swift in Sources */,
|
||||
D6F6A552291F098700F496A8 /* RenameListService.swift in Sources */,
|
||||
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */,
|
||||
D698F46B2BD079F00054DB14 /* AnnouncementListRow.swift in Sources */,
|
||||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */,
|
||||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */,
|
||||
D6934F362BA8E020002B1C8D /* GifvGalleryContentViewController.swift in Sources */,
|
||||
|
@ -2188,10 +2159,8 @@
|
|||
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */,
|
||||
D6E77D0F286F773900D8B732 /* SplitNavigationController.swift in Sources */,
|
||||
D6B30E09254BAF63009CAEE5 /* ImageGrayscalifier.swift in Sources */,
|
||||
D698F4692BD0799F0054DB14 /* AnnouncementsView.swift in Sources */,
|
||||
D6A6C10F25B62D2400298D0F /* DiskCache.swift in Sources */,
|
||||
D6C3F4F9298EDBF20009FCFF /* ConversationTree.swift in Sources */,
|
||||
D698F46F2BD0B8DF0054DB14 /* AddReactionView.swift in Sources */,
|
||||
D6B81F3C2560365300F6E31D /* RefreshableViewController.swift in Sources */,
|
||||
D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */,
|
||||
D6DD8FFF2984D327002AD3FD /* BookmarksViewController.swift in Sources */,
|
||||
|
@ -2219,7 +2188,6 @@
|
|||
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */,
|
||||
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */,
|
||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
|
||||
D698F4672BD079800054DB14 /* AnnouncementsHostingController.swift in Sources */,
|
||||
D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */,
|
||||
D65B4B6629773AE600DABDFB /* DeleteStatusService.swift in Sources */,
|
||||
D61DC84628F498F200B82C6E /* Logging.swift in Sources */,
|
||||
|
@ -2355,7 +2323,6 @@
|
|||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */,
|
||||
D621733328F1D5ED004C7DB1 /* ReblogService.swift in Sources */,
|
||||
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */,
|
||||
D698F46D2BD0B8310054DB14 /* AnnouncementsCollection.swift in Sources */,
|
||||
D61F75AF293AF50C00C0B37F /* EditedFilter.swift in Sources */,
|
||||
D691771929A7B8820054D7EF /* ProfileHeaderMovedOverlayView.swift in Sources */,
|
||||
D61F75B9293C15A000C0B37F /* ZeroHeightCollectionViewCell.swift in Sources */,
|
||||
|
@ -2400,7 +2367,6 @@
|
|||
D6934F2E2BA7AEF9002B1C8D /* StatusAttachmentsGalleryDataSource.swift in Sources */,
|
||||
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */,
|
||||
D6C4532F2BCB873400E26A0E /* MockStatusView.swift in Sources */,
|
||||
D698F4712BD0CBAA0054DB14 /* AnnouncementContentTextView.swift in Sources */,
|
||||
D68A76E829527884001DA1B3 /* PinnedTimelinesView.swift in Sources */,
|
||||
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */,
|
||||
D6D12B5A292D684600D528E1 /* AccountListViewController.swift in Sources */,
|
||||
|
|
|
@ -58,8 +58,7 @@ private extension Pachyderm.PushSubscription.Alerts {
|
|||
followRequest: alerts.contains(.followRequest),
|
||||
favourite: alerts.contains(.favorite),
|
||||
poll: alerts.contains(.poll),
|
||||
update: alerts.contains(.update),
|
||||
emojiReaction: alerts.contains(.emojiReaction)
|
||||
update: alerts.contains(.update)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"symbols" : [
|
||||
{
|
||||
"filename" : "face.smiling.badge.plus.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--Generator: Apple Native CoreSVG 232.5-->
|
||||
<!DOCTYPE svg
|
||||
PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3300" height="2200">
|
||||
<!--glyph: "", point size: 100.0, font version: "19.2d2e1", template writer version: "128"-->
|
||||
<style>.monochrome-0 {-sfsymbols-motion-group:1}
|
||||
.monochrome-1 {-sfsymbols-motion-group:1}
|
||||
.monochrome-2 {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0}
|
||||
.monochrome-3 {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true}
|
||||
.monochrome-4 {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true}
|
||||
|
||||
.multicolor-0:tintColor {-sfsymbols-motion-group:1}
|
||||
.multicolor-1:tintColor {-sfsymbols-motion-group:1}
|
||||
.multicolor-2:tintColor {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0}
|
||||
.multicolor-3:systemGreenColor {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true}
|
||||
.multicolor-4:white {-sfsymbols-motion-group:0;-sfsymbols-always-pulses:true}
|
||||
|
||||
.hierarchical-0:secondary {-sfsymbols-motion-group:1}
|
||||
.hierarchical-1:secondary {-sfsymbols-motion-group:1}
|
||||
.hierarchical-2:primary {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0}
|
||||
.hierarchical-3:primary {-sfsymbols-motion-group:0}
|
||||
.hierarchical-4:primary {opacity:0.0;-sfsymbols-clear-behind:true;-sfsymbols-motion-group:0}
|
||||
|
||||
.SFSymbolsPreviewWireframe {fill:none;opacity:1.0;stroke:black;stroke-width:0.5}
|
||||
</style>
|
||||
<g id="Notes">
|
||||
<rect height="2200" id="artboard" style="fill:white;opacity:1" width="3300" x="0" y="0"/>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="292" y2="292"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 322)">Weight/Scale Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 559.711 322)">Ultralight</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 856.422 322)">Thin</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1153.13 322)">Light</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1449.84 322)">Regular</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 1746.56 322)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2043.27 322)">Semibold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2339.98 322)">Bold</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2636.69 322)">Heavy</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:middle;" transform="matrix(1 0 0 1 2933.4 322)">Black</text>
|
||||
<line style="fill:none;stroke:black;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1903" y2="1903"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 263 1933)">
|
||||
<path d="m46.2402 4.15039c21.5332 0 39.4531-17.8711 39.4531-39.4043s-17.9688-39.4043-39.502-39.4043c-21.4844 0-39.3555 17.8711-39.3555 39.4043s17.9199 39.4043 39.4043 39.4043Zm0-7.42188c-17.7246 0-31.8848-14.209-31.8848-31.9824s14.1113-31.9824 31.8359-31.9824c17.7734 0 32.0312 14.209 32.0312 31.9824s-14.209 31.9824-31.9824 31.9824Zm-17.9688-31.9824c0 2.14844 1.51367 3.61328 3.75977 3.61328h10.498v10.5957c0 2.19727 1.46484 3.71094 3.61328 3.71094 2.24609 0 3.71094-1.51367 3.71094-3.71094v-10.5957h10.5957c2.19727 0 3.71094-1.46484 3.71094-3.61328 0-2.19727-1.51367-3.71094-3.71094-3.71094h-10.5957v-10.5469c0-2.24609-1.46484-3.75977-3.71094-3.75977-2.14844 0-3.61328 1.51367-3.61328 3.75977v10.5469h-10.498c-2.24609 0-3.75977 1.51367-3.75977 3.71094Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 281.506 1933)">
|
||||
<path d="m58.5449 14.5508c27.2461 0 49.8047-22.6074 49.8047-49.8047 0-27.2461-22.6074-49.8047-49.8535-49.8047-27.1973 0-49.7559 22.5586-49.7559 49.8047 0 27.1973 22.6074 49.8047 49.8047 49.8047Zm0-8.30078c-23.0469 0-41.4551-18.457-41.4551-41.5039s18.3594-41.5039 41.4062-41.5039 41.5527 18.457 41.5527 41.5039-18.457 41.5039-41.5039 41.5039Zm-22.6562-41.5039c0 2.39258 1.66016 4.00391 4.15039 4.00391h14.3555v14.4043c0 2.44141 1.66016 4.15039 4.05273 4.15039 2.44141 0 4.15039-1.66016 4.15039-4.15039v-14.4043h14.4043c2.44141 0 4.15039-1.61133 4.15039-4.00391 0-2.44141-1.70898-4.15039-4.15039-4.15039h-14.4043v-14.3555c0-2.49023-1.70898-4.19922-4.15039-4.19922-2.39258 0-4.05273 1.70898-4.05273 4.19922v14.3555h-14.3555c-2.49023 0-4.15039 1.70898-4.15039 4.15039Z"/>
|
||||
</g>
|
||||
<g transform="matrix(0.2 0 0 0.2 304.924 1933)">
|
||||
<path d="m74.8535 28.3203c34.8145 0 63.623-28.8086 63.623-63.5742 0-34.8145-28.8574-63.623-63.6719-63.623-34.7656 0-63.5254 28.8086-63.5254 63.623 0 34.7656 28.8086 63.5742 63.5742 63.5742Zm0-9.08203c-30.1758 0-54.4434-24.3164-54.4434-54.4922 0-30.2246 24.2188-54.4922 54.3945-54.4922 30.2246 0 54.541 24.2676 54.541 54.4922 0 30.1758-24.2676 54.4922-54.4922 54.4922Zm-28.8574-54.4922c0 2.58789 1.85547 4.39453 4.58984 4.39453h19.7266v19.7754c0 2.68555 1.85547 4.58984 4.44336 4.58984 2.68555 0 4.54102-1.85547 4.54102-4.58984v-19.7754h19.7754c2.68555 0 4.58984-1.80664 4.58984-4.39453 0-2.73438-1.85547-4.58984-4.58984-4.58984h-19.7754v-19.7266c0-2.73438-1.85547-4.63867-4.54102-4.63867-2.58789 0-4.44336 1.9043-4.44336 4.63867v19.7266h-19.7266c-2.73438 0-4.58984 1.85547-4.58984 4.58984Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 263 1953)">Design Variations</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1971)">Symbols are supported in up to nine weights and three scales.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1989)">For optimal layout with text and other symbols, vertically align</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 2007)">symbols with the adjacent text.</text>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="776" x2="776" y1="1919" y2="1933"/>
|
||||
<g transform="matrix(0.2 0 0 0.2 776 1933)">
|
||||
<path d="m16.5527 0.78125c2.58789 0 3.85742-0.976562 4.78516-3.71094l6.29883-17.2363h28.8086l6.29883 17.2363c0.927734 2.73438 2.19727 3.71094 4.73633 3.71094 2.58789 0 4.24805-1.5625 4.24805-4.00391 0-0.830078-0.146484-1.61133-0.537109-2.63672l-22.9004-60.9863c-1.12305-2.97852-3.125-4.49219-6.25-4.49219-3.02734 0-5.07812 1.46484-6.15234 4.44336l-22.9004 61.084c-0.390625 1.02539-0.537109 1.80664-0.537109 2.63672 0 2.44141 1.5625 3.95508 4.10156 3.95508Zm13.4766-28.3691 11.8652-32.8613h0.244141l11.8652 32.8613Z"/>
|
||||
</g>
|
||||
<line style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="792.836" x2="792.836" y1="1919" y2="1933"/>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 776 1953)">Margins</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1971)">Leading and trailing margins on the left and right side of each symbol</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 1989)">can be adjusted by modifying the x-location of the margin guidelines.</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2007)">Modifications are automatically applied proportionally to all</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 776 2025)">scales and weights.</text>
|
||||
<g transform="matrix(0.2 0 0 0.2 1289 1933)">
|
||||
<path d="m14.209 9.32617 8.49609 8.54492c4.29688 4.3457 9.22852 4.05273 13.8672-1.07422l53.4668-58.9355-4.83398-4.88281-53.0762 58.3984c-1.75781 2.00195-3.41797 2.49023-5.76172 0.146484l-5.85938-5.81055c-2.34375-2.29492-1.80664-4.00391 0.195312-5.81055l57.373-54.0039-4.88281-4.83398-57.959 54.4434c-4.93164 4.58984-5.32227 9.47266-1.02539 13.8184Zm32.0801-90.9668c-2.09961 2.05078-2.24609 4.93164-1.07422 6.88477 1.17188 1.80664 3.4668 2.97852 6.68945 2.14844 7.32422-1.70898 14.9414-2.00195 22.0703 2.68555l-2.92969 7.27539c-1.70898 4.15039-0.830078 7.08008 1.85547 9.81445l11.4746 11.5723c2.44141 2.44141 4.49219 2.53906 7.32422 2.05078l5.32227-0.976562 3.32031 3.36914-0.195312 2.7832c-0.195312 2.49023 0.439453 4.39453 2.88086 6.78711l3.80859 3.71094c2.39258 2.39258 5.46875 2.53906 7.8125 0.195312l14.5508-14.5996c2.34375-2.34375 2.24609-5.32227-0.146484-7.71484l-3.85742-3.80859c-2.39258-2.39258-4.24805-3.17383-6.64062-2.97852l-2.88086 0.244141-3.22266-3.17383 1.2207-5.61523c0.634766-2.83203-0.146484-5.0293-3.07617-7.95898l-10.9863-10.9375c-16.6992-16.6016-38.8672-16.2109-53.3203-1.75781Zm7.4707 1.85547c12.1582-8.88672 28.6133-7.37305 39.7461 3.75977l12.1582 12.0605c1.17188 1.17188 1.36719 2.09961 1.02539 3.80859l-1.61133 7.42188 7.51953 7.42188 4.93164-0.292969c1.26953-0.0488281 1.66016 0.0488281 2.63672 1.02539l2.88086 2.88086-12.207 12.207-2.88086-2.88086c-0.976562-0.976562-1.12305-1.36719-1.07422-2.68555l0.341797-4.88281-7.4707-7.42188-7.61719 1.26953c-1.61133 0.341797-2.34375 0.195312-3.56445-0.976562l-10.0098-10.0098c-1.26953-1.17188-1.41602-2.00195-0.634766-3.85742l4.39453-10.4492c-7.8125-7.27539-17.9688-10.4004-28.125-7.42188-0.78125 0.195312-1.07422-0.439453-0.439453-0.976562Z"/>
|
||||
</g>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;font-weight:bold;" transform="matrix(1 0 0 1 1289 1953)">Exporting</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1971)">Symbols should be outlined when exporting to ensure the</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 1289 1989)">design is preserved when submitting to Xcode.</text>
|
||||
<text id="template-version" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1933)">Template v.5.0</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1951)">Requires Xcode 15 or greater</text>
|
||||
<text id="descriptive-name" style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1969)">Generated from </text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;text-anchor:end;" transform="matrix(1 0 0 1 3036 1987)">Typeset at 100.0 points</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 726)">Small</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1156)">Medium</text>
|
||||
<text style="stroke:none;fill:black;font-family:sans-serif;font-size:13;" transform="matrix(1 0 0 1 263 1586)">Large</text>
|
||||
</g>
|
||||
<g id="Guides">
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 696)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="696" y2="696"/>
|
||||
<line id="Capline-S" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="625.541" y2="625.541"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1126)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1126" y2="1126"/>
|
||||
<line id="Capline-M" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1055.54" y2="1055.54"/>
|
||||
<g id="H-reference" style="fill:#27AAE1;stroke:none;" transform="matrix(1 0 0 1 339 1556)">
|
||||
<path d="M0.993654 0L3.63775 0L29.3281-67.1323L30.0303-67.1323L30.0303-70.459L28.1226-70.459ZM11.6885-24.4799L46.9815-24.4799L46.2315-26.7285L12.4385-26.7285ZM55.1196 0L57.7637 0L30.6382-70.459L29.4326-70.459L29.4326-67.1323Z"/>
|
||||
</g>
|
||||
<line id="Baseline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1556" y2="1556"/>
|
||||
<line id="Capline-L" style="fill:none;stroke:#27AAE1;opacity:1;stroke-width:0.5;" x1="263" x2="3036" y1="1485.54" y2="1485.54"/>
|
||||
<line id="left-margin-Ultralight-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="515.649" x2="515.649" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Ultralight-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="603.773" x2="603.773" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Regular-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="1403.58" x2="1403.58" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Regular-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="1496.11" x2="1496.11" y1="600.785" y2="720.121"/>
|
||||
<line id="left-margin-Black-S" style="fill:none;stroke:#00AEEF;stroke-width:0.5;opacity:1.0;" x1="2884.57" x2="2884.57" y1="600.785" y2="720.121"/>
|
||||
<line id="right-margin-Black-S" style="fill:none;stroke:#FF3B30;stroke-width:0.5;opacity:1.0;" x1="2982.23" x2="2982.23" y1="600.785" y2="720.121"/>
|
||||
</g>
|
||||
<g id="Symbols">
|
||||
<g id="Black-S" transform="matrix(1 0 0 1 2884.57 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M48.8281 6.78711C71.9727 6.78711 90.8203-12.0605 90.8203-35.2051C90.8203-58.3496 71.9727-77.1973 48.8281-77.1973C25.6836-77.1973 6.83594-58.3496 6.83594-35.2051C6.83594-12.0605 25.6836 6.78711 48.8281 6.78711ZM48.8281-7.37305C33.4473-7.37305 20.9961-19.8242 20.9961-35.2051C20.9961-50.5859 33.4473-63.0371 48.8281-63.0371C64.209-63.0371 76.6602-50.5859 76.6602-35.2051C76.6602-19.8242 64.209-7.37305 48.8281-7.37305Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:secondary SFSymbolsPreviewWireframe" d="M48.8281-18.1641C56.9824-18.1641 62.4512-23.5352 62.4512-26.1719C62.4512-27.1973 61.4258-27.6855 60.4004-27.2461C57.4707-25.9766 54.3945-24.2676 48.8281-24.2676C43.2617-24.2676 40.0879-25.8789 37.2559-27.2461C36.2305-27.7344 35.2051-27.1973 35.2051-26.1719C35.2051-23.5352 40.625-18.1641 48.8281-18.1641ZM37.793-38.916C40.0879-38.916 42.0898-40.9668 42.0898-43.7988C42.0898-46.6797 40.0879-48.7305 37.793-48.7305C35.498-48.7305 33.5938-46.6797 33.5938-43.7988C33.5938-40.9668 35.498-38.916 37.793-38.916ZM59.8145-38.916C62.0605-38.916 64.1113-40.9668 64.1113-43.7988C64.1113-46.6797 62.0605-48.7305 59.8145-48.7305C57.4707-48.7305 55.5664-46.6797 55.5664-43.7988C55.5664-40.9668 57.4707-38.916 59.8145-38.916Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M87.8941 20.2949C102.836 20.2949 115.189 7.89255 115.189-7.04885C115.189-21.9903 102.836-34.2949 87.8941-34.2949C72.9527-34.2949 60.5992-21.9903 60.5992-7.04885C60.5992 7.89255 72.9527 20.2949 87.8941 20.2949Z"/>
|
||||
<path class="monochrome-3 multicolor-3:systemGreenColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M87.8941 13.9473C99.3688 13.9473 108.842 4.37695 108.842-7.04885C108.842-18.4746 99.3688-27.9472 87.8941-27.9472C76.4195-27.9472 66.9468-18.4746 66.9468-7.04885C66.9468 4.37695 76.4195 13.9473 87.8941 13.9473Z"/>
|
||||
<path class="monochrome-4 multicolor-4:white hierarchical-4:primary SFSymbolsPreviewWireframe" d="M87.8941 7.01365C85.5503 7.01365 83.9878 5.45115 83.9878 3.15625L83.9878-3.09375L77.8355-3.09375C75.5406-3.09375 73.9292-4.65625 73.9292-7.00005C73.9292-9.34375 75.4429-10.9062 77.8355-10.9062L83.9878-10.9062L83.9878-17.0097C83.9878-19.3047 85.5503-20.9161 87.8941-20.9161C90.2378-20.9161 91.8003-19.4023 91.8003-17.0097L91.8003-10.9062L98.0018-10.9062C100.297-10.9062 101.859-9.34375 101.859-7.00005C101.859-4.65625 100.297-3.09375 98.0018-3.09375L91.8003-3.09375L91.8003 3.15625C91.8003 5.45115 90.2378 7.01365 87.8941 7.01365Z"/>
|
||||
</g>
|
||||
<g id="Regular-S" transform="matrix(1 0 0 1 1403.58 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M46.2402 4.15039C67.7734 4.15039 85.6934-13.7207 85.6934-35.2539C85.6934-56.7871 67.7246-74.6582 46.1914-74.6582C24.707-74.6582 6.83594-56.7871 6.83594-35.2539C6.83594-13.7207 24.7559 4.15039 46.2402 4.15039ZM46.2402-3.27148C28.5156-3.27148 14.3555-17.4805 14.3555-35.2539C14.3555-53.0273 28.4668-67.2363 46.1914-67.2363C63.9648-67.2363 78.2227-53.0273 78.2227-35.2539C78.2227-17.4805 64.0137-3.27148 46.2402-3.27148Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:secondary SFSymbolsPreviewWireframe" d="M46.1914-15.8691C54.3457-15.8691 59.8145-21.2402 59.8145-23.877C59.8145-24.8535 58.8379-25.3418 57.8613-24.9512C54.9805-23.584 51.8066-21.875 46.1914-21.875C40.625-21.875 37.4512-23.584 34.5703-24.9512C33.5938-25.3418 32.6172-24.8535 32.6172-23.877C32.6172-21.2402 38.0859-15.8691 46.1914-15.8691ZM34.9121-38.5742C37.4512-38.5742 39.6973-40.7715 39.6973-43.9453C39.6973-47.2168 37.4512-49.4141 34.9121-49.4141C32.4219-49.4141 30.2246-47.2168 30.2246-43.9453C30.2246-40.7715 32.4219-38.5742 34.9121-38.5742ZM57.5195-38.5742C60.0586-38.5742 62.2559-40.7715 62.2559-43.9453C62.2559-47.2168 60.0586-49.4141 57.5195-49.4141C54.9805-49.4141 52.832-47.2168 52.832-43.9453C52.832-40.7715 54.9805-38.5742 57.5195-38.5742Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M83.277 18.3906C97.0956 18.3906 108.668 6.8184 108.668-7C108.668-20.916 97.1926-32.3906 83.277-32.3906C69.3121-32.3906 57.8864-20.916 57.8864-7C57.8864 6.9649 69.3121 18.3906 83.277 18.3906Z"/>
|
||||
<path class="monochrome-3 multicolor-3:systemGreenColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M83.277 12.6777C93.9216 12.6777 102.955 3.7422 102.955-7C102.955-17.791 94.0676-26.6777 83.277-26.6777C72.486-26.6777 63.5504-17.791 63.5504-7C63.5504 3.8399 72.486 12.6777 83.277 12.6777Z"/>
|
||||
<path class="monochrome-4 multicolor-4:white hierarchical-4:primary SFSymbolsPreviewWireframe" d="M83.277 5.2559C81.7145 5.2559 80.7379 4.2305 80.7379 2.7168L80.7379-4.4609L73.5602-4.4609C72.0465-4.4609 71.0211-5.4863 71.0211-7C71.0211-8.5137 72.0465-9.5391 73.5602-9.5391L80.7379-9.5391L80.7379-16.7168C80.7379-18.2305 81.7145-19.2559 83.277-19.2559C84.7906-19.2559 85.816-18.2305 85.816-16.7168L85.816-9.5391L92.9936-9.5391C94.5076-9.5391 95.4836-8.5137 95.4836-7C95.4836-5.4863 94.5076-4.4609 92.9936-4.4609L85.816-4.4609L85.816 2.7168C85.816 4.2305 84.7906 5.2559 83.277 5.2559Z"/>
|
||||
</g>
|
||||
<g id="Ultralight-S" transform="matrix(1 0 0 1 515.649 696)">
|
||||
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:secondary SFSymbolsPreviewWireframe" d="M44.0606 1.97072C64.5039 1.97072 81.2886-14.8105 81.2886-35.2539C81.2886-55.6973 64.5005-72.4785 44.0571-72.4785C23.5718-72.4785 6.83594-55.6973 6.83594-35.2539C6.83594-14.8105 23.5752 1.97072 44.0606 1.97072ZM44.0606-0.274438C24.7466-0.274438 9.04252-15.9365 9.04252-35.2539C9.04252-54.5713 24.7432-70.2334 44.0571-70.2334C63.3745-70.2334 79.04-54.5713 79.04-35.2539C79.04-15.9365 63.3779-0.274438 44.0606-0.274438Z"/>
|
||||
<path class="monochrome-1 multicolor-1:tintColor hierarchical-1:secondary SFSymbolsPreviewWireframe" d="M44.0571-17.0044C51.3032-17.0044 56.3633-22.103 56.3633-24.2856C56.3633-24.7627 55.9317-24.8423 55.6362-24.5879C53.4365-22.4487 49.4453-20.6489 44.0571-20.6489C38.6724-20.6489 34.7266-22.4941 32.4815-24.5879C32.186-24.8423 31.7544-24.7627 31.7544-24.2856C31.7544-22.103 36.8145-17.0044 44.0571-17.0044ZM32.5054-39.0283C34.4541-39.0283 36.2007-41.0439 36.2007-43.5366C36.2007-45.9907 34.4995-48.0064 32.5054-48.0064C30.5147-48.0064 28.8169-45.9907 28.8169-43.5366C28.8169-41.0439 30.5601-39.0283 32.5054-39.0283ZM55.6123-39.0283C57.5611-39.0283 59.3042-41.0439 59.3042-43.5366C59.3042-45.9907 57.6065-48.0064 55.6123-48.0064C53.6182-48.0064 51.9238-45.9907 51.9238-43.5366C51.9238-41.0439 53.6636-39.0283 55.6123-39.0283Z"/>
|
||||
<path class="monochrome-2 multicolor-2:tintColor hierarchical-2:primary SFSymbolsPreviewWireframe" d="M79.3341 14.3492C90.9727 14.3492 100.684 4.72955 100.684-6.99995C100.684-18.7362 91.0247-28.3492 79.3341-28.3492C67.6397-28.3492 57.9395-18.6908 57.9395-6.99995C57.9395 4.73985 67.6397 14.3492 79.3341 14.3492Z"/>
|
||||
<path class="monochrome-3 multicolor-3:systemGreenColor hierarchical-3:primary SFSymbolsPreviewWireframe" d="M79.3341 11.2701C89.2977 11.2701 97.6037 3.06105 97.6037-6.99995C97.6037-17.0644 89.3527-25.2699 79.3341-25.2699C69.315-25.2699 61.0152-17.019 61.0152-6.99995C61.0152 3.06795 69.315 11.2701 79.3341 11.2701Z"/>
|
||||
<path class="monochrome-4 multicolor-4:white hierarchical-4:primary SFSymbolsPreviewWireframe" d="M79.3341 4.89265C78.3619 4.89265 77.794 4.23055 77.794 3.35255L77.794-5.50535L68.9361-5.50535C68.149-5.50535 67.4415-6.03115 67.4415-6.99995C67.4415-7.96875 68.149-8.54005 68.9361-8.54005L77.794-8.54005L77.794-17.3071C77.794-18.1396 78.3619-18.8471 79.3341-18.8471C80.2574-18.8471 80.8287-18.1396 80.8287-17.3071L80.8287-8.54005L89.6407-8.54005C90.4737-8.54005 91.1327-7.96875 91.1327-6.99995C91.1327-6.03115 90.4737-5.50535 89.6407-5.50535L80.8287-5.50535L80.8287 3.35255C80.8287 4.23055 80.2574 4.89265 79.3341 4.89265Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 22 KiB |
|
@ -124,13 +124,12 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
|
|||
|
||||
// migrate saved data from local store to cloud store
|
||||
// this can be removed pre-app store release
|
||||
if !FileManager.default.fileExists(atPath: cloudStoreLocation.path) {
|
||||
var defaultPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
defaultPath.appendPathComponent("\(accountInfo!.persistenceKey)_cache.sqlite", isDirectory: false)
|
||||
if FileManager.default.fileExists(atPath: defaultPath.path) {
|
||||
group.enter()
|
||||
var defaultPath = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
||||
defaultPath.appendPathComponent("\(accountInfo!.persistenceKey)_cache.sqlite", isDirectory: false)
|
||||
let defaultDesc = NSPersistentStoreDescription(url: defaultPath)
|
||||
defaultDesc.configuration = "Default"
|
||||
defaultDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
||||
let defaultPSC = NSPersistentContainer(name: "\(accountInfo!.persistenceKey)_cache", managedObjectModel: MastodonCachePersistentStore.managedObjectModel)
|
||||
defaultPSC.persistentStoreDescriptions = [defaultDesc]
|
||||
defaultPSC.loadPersistentStores { _, error in
|
||||
|
|
|
@ -8,11 +8,26 @@
|
|||
|
||||
import SwiftUI
|
||||
import Combine
|
||||
import TuskerPreferences
|
||||
|
||||
extension View {
|
||||
func appGroupedListBackground(container: UIAppearanceContainer.Type) -> some View {
|
||||
self.modifier(AppGroupedListBackground(container: container))
|
||||
@MainActor
|
||||
@ViewBuilder
|
||||
func appGroupedListBackground(container: UIAppearanceContainer.Type, applyBackground: Bool = true) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
if applyBackground {
|
||||
self
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appGroupedBackground.edgesIgnoringSafeArea(.all))
|
||||
} else {
|
||||
self
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
} else {
|
||||
self
|
||||
.onAppear {
|
||||
UITableView.appearance(whenContainedInInstancesOf: [container]).backgroundColor = .appGroupedBackground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appGroupedListRowBackground() -> some View {
|
||||
|
@ -20,45 +35,11 @@ extension View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct AppGroupedListBackground: ViewModifier {
|
||||
let container: any UIAppearanceContainer.Type
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.pureBlackDarkMode) private var environmentPureBlackDarkMode
|
||||
|
||||
private var pureBlackDarkMode: Bool {
|
||||
// using @PreferenceObserving just does not work for this, so try the environment key when available
|
||||
// if it's not available, the color won't update automatically, but it will be correct when the view is created
|
||||
if #available(iOS 17.0, *) {
|
||||
environmentPureBlackDarkMode
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
if colorScheme == .dark, !pureBlackDarkMode {
|
||||
content
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.appGroupedBackground.edgesIgnoringSafeArea(.all))
|
||||
} else {
|
||||
content
|
||||
}
|
||||
} else {
|
||||
content
|
||||
.onAppear {
|
||||
UITableView.appearance(whenContainedInInstancesOf: [container]).backgroundColor = .appGroupedBackground
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppGroupedListRowBackground: ViewModifier {
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@PreferenceObserving(\.$pureBlackDarkMode) private var pureBlackDarkMode
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if colorScheme == .dark, !pureBlackDarkMode {
|
||||
if colorScheme == .dark, !Preferences.shared.pureBlackDarkMode {
|
||||
content
|
||||
.listRowBackground(Color.appGroupedCellBackground)
|
||||
} else {
|
||||
|
@ -66,31 +47,3 @@ private struct AppGroupedListRowBackground: ViewModifier {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@propertyWrapper
|
||||
private struct PreferenceObserving<Key: TuskerPreferences.PreferenceKey>: DynamicProperty {
|
||||
typealias PrefKeyPath = KeyPath<PreferenceStore, PreferencePublisher<Key>>
|
||||
|
||||
let keyPath: PrefKeyPath
|
||||
@StateObject private var observer: Observer
|
||||
|
||||
init(_ keyPath: PrefKeyPath) {
|
||||
self.keyPath = keyPath
|
||||
self._observer = StateObject(wrappedValue: Observer(keyPath: keyPath))
|
||||
}
|
||||
|
||||
var wrappedValue: Key.Value {
|
||||
Preferences.shared.getValue(preferenceKeyPath: keyPath)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private class Observer: ObservableObject {
|
||||
private var cancellable: AnyCancellable?
|
||||
|
||||
init(keyPath: PrefKeyPath) {
|
||||
cancellable = Preferences.shared[keyPath: keyPath].sink { [unowned self] _ in
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,33 +143,3 @@ extension UIMutableTraits {
|
|||
set { self[PureBlackDarkModeTrait.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 17.0, *)
|
||||
private struct PureBlackDarkModeKey: UITraitBridgedEnvironmentKey {
|
||||
static let defaultValue: Bool = false
|
||||
|
||||
static func read(from traitCollection: UITraitCollection) -> Bool {
|
||||
traitCollection[PureBlackDarkModeTrait.self]
|
||||
}
|
||||
|
||||
static func write(to mutableTraits: inout any UIMutableTraits, value: Bool) {
|
||||
mutableTraits[PureBlackDarkModeTrait.self] = value
|
||||
}
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var pureBlackDarkMode: Bool {
|
||||
get {
|
||||
if #available(iOS 17.0, *) {
|
||||
self[PureBlackDarkModeKey.self]
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
set {
|
||||
if #available(iOS 17.0, *) {
|
||||
self[PureBlackDarkModeKey.self] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ extension StatusSwipeAction {
|
|||
protocol StatusSwipeActionContainer: UIView {
|
||||
var mastodonController: MastodonController! { get }
|
||||
var navigationDelegate: any TuskerNavigationDelegate { get }
|
||||
var toastableViewController: ToastableViewController? { get }
|
||||
|
||||
var canReblog: Bool { get }
|
||||
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
//
|
||||
// AddReactionView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/17/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
|
||||
struct AddReactionView: View {
|
||||
let mastodonController: MastodonController
|
||||
let addReaction: (Reaction) async throws -> Void
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@ScaledMetric private var emojiSize = 30
|
||||
@State private var allEmojis: [Emoji] = []
|
||||
@State private var emojisBySection: [String: [Emoji]] = [:]
|
||||
@State private var query = ""
|
||||
@State private var error: (any Error)?
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: emojiSize), spacing: 4)]) {
|
||||
if query.count == 1 {
|
||||
Section {
|
||||
AddReactionButton {
|
||||
await doAddReaction(.emoji(query))
|
||||
} label: {
|
||||
Text(query)
|
||||
.font(.system(size: 25))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(emojisBySection.keys.sorted(), id: \.self) { section in
|
||||
Section {
|
||||
ForEach(emojisBySection[section]!, id: \.shortcode) { emoji in
|
||||
AddReactionButton {
|
||||
await doAddReaction(.custom(emoji))
|
||||
} label: {
|
||||
CustomEmojiImageView(emoji: emoji)
|
||||
.frame(height: emojiSize)
|
||||
.accessibilityLabel(emoji.shortcode)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
if !section.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(section)
|
||||
.font(.caption)
|
||||
|
||||
Divider()
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.searchable(text: $query, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.searchPresentationToolbarBehaviorIfAvailable()
|
||||
.onChange(of: query) { _ in
|
||||
updateFilteredEmojis()
|
||||
}
|
||||
.navigationTitle("Add Reaction")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("Cancel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(.stack)
|
||||
.mediumPresentationDetentIfAvailable()
|
||||
.alertWithData("Error Adding Reaction", data: $error, actions: { _ in
|
||||
Button("OK") {}
|
||||
}, message: { error in
|
||||
Text(error.localizedDescription)
|
||||
})
|
||||
.task {
|
||||
allEmojis = await mastodonController.getCustomEmojis()
|
||||
updateFilteredEmojis()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFilteredEmojis() {
|
||||
let filteredEmojis = if !query.isEmpty {
|
||||
allEmojis.map { emoji -> (Emoji, (matched: Bool, score: Int)) in
|
||||
(emoji, FuzzyMatcher.match(pattern: query, str: emoji.shortcode))
|
||||
}
|
||||
.filter(\.1.matched)
|
||||
.sorted { $0.1.score > $1.1.score }
|
||||
.map(\.0)
|
||||
} else {
|
||||
allEmojis
|
||||
}
|
||||
|
||||
var shortcodes = Set<String>()
|
||||
var newEmojis = [Emoji]()
|
||||
var newEmojisBySection = [String: [Emoji]]()
|
||||
for emoji in filteredEmojis where !shortcodes.contains(emoji.shortcode) {
|
||||
newEmojis.append(emoji)
|
||||
shortcodes.insert(emoji.shortcode)
|
||||
|
||||
let category = emoji.category ?? ""
|
||||
if newEmojisBySection.keys.contains(category) {
|
||||
newEmojisBySection[category]!.append(emoji)
|
||||
} else {
|
||||
newEmojisBySection[category] = [emoji]
|
||||
}
|
||||
}
|
||||
emojisBySection = newEmojisBySection
|
||||
}
|
||||
|
||||
private func doAddReaction(_ reaction: Reaction) async {
|
||||
try! await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
||||
do {
|
||||
try await addReaction(reaction)
|
||||
dismiss()
|
||||
} catch {
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
|
||||
enum Reaction {
|
||||
case emoji(String)
|
||||
case custom(Emoji)
|
||||
}
|
||||
}
|
||||
|
||||
private struct AddReactionButton<Label: View>: View {
|
||||
let addReaction: () async -> Void
|
||||
@ViewBuilder let label: Label
|
||||
@State private var isLoading = false
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
isLoading = true
|
||||
Task {
|
||||
await addReaction()
|
||||
isLoading = false
|
||||
}
|
||||
} label: {
|
||||
ZStack {
|
||||
label
|
||||
.opacity(isLoading ? 0 : 1)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(2)
|
||||
.hoverEffect()
|
||||
}
|
||||
}
|
||||
|
||||
private extension View {
|
||||
@available(iOS, obsoleted: 16.0)
|
||||
@ViewBuilder
|
||||
func mediumPresentationDetentIfAvailable() -> some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
self.presentationDetents([.medium, .large])
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS, obsoleted: 17.1)
|
||||
@ViewBuilder
|
||||
func searchPresentationToolbarBehaviorIfAvailable() -> some View {
|
||||
if #available(iOS 17.1, *) {
|
||||
self.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// AddReactionView()
|
||||
//}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// AnnouncementContentTextView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/16/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
class AnnouncementContentTextView: ContentTextView {
|
||||
|
||||
var heightChanged: ((CGFloat) -> Void)?
|
||||
|
||||
private var announcement: Announcement?
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
heightChanged?(contentSize.height)
|
||||
}
|
||||
|
||||
func setTextFrom(announcement: Announcement, content: NSAttributedString) {
|
||||
self.announcement = announcement
|
||||
self.attributedText = content
|
||||
setEmojis(announcement.emojis, identifier: announcement.id)
|
||||
}
|
||||
|
||||
override func getMention(for url: URL, text: String) -> Mention? {
|
||||
announcement?.mentions.first {
|
||||
URL($0.url) == url
|
||||
}.map {
|
||||
Mention(url: $0.url, username: $0.username, acct: $0.acct, id: $0.id)
|
||||
}
|
||||
}
|
||||
|
||||
override func getHashtag(for url: URL, text: String) -> Hashtag? {
|
||||
announcement?.tags.first {
|
||||
URL($0.url) == url
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
//
|
||||
// AnnouncementListRow.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/17/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
import TuskerComponents
|
||||
import WebURLFoundationExtras
|
||||
|
||||
struct AnnouncementListRow: View {
|
||||
@Binding var announcement: Announcement
|
||||
let mastodonController: MastodonController
|
||||
let navigationDelegate: TuskerNavigationDelegate?
|
||||
let removeAnnouncement: @MainActor () -> Void
|
||||
@State private var contentTextViewHeight: CGFloat?
|
||||
@State private var isShowingAddReactionSheet = false
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
mostOfTheBody
|
||||
.alignmentGuide(.listRowSeparatorLeading, computeValue: { dimension in
|
||||
dimension[.leading]
|
||||
})
|
||||
} else {
|
||||
mostOfTheBody
|
||||
}
|
||||
}
|
||||
|
||||
private var mostOfTheBody: some View {
|
||||
VStack {
|
||||
HStack(alignment: .top) {
|
||||
AnnouncementContentTextViewRepresentable(announcement: announcement, navigationDelegate: navigationDelegate) { newHeight in
|
||||
DispatchQueue.main.async {
|
||||
contentTextViewHeight = newHeight
|
||||
}
|
||||
}
|
||||
.frame(height: contentTextViewHeight)
|
||||
|
||||
Text(announcement.publishedAt, format: .abbreviatedTimeAgo)
|
||||
.fontWeight(.light)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
ScrollView(.horizontal) {
|
||||
LazyHStack {
|
||||
Button {
|
||||
isShowingAddReactionSheet = true
|
||||
} label: {
|
||||
Label {
|
||||
Text("Add Reaction")
|
||||
} icon: {
|
||||
if #available(iOS 16.0, *) {
|
||||
Image("face.smiling.badge.plus")
|
||||
} else {
|
||||
Image(systemName: "face.smiling")
|
||||
}
|
||||
}
|
||||
}
|
||||
.labelStyle(.iconOnly)
|
||||
.padding(4)
|
||||
.hoverEffect()
|
||||
|
||||
ForEach($announcement.reactions, id: \.name) { $reaction in
|
||||
ReactionButton(announcement: announcement, reaction: $reaction, mastodonController: mastodonController)
|
||||
}
|
||||
}
|
||||
.frame(height: 32)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.swipeActions {
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
await dismissAnnouncement()
|
||||
}
|
||||
} label: {
|
||||
Text("Dismiss")
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
await dismissAnnouncement()
|
||||
await removeAnnouncement()
|
||||
}
|
||||
} label: {
|
||||
Label("Dismiss", systemImage: "xmark")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isShowingAddReactionSheet) {
|
||||
AddReactionView(mastodonController: mastodonController, addReaction: self.addReaction)
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissAnnouncement() async {
|
||||
do {
|
||||
_ = try await mastodonController.run(Announcement.dismiss(id: announcement.id))
|
||||
} catch {
|
||||
Logging.general.error("Error dismissing attachment: \(String(describing: error))")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func addReaction(_ reaction: AddReactionView.Reaction) async throws {
|
||||
let name = switch reaction {
|
||||
case .emoji(let s): s
|
||||
case .custom(let emoji): emoji.shortcode
|
||||
}
|
||||
_ = try await mastodonController.run(Announcement.react(id: announcement.id, name: name))
|
||||
for (idx, reaction) in announcement.reactions.enumerated() {
|
||||
if reaction.name == name {
|
||||
announcement.reactions[idx].me = true
|
||||
announcement.reactions[idx].count += 1
|
||||
return
|
||||
}
|
||||
}
|
||||
let url: URL?
|
||||
let staticURL: URL?
|
||||
if case .custom(let emoji) = reaction {
|
||||
url = URL(emoji.url)
|
||||
staticURL = URL(emoji.staticURL)
|
||||
} else {
|
||||
url = nil
|
||||
staticURL = nil
|
||||
}
|
||||
announcement.reactions.append(.init(name: name, count: 1, me: true, url: url, staticURL: staticURL))
|
||||
}
|
||||
}
|
||||
|
||||
private struct AnnouncementContentTextViewRepresentable: UIViewRepresentable {
|
||||
let announcement: Announcement
|
||||
let navigationDelegate: TuskerNavigationDelegate?
|
||||
let heightChanged: (CGFloat) -> Void
|
||||
|
||||
func makeUIView(context: Context) -> AnnouncementContentTextView {
|
||||
let view = AnnouncementContentTextView()
|
||||
view.isScrollEnabled = true
|
||||
view.backgroundColor = .clear
|
||||
view.isEditable = false
|
||||
view.isSelectable = false
|
||||
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
view.adjustsFontForContentSizeCategory = true
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: AnnouncementContentTextView, context: Context) {
|
||||
uiView.navigationDelegate = navigationDelegate
|
||||
uiView.setTextFrom(announcement: announcement, content: TimelineStatusCollectionViewCell.htmlConverter.convert(announcement.content))
|
||||
uiView.heightChanged = heightChanged
|
||||
}
|
||||
}
|
||||
|
||||
private struct ReactionButton: View {
|
||||
let announcement: Announcement
|
||||
@Binding var reaction: Announcement.Reaction
|
||||
let mastodonController: MastodonController
|
||||
@State private var customEmojiImage: (Image, CGFloat)?
|
||||
|
||||
var body: some View {
|
||||
Button(action: self.toggleReaction) {
|
||||
let countStr = reaction.count.formatted(.number)
|
||||
let title = if reaction.name.count == 1 {
|
||||
"\(reaction.name) \(countStr)"
|
||||
} else {
|
||||
countStr
|
||||
}
|
||||
if reaction.url != nil {
|
||||
Label {
|
||||
Text(title)
|
||||
} icon: {
|
||||
if let (image, aspectRatio) = customEmojiImage {
|
||||
image.aspectRatio(aspectRatio, contentMode: .fit)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
.buttonStyle(TintedButtonStyle(highlighted: reaction.me == true))
|
||||
.font(.body.monospacedDigit())
|
||||
.hoverEffect()
|
||||
.task {
|
||||
if let url = reaction.url,
|
||||
let image = await ImageCache.emojis.get(url).1 {
|
||||
let aspectRatio = image.size.width / image.size.height
|
||||
customEmojiImage = (
|
||||
Image(uiImage: image).resizable(),
|
||||
aspectRatio
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleReaction() {
|
||||
if reaction.me == true {
|
||||
let oldCount = reaction.count
|
||||
reaction.me = false
|
||||
reaction.count -= 1
|
||||
Task {
|
||||
do {
|
||||
_ = try await mastodonController.run(Announcement.unreact(id: announcement.id, name: reaction.name))
|
||||
} catch {
|
||||
reaction.me = true
|
||||
reaction.count = oldCount
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let oldCount = reaction.count
|
||||
reaction.me = true
|
||||
reaction.count += 1
|
||||
Task {
|
||||
do {
|
||||
_ = try await mastodonController.run(Announcement.react(id: announcement.id, name: reaction.name))
|
||||
} catch {
|
||||
reaction.me = false
|
||||
reaction.count = oldCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TintedButtonStyle: ButtonStyle {
|
||||
let highlighted: Bool
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
configuration.label
|
||||
.foregroundStyle(highlighted ? AnyShapeStyle(.white) : AnyShapeStyle(.tint))
|
||||
.padding(.vertical, 4)
|
||||
.padding(.horizontal, 8)
|
||||
.frame(height: 32)
|
||||
.background(.tint.opacity(highlighted ? 1 : 0.2), in: RoundedRectangle(cornerRadius: 4))
|
||||
.opacity(configuration.isPressed ? 0.8 : 1)
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// AnnouncementListRow()
|
||||
//}
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// AnnouncementsCollection.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/17/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Pachyderm
|
||||
|
||||
class AnnouncementsCollection: ObservableObject {
|
||||
@Published var announcements: [Announcement]
|
||||
|
||||
init(announcements: [Announcement]) {
|
||||
self.announcements = announcements
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
//
|
||||
// AnnouncementsHostingController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/17/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
|
||||
class AnnouncementsHostingController: UIHostingController<AnnouncementsView> {
|
||||
private let mastodonController: MastodonController
|
||||
|
||||
init(announcements: AnnouncementsCollection, mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
|
||||
@Box var boxedSelf: TuskerNavigationDelegate?
|
||||
super.init(rootView: AnnouncementsView(announcements: announcements, mastodonController: mastodonController, navigationDelegate: _boxedSelf))
|
||||
boxedSelf = self
|
||||
|
||||
navigationItem.title = "Announcements"
|
||||
}
|
||||
|
||||
@MainActor required dynamic init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
extension AnnouncementsHostingController: TuskerNavigationDelegate {
|
||||
nonisolated var apiController: MastodonController! { mastodonController }
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// AnnouncementsView.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 4/17/24.
|
||||
// Copyright © 2024 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Pachyderm
|
||||
|
||||
struct AnnouncementsView: View {
|
||||
@ObservedObject var state: AnnouncementsCollection
|
||||
let mastodonController: MastodonController
|
||||
@Box var navigationDelegate: TuskerNavigationDelegate?
|
||||
|
||||
init(announcements: AnnouncementsCollection, mastodonController: MastodonController, navigationDelegate: Box<TuskerNavigationDelegate?>) {
|
||||
self.state = announcements
|
||||
self.mastodonController = mastodonController
|
||||
self._navigationDelegate = navigationDelegate
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach($state.announcements) { $announcement in
|
||||
AnnouncementListRow(announcement: $announcement, mastodonController: mastodonController, navigationDelegate: navigationDelegate) {
|
||||
withAnimation {
|
||||
state.announcements.removeAll(where: { $0.id == announcement.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// AnnouncementsView()
|
||||
//}
|
|
@ -12,16 +12,7 @@ import HTMLStreamer
|
|||
|
||||
class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
||||
|
||||
private static func canDisplay(_ kind: NotificationGroup.Kind) -> Bool {
|
||||
switch kind {
|
||||
case .favourite, .reblog, .emojiReaction:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private let iconImageView = UIImageView().configure {
|
||||
private let iconView = UIImageView().configure {
|
||||
$0.tintColor = UIColor(red: 1, green: 204/255, blue: 0, alpha: 1)
|
||||
$0.contentMode = .scaleAspectFit
|
||||
NSLayoutConstraint.activate([
|
||||
|
@ -30,10 +21,6 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
])
|
||||
}
|
||||
|
||||
private let iconLabel = UILabel().configure {
|
||||
$0.font = .systemFont(ofSize: 30)
|
||||
}
|
||||
|
||||
private let avatarStack = UIStackView().configure {
|
||||
$0.axis = .horizontal
|
||||
$0.alignment = .fill
|
||||
|
@ -94,7 +81,6 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
private var group: NotificationGroup!
|
||||
private var statusID: String!
|
||||
|
||||
private var fetchCustomEmojiImage: (URL, Task<Void, Never>)?
|
||||
private var updateTimestampWorkItem: DispatchWorkItem?
|
||||
|
||||
deinit {
|
||||
|
@ -104,21 +90,15 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
iconImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(iconImageView)
|
||||
iconLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(iconLabel)
|
||||
iconView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(iconView)
|
||||
vStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(vStack)
|
||||
NSLayoutConstraint.activate([
|
||||
iconImageView.topAnchor.constraint(equalTo: vStack.topAnchor),
|
||||
iconImageView.trailingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16 + 50),
|
||||
iconLabel.topAnchor.constraint(equalTo: iconImageView.topAnchor),
|
||||
iconLabel.bottomAnchor.constraint(equalTo: iconImageView.bottomAnchor),
|
||||
iconLabel.leadingAnchor.constraint(equalTo: iconImageView.leadingAnchor),
|
||||
iconLabel.trailingAnchor.constraint(equalTo: iconImageView.trailingAnchor),
|
||||
iconView.topAnchor.constraint(equalTo: vStack.topAnchor),
|
||||
iconView.trailingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16 + 50),
|
||||
|
||||
vStack.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 8),
|
||||
vStack.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 8),
|
||||
vStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
|
||||
vStack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
|
||||
vStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
|
||||
|
@ -136,7 +116,7 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
}
|
||||
|
||||
func updateUI(group: NotificationGroup) {
|
||||
guard ActionNotificationGroupCollectionViewCell.canDisplay(group.kind),
|
||||
guard group.kind == .favourite || group.kind == .reblog,
|
||||
let firstNotification = group.notifications.first,
|
||||
let status = firstNotification.status else {
|
||||
fatalError()
|
||||
|
@ -146,29 +126,9 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
|
||||
switch group.kind {
|
||||
case .favourite:
|
||||
iconImageView.image = UIImage(systemName: "star.fill")
|
||||
iconLabel.text = ""
|
||||
fetchCustomEmojiImage?.1.cancel()
|
||||
iconView.image = UIImage(systemName: "star.fill")
|
||||
case .reblog:
|
||||
iconImageView.image = UIImage(systemName: "repeat")
|
||||
iconLabel.text = ""
|
||||
fetchCustomEmojiImage?.1.cancel()
|
||||
case .emojiReaction(let emojiOrShortcode, let url):
|
||||
iconImageView.image = nil
|
||||
if let url = url.flatMap({ URL($0) }),
|
||||
fetchCustomEmojiImage?.0 != url {
|
||||
fetchCustomEmojiImage?.1.cancel()
|
||||
let task = Task {
|
||||
let (_, image) = await ImageCache.emojis.get(url)
|
||||
if !Task.isCancelled {
|
||||
self.iconImageView.image = image
|
||||
}
|
||||
}
|
||||
fetchCustomEmojiImage = (url, task)
|
||||
} else {
|
||||
iconLabel.text = emojiOrShortcode
|
||||
fetchCustomEmojiImage?.1.cancel()
|
||||
}
|
||||
iconView.image = UIImage(systemName: "repeat")
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
@ -247,8 +207,6 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
verb = "Favorited"
|
||||
case .reblog:
|
||||
verb = "Reblogged"
|
||||
case .emojiReaction(_, _):
|
||||
verb = "Reacted to"
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
|
@ -294,8 +252,6 @@ class ActionNotificationGroupCollectionViewCell: UICollectionViewListCell {
|
|||
str += "Favorited by "
|
||||
case .reblog:
|
||||
str += "Reblogged by "
|
||||
case .emojiReaction(let emoji, _):
|
||||
str += "Reacted \(emoji) by "
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -75,17 +75,6 @@ class NotificationLoadingViewController: UIViewController {
|
|||
}
|
||||
let actionType = notification.kind == .reblog ? StatusActionAccountListViewController.ActionType.reblog : .favorite
|
||||
vc = StatusActionAccountListViewController(actionType: actionType, statusID: statusID, statusState: .unknown, accountIDs: [notification.account.id], mastodonController: mastodonController)
|
||||
case .emojiReaction:
|
||||
guard let statusID = notification.status?.id else {
|
||||
showLoadingError(Error.missingStatus)
|
||||
return
|
||||
}
|
||||
guard let emoji = notification.emoji else {
|
||||
showLoadingError(Error.unknownType)
|
||||
return
|
||||
}
|
||||
let actionType = StatusActionAccountListViewController.ActionType.emojiReaction(emoji, notification.emojiURL)
|
||||
vc = StatusActionAccountListViewController(actionType: actionType, statusID: statusID, statusState: .unknown, accountIDs: [notification.account.id], mastodonController: mastodonController)
|
||||
case .follow:
|
||||
vc = ProfileViewController(accountID: notification.account.id, mastodonController: mastodonController)
|
||||
case .followRequest:
|
||||
|
|
|
@ -178,7 +178,7 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
|||
case .hide:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: zeroHeightCell, for: indexPath, item: ())
|
||||
}
|
||||
case .favourite, .reblog, .emojiReaction:
|
||||
case .favourite, .reblog:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: actionGroupCell, for: indexPath, item: group)
|
||||
case .follow:
|
||||
return collectionView.dequeueConfiguredReusableCell(using: followCell, for: indexPath, item: group)
|
||||
|
@ -317,7 +317,7 @@ class NotificationsCollectionViewController: UIViewController, TimelineLikeColle
|
|||
snapshot.deleteItems([.group(group, collapseState, filterState)])
|
||||
} else if !dismissFailedIndices.isEmpty && dismissFailedIndices.count == notifications.count {
|
||||
let dismissFailed = dismissFailedIndices.sorted().map { notifications[$0] }
|
||||
snapshot.insertItems([.group(NotificationGroup(notifications: dismissFailed, kind: group.kind)!, collapseState, filterState)], afterItem: .group(group, collapseState, filterState))
|
||||
snapshot.insertItems([.group(NotificationGroup(notifications: dismissFailed)!, collapseState, filterState)], afterItem: .group(group, collapseState, filterState))
|
||||
snapshot.deleteItems([.group(group, collapseState, filterState)])
|
||||
}
|
||||
await apply(snapshot, animatingDifferences: true)
|
||||
|
@ -624,8 +624,8 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate {
|
|||
let state = collapseState?.copy() ?? .unknown
|
||||
selected(status: statusID, state: state)
|
||||
}
|
||||
case .favourite, .reblog, .emojiReaction(_, _):
|
||||
let type = StatusActionAccountListViewController.ActionType(group.kind)!
|
||||
case .favourite, .reblog:
|
||||
let type = group.kind == .favourite ? StatusActionAccountListViewController.ActionType.favorite : .reblog
|
||||
let statusID = group.notifications.first!.status!.id
|
||||
let accountIDs = group.notifications.map(\.account.id).uniques()
|
||||
let vc = StatusActionAccountListViewController(actionType: type, statusID: statusID, statusState: .unknown, accountIDs: accountIDs, mastodonController: mastodonController)
|
||||
|
@ -666,9 +666,9 @@ extension NotificationsCollectionViewController: UICollectionViewDelegate {
|
|||
} actionProvider: { _ in
|
||||
UIMenu(children: self.actionsForStatus(status, source: .view(cell), includeStatusButtonActions: group.kind == .poll || group.kind == .update))
|
||||
}
|
||||
case .favourite, .reblog, .emojiReaction(_, _):
|
||||
case .favourite, .reblog:
|
||||
return UIContextMenuConfiguration(previewProvider: {
|
||||
let type = StatusActionAccountListViewController.ActionType(group.kind)!
|
||||
let type = group.kind == .favourite ? StatusActionAccountListViewController.ActionType.favorite : .reblog
|
||||
let statusID = group.notifications.first!.status!.id
|
||||
let accountIDs = group.notifications.map(\.account.id).uniques()
|
||||
return StatusActionAccountListViewController(actionType: type, statusID: statusID, statusState: .unknown, accountIDs: accountIDs, mastodonController: self.mastodonController)
|
||||
|
@ -751,7 +751,7 @@ extension NotificationsCollectionViewController: UICollectionViewDragDelegate {
|
|||
activity.displaysAuxiliaryScene = true
|
||||
provider.registerObject(activity, visibility: .all)
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
case .favourite, .reblog, .emojiReaction(_, _):
|
||||
case .favourite, .reblog:
|
||||
return []
|
||||
case .follow, .followRequest:
|
||||
guard group.notifications.count == 1 else {
|
||||
|
|
|
@ -16,22 +16,6 @@ class NotificationsPageViewController: SegmentedPageViewController<Notifications
|
|||
|
||||
var initialMode: NotificationsMode?
|
||||
|
||||
private lazy var announcementsButton: UIButton = {
|
||||
#if os(visionOS)
|
||||
var config = UIButton.Configuration.borderedProminent()
|
||||
#else
|
||||
var config = UIButton.Configuration.plain()
|
||||
// We don't want a background for this button, even when accessibility button shapes are enabled, because it's in the navbar.
|
||||
config.background.backgroundColor = .clear
|
||||
#endif
|
||||
config.image = UIImage(systemName: "megaphone.fill")
|
||||
config.contentInsets = .zero
|
||||
let button = UIButton(configuration: config)
|
||||
button.addTarget(self, action: #selector(announcementsButtonPresesd), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
private var unreadAnnouncements: AnnouncementsCollection?
|
||||
|
||||
init(initialMode: NotificationsMode? = nil, mastodonController: MastodonController) {
|
||||
self.initialMode = initialMode
|
||||
self.mastodonController = mastodonController
|
||||
|
@ -46,8 +30,6 @@ class NotificationsPageViewController: SegmentedPageViewController<Notifications
|
|||
|
||||
title = Page.all.title
|
||||
tabBarItem.image = UIImage(systemName: "bell.fill")
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: announcementsButton)
|
||||
announcementsButton.isHidden = true
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
@ -60,14 +42,6 @@ class NotificationsPageViewController: SegmentedPageViewController<Notifications
|
|||
selectMode(initialMode ?? Preferences.shared.defaultNotificationsMode)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
Task {
|
||||
await checkForAnnouncements()
|
||||
}
|
||||
}
|
||||
|
||||
func selectMode(_ mode: NotificationsMode) {
|
||||
let page: Page
|
||||
switch mode {
|
||||
|
@ -79,61 +53,6 @@ class NotificationsPageViewController: SegmentedPageViewController<Notifications
|
|||
selectPage(page, animated: false)
|
||||
}
|
||||
|
||||
private func checkForAnnouncements() async {
|
||||
guard mastodonController.instanceFeatures.instanceAnnouncements else {
|
||||
navigationItem.rightBarButtonItem = nil
|
||||
return
|
||||
}
|
||||
let announcements: [Announcement]
|
||||
do {
|
||||
(announcements, _) = try await mastodonController.run(Announcement.all())
|
||||
} catch {
|
||||
Logging.general.error("Error fetching announcements: \(String(describing: error))")
|
||||
return
|
||||
}
|
||||
let unread = announcements.filter { $0.read == false }
|
||||
if unread.isEmpty {
|
||||
unreadAnnouncements = nil
|
||||
if #available(iOS 17.0, *) {
|
||||
announcementsButton.imageView!.addSymbolEffect(.disappear)
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseInOut) {
|
||||
self.announcementsButton.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
|
||||
self.announcementsButton.layer.opacity = 0
|
||||
} completion: { _ in
|
||||
self.announcementsButton.transform = .identity
|
||||
self.announcementsButton.layer.opacity = 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
announcementsButton.isHidden = false
|
||||
announcementsButton.layer.opacity = 1
|
||||
unreadAnnouncements = AnnouncementsCollection(announcements: unread)
|
||||
if #available(iOS 17.0, *) {
|
||||
// make sure to remove the .disappear effect, which stays around indefinitely
|
||||
announcementsButton.imageView!.removeAllSymbolEffects()
|
||||
announcementsButton.imageView!.addSymbolEffect(.bounce)
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseInOut) {
|
||||
self.announcementsButton.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
|
||||
} completion: { _ in
|
||||
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) {
|
||||
self.announcementsButton.transform = .identity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func announcementsButtonPresesd() {
|
||||
guard let unreadAnnouncements else {
|
||||
return
|
||||
}
|
||||
show(AnnouncementsHostingController(announcements: unreadAnnouncements, mastodonController: mastodonController), sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension NotificationsPageViewController {
|
||||
enum Page: SegmentedPageViewControllerPage {
|
||||
case all
|
||||
case mentions
|
||||
|
|
|
@ -116,13 +116,10 @@ class OnboardingViewController: UINavigationController {
|
|||
}
|
||||
|
||||
private func tryLogin(to instanceURL: URL, updateStatus: (String) -> Void) async throws {
|
||||
logger.debug("Attempting to log in to \(instanceURL, privacy: .public)")
|
||||
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL, transient: true)
|
||||
let clientID: String
|
||||
let clientSecret: String
|
||||
if let clientInfo, clientInfo.url == instanceURL {
|
||||
logger.debug("Using client info from previous attempt")
|
||||
clientID = clientInfo.id
|
||||
clientSecret = clientInfo.secret
|
||||
} else {
|
||||
|
@ -130,32 +127,21 @@ class OnboardingViewController: UINavigationController {
|
|||
do {
|
||||
(clientID, clientSecret) = try await mastodonController.registerApp()
|
||||
self.clientInfo = (instanceURL, clientID, clientSecret)
|
||||
logger.debug("Obtained client info")
|
||||
updateStatus("Reticulating Splines")
|
||||
try await Task.sleep(nanoseconds: 500 * NSEC_PER_MSEC)
|
||||
} catch {
|
||||
logger.error("Failed to register app: \(String(describing: error), privacy: .public)")
|
||||
throw Error.registeringApp(error)
|
||||
}
|
||||
}
|
||||
updateStatus("Logging in")
|
||||
let authCode: String
|
||||
do {
|
||||
authCode = try await getAuthorizationCode(instanceURL: instanceURL, clientID: clientID)
|
||||
logger.debug("Obtained authorization code")
|
||||
} catch {
|
||||
logger.error("Failed to get auth code: \(String(describing: error), privacy: .public)")
|
||||
throw error
|
||||
}
|
||||
let authCode = try await getAuthorizationCode(instanceURL: instanceURL, clientID: clientID)
|
||||
updateStatus("Authorizing")
|
||||
let accessToken: String
|
||||
do {
|
||||
accessToken = try await retrying("Getting access token") {
|
||||
try await mastodonController.authorize(authorizationCode: authCode)
|
||||
}
|
||||
logger.debug("Obtained access token")
|
||||
} catch {
|
||||
logger.error("Failed to get access token: \(String(describing: error), privacy: .public)")
|
||||
throw Error.gettingAccessToken(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -74,27 +74,20 @@ private struct PushSubscriptionSettingsView: View {
|
|||
if mastodonController.instanceFeatures.pushNotificationTypeUpdate {
|
||||
toggle("Edits", alert: .update)
|
||||
}
|
||||
if mastodonController.instanceFeatures.emojiReactionNotifications {
|
||||
toggle("Reactions", alert: .emojiReaction)
|
||||
}
|
||||
// status notifications not supported until we can enable/disable them in the app
|
||||
}
|
||||
}
|
||||
.groupBoxStyle(AppBackgroundGroupBoxStyle())
|
||||
}
|
||||
|
||||
private var allSupportedAlertTypes: PushSubscription.Alerts {
|
||||
var all: PushSubscription.Alerts = [.mention, .favorite, .reblog, .follow, .poll]
|
||||
var alerts: PushSubscription.Alerts = [.mention, .favorite, .reblog, .follow, .poll]
|
||||
if mastodonController.instanceFeatures.pushNotificationTypeFollowRequest {
|
||||
all.insert(.followRequest)
|
||||
alerts.insert(.followRequest)
|
||||
}
|
||||
if mastodonController.instanceFeatures.pushNotificationTypeUpdate {
|
||||
all.insert(.update)
|
||||
alerts.insert(.update)
|
||||
}
|
||||
if mastodonController.instanceFeatures.emojiReactionNotifications {
|
||||
all.insert(.emojiReaction)
|
||||
}
|
||||
return all
|
||||
return alerts
|
||||
}
|
||||
|
||||
private func toggle(_ titleKey: LocalizedStringKey, alert: PushSubscription.Alerts) -> some View {
|
||||
|
@ -132,19 +125,6 @@ private extension PushSubscription.Policy {
|
|||
}
|
||||
}
|
||||
|
||||
private struct AppBackgroundGroupBoxStyle: GroupBoxStyle {
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
configuration.label
|
||||
.font(.headline)
|
||||
|
||||
configuration.content
|
||||
}
|
||||
.padding()
|
||||
.background(Color.appGroupedBackground, in: RoundedRectangle(cornerRadius: 8))
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// PushSubscriptionView()
|
||||
//}
|
||||
|
|
|
@ -108,12 +108,7 @@ struct PreferencesView: View {
|
|||
PreferenceSectionLabel(title: "Composing", systemImageName: "pencil", backgroundColor: .blue)
|
||||
}
|
||||
NavigationLink(destination: WellnessPrefsView()) {
|
||||
let brainImageName = if #available(iOS 17.0, *) {
|
||||
"brain.fill"
|
||||
} else {
|
||||
"brain"
|
||||
}
|
||||
PreferenceSectionLabel(title: "Digital Wellness", systemImageName: brainImageName, backgroundColor: .purple)
|
||||
PreferenceSectionLabel(title: "Digital Wellness", systemImageName: "brain.fill", backgroundColor: .purple)
|
||||
}
|
||||
NavigationLink(destination: AdvancedPrefsView()) {
|
||||
PreferenceSectionLabel(title: "Advanced", systemImageName: "gearshape.2.fill", backgroundColor: .gray)
|
||||
|
|
|
@ -157,7 +157,7 @@ struct ReportView: View {
|
|||
.appGroupedListRowBackground()
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
.appGroupedListBackground(container: UIHostingController<ReportView>.self)
|
||||
.appGroupedListBackground(container: UIHostingController<ReportView>.self, applyBackground: true)
|
||||
.alertWithData("Error Reporting", data: $error, actions: { error in
|
||||
Button("OK") {}
|
||||
}, message: { error in
|
||||
|
|
|
@ -172,8 +172,6 @@ class StatusActionAccountListCollectionViewController: UIViewController, Collect
|
|||
return Status.getFavourites(statusID, range: range.withCount(Self.pageSize))
|
||||
case .reblog:
|
||||
return Status.getReblogs(statusID, range: range.withCount(Self.pageSize))
|
||||
case .emojiReaction(let name, _):
|
||||
return Status.getReactions(statusID, emoji: name, range: range.withCount(Self.pageSize))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import WebURL
|
||||
|
||||
class StatusActionAccountListViewController: UIViewController {
|
||||
|
||||
|
@ -81,8 +80,6 @@ class StatusActionAccountListViewController: UIViewController {
|
|||
title = NSLocalizedString("Favorited By", comment: "status favorited by accounts list title")
|
||||
case .reblog:
|
||||
title = NSLocalizedString("Reblogged By", comment: "status reblogged by accounts list title")
|
||||
case .emojiReaction(_, _):
|
||||
title = "Reacted To By"
|
||||
}
|
||||
|
||||
view.backgroundColor = .appBackground
|
||||
|
@ -181,22 +178,7 @@ extension StatusActionAccountListViewController {
|
|||
|
||||
extension StatusActionAccountListViewController {
|
||||
enum ActionType {
|
||||
case favorite
|
||||
case reblog
|
||||
case emojiReaction(String, WebURL?)
|
||||
|
||||
init?(_ groupKind: NotificationGroup.Kind) {
|
||||
switch groupKind {
|
||||
case .reblog:
|
||||
self = .reblog
|
||||
case .favourite:
|
||||
self = .favorite
|
||||
case .emojiReaction(let emoji, let url):
|
||||
self = .emojiReaction(emoji, url)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case favorite, reblog
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import SwiftUI
|
|||
@MainActor
|
||||
protocol MenuActionProvider: AnyObject {
|
||||
var navigationDelegate: TuskerNavigationDelegate? { get }
|
||||
var toastableViewController: ToastableViewController? { get }
|
||||
}
|
||||
|
||||
@MainActor
|
||||
|
@ -33,6 +34,10 @@ extension MenuActionProvider where Self: TuskerNavigationDelegate {
|
|||
var navigationDelegate: TuskerNavigationDelegate? { self }
|
||||
}
|
||||
|
||||
extension MenuActionProvider where Self: ToastableViewController {
|
||||
var toastableViewController: ToastableViewController? { self }
|
||||
}
|
||||
|
||||
extension MenuActionProvider {
|
||||
|
||||
private var mastodonController: MastodonController? { navigationDelegate?.apiController }
|
||||
|
@ -454,7 +459,7 @@ extension MenuActionProvider {
|
|||
}
|
||||
|
||||
private func handleSuccess(title: String) {
|
||||
if let toastable = self.navigationDelegate {
|
||||
if let toastable = self.toastableViewController {
|
||||
var config = ToastConfiguration(title: title)
|
||||
config.systemImageName = "checkmark"
|
||||
config.dismissAutomaticallyAfter = 2
|
||||
|
|
|
@ -267,6 +267,10 @@ extension ContentTextView: UITextViewDelegate {
|
|||
}
|
||||
|
||||
extension ContentTextView: MenuActionProvider {
|
||||
var toastableViewController: ToastableViewController? {
|
||||
// todo: pass this down through the text view
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||
|
|
|
@ -170,6 +170,10 @@ class ProfileFieldValueView: UIView {
|
|||
}
|
||||
|
||||
extension ProfileFieldValueView: UIContextMenuInteractionDelegate, MenuActionProvider {
|
||||
var toastableViewController: ToastableViewController? {
|
||||
navigationDelegate
|
||||
}
|
||||
|
||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let (hashtag, url) = getHashtagOrURL(),
|
||||
let navigationDelegate else {
|
||||
|
|
|
@ -10,7 +10,7 @@ import UIKit
|
|||
|
||||
class StatusCollapseButton: UIButton {
|
||||
|
||||
private var interactionBounds: CGRect?
|
||||
private var interactionBounds: CGRect!
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
@ -19,7 +19,7 @@ class StatusCollapseButton: UIButton {
|
|||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return interactionBounds?.contains(point) ?? false
|
||||
return interactionBounds.contains(point)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -945,6 +945,7 @@ extension TimelineStatusCollectionViewCell: UIPointerInteractionDelegate {
|
|||
|
||||
extension TimelineStatusCollectionViewCell: StatusSwipeActionContainer {
|
||||
var navigationDelegate: TuskerNavigationDelegate { delegate! }
|
||||
var toastableViewController: ToastableViewController? { delegate }
|
||||
|
||||
var canReblog: Bool {
|
||||
reblogButton.isEnabled
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 2024.2
|
||||
CURRENT_PROJECT_VERSION = 122
|
||||
CURRENT_PROJECT_VERSION = 121
|
||||
CURRENT_PROJECT_VERSION = $(inherited)$(CURRENT_PROJECT_VERSION_BUILD_SUFFIX_$(CONFIGURATION))
|
||||
|
||||
CURRENT_PROJECT_VERSION_BUILD_SUFFIX_Debug=-dev
|
||||
|
|
Loading…
Reference in New Issue