Compare commits

..

No commits in common. "0a7709526f839ac5c5ce8433a6db7c91d815969e" and "80c79ded3bef850788897343d88c8a566bc0a27f" have entirely different histories.

113 changed files with 1312 additions and 1636 deletions

View File

@ -1,23 +1,5 @@
# Changelog # Changelog
## 2022.1 (25)
Features/Improvements:
- Improve error reporting for non-crash errors
- Long-press on the blue error bubble to send a report
- Improve error feedback during login process
- Add Trending Post and Trending Links on Mastodon 3.5
- Add Digital Wellness preference to disable Discover
- Basic support for GotoSocial
- Reduce app file size
Bugfixes:
- Fix all statues appearing as pinned on Pixelfed
- Fix crash when refreshing My Profile
- Fix My Profile never loading in some circumstances
- Fix crash the first time the attachment picker is opened
- Fix crash when closing certain screens
- Fix certain links in posts not being detected
## 2022.1 (24) ## 2022.1 (24)
Features/Improvements: Features/Improvements:
- Local only posts (Glitch/Hometown) - Local only posts (Glitch/Hometown)

View File

@ -68,32 +68,29 @@ public class Client {
@discardableResult @discardableResult
public func run<Result>(_ request: Request<Result>, completion: @escaping Callback<Result>) -> URLSessionTask? { public func run<Result>(_ request: Request<Result>, completion: @escaping Callback<Result>) -> URLSessionTask? {
guard let urlRequest = createURLRequest(request: request) else { guard let request = createURLRequest(request: request) else {
completion(.failure(Error(request: request, type: .invalidRequest))) completion(.failure(Error.invalidRequest))
return nil return nil
} }
let task = session.dataTask(with: urlRequest) { data, response, error in let task = session.dataTask(with: request) { data, response, error in
if let error = error { if let error = error {
completion(.failure(Error(request: request, type: .networkError(error)))) completion(.failure(.networkError(error)))
return return
} }
guard let data = data, guard let data = data,
let response = response as? HTTPURLResponse else { let response = response as? HTTPURLResponse else {
completion(.failure(Error(request: request, type: .invalidResponse))) completion(.failure(.invalidResponse))
return return
} }
guard response.statusCode == 200 else { guard response.statusCode == 200 else {
let mastodonError = try? Client.decoder.decode(MastodonError.self, from: data) let mastodonError = try? Client.decoder.decode(MastodonError.self, from: data)
let type: ErrorType = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode) let error: Error = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode)
completion(.failure(Error(request: request, type: type))) completion(.failure(error))
return return
} }
let result: Result guard let result = try? Client.decoder.decode(Result.self, from: data) else {
do { completion(.failure(.invalidModel))
result = try Client.decoder.decode(Result.self, from: data)
} catch {
completion(.failure(Error(request: request, type: .invalidModel(error))))
return return
} }
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init) let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
@ -106,7 +103,7 @@ public class Client {
func createURLRequest<Result>(request: Request<Result>) -> URLRequest? { func createURLRequest<Result>(request: Request<Result>) -> URLRequest? {
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil } guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil }
components.path = request.endpoint.path components.path = request.path
components.queryItems = request.queryParameters.isEmpty ? nil : request.queryParameters.queryItems components.queryItems = request.queryParameters.isEmpty ? nil : request.queryParameters.queryItems
guard let url = components.url else { return nil } guard let url = components.url else { return nil }
var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval) var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval)
@ -166,7 +163,7 @@ public class Client {
if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }), if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }),
let components = URLComponents(string: url.href), let components = URLComponents(string: url.href),
components.host == self.baseURL.host { components.host == self.baseURL.host {
let nodeInfo = Request<NodeInfo>(method: .get, path: Endpoint(stringLiteral: components.path)) let nodeInfo = Request<NodeInfo>(method: .get, path: components.path)
self.run(nodeInfo, completion: completion) self.run(nodeInfo, completion: completion)
} }
} }
@ -368,7 +365,7 @@ public class Client {
} }
// MARK: - Instance // MARK: - Instance
public static func getTrendingHashtags(limit: Int? = nil) -> Request<[Hashtag]> { public static func getTrends(limit: Int? = nil) -> Request<[Hashtag]> {
let parameters: [Parameter] let parameters: [Parameter]
if let limit = limit { if let limit = limit {
parameters = ["limit" => limit] parameters = ["limit" => limit]
@ -378,26 +375,6 @@ public class Client {
return Request<[Hashtag]>(method: .get, path: "/api/v1/trends", queryParameters: parameters) return Request<[Hashtag]>(method: .get, path: "/api/v1/trends", queryParameters: parameters)
} }
public static func getTrendingStatuses(limit: Int? = nil) -> Request<[Status]> {
let parameters: [Parameter]
if let limit = limit {
parameters = ["limit" => limit]
} else {
parameters = []
}
return Request(method: .get, path: "/api/v1/trends/statuses", queryParameters: parameters)
}
public static func getTrendingLinks(limit: Int? = nil) -> Request<[Card]> {
let parameters: [Parameter]
if let limit = limit {
parameters = ["limit" => limit]
} else {
parameters = []
}
return Request(method: .get, path: "/api/v1/trends/links", queryParameters: parameters)
}
public static func getFeaturedProfiles(local: Bool, order: DirectoryOrder, offset: Int? = nil, limit: Int? = nil) -> Request<[Account]> { public static func getFeaturedProfiles(local: Bool, order: DirectoryOrder, offset: Int? = nil, limit: Int? = nil) -> Request<[Account]> {
var parameters = [ var parameters = [
"order" => order.rawValue, "order" => order.rawValue,
@ -415,19 +392,16 @@ public class Client {
} }
extension Client { extension Client {
public struct Error: LocalizedError { public enum Error: LocalizedError {
public let requestMethod: Method case networkError(Swift.Error)
public let requestEndpoint: Endpoint case unexpectedStatus(Int)
public let type: ErrorType case invalidRequest
case invalidResponse
init<ResultType: Decodable>(request: Request<ResultType>, type: ErrorType) { case invalidModel
self.requestMethod = request.method case mastodonError(String)
self.requestEndpoint = request.endpoint
self.type = type
}
public var localizedDescription: String { public var localizedDescription: String {
switch type { switch self {
case .networkError(let error): case .networkError(let error):
return "Network Error: \(error.localizedDescription)" return "Network Error: \(error.localizedDescription)"
// todo: support more status codes // todo: support more status codes
@ -439,19 +413,11 @@ extension Client {
return "Invalid Request" return "Invalid Request"
case .invalidResponse: case .invalidResponse:
return "Invalid Response" return "Invalid Response"
case .invalidModel(_): case .invalidModel:
return "Invalid Model" return "Invalid Model"
case .mastodonError(let error): case .mastodonError(let error):
return "Server Error: \(error)" return "Server Error: \(error)"
} }
} }
} }
public enum ErrorType: LocalizedError {
case networkError(Swift.Error)
case unexpectedStatus(Int)
case invalidRequest
case invalidResponse
case invalidModel(Swift.Error)
case mastodonError(String)
}
} }

22
Pachyderm/Info.plist Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>

View File

@ -20,9 +20,8 @@ public final class Account: AccountProtocol, Decodable {
public let statusesCount: Int public let statusesCount: Int
public let note: String public let note: String
public let url: URL public let url: URL
// required on mastodon, but optional on gotosocial public let avatar: URL
public let avatar: URL? public let avatarStatic: URL
public let avatarStatic: URL?
public let header: URL? public let header: URL?
public let headerStatic: URL? public let headerStatic: URL?
public private(set) var emojis: [Emoji] public private(set) var emojis: [Emoji]
@ -45,8 +44,8 @@ public final class Account: AccountProtocol, Decodable {
self.statusesCount = try container.decode(Int.self, forKey: .statusesCount) self.statusesCount = try container.decode(Int.self, forKey: .statusesCount)
self.note = try container.decode(String.self, forKey: .note) self.note = try container.decode(String.self, forKey: .note)
self.url = try container.decode(URL.self, forKey: .url) self.url = try container.decode(URL.self, forKey: .url)
self.avatar = try? container.decode(URL.self, forKey: .avatar) self.avatar = try container.decode(URL.self, forKey: .avatar)
self.avatarStatic = try? container.decode(URL.self, forKey: .avatarStatic) self.avatarStatic = try container.decode(URL.self, forKey: .avatarStatic)
self.header = try? container.decode(URL.self, forKey: .header) self.header = try? container.decode(URL.self, forKey: .header)
self.headerStatic = try? container.decode(URL.self, forKey: .headerStatic) self.headerStatic = try? container.decode(URL.self, forKey: .headerStatic)
self.emojis = try container.decode([Emoji].self, forKey: .emojis) self.emojis = try container.decode([Emoji].self, forKey: .emojis)

View File

@ -56,23 +56,6 @@ extension Attachment {
case gifv case gifv
case audio case audio
case unknown case unknown
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
switch try container.decode(String.self) {
// gotosocial uses "gif" for gif images
case "image", "gif":
self = .image
case "video":
self = .video
case "gifv":
self = .gifv
case "audio":
self = .audio
default:
self = .unknown
}
}
} }
} }

View File

@ -23,8 +23,6 @@ public class Card: Codable {
public let width: Int? public let width: Int?
public let height: Int? public let height: Int?
public let blurhash: String? public let blurhash: String?
/// Only present when returned from the trending links endpoint
public let history: [History]?
public required init(from decoder: Decoder) throws { public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
@ -42,7 +40,6 @@ public class Card: Codable {
self.width = try? container.decodeIfPresent(Int.self, forKey: .width) self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
self.height = try? container.decodeIfPresent(Int.self, forKey: .height) self.height = try? container.decodeIfPresent(Int.self, forKey: .height)
self.blurhash = try? container.decodeIfPresent(String.self, forKey: .blurhash) self.blurhash = try? container.decodeIfPresent(String.self, forKey: .blurhash)
self.history = try? container.decodeIfPresent([History].self, forKey: .history)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -70,7 +67,6 @@ public class Card: Codable {
case width case width
case height case height
case blurhash case blurhash
case history
} }
} }

View File

@ -0,0 +1,84 @@
//
// Hashtag.swift
// Pachyderm
//
// Created by Shadowfacts on 9/9/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import Foundation
public class Hashtag: Codable {
public let name: String
public let url: URL
public let history: [History]?
public init(name: String, url: URL) {
self.name = name
self.url = url
self.history = nil
}
private enum CodingKeys: String, CodingKey {
case name
case url
case history
}
}
extension Hashtag {
public class History: Codable {
public let day: Date
public let uses: Int
public let accounts: Int
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let day = try? container.decode(Date.self, forKey: .day) {
self.day = day
} else if let unixTimestamp = try? container.decode(Double.self, forKey: .day) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else if let str = try? container.decode(String.self, forKey: .day),
let unixTimestamp = Double(str) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else {
throw DecodingError.dataCorruptedError(forKey: .day, in: container, debugDescription: "day must be either date or UNIX timestamp")
}
if let uses = try? container.decode(Int.self, forKey: .uses) {
self.uses = uses
} else if let str = try? container.decode(String.self, forKey: .uses),
let uses = Int(str) {
self.uses = uses
} else {
throw DecodingError.dataCorruptedError(forKey: .uses, in: container, debugDescription: "uses must either be int or string containing int")
}
if let accounts = try? container.decode(Int.self, forKey: .accounts) {
self.accounts = accounts
} else if let str = try? container.decode(String.self, forKey: .accounts),
let accounts = Int(str) {
self.accounts = accounts
} else {
throw DecodingError.dataCorruptedError(forKey: .accounts, in: container, debugDescription: "accounts must either be int or string containing int")
}
}
private enum CodingKeys: String, CodingKey {
case day
case uses
case accounts
}
}
}
extension Hashtag: Equatable, Hashable {
public static func ==(lhs: Hashtag, rhs: Hashtag) -> Bool {
return lhs.name == rhs.name
}
public func hash(into hasher: inout Hasher) {
hasher.combine(url)
}
}

View File

@ -22,7 +22,7 @@ public protocol AccountProtocol {
var statusesCount: Int { get } var statusesCount: Int { get }
var note: String { get } var note: String { get }
var url: URL { get } var url: URL { get }
var avatar: URL? { get } var avatar: URL { get }
var header: URL? { get } var header: URL? { get }
var moved: Bool? { get } var moved: Bool? { get }
var bot: Bool? { get } var bot: Bool? { get }

View File

@ -17,7 +17,7 @@ public enum Timeline {
} }
extension Timeline { extension Timeline {
var endpoint: Endpoint { var endpoint: String {
switch self { switch self {
case .home: case .home:
return "/api/v1/timelines/home" return "/api/v1/timelines/home"

19
Pachyderm/Pachyderm.h Normal file
View File

@ -0,0 +1,19 @@
//
// Pachyderm.h
// Pachyderm
//
// Created by Shadowfacts on 9/8/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
#import <UIKit/UIKit.h>
//! Project version number for Pachyderm.
FOUNDATION_EXPORT double PachydermVersionNumber;
//! Project version string for Pachyderm.
FOUNDATION_EXPORT const unsigned char PachydermVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Pachyderm/PublicHeader.h>

View File

@ -1,33 +0,0 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "Pachyderm",
platforms: [
.iOS(.v14),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Pachyderm",
targets: ["Pachyderm"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/karwa/swift-url.git", from: "0.3.1"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Pachyderm",
dependencies: [
.product(name: "WebURL", package: "swift-url"),
]),
.testTarget(
name: "PachydermTests",
dependencies: ["Pachyderm"]),
]
)

View File

@ -1,3 +0,0 @@
# Pachyderm
A description of this package.

View File

@ -8,12 +8,12 @@
import Foundation import Foundation
public enum Method { enum Method {
case get, post, put, patch, delete case get, post, put, patch, delete
} }
extension Method { extension Method {
public var name: String { var name: String {
switch self { switch self {
case .get: case .get:
return "GET" return "GET"

View File

@ -10,13 +10,13 @@ import Foundation
public struct Request<ResultType: Decodable> { public struct Request<ResultType: Decodable> {
let method: Method let method: Method
let endpoint: Endpoint let path: String
let body: Body let body: Body
var queryParameters: [Parameter] var queryParameters: [Parameter]
init(method: Method, path: Endpoint, body: Body = EmptyBody(), queryParameters: [Parameter] = []) { init(method: Method, path: String, body: Body = EmptyBody(), queryParameters: [Parameter] = []) {
self.method = method self.method = method
self.endpoint = path self.path = path
self.body = body self.body = body
self.queryParameters = queryParameters self.queryParameters = queryParameters
} }

View File

@ -1,38 +0,0 @@
//
// Hashtag.swift
// Pachyderm
//
// Created by Shadowfacts on 9/9/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import Foundation
public class Hashtag: Codable {
public let name: String
public let url: URL
/// Only present when returned from the trending hashtags endpoint
public let history: [History]?
public init(name: String, url: URL) {
self.name = name
self.url = url
self.history = nil
}
private enum CodingKeys: String, CodingKey {
case name
case url
case history
}
}
extension Hashtag: Equatable, Hashable {
public static func ==(lhs: Hashtag, rhs: Hashtag) -> Bool {
return lhs.name == rhs.name
}
public func hash(into hasher: inout Hasher) {
hasher.combine(url)
}
}

View File

@ -1,54 +0,0 @@
//
// History.swift
// Pachyderm
//
// Created by Shadowfacts on 4/2/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import Foundation
public class History: Codable {
public let day: Date
public let uses: Int
public let accounts: Int
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let day = try? container.decode(Date.self, forKey: .day) {
self.day = day
} else if let unixTimestamp = try? container.decode(Double.self, forKey: .day) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else if let str = try? container.decode(String.self, forKey: .day),
let unixTimestamp = Double(str) {
self.day = Date(timeIntervalSince1970: unixTimestamp)
} else {
throw DecodingError.dataCorruptedError(forKey: .day, in: container, debugDescription: "day must be either date or UNIX timestamp")
}
if let uses = try? container.decode(Int.self, forKey: .uses) {
self.uses = uses
} else if let str = try? container.decode(String.self, forKey: .uses),
let uses = Int(str) {
self.uses = uses
} else {
throw DecodingError.dataCorruptedError(forKey: .uses, in: container, debugDescription: "uses must either be int or string containing int")
}
if let accounts = try? container.decode(Int.self, forKey: .accounts) {
self.accounts = accounts
} else if let str = try? container.decode(String.self, forKey: .accounts),
let accounts = Int(str) {
self.accounts = accounts
} else {
throw DecodingError.dataCorruptedError(forKey: .accounts, in: container, debugDescription: "accounts must either be int or string containing int")
}
}
private enum CodingKeys: String, CodingKey {
case day
case uses
case accounts
}
}

View File

@ -1,62 +0,0 @@
//
// Endpoint.swift
// Pachyderm
//
// Created by Shadowfacts on 3/29/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import Foundation
public struct Endpoint: ExpressibleByStringInterpolation, CustomStringConvertible {
let components: [Component]
public init(stringLiteral value: StringLiteralType) {
self.components = [.literal(value)]
}
public init(stringInterpolation: StringInterpolation) {
self.components = stringInterpolation.components
}
var path: String {
components.map {
switch $0 {
case .literal(let s), .interpolated(let s):
return s
}
}.joined(separator: "")
}
public var description: String {
components.map {
switch $0 {
case .literal(let s):
return s
case .interpolated(_):
return "<redacted>"
}
}.joined(separator: "")
}
public struct StringInterpolation: StringInterpolationProtocol {
var components = [Component]()
public init(literalCapacity: Int, interpolationCount: Int) {
}
public mutating func appendLiteral(_ literal: StringLiteralType) {
components.append(.literal(literal))
}
public mutating func appendInterpolation(_ value: String) {
components.append(.interpolated(value))
}
}
enum Component {
case literal(String)
case interpolated(String)
}
}

View File

@ -12,7 +12,7 @@ public class InstanceSelector {
private static let decoder = JSONDecoder() private static let decoder = JSONDecoder()
public static func getInstances(category: String?, completion: @escaping (Result<[Instance], Client.ErrorType>) -> Void) { public static func getInstances(category: String?, completion: @escaping Client.Callback<[Instance]>) {
let url: URL let url: URL
if let category = category { if let category = category {
url = URL(string: "https://api.joinmastodon.org/servers?category=\(category)")! url = URL(string: "https://api.joinmastodon.org/servers?category=\(category)")!
@ -34,14 +34,11 @@ public class InstanceSelector {
completion(.failure(.unexpectedStatus(response.statusCode))) completion(.failure(.unexpectedStatus(response.statusCode)))
return return
} }
let result: [Instance] guard let result = try? decoder.decode([Instance].self, from: data) else {
do { completion(.failure(Client.Error.invalidModel))
result = try decoder.decode([Instance].self, from: data)
} catch {
completion(.failure(.invalidModel(error)))
return return
} }
completion(.success(result)) completion(.success(result, nil))
} }
task.resume() task.resume()
} }

22
PachydermTests/Info.plist Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -0,0 +1,34 @@
//
// PachydermTests.swift
// PachydermTests
//
// Created by Shadowfacts on 9/8/18.
// Copyright © 2018 Shadowfacts. All rights reserved.
//
import XCTest
@testable import Pachyderm
class PachydermTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 55; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -22,20 +22,56 @@
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; }; D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; }; D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; }; D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; }; D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */; };
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; }; D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; }; D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; }; D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; }; D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; }; D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; }; D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; };
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */; }; D60E2F3124424F1A005F8713 /* StatusProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3024424F1A005F8713 /* StatusProtocol.swift */; };
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */; }; D60E2F3324425374005F8713 /* AccountProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3224425374005F8713 /* AccountProtocol.swift */; };
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */; }; D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */; }; D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.swift */; };
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1627F8BB210080E273 /* VersionTests.swift */; }; D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */ = {isa = PBXBuildFile; fileRef = D61099AD2144B0CC00432DC2 /* Pachyderm.h */; settings = {ATTRIBUTES = (Public, ); }; };
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D61099C92144B13C00432DC2 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099C82144B13C00432DC2 /* Client.swift */; };
D61099CB2144B20500432DC2 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099CA2144B20500432DC2 /* Request.swift */; };
D61099D02144B2D700432DC2 /* Method.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099CF2144B2D700432DC2 /* Method.swift */; };
D61099D22144B2E600432DC2 /* Body.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099D12144B2E600432DC2 /* Body.swift */; };
D61099D42144B32E00432DC2 /* Parameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099D32144B32E00432DC2 /* Parameter.swift */; };
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099D52144B4B200432DC2 /* FormAttachment.swift */; };
D61099D92144B76400432DC2 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099D82144B76400432DC2 /* Data.swift */; };
D61099DC2144BDBF00432DC2 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099DB2144BDBF00432DC2 /* Response.swift */; };
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099DE2144C11400432DC2 /* MastodonError.swift */; };
D61099E12144C1DC00432DC2 /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099E02144C1DC00432DC2 /* Account.swift */; };
D61099E32144C38900432DC2 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099E22144C38900432DC2 /* Emoji.swift */; };
D61099E5214561AB00432DC2 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099E4214561AB00432DC2 /* Application.swift */; };
D61099E7214561FF00432DC2 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099E6214561FF00432DC2 /* Attachment.swift */; };
D61099E92145658300432DC2 /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099E82145658300432DC2 /* Card.swift */; };
D61099EB2145661700432DC2 /* ConversationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099EA2145661700432DC2 /* ConversationContext.swift */; };
D61099ED2145664800432DC2 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099EC2145664800432DC2 /* Filter.swift */; };
D61099EF214566C000432DC2 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099EE214566C000432DC2 /* Instance.swift */; };
D61099F12145686D00432DC2 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099F02145686D00432DC2 /* List.swift */; };
D61099F32145688600432DC2 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099F22145688600432DC2 /* Mention.swift */; };
D61099F5214568C300432DC2 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099F4214568C300432DC2 /* Notification.swift */; };
D61099F72145693500432DC2 /* PushSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099F62145693500432DC2 /* PushSubscription.swift */; };
D61099F92145698900432DC2 /* Relationship.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099F82145698900432DC2 /* Relationship.swift */; };
D61099FB214569F600432DC2 /* Report.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099FA214569F600432DC2 /* Report.swift */; };
D61099FD21456A1D00432DC2 /* SearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099FC21456A1D00432DC2 /* SearchResults.swift */; };
D61099FF21456A4C00432DC2 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099FE21456A4C00432DC2 /* Status.swift */; };
D6109A0121456B0800432DC2 /* Hashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A0021456B0800432DC2 /* Hashtag.swift */; };
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A022145722C00432DC2 /* RegisteredApplication.swift */; };
D6109A05214572BF00432DC2 /* Scope.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A04214572BF00432DC2 /* Scope.swift */; };
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A062145756700432DC2 /* LoginSettings.swift */; };
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A0821458C4A00432DC2 /* Empty.swift */; };
D6109A0D214599E100432DC2 /* RequestRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A0C214599E100432DC2 /* RequestRange.swift */; };
D6109A0F21459B6900432DC2 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A0E21459B6900432DC2 /* Pagination.swift */; };
D6109A11214607D500432DC2 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A10214607D500432DC2 /* Timeline.swift */; };
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; }; D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; };
D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; }; D611C2D0232DC61100C86A49 /* HashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */; };
D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D2232E928600C54D2D /* InstanceSelector.swift */; };
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
@ -50,6 +86,7 @@
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; }; D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; };
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A924F1E01C00B82A16 /* ComposeTextView.swift */; }; D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A924F1E01C00B82A16 /* ComposeTextView.swift */; };
D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A53C2635F5590095BD04 /* StatusPollView.swift */; }; D623A53D2635F5590095BD04 /* StatusPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A53C2635F5590095BD04 /* StatusPollView.swift */; };
D623A53F2635F6910095BD04 /* Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A53E2635F6910095BD04 /* Poll.swift */; };
D623A5412635FB3C0095BD04 /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A5402635FB3C0095BD04 /* PollOptionView.swift */; }; D623A5412635FB3C0095BD04 /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A5402635FB3C0095BD04 /* PollOptionView.swift */; };
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; }; D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; };
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; }; D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
@ -73,11 +110,14 @@
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; }; D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; };
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; }; D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; }; D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */; };
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B4E21EA695800FE4B39 /* StatusContentType.swift */; };
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; }; D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6285B5221EA708700FE4B39 /* StatusFormat.swift */; };
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; }; D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; }; D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.swift */; };
D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; }; D62D2424217ABF3F005076CC /* NSUserActivity+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */; };
D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; }; D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2425217ABF63005076CC /* UserActivityType.swift */; };
D62E9981279C691F00C26176 /* NodeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9980279C691F00C26176 /* NodeInfo.swift */; };
D62E9983279C69D400C26176 /* WellKnown.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9982279C69D400C26176 /* WellKnown.swift */; };
D62E9985279CA23900C26176 /* URLSession+Development.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9984279CA23900C26176 /* URLSession+Development.swift */; }; D62E9985279CA23900C26176 /* URLSession+Development.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9984279CA23900C26176 /* URLSession+Development.swift */; };
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */; }; D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */; };
D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9988279DB2D100C26176 /* InstanceFeatures.swift */; }; D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9988279DB2D100C26176 /* InstanceFeatures.swift */; };
@ -85,6 +125,7 @@
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; }; D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.swift */; };
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; }; D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Helpers.swift */; };
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; }; D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; }; D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; }; D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; }; D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; };
@ -143,7 +184,6 @@
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; }; D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; };
D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; }; D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */; };
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */; }; D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */; };
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */ = {isa = PBXBuildFile; productRef = D674A50827F9128D00BA03AC /* Pachyderm */; };
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7B2157E01900721E32 /* XCBManager.swift */; }; D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7B2157E01900721E32 /* XCBManager.swift */; };
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; }; D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; };
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; }; D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
@ -177,6 +217,7 @@
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; }; D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; };
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; }; D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */; };
D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; }; D693A72A25CF8C1E003A14E2 /* ProfileDirectoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */; };
D693A72C25CF8D15003A14E2 /* DirectoryOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */; };
D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; }; D693A72F25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */; };
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */; }; D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */; };
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; }; D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
@ -192,6 +233,8 @@
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; }; D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = D69CCBBE249E6EFD000AF167 /* CrashReporter */; }; D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = D69CCBBE249E6EFD000AF167 /* CrashReporter */; };
D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; }; D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */; };
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */; };
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */; };
D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; }; D6A3BC7C232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */; };
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; }; D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; };
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; }; D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; };
@ -286,20 +329,45 @@
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; }; D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; }; D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */; };
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; }; D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */; };
D6E426B9253382B300C02E1C /* SearchResultType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426B8253382B300C02E1C /* SearchResultType.swift */; };
D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; }; D6E57FA325C26FAB00341037 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D6E57FA525C26FAB00341037 /* Localizable.stringsdict */; };
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26221603F8B006A8599 /* CharacterCounter.swift */; };
D6E6F26521604242006A8599 /* CharacterCounterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E6F26421604242006A8599 /* CharacterCounterTests.swift */; };
D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */; }; D6EAE0DB2550CC8A002DB0AC /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */; };
D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; }; D6EBF01523C55C0900AE061B /* UIApplication+Scenes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */; };
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; }; D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */; };
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; }; D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; };
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; }; D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; };
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; }; D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; };
D6F2E965249E8BFD005846BB /* IssueReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */; }; D6F1F9DF27B0613300CB7D88 /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D6F1F9DE27B0613300CB7D88 /* WebURL */; settings = {ATTRIBUTES = (Required, ); }; };
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */; }; D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */; };
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */; };
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; }; D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; }; D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
proxyType = 1;
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
remoteInfo = Pachyderm;
};
D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
proxyType = 1;
remoteGlobalIDString = D6D4DDCB212518A000E1C4BB;
remoteInfo = Tusker;
};
D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
proxyType = 1;
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
remoteInfo = Pachyderm;
};
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = { D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */; containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
@ -341,6 +409,7 @@
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
); );
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -363,19 +432,57 @@
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = "<group>"; }; D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagSearchResultsViewController.swift; sourceTree = "<group>"; };
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagTableViewCell.swift; sourceTree = "<group>"; }; D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagTableViewCell.swift; sourceTree = "<group>"; };
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = "<group>"; }; D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; sourceTree = "<group>"; };
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendHistoryView.swift; sourceTree = "<group>"; }; D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagHistoryView.swift; sourceTree = "<group>"; };
D60A4FFB238B726A008AC647 /* StatusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusState.swift; sourceTree = "<group>"; };
D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; }; D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseStatusTableViewCell.swift; sourceTree = "<group>"; };
D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = "<group>"; }; D60E2F232442372B005F8713 /* StatusMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMO.swift; sourceTree = "<group>"; };
D60E2F252442372B005F8713 /* AccountMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMO.swift; sourceTree = "<group>"; }; D60E2F252442372B005F8713 /* AccountMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMO.swift; sourceTree = "<group>"; };
D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazilyDecoding.swift; sourceTree = "<group>"; }; D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazilyDecoding.swift; sourceTree = "<group>"; };
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; }; D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; };
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; }; D60E2F3024424F1A005F8713 /* StatusProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProtocol.swift; sourceTree = "<group>"; };
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusesViewController.swift; sourceTree = "<group>"; }; D60E2F3224425374005F8713 /* AccountProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProtocol.swift; sourceTree = "<group>"; };
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinksViewController.swift; sourceTree = "<group>"; }; D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkTableViewCell.swift; sourceTree = "<group>"; }; D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; sourceTree = "<group>"; };
D6114E1627F8BB210080E273 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.swift; sourceTree = "<group>"; }; D61099AE2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PachydermTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D61099BA2144B0CC00432DC2 /* PachydermTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PachydermTests.swift; sourceTree = "<group>"; };
D61099BC2144B0CC00432DC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D61099C82144B13C00432DC2 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
D61099CA2144B20500432DC2 /* Request.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
D61099CF2144B2D700432DC2 /* Method.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Method.swift; sourceTree = "<group>"; };
D61099D12144B2E600432DC2 /* Body.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Body.swift; sourceTree = "<group>"; };
D61099D32144B32E00432DC2 /* Parameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parameter.swift; sourceTree = "<group>"; };
D61099D52144B4B200432DC2 /* FormAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormAttachment.swift; sourceTree = "<group>"; };
D61099D82144B76400432DC2 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
D61099DB2144BDBF00432DC2 /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
D61099DE2144C11400432DC2 /* MastodonError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonError.swift; sourceTree = "<group>"; };
D61099E02144C1DC00432DC2 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
D61099E22144C38900432DC2 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
D61099E4214561AB00432DC2 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
D61099E6214561FF00432DC2 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
D61099E82145658300432DC2 /* Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = "<group>"; };
D61099EA2145661700432DC2 /* ConversationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationContext.swift; sourceTree = "<group>"; };
D61099EC2145664800432DC2 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
D61099EE214566C000432DC2 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
D61099F02145686D00432DC2 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; };
D61099F22145688600432DC2 /* Mention.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
D61099F4214568C300432DC2 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
D61099F62145693500432DC2 /* PushSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSubscription.swift; sourceTree = "<group>"; };
D61099F82145698900432DC2 /* Relationship.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Relationship.swift; sourceTree = "<group>"; };
D61099FA214569F600432DC2 /* Report.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Report.swift; sourceTree = "<group>"; };
D61099FC21456A1D00432DC2 /* SearchResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResults.swift; sourceTree = "<group>"; };
D61099FE21456A4C00432DC2 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
D6109A0021456B0800432DC2 /* Hashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hashtag.swift; sourceTree = "<group>"; };
D6109A022145722C00432DC2 /* RegisteredApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisteredApplication.swift; sourceTree = "<group>"; };
D6109A04214572BF00432DC2 /* Scope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scope.swift; sourceTree = "<group>"; };
D6109A062145756700432DC2 /* LoginSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginSettings.swift; sourceTree = "<group>"; };
D6109A0821458C4A00432DC2 /* Empty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Empty.swift; sourceTree = "<group>"; };
D6109A0C214599E100432DC2 /* RequestRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestRange.swift; sourceTree = "<group>"; };
D6109A0E21459B6900432DC2 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = "<group>"; };
D6109A10214607D500432DC2 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = "<group>"; }; D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTableViewCell.swift; sourceTree = "<group>"; };
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HashtagTableViewCell.xib; sourceTree = "<group>"; }; D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HashtagTableViewCell.xib; sourceTree = "<group>"; };
D61AC1D2232E928600C54D2D /* InstanceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelector.swift; sourceTree = "<group>"; };
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; }; D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; }; D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; }; D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
@ -390,6 +497,7 @@
D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyContentView.swift; sourceTree = "<group>"; }; D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeReplyContentView.swift; sourceTree = "<group>"; };
D62275A924F1E01C00B82A16 /* ComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTextView.swift; sourceTree = "<group>"; }; D62275A924F1E01C00B82A16 /* ComposeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeTextView.swift; sourceTree = "<group>"; };
D623A53C2635F5590095BD04 /* StatusPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPollView.swift; sourceTree = "<group>"; }; D623A53C2635F5590095BD04 /* StatusPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPollView.swift; sourceTree = "<group>"; };
D623A53E2635F6910095BD04 /* Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Poll.swift; sourceTree = "<group>"; };
D623A5402635FB3C0095BD04 /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; }; D623A5402635FB3C0095BD04 /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = "<group>"; };
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = "<group>"; }; D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = "<group>"; };
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; }; D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
@ -413,11 +521,14 @@
D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = "<group>"; }; D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsTableViewController.swift; sourceTree = "<group>"; };
D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; }; D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraftTableViewCell.xib; sourceTree = "<group>"; };
D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; }; D627FF7E217E95E000CC0648 /* DraftTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftTableViewCell.swift; sourceTree = "<group>"; };
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentType.swift; sourceTree = "<group>"; };
D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; }; D6285B5221EA708700FE4B39 /* StatusFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusFormat.swift; sourceTree = "<group>"; };
D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; }; D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LargeImageViewController.xib; sourceTree = "<group>"; };
D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; }; D62D2421217AA7E1005076CC /* UserActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityManager.swift; sourceTree = "<group>"; };
D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; }; D62D2423217ABF3F005076CC /* NSUserActivity+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSUserActivity+Extensions.swift"; sourceTree = "<group>"; };
D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; }; D62D2425217ABF63005076CC /* UserActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivityType.swift; sourceTree = "<group>"; };
D62E9980279C691F00C26176 /* NodeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeInfo.swift; sourceTree = "<group>"; };
D62E9982279C69D400C26176 /* WellKnown.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WellKnown.swift; sourceTree = "<group>"; };
D62E9984279CA23900C26176 /* URLSession+Development.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Development.swift"; sourceTree = "<group>"; }; D62E9984279CA23900C26176 /* URLSession+Development.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSession+Development.swift"; sourceTree = "<group>"; };
D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMetaIndicatorsView.swift; sourceTree = "<group>"; }; D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMetaIndicatorsView.swift; sourceTree = "<group>"; };
D62E9988279DB2D100C26176 /* InstanceFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFeatures.swift; sourceTree = "<group>"; }; D62E9988279DB2D100C26176 /* InstanceFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFeatures.swift; sourceTree = "<group>"; };
@ -486,7 +597,6 @@
D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Equatable.swift"; sourceTree = "<group>"; }; D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Equatable.swift"; sourceTree = "<group>"; };
D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Traits.swift"; sourceTree = "<group>"; }; D66A77BA233838DC0058F1EC /* UIFont+Traits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Traits.swift"; sourceTree = "<group>"; };
D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerWrapper.swift; sourceTree = "<group>"; }; D670F8B52537DC890046588A /* EmojiPickerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerWrapper.swift; sourceTree = "<group>"; };
D674A50727F910F300BA03AC /* Pachyderm */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Pachyderm; sourceTree = "<group>"; };
D6757A7B2157E01900721E32 /* XCBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBManager.swift; sourceTree = "<group>"; }; D6757A7B2157E01900721E32 /* XCBManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBManager.swift; sourceTree = "<group>"; };
D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; }; D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBRequestSpec.swift; sourceTree = "<group>"; };
D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; }; D6757A812157E8FA00721E32 /* XCBSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCBSession.swift; sourceTree = "<group>"; };
@ -520,6 +630,7 @@
D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = "<group>"; }; D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Helpers.swift"; sourceTree = "<group>"; };
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; }; D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingHashtagsViewController.swift; sourceTree = "<group>"; };
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = "<group>"; }; D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDirectoryViewController.swift; sourceTree = "<group>"; };
D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectoryOrder.swift; sourceTree = "<group>"; };
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; }; D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedProfileCollectionViewCell.swift; sourceTree = "<group>"; };
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeaturedProfileCollectionViewCell.xib; sourceTree = "<group>"; }; D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FeaturedProfileCollectionViewCell.xib; sourceTree = "<group>"; };
D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; }; D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedNavigationViewController.swift; sourceTree = "<group>"; };
@ -534,6 +645,8 @@
D6969E9D240C81B9002843CE /* NSTextAttachment+Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextAttachment+Emoji.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>"; }; D6969E9F240C8384002843CE /* EmojiLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiLabel.swift; sourceTree = "<group>"; };
D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionsView.swift; sourceTree = "<group>"; }; D6A00B1C26379FC900316AD4 /* PollOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionsView.swift; sourceTree = "<group>"; };
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSegment.swift; sourceTree = "<group>"; };
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationGroup.swift; sourceTree = "<group>"; };
D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = "<group>"; }; D6A3BC7A232195C600FD64D5 /* ActionNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationGroupTableViewCell.xib; sourceTree = "<group>"; }; D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ActionNotificationGroupTableViewCell.xib; sourceTree = "<group>"; };
D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationGroupTableViewCell.swift; sourceTree = "<group>"; }; D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowNotificationGroupTableViewCell.swift; sourceTree = "<group>"; };
@ -633,26 +746,45 @@
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcher.swift; sourceTree = "<group>"; }; D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcher.swift; sourceTree = "<group>"; };
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcherTests.swift; sourceTree = "<group>"; }; D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FuzzyMatcherTests.swift; sourceTree = "<group>"; };
D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = "<group>"; }; D6E426B225337C7000C02E1C /* CustomEmojiImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEmojiImageView.swift; sourceTree = "<group>"; };
D6E426B8253382B300C02E1C /* SearchResultType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultType.swift; sourceTree = "<group>"; };
D6E4885C24A2890C0011C13E /* Tusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tusker.entitlements; sourceTree = "<group>"; }; D6E4885C24A2890C0011C13E /* Tusker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Tusker.entitlements; sourceTree = "<group>"; };
D6E57FA425C26FAB00341037 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; }; D6E57FA425C26FAB00341037 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounter.swift; sourceTree = "<group>"; };
D6E6F26421604242006A8599 /* CharacterCounterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCounterTests.swift; sourceTree = "<group>"; };
D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; }; D6EAE0DA2550CC8A002DB0AC /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = "<group>"; };
D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; }; D6EBF01423C55C0900AE061B /* UIApplication+Scenes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Scenes.swift"; sourceTree = "<group>"; };
D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; }; D6EBF01623C55E0D00AE061B /* UISceneSession+MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISceneSession+MastodonController.swift"; sourceTree = "<group>"; };
D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollapseButton.swift; sourceTree = "<group>"; }; D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCollapseButton.swift; sourceTree = "<group>"; };
D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; }; D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; }; D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; };
D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReporterViewController.swift; sourceTree = "<group>"; }; D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IssueReporterViewController.xib; sourceTree = "<group>"; }; D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterViewController.xib; sourceTree = "<group>"; };
D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; }; D6F953EF21251A2900CF0F2B /* MastodonController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonController.swift; sourceTree = "<group>"; };
D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitchingContainerViewController.swift; sourceTree = "<group>"; }; D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSwitchingContainerViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
D61099A82144B0CC00432DC2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D6F1F9DF27B0613300CB7D88 /* WebURL in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D61099B02144B0CC00432DC2 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDC9212518A000E1C4BB /* Frameworks */ = { D6D4DDC9212518A000E1C4BB /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */, D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */, D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */, D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */, D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
@ -697,6 +829,107 @@
path = "Attachment Gallery"; path = "Attachment Gallery";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D60E2F2F24424F0D005F8713 /* Protocols */ = {
isa = PBXGroup;
children = (
D60E2F3024424F1A005F8713 /* StatusProtocol.swift */,
D60E2F3224425374005F8713 /* AccountProtocol.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
isa = PBXGroup;
children = (
D61099AD2144B0CC00432DC2 /* Pachyderm.h */,
D61099AE2144B0CC00432DC2 /* Info.plist */,
D61099C82144B13C00432DC2 /* Client.swift */,
D6A3BC7223218C6E00FD64D5 /* Utilities */,
D61099D72144B74500432DC2 /* Extensions */,
D61099CC2144B2C300432DC2 /* Request */,
D61099DA2144BDB600432DC2 /* Response */,
D61099DD2144C10C00432DC2 /* Model */,
);
path = Pachyderm;
sourceTree = "<group>";
};
D61099B92144B0CC00432DC2 /* PachydermTests */ = {
isa = PBXGroup;
children = (
D61099BA2144B0CC00432DC2 /* PachydermTests.swift */,
D6E6F26421604242006A8599 /* CharacterCounterTests.swift */,
D61099BC2144B0CC00432DC2 /* Info.plist */,
);
path = PachydermTests;
sourceTree = "<group>";
};
D61099CC2144B2C300432DC2 /* Request */ = {
isa = PBXGroup;
children = (
D61099CA2144B20500432DC2 /* Request.swift */,
D6109A0C214599E100432DC2 /* RequestRange.swift */,
D61099CF2144B2D700432DC2 /* Method.swift */,
D61099D12144B2E600432DC2 /* Body.swift */,
D61099D32144B32E00432DC2 /* Parameter.swift */,
D61099D52144B4B200432DC2 /* FormAttachment.swift */,
);
path = Request;
sourceTree = "<group>";
};
D61099D72144B74500432DC2 /* Extensions */ = {
isa = PBXGroup;
children = (
D61099D82144B76400432DC2 /* Data.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D61099DA2144BDB600432DC2 /* Response */ = {
isa = PBXGroup;
children = (
D61099DB2144BDBF00432DC2 /* Response.swift */,
D6109A0821458C4A00432DC2 /* Empty.swift */,
D6109A0E21459B6900432DC2 /* Pagination.swift */,
);
path = Response;
sourceTree = "<group>";
};
D61099DD2144C10C00432DC2 /* Model */ = {
isa = PBXGroup;
children = (
D60E2F2F24424F0D005F8713 /* Protocols */,
D61099DE2144C11400432DC2 /* MastodonError.swift */,
D6109A04214572BF00432DC2 /* Scope.swift */,
D61099E02144C1DC00432DC2 /* Account.swift */,
D61099E4214561AB00432DC2 /* Application.swift */,
D61099E6214561FF00432DC2 /* Attachment.swift */,
D61099E82145658300432DC2 /* Card.swift */,
D61099EA2145661700432DC2 /* ConversationContext.swift */,
D693A72B25CF8D15003A14E2 /* DirectoryOrder.swift */,
D61099E22144C38900432DC2 /* Emoji.swift */,
D61099EC2145664800432DC2 /* Filter.swift */,
D6109A0021456B0800432DC2 /* Hashtag.swift */,
D61099EE214566C000432DC2 /* Instance.swift */,
D61099F02145686D00432DC2 /* List.swift */,
D6109A062145756700432DC2 /* LoginSettings.swift */,
D61099F22145688600432DC2 /* Mention.swift */,
D62E9980279C691F00C26176 /* NodeInfo.swift */,
D61099F4214568C300432DC2 /* Notification.swift */,
D623A53E2635F6910095BD04 /* Poll.swift */,
D61099F62145693500432DC2 /* PushSubscription.swift */,
D6109A022145722C00432DC2 /* RegisteredApplication.swift */,
D61099F82145698900432DC2 /* Relationship.swift */,
D61099FA214569F600432DC2 /* Report.swift */,
D61099FC21456A1D00432DC2 /* SearchResults.swift */,
D6E426B8253382B300C02E1C /* SearchResultType.swift */,
D61099FE21456A4C00432DC2 /* Status.swift */,
D6285B4E21EA695800FE4B39 /* StatusContentType.swift */,
D6109A10214607D500432DC2 /* Timeline.swift */,
D62E9982279C69D400C26176 /* WellKnown.swift */,
);
path = Model;
sourceTree = "<group>";
};
D611C2CC232DC5FC00C86A49 /* Hashtag Cell */ = { D611C2CC232DC5FC00C86A49 /* Hashtag Cell */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -704,6 +937,7 @@
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */, D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */, D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */,
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */, D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */,
D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */,
); );
path = "Hashtag Cell"; path = "Hashtag Cell";
sourceTree = "<group>"; sourceTree = "<group>";
@ -784,10 +1018,7 @@
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */, D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */,
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */, D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */,
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */, D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */,
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */, D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */, D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */, D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */, D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
@ -1138,6 +1369,18 @@
path = "Account Detail"; path = "Account Detail";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D6A3BC7223218C6E00FD64D5 /* Utilities */ = {
isa = PBXGroup;
children = (
D60A4FFB238B726A008AC647 /* StatusState.swift */,
D6E6F26221603F8B006A8599 /* CharacterCounter.swift */,
D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */,
D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */,
D61AC1D2232E928600C54D2D /* InstanceSelector.swift */,
);
path = Utilities;
sourceTree = "<group>";
};
D6A3BC822321F69400FD64D5 /* Account List */ = { D6A3BC822321F69400FD64D5 /* Account List */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1255,7 +1498,6 @@
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */, D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
D620483723D38190008A63EF /* StatusContentTextView.swift */, D620483723D38190008A63EF /* StatusContentTextView.swift */,
04ED00B021481ED800567C53 /* SteppedProgressView.swift */, 04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */, D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */, D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */, D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
@ -1314,7 +1556,8 @@
D6D4DDC3212518A000E1C4BB = { D6D4DDC3212518A000E1C4BB = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D674A50727F910F300BA03AC /* Pachyderm */, D61099AC2144B0CC00432DC2 /* Pachyderm */,
D61099B92144B0CC00432DC2 /* PachydermTests */,
D6D4DDCE212518A000E1C4BB /* Tusker */, D6D4DDCE212518A000E1C4BB /* Tusker */,
D6D4DDE3212518A200E1C4BB /* TuskerTests */, D6D4DDE3212518A200E1C4BB /* TuskerTests */,
D6D4DDEE212518A200E1C4BB /* TuskerUITests */, D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
@ -1330,6 +1573,8 @@
D6D4DDCC212518A000E1C4BB /* Tusker.app */, D6D4DDCC212518A000E1C4BB /* Tusker.app */,
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */, D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */, D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */, D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
); );
name = Products; name = Products;
@ -1380,7 +1625,6 @@
D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */, D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */,
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */, D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */,
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */, D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */,
D6114E1627F8BB210080E273 /* VersionTests.swift */,
D6D4DDE6212518A200E1C4BB /* Info.plist */, D6D4DDE6212518A200E1C4BB /* Info.plist */,
); );
path = TuskerTests; path = TuskerTests;
@ -1437,9 +1681,8 @@
D6F2E960249E772F005846BB /* Crash Reporter */ = { D6F2E960249E772F005846BB /* Crash Reporter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */, D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */,
D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */, D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */,
D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */,
); );
path = "Crash Reporter"; path = "Crash Reporter";
sourceTree = "<group>"; sourceTree = "<group>";
@ -1455,7 +1698,58 @@
}; };
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
D61099A62144B0CC00432DC2 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
isa = PBXNativeTarget;
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
buildPhases = (
D61099A62144B0CC00432DC2 /* Headers */,
D61099A72144B0CC00432DC2 /* Sources */,
D61099A82144B0CC00432DC2 /* Frameworks */,
D61099A92144B0CC00432DC2 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Pachyderm;
packageProductDependencies = (
D6F1F9DE27B0613300CB7D88 /* WebURL */,
);
productName = Pachyderm;
productReference = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */;
productType = "com.apple.product-type.framework";
};
D61099B22144B0CC00432DC2 /* PachydermTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */;
buildPhases = (
D61099AF2144B0CC00432DC2 /* Sources */,
D61099B02144B0CC00432DC2 /* Frameworks */,
D61099B12144B0CC00432DC2 /* Resources */,
);
buildRules = (
);
dependencies = (
D61099B62144B0CC00432DC2 /* PBXTargetDependency */,
D61099B82144B0CC00432DC2 /* PBXTargetDependency */,
);
name = PachydermTests;
productName = PachydermTests;
productReference = D61099B32144B0CC00432DC2 /* PachydermTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
D6D4DDCB212518A000E1C4BB /* Tusker */ = { D6D4DDCB212518A000E1C4BB /* Tusker */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */; buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
@ -1471,6 +1765,7 @@
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */, D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
); );
name = Tusker; name = Tusker;
@ -1479,7 +1774,6 @@
D69CCBBE249E6EFD000AF167 /* CrashReporter */, D69CCBBE249E6EFD000AF167 /* CrashReporter */,
D60CFFDA24A290BA00D00083 /* SwiftSoup */, D60CFFDA24A290BA00D00083 /* SwiftSoup */,
D6676CA427A8D0020052936B /* WebURLFoundationExtras */, D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
D674A50827F9128D00BA03AC /* Pachyderm */,
); );
productName = Tusker; productName = Tusker;
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */; productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
@ -1550,6 +1844,15 @@
LastUpgradeCheck = 1250; LastUpgradeCheck = 1250;
ORGANIZATIONNAME = Shadowfacts; ORGANIZATIONNAME = Shadowfacts;
TargetAttributes = { TargetAttributes = {
D61099AA2144B0CC00432DC2 = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1020;
};
D61099B22144B0CC00432DC2 = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1020;
TestTargetID = D6D4DDCB212518A000E1C4BB;
};
D6D4DDCB212518A000E1C4BB = { D6D4DDCB212518A000E1C4BB = {
CreatedOnToolsVersion = 10.0; CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1200; LastSwiftMigration = 1200;
@ -1570,7 +1873,7 @@
}; };
}; };
buildConfigurationList = D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */; buildConfigurationList = D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */;
compatibilityVersion = "Xcode 13.0"; compatibilityVersion = "Xcode 9.3";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
@ -1597,12 +1900,28 @@
D6D4DDCB212518A000E1C4BB /* Tusker */, D6D4DDCB212518A000E1C4BB /* Tusker */,
D6D4DDDF212518A200E1C4BB /* TuskerTests */, D6D4DDDF212518A200E1C4BB /* TuskerTests */,
D6D4DDEA212518A200E1C4BB /* TuskerUITests */, D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
D61099AA2144B0CC00432DC2 /* Pachyderm */,
D61099B22144B0CC00432DC2 /* PachydermTests */,
D6E343A7265AAD6B00C4AA01 /* OpenInTusker */, D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
D61099A92144B0CC00432DC2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D61099B12144B0CC00432DC2 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDCA212518A000E1C4BB /* Resources */ = { D6D4DDCA212518A000E1C4BB /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1631,7 +1950,7 @@
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */, D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */, D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */, D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */, D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */,
D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */, D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */,
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */, D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
D6DEA0DF268400C300FE896A /* ConfirmLoadMoreTableViewCell.xib in Resources */, D6DEA0DF268400C300FE896A /* ConfirmLoadMoreTableViewCell.xib in Resources */,
@ -1708,6 +2027,68 @@
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
D61099A72144B0CC00432DC2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D61099E5214561AB00432DC2 /* Application.swift in Sources */,
D62E9983279C69D400C26176 /* WellKnown.swift in Sources */,
D61099FF21456A4C00432DC2 /* Status.swift in Sources */,
D61099E32144C38900432DC2 /* Emoji.swift in Sources */,
D6109A0D214599E100432DC2 /* RequestRange.swift in Sources */,
D61099D92144B76400432DC2 /* Data.swift in Sources */,
D61099EB2145661700432DC2 /* ConversationContext.swift in Sources */,
D61AC1D3232E928600C54D2D /* InstanceSelector.swift in Sources */,
D61099C92144B13C00432DC2 /* Client.swift in Sources */,
D61099D42144B32E00432DC2 /* Parameter.swift in Sources */,
D61099CB2144B20500432DC2 /* Request.swift in Sources */,
D6109A05214572BF00432DC2 /* Scope.swift in Sources */,
D6109A11214607D500432DC2 /* Timeline.swift in Sources */,
D6E426B9253382B300C02E1C /* SearchResultType.swift in Sources */,
D60E2F3324425374005F8713 /* AccountProtocol.swift in Sources */,
D61099E7214561FF00432DC2 /* Attachment.swift in Sources */,
D60E2F3124424F1A005F8713 /* StatusProtocol.swift in Sources */,
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
D6E6F26321603F8B006A8599 /* CharacterCounter.swift in Sources */,
D61099FB214569F600432DC2 /* Report.swift in Sources */,
D693A72C25CF8D15003A14E2 /* DirectoryOrder.swift in Sources */,
D61099F92145698900432DC2 /* Relationship.swift in Sources */,
D61099E12144C1DC00432DC2 /* Account.swift in Sources */,
D61099E92145658300432DC2 /* Card.swift in Sources */,
D61099F32145688600432DC2 /* Mention.swift in Sources */,
D6109A0F21459B6900432DC2 /* Pagination.swift in Sources */,
D6109A032145722C00432DC2 /* RegisteredApplication.swift in Sources */,
D6109A0921458C4A00432DC2 /* Empty.swift in Sources */,
D6285B4F21EA695800FE4B39 /* StatusContentType.swift in Sources */,
D62E9981279C691F00C26176 /* NodeInfo.swift in Sources */,
D6A3BC7923218E9200FD64D5 /* NotificationGroup.swift in Sources */,
D61099DF2144C11400432DC2 /* MastodonError.swift in Sources */,
D6A3BC7723218E1300FD64D5 /* TimelineSegment.swift in Sources */,
D61099D62144B4B200432DC2 /* FormAttachment.swift in Sources */,
D6109A072145756700432DC2 /* LoginSettings.swift in Sources */,
D61099ED2145664800432DC2 /* Filter.swift in Sources */,
D61099DC2144BDBF00432DC2 /* Response.swift in Sources */,
D61099F72145693500432DC2 /* PushSubscription.swift in Sources */,
D61099F5214568C300432DC2 /* Notification.swift in Sources */,
D61099EF214566C000432DC2 /* Instance.swift in Sources */,
D61099D22144B2E600432DC2 /* Body.swift in Sources */,
D623A53F2635F6910095BD04 /* Poll.swift in Sources */,
D63569E023908A8D003DD353 /* StatusState.swift in Sources */,
D6109A0121456B0800432DC2 /* Hashtag.swift in Sources */,
D61099FD21456A1D00432DC2 /* SearchResults.swift in Sources */,
D61099F12145686D00432DC2 /* List.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D61099AF2144B0CC00432DC2 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D6E6F26521604242006A8599 /* CharacterCounterTests.swift in Sources */,
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
D6D4DDC8212518A000E1C4BB /* Sources */ = { D6D4DDC8212518A000E1C4BB /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -1724,7 +2105,7 @@
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */, D60E2F292442372B005F8713 /* AccountMO.swift in Sources */,
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */, D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */,
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */, D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */, D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */,
D6DEA0DE268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift in Sources */, D6DEA0DE268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift in Sources */,
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */, D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */,
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */, 04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
@ -1763,7 +2144,7 @@
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */, D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */, D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
D620483623D38075008A63EF /* ContentTextView.swift in Sources */, D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
D6F2E965249E8BFD005846BB /* IssueReporterViewController.swift in Sources */, D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */,
D6A57408255C53EC00674551 /* ComposeTextViewCaretScrolling.swift in Sources */, D6A57408255C53EC00674551 /* ComposeTextViewCaretScrolling.swift in Sources */,
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */, D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */,
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */, D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
@ -1806,7 +2187,6 @@
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */, 04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
D677284C24ECBE9100C732D3 /* ComposeAvatarImageView.swift in Sources */, D677284C24ECBE9100C732D3 /* ComposeAvatarImageView.swift in Sources */,
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */, D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */,
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */,
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */, D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */, D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */, D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
@ -1884,7 +2264,6 @@
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */, D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */, D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */, D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */,
D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */, D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */,
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */, D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */, D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
@ -1894,7 +2273,6 @@
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */, D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */, 04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */, D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */, D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
D6B81F442560390300F6E31D /* MenuController.swift in Sources */, D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */, D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */,
@ -1940,7 +2318,6 @@
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */, D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */, D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */, D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */,
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */, D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */, D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */,
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */, D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
@ -1960,7 +2337,6 @@
files = ( files = (
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */, D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */,
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */, D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */,
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */,
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */, D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -1990,6 +2366,21 @@
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */ /* Begin PBXTargetDependency section */
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
targetProxy = D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */;
};
D61099B82144B0CC00432DC2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
targetProxy = D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */;
};
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
targetProxy = D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */;
};
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = { D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = D6D4DDCB212518A000E1C4BB /* Tusker */; target = D6D4DDCB212518A000E1C4BB /* Tusker */;
@ -2035,6 +2426,111 @@
/* End PBXVariantGroup section */ /* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
D61099C32144B0CC00432DC2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = V4WK9KR9U2;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Pachyderm/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_CODE_SIGN_FLAGS = "--deep";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.Pachyderm;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
D61099C42144B0CC00432DC2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = V4WK9KR9U2;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Pachyderm/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
OTHER_CODE_SIGN_FLAGS = "--deep";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = space.vaccor.Tusker.Pachyderm;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
D61099C62144B0CC00432DC2 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = PachydermTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker.PachydermTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tusker.app/Tusker";
};
name = Debug;
};
D61099C72144B0CC00432DC2 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = HGYVAQA9FW;
INFOPLIST_FILE = PachydermTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker.PachydermTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tusker.app/Tusker";
};
name = Release;
};
D6D4DDF2212518A200E1C4BB /* Debug */ = { D6D4DDF2212518A200E1C4BB /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
@ -2165,7 +2661,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2196,7 +2692,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2306,7 +2802,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2333,7 +2829,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 25; CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_TEAM = V4WK9KR9U2; DEVELOPMENT_TEAM = V4WK9KR9U2;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -2356,6 +2852,24 @@
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D61099C32144B0CC00432DC2 /* Debug */,
D61099C42144B0CC00432DC2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
D61099C62144B0CC00432DC2 /* Debug */,
D61099C72144B0CC00432DC2 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = { D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@ -2449,10 +2963,6 @@
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */; package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
productName = WebURLFoundationExtras; productName = WebURLFoundationExtras;
}; };
D674A50827F9128D00BA03AC /* Pachyderm */ = {
isa = XCSwiftPackageProductDependency;
productName = Pachyderm;
};
D69CCBBE249E6EFD000AF167 /* CrashReporter */ = { D69CCBBE249E6EFD000AF167 /* CrashReporter */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */; package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
@ -2463,6 +2973,11 @@
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */; package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
productName = SheetController; productName = SheetController;
}; };
D6F1F9DE27B0613300CB7D88 /* WebURL */ = {
isa = XCSwiftPackageProductDependency;
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
productName = WebURL;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
/* Begin XCVersionGroup section */ /* Begin XCVersionGroup section */

View File

@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "self:"> location = "self:Tusker.xcodeproj">
</FileRef> </FileRef>
</Workspace> </Workspace>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:Tusker.xcodeproj">
</FileRef>
<FileRef
location = "group:BlankSlate.xcappdata">
</FileRef>
<FileRef
location = "group:Embassy/Embassy.xcodeproj">
</FileRef>
<FileRef
location = "group:Ambassador/Ambassador.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,52 @@
{
"object": {
"pins": [
{
"package": "PLCrashReporter",
"repositoryURL": "https://github.com/microsoft/plcrashreporter",
"state": {
"branch": null,
"revision": "de6b8f9db4b2a0aa859a5507550a70548e4da936",
"version": "1.8.1"
}
},
{
"package": "SheetController",
"repositoryURL": "https://git.shadowfacts.net/shadowfacts/SheetController.git",
"state": {
"branch": "master",
"revision": "aa0f5192eaf19d01c89dbfa9ec5878a700376f23",
"version": null
}
},
{
"package": "swift-system",
"repositoryURL": "https://github.com/apple/swift-system.git",
"state": {
"branch": null,
"revision": "836bc4557b74fe6d2660218d56e3ce96aff76574",
"version": "1.1.1"
}
},
{
"package": "swift-url",
"repositoryURL": "https://github.com/karwa/swift-url",
"state": {
"branch": "main",
"revision": "9d06f9f89397de16c8942aa123c425568654fd6a",
"version": null
}
},
{
"package": "SwiftSoup",
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git",
"state": {
"branch": null,
"revision": "774dc9c7213085db8aa59595e27c1cd22e428904",
"version": "2.3.2"
}
}
]
},
"version": 1
}

View File

@ -29,9 +29,8 @@ class AccountActivityItemSource: NSObject, UIActivityItemSource {
metadata.originalURL = account.url metadata.originalURL = account.url
metadata.url = account.url metadata.url = account.url
metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)" metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)"
if let avatar = account.avatar, if let data = ImageCache.avatars.getData(account.avatar),
let data = ImageCache.avatars.getData(avatar), let image = UIImage(data: data) {
let image = UIImage(data: data) {
metadata.iconProvider = NSItemProvider(object: image) metadata.iconProvider = NSItemProvider(object: image)
} }
return metadata return metadata

View File

@ -32,9 +32,8 @@ class StatusActivityItemSource: NSObject, UIActivityItemSource {
let doc = try! SwiftSoup.parse(status.content) let doc = try! SwiftSoup.parse(status.content)
let content = try! doc.text() let content = try! doc.text()
metadata.title = "\(status.account.displayName): \"\(content)\"" metadata.title = "\(status.account.displayName): \"\(content)\""
if let avatar = status.account.avatar, if let data = ImageCache.avatars.getData(status.account.avatar),
let data = ImageCache.avatars.getData(avatar), let image = UIImage(data: data) {
let image = UIImage(data: data) {
metadata.iconProvider = NSItemProvider(object: image) metadata.iconProvider = NSItemProvider(object: image)
} }
return metadata return metadata

View File

@ -67,54 +67,28 @@ class MastodonController: ObservableObject {
return client.run(request, completion: completion) return client.run(request, completion: completion)
} }
func run<Result>(_ request: Request<Result>) async throws -> (Result, Pagination?) { func registerApp(completion: @escaping (_ clientID: String, _ clientSecret: String) -> Void) {
return try await withCheckedThrowingContinuation({ continuation in guard client.clientID == nil,
client.run(request) { response in client.clientSecret == nil else {
switch response {
case .failure(let error): completion(client.clientID!, client.clientSecret!)
continuation.resume(throwing: error) return
case .success(let result, let pagination): }
continuation.resume(returning: (result, pagination))
} client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
} guard case let .success(app, _) = response else { fatalError() }
})
}
/// - Returns: A tuple of client ID and client secret.
func registerApp() async throws -> (String, String) {
if let clientID = client.clientID,
let clientSecret = client.clientSecret {
return (clientID, clientSecret)
} else {
let app: RegisteredApplication = try await withCheckedThrowingContinuation({ continuation in
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
switch response {
case .failure(let error):
continuation.resume(throwing: error)
case .success(let app, _):
continuation.resume(returning: app)
}
}
})
self.client.clientID = app.clientID self.client.clientID = app.clientID
self.client.clientSecret = app.clientSecret self.client.clientSecret = app.clientSecret
return (app.clientID, app.clientSecret) completion(app.clientID, app.clientSecret)
} }
} }
/// - Returns: The access token func authorize(authorizationCode: String, completion: @escaping (_ accessToken: String) -> Void) {
func authorize(authorizationCode: String) async throws -> String { client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
return try await withCheckedThrowingContinuation({ continuation in guard case let .success(settings, _) = response else { fatalError() }
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in self.client.accessToken = settings.accessToken
switch response { completion(settings.accessToken)
case .failure(let error): }
continuation.resume(throwing: error)
case .success(let settings, _):
self.client.accessToken = settings.accessToken
continuation.resume(returning: settings.accessToken)
}
}
})
} }
func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) { func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) {
@ -146,18 +120,6 @@ class MastodonController: ObservableObject {
} }
} }
func getOwnAccount() async throws -> Account {
if let account = account {
return account
} else {
return try await withCheckedThrowingContinuation({ continuation in
self.getOwnAccount { result in
continuation.resume(with: result)
}
})
}
}
func getOwnInstance(completion: ((Instance) -> Void)? = nil) { func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
getOwnInstanceInternal(retryAttempt: 0, completion: completion) getOwnInstanceInternal(retryAttempt: 0, completion: completion)
} }

View File

@ -19,7 +19,7 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
} }
@NSManaged public var acct: String @NSManaged public var acct: String
@NSManaged public var avatar: URL? @NSManaged public var avatar: URL
@NSManaged public var botCD: Bool @NSManaged public var botCD: Bool
@NSManaged public var createdAt: Date @NSManaged public var createdAt: Date
@NSManaged public var displayName: String @NSManaged public var displayName: String

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Account" representedClassName="AccountMO" syncable="YES"> <entity name="Account" representedClassName="AccountMO" syncable="YES">
<attribute name="acct" attributeType="String"/> <attribute name="acct" attributeType="String"/>
<attribute name="avatar" optional="YES" attributeType="URI"/> <attribute name="avatar" attributeType="URI"/>
<attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/> <attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="displayName" attributeType="String"/> <attribute name="displayName" attributeType="String"/>

View File

@ -11,7 +11,6 @@ import Pachyderm
struct InstanceFeatures { struct InstanceFeatures {
private(set) var instanceType = InstanceType.mastodon private(set) var instanceType = InstanceType.mastodon
private(set) var version: Version?
private(set) var maxStatusChars = 500 private(set) var maxStatusChars = 500
var localOnlyPosts: Bool { var localOnlyPosts: Bool {
@ -30,14 +29,6 @@ struct InstanceFeatures {
instanceType == .pleroma instanceType == .pleroma
} }
var profilePinnedStatuses: Bool {
instanceType != .pixelfed
}
var trendingStatusesAndLinks: Bool {
instanceType == .mastodon && version != nil && version! >= Version(3, 5, 0)
}
mutating func update(instance: Instance, nodeInfo: NodeInfo?) { mutating func update(instance: Instance, nodeInfo: NodeInfo?) {
let ver = instance.version.lowercased() let ver = instance.version.lowercased()
if ver.contains("glitch") { if ver.contains("glitch") {
@ -46,14 +37,10 @@ struct InstanceFeatures {
instanceType = .hometown instanceType = .hometown
} else if ver.contains("pleroma") { } else if ver.contains("pleroma") {
instanceType = .pleroma instanceType = .pleroma
} else if ver.contains("pixelfed") {
instanceType = .pixelfed
} else { } else {
instanceType = .mastodon instanceType = .mastodon
} }
version = Version(string: ver)
maxStatusChars = instance.maxStatusCharacters ?? 500 maxStatusChars = instance.maxStatusCharacters ?? 500
} }
} }
@ -64,7 +51,6 @@ extension InstanceFeatures {
case pleroma case pleroma
case hometown case hometown
case glitch case glitch
case pixelfed
var isMastodon: Bool { var isMastodon: Bool {
switch self { switch self {
@ -76,56 +62,3 @@ extension InstanceFeatures {
} }
} }
} }
extension InstanceFeatures {
struct Version: Equatable, Comparable {
let major: Int
let minor: Int
let patch: Int
init(_ major: Int, _ minor: Int, _ patch: Int) {
self.major = major
self.minor = minor
self.patch = patch
}
init?(string: String) {
let regex = try! NSRegularExpression(pattern: "^(\\d+)\\.(\\d+)\\.(\\d+).*$")
guard let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)),
match.numberOfRanges == 4 else {
return nil
}
let majorStr = (string as NSString).substring(with: match.range(at: 1))
let minorStr = (string as NSString).substring(with: match.range(at: 2))
let patchStr = (string as NSString).substring(with: match.range(at: 3))
guard let major = Int(majorStr),
let minor = Int(minorStr),
let patch = Int(patchStr) else {
return nil
}
self.major = major
self.minor = minor
self.patch = patch
}
static func ==(lhs: Version, rhs: Version) -> Bool {
return lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch == rhs.patch
}
static func < (lhs: InstanceFeatures.Version, rhs: InstanceFeatures.Version) -> Bool {
if lhs.major < rhs.major {
return true
} else if lhs.major > rhs.major {
return false
} else if lhs.minor < rhs.minor {
return true
} else if lhs.minor > rhs.minor {
return false
} else if lhs.patch < rhs.patch {
return true
} else {
return false
}
}
}
}

View File

@ -127,9 +127,7 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
return return
} }
window!.rootViewController = CrashReporterViewController.create(report: report, dismiss: { window!.rootViewController = CrashReporterViewController.create(report: report)
self.showAppOrOnboardingUI()
})
#endif #endif
} }
@ -209,3 +207,11 @@ extension MainSceneDelegate: OnboardingViewControllerDelegate {
activateAccount(account, animated: false) activateAccount(account, animated: false)
} }
} }
extension MainSceneDelegate: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true) {
self.showAppOrOnboardingUI()
}
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Shadowfacts. All rights reserved. // Copyright © 2019 Shadowfacts. All rights reserved.
// //
import UIKit import Foundation
import Pachyderm import Pachyderm
enum StatusFormat: CaseIterable { enum StatusFormat: CaseIterable {

View File

@ -6,7 +6,7 @@
// Copyright © 2018 Shadowfacts. All rights reserved. // Copyright © 2018 Shadowfacts. All rights reserved.
// //
import UIKit import Foundation
import Pachyderm import Pachyderm
import Combine import Combine
@ -64,7 +64,6 @@ class Preferences: Codable, ObservableObject {
self.defaultNotificationsMode = try container.decode(NotificationsMode.self, forKey: .defaultNotificationsType) self.defaultNotificationsMode = try container.decode(NotificationsMode.self, forKey: .defaultNotificationsType)
self.grayscaleImages = try container.decodeIfPresent(Bool.self, forKey: .grayscaleImages) ?? false self.grayscaleImages = try container.decodeIfPresent(Bool.self, forKey: .grayscaleImages) ?? false
self.disableInfiniteScrolling = try container.decodeIfPresent(Bool.self, forKey: .disableInfiniteScrolling) ?? false self.disableInfiniteScrolling = try container.decodeIfPresent(Bool.self, forKey: .disableInfiniteScrolling) ?? false
self.hideDiscover = try container.decodeIfPresent(Bool.self, forKey: .hideDiscover) ?? false
self.silentActions = try container.decode([String: Permission].self, forKey: .silentActions) self.silentActions = try container.decode([String: Permission].self, forKey: .silentActions)
self.statusContentType = try container.decode(StatusContentType.self, forKey: .statusContentType) self.statusContentType = try container.decode(StatusContentType.self, forKey: .statusContentType)
@ -103,7 +102,6 @@ class Preferences: Codable, ObservableObject {
try container.encode(defaultNotificationsMode, forKey: .defaultNotificationsType) try container.encode(defaultNotificationsMode, forKey: .defaultNotificationsType)
try container.encode(grayscaleImages, forKey: .grayscaleImages) try container.encode(grayscaleImages, forKey: .grayscaleImages)
try container.encode(disableInfiniteScrolling, forKey: .disableInfiniteScrolling) try container.encode(disableInfiniteScrolling, forKey: .disableInfiniteScrolling)
try container.encode(hideDiscover, forKey: .hideDiscover)
try container.encode(silentActions, forKey: .silentActions) try container.encode(silentActions, forKey: .silentActions)
try container.encode(statusContentType, forKey: .statusContentType) try container.encode(statusContentType, forKey: .statusContentType)
@ -144,7 +142,6 @@ class Preferences: Codable, ObservableObject {
@Published var defaultNotificationsMode = NotificationsMode.allNotifications @Published var defaultNotificationsMode = NotificationsMode.allNotifications
@Published var grayscaleImages = false @Published var grayscaleImages = false
@Published var disableInfiniteScrolling = false @Published var disableInfiniteScrolling = false
@Published var hideDiscover = false
// MARK: Advanced // MARK: Advanced
@Published var silentActions: [String: Permission] = [:] @Published var silentActions: [String: Permission] = [:]
@ -182,7 +179,6 @@ class Preferences: Codable, ObservableObject {
case defaultNotificationsType case defaultNotificationsType
case grayscaleImages case grayscaleImages
case disableInfiniteScrolling case disableInfiniteScrolling
case hideDiscover
case silentActions case silentActions
case statusContentType case statusContentType

View File

@ -143,9 +143,7 @@ class AssetCollectionViewController: UIViewController, UICollectionViewDelegate
switch PHPhotoLibrary.authorizationStatus(for: .readWrite) { switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
case .notDetermined: case .notDetermined:
PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in
DispatchQueue.main.async { self.loadAssets()
self.loadAssets()
}
} }
return return

View File

@ -189,7 +189,7 @@ struct ComposeAutocompleteMentionsView: View {
} }
} }
var avatar: URL? { var avatar: URL {
switch self { switch self {
case let .pachyderm(account): case let .pachyderm(account):
return account.avatar return account.avatar
@ -346,7 +346,7 @@ struct ComposeAutocompleteHashtagsView: View {
let group = DispatchGroup() let group = DispatchGroup()
group.enter() group.enter()
trendingRequest = mastodonController.run(Client.getTrendingHashtags()) { (response) in trendingRequest = mastodonController.run(Client.getTrends()) { (response) in
defer { group.leave() } defer { group.leave() }
guard case let .success(trends, _) = response else { return } guard case let .success(trends, _) = response else { return }
trendingTags = trends trendingTags = trends

View File

@ -157,7 +157,7 @@ class ConversationTableViewController: EnhancedTableViewController {
DispatchQueue.main.async { DispatchQueue.main.async {
self.loadingState = .unloaded self.loadingState = .unloaded
let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading Status") { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadMainStatus() self?.loadMainStatus()
} }
@ -210,7 +210,7 @@ class ConversationTableViewController: EnhancedTableViewController {
DispatchQueue.main.async { DispatchQueue.main.async {
self.loadingState = .loadedMain self.loadingState = .loadedMain
let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading Content") { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadContext(for: mainStatus) self?.loadContext(for: mainStatus)
} }

View File

@ -86,16 +86,14 @@ class ExpandThreadTableViewCell: UITableViewCell {
xConstraint xConstraint
]) ])
if let avatar = account.avatar { let req = ImageCache.avatars.get(account.avatar) { [weak accountImageView] (_, image) in
let req = ImageCache.avatars.get(avatar) { [weak accountImageView] (_, image) in DispatchQueue.main.async {
DispatchQueue.main.async { accountImageView?.image = image
accountImageView?.image = image
}
}
if let req = req {
avatarRequests.append(req)
} }
} }
if let req = req {
avatarRequests.append(req)
}
} }
} }

View File

@ -2,46 +2,132 @@
// CrashReporterViewController.swift // CrashReporterViewController.swift
// Tusker // Tusker
// //
// Created by Shadowfacts on 3/29/22. // Created by Shadowfacts on 6/20/20.
// Copyright © 2022 Shadowfacts. All rights reserved. // Copyright © 2020 Shadowfacts. All rights reserved.
// //
import UIKit import UIKit
import CrashReporter import CrashReporter
import MessageUI
class CrashReporterViewController: UIViewController {
class CrashReporterViewController: IssueReporterViewController {
private let report: PLCrashReport private let report: PLCrashReport
private var reportText: String!
override var preamble: String { private var reportFilename: String {
"Tusker has detected that it crashed the last time it was running. You can email the report to the developer or skip sending and continue to the app. You may review the report below before sending.\n\nIf you choose to send the report, please include any additional details about what you were doing prior to the crash that may be pertinent."
}
override var subject: String {
"Tusker Crash Report"
}
static func create(report: PLCrashReport, dismiss: @escaping () -> Void) -> UINavigationController {
return create(CrashReporterViewController(report: report, dismiss: dismiss))
}
private init(report: PLCrashReport, dismiss: @escaping () -> Void) {
self.report = report
let reportText = PLCrashReportTextFormatter.stringValue(for: report, with: PLCrashReportTextFormatiOS)!
let timestamp = ISO8601DateFormatter().string(from: report.systemInfo.timestamp) let timestamp = ISO8601DateFormatter().string(from: report.systemInfo.timestamp)
let reportFilename = "Tusker-crash-\(timestamp).crash" return "Tusker-crash-\(timestamp).crash"
}
@IBOutlet weak var crashReportTextView: UITextView!
@IBOutlet weak var sendReportButton: UIButton!
static func create(report: PLCrashReport) -> UINavigationController {
let nav = UINavigationController(rootViewController: CrashReporterViewController(report: report))
nav.navigationBar.prefersLargeTitles = true
return nav
}
private init(report: PLCrashReport){
self.report = report
super.init(reportText: reportText, reportFilename: reportFilename, dismiss: dismiss) super.init(nibName: "CrashReporterViewController", bundle: .main)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
navigationItem.title = NSLocalizedString("Crash Detected", comment: "crash reporter title") navigationItem.title = NSLocalizedString("Crash Detected", comment: "crash reporter title")
navigationItem.largeTitleDisplayMode = .always
crashReportTextView.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
reportText = PLCrashReportTextFormatter.stringValue(for: report, with: PLCrashReportTextFormatiOS)!
let info = "Tusker has detected that it crashed the last time it was running. You can email the report to the developer or skip sending and continue to the app. You may review the report below before sending.\n\nIf you choose to send the report, please include any additional details about what you were doing prior to the crash that may be pertinent.\n\n"
let attributed = NSMutableAttributedString()
attributed.append(NSAttributedString(string: info, attributes: [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
NSAttributedString.Key.foregroundColor: UIColor.label
]))
attributed.append(NSAttributedString(string: reportText, attributes: [
NSAttributedString.Key.font: UIFont.monospacedSystemFont(ofSize: 14, weight: .regular),
NSAttributedString.Key.foregroundColor: UIColor.label
]))
crashReportTextView.attributedText = attributed
sendReportButton.layer.cornerRadius = 12.5
sendReportButton.layer.masksToBounds = true
sendReportButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(sendReportButtonLongPressed)))
} }
private func updateSendReportButtonColor(lightened: Bool, animate: Bool) {
let color: UIColor
if lightened {
var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0
UIColor.systemBlue.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
color = UIColor(hue: hue, saturation: 0.85 * saturation, brightness: brightness, alpha: alpha)
} else {
color = .systemBlue
}
if animate {
UIView.animate(withDuration: 0.25) {
self.sendReportButton.backgroundColor = color
}
} else {
sendReportButton.backgroundColor = color
}
}
@IBAction func sendReportTouchDown(_ sender: Any) {
updateSendReportButtonColor(lightened: true, animate: false)
}
@IBAction func sendReportButtonTouchDragExit(_ sender: Any) {
updateSendReportButtonColor(lightened: false, animate: true)
}
@IBAction func sendReportButtonTouchDragEnter(_ sender: Any) {
updateSendReportButtonColor(lightened: true, animate: true)
}
@IBAction func sendReportTouchUpInside(_ sender: Any) {
updateSendReportButtonColor(lightened: false, animate: true)
let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self
composeVC.setToRecipients(["me@shadowfacts.net"])
composeVC.setSubject("Tusker Crash Report")
let data = reportText.data(using: .utf8)!
composeVC.addAttachmentData(data, mimeType: "text/plain", fileName: reportFilename)
self.present(composeVC, animated: true)
}
@objc func sendReportButtonLongPressed() {
let dir = FileManager.default.temporaryDirectory
let url = dir.appendingPathComponent(reportFilename)
try! reportText.data(using: .utf8)!.write(to: url)
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
present(activityController, animated: true)
}
@IBAction func cancelPressed(_ sender: Any) {
(view.window!.windowScene!.delegate as! MainSceneDelegate).showAppOrOnboardingUI()
}
}
extension CrashReporterViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true) {
(self.view.window!.windowScene!.delegate as! MainSceneDelegate).showAppOrOnboardingUI()
}
}
} }

View File

@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="IssueReporterViewController" customModule="Tusker" customModuleProvider="target"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CrashReporterViewController" customModule="Tusker" customModuleProvider="target">
<connections> <connections>
<outlet property="crashReportTextView" destination="hxN-7J-Usc" id="TGd-yq-Ds5"/> <outlet property="crashReportTextView" destination="hxN-7J-Usc" id="TGd-yq-Ds5"/>
<outlet property="sendReportButton" destination="Ofm-5l-nAp" id="6xM-hz-uvw"/> <outlet property="sendReportButton" destination="Ofm-5l-nAp" id="6xM-hz-uvw"/>
@ -29,9 +27,9 @@
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="hxN-7J-Usc"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalHuggingPriority="249" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="hxN-7J-Usc">
<rect key="frame" x="0.0" y="0.0" width="414" height="166.5"/> <rect key="frame" x="0.0" y="0.0" width="414" height="166.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string> <string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<color key="textColor" systemColor="labelColor"/> <color key="textColor" systemColor="labelColor" cocoaTouchSystemColor="darkTextColor"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView> </textView>
@ -45,13 +43,13 @@
<viewLayoutGuide key="contentLayoutGuide" id="LRh-7Z-mV1"/> <viewLayoutGuide key="contentLayoutGuide" id="LRh-7Z-mV1"/>
<viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/> <viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/>
</scrollView> </scrollView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp">
<rect key="frame" x="52" y="730" width="310.5" height="50"/> <rect key="frame" x="52" y="730" width="310.5" height="50"/>
<color key="backgroundColor" systemColor="systemBlueColor"/> <color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/> <constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/>
</constraints> </constraints>
<state key="normal" title="Send Report"> <state key="normal" title="Send Crash Report">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state> </state>
<connections> <connections>
@ -61,8 +59,8 @@
<action selector="sendReportTouchUpInside:" destination="-1" eventType="touchUpInside" id="ggd-fm-Orq"/> <action selector="sendReportTouchUpInside:" destination="-1" eventType="touchUpInside" id="ggd-fm-Orq"/>
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz"> <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz">
<rect key="frame" x="168.5" y="788" width="77" height="30"/> <rect key="frame" x="169" y="788" width="76" height="30"/>
<state key="normal" title="Don't Send"/> <state key="normal" title="Don't Send"/>
<connections> <connections>
<action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/> <action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/>
@ -71,8 +69,7 @@
</subviews> </subviews>
</stackView> </stackView>
</subviews> </subviews>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/> <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints> <constraints>
<constraint firstItem="uQy-Yw-Dba" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="AX2-9e-cO0"/> <constraint firstItem="uQy-Yw-Dba" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" id="AX2-9e-cO0"/>
<constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="a8U-KI-8PM" secondAttribute="bottom" id="Ec3-Px-dSW"/> <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="a8U-KI-8PM" secondAttribute="bottom" id="Ec3-Px-dSW"/>
@ -82,18 +79,8 @@
<constraint firstItem="a8U-KI-8PM" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="f59-qB-5T7"/> <constraint firstItem="a8U-KI-8PM" firstAttribute="trailing" secondItem="fnl-2z-Ty3" secondAttribute="trailing" id="f59-qB-5T7"/>
<constraint firstItem="Ofm-5l-nAp" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" multiplier="0.75" id="ueo-xb-Tfm"/> <constraint firstItem="Ofm-5l-nAp" firstAttribute="width" secondItem="i5M-Pr-FkT" secondAttribute="width" multiplier="0.75" id="ueo-xb-Tfm"/>
</constraints> </constraints>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<point key="canvasLocation" x="133" y="154"/> <point key="canvasLocation" x="133" y="154"/>
</view> </view>
</objects> </objects>
<resources>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
<systemColor name="systemBlueColor">
<color red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
</resources>
</document> </document>

View File

@ -1,141 +0,0 @@
//
// IssueReporterViewController.swift
// Tusker
//
// Created by Shadowfacts on 6/20/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import UIKit
import CrashReporter
import MessageUI
class IssueReporterViewController: UIViewController {
static func create(_ self: IssueReporterViewController) -> UINavigationController {
let nav = UINavigationController(rootViewController: self)
nav.navigationBar.prefersLargeTitles = true
return nav
}
static func create(reportText: String, reportFilename: String? = nil, dismiss: @escaping () -> Void) -> UINavigationController {
let filename = reportFilename ?? "Tusker-error-\(ISO8601DateFormatter().string(from: Date())).txt"
return create(IssueReporterViewController(reportText: reportText, reportFilename: filename, dismiss: dismiss))
}
let reportText: String
let reportFilename: String
private let dismiss: () -> Void
var preamble: String {
"Tusker has encountered an error. You can email a report to the developer. You may review the report below before sending.\n\nIf you choose to send the report, please include any additional details about what you were doing prior that may be pertinent."
}
var subject: String {
"Tusker Error Report"
}
@IBOutlet weak var crashReportTextView: UITextView!
@IBOutlet weak var sendReportButton: UIButton!
init(reportText: String, reportFilename: String, dismiss: @escaping () -> Void) {
self.reportText = reportText
self.reportFilename = reportFilename
self.dismiss = dismiss
super.init(nibName: "IssueReporterViewController", bundle: .main)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Report an Error"
navigationItem.largeTitleDisplayMode = .always
crashReportTextView.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
let attributed = NSMutableAttributedString()
attributed.append(NSAttributedString(string: preamble, attributes: [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
NSAttributedString.Key.foregroundColor: UIColor.label
]))
attributed.append(NSAttributedString(string: "\n\n"))
attributed.append(NSAttributedString(string: reportText, attributes: [
NSAttributedString.Key.font: UIFont.monospacedSystemFont(ofSize: 14, weight: .regular),
NSAttributedString.Key.foregroundColor: UIColor.label
]))
crashReportTextView.attributedText = attributed
sendReportButton.layer.cornerRadius = 12.5
sendReportButton.layer.masksToBounds = true
sendReportButton.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(sendReportButtonLongPressed)))
}
private func updateSendReportButtonColor(lightened: Bool, animate: Bool) {
let color: UIColor
if lightened {
var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0
UIColor.systemBlue.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
color = UIColor(hue: hue, saturation: 0.85 * saturation, brightness: brightness, alpha: alpha)
} else {
color = .systemBlue
}
if animate {
UIView.animate(withDuration: 0.25) {
self.sendReportButton.backgroundColor = color
}
} else {
sendReportButton.backgroundColor = color
}
}
@IBAction func sendReportTouchDown(_ sender: Any) {
updateSendReportButtonColor(lightened: true, animate: false)
}
@IBAction func sendReportButtonTouchDragExit(_ sender: Any) {
updateSendReportButtonColor(lightened: false, animate: true)
}
@IBAction func sendReportButtonTouchDragEnter(_ sender: Any) {
updateSendReportButtonColor(lightened: true, animate: true)
}
@IBAction func sendReportTouchUpInside(_ sender: Any) {
updateSendReportButtonColor(lightened: false, animate: true)
let composeVC = MFMailComposeViewController()
composeVC.mailComposeDelegate = self
composeVC.setToRecipients(["me@shadowfacts.net"])
composeVC.setSubject(subject)
let data = reportText.data(using: .utf8)!
composeVC.addAttachmentData(data, mimeType: "text/plain", fileName: reportFilename)
self.present(composeVC, animated: true)
}
@objc func sendReportButtonLongPressed() {
let dir = FileManager.default.temporaryDirectory
let url = dir.appendingPathComponent(reportFilename)
try! reportText.data(using: .utf8)!.write(to: url)
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
present(activityController, animated: true)
}
@IBAction func cancelPressed(_ sender: Any) {
dismiss()
}
}
extension IssueReporterViewController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true) {
self.dismiss()
}
}
}

View File

@ -69,7 +69,7 @@ class AddSavedHashtagViewController: EnhancedTableViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
let request = Client.getTrendingHashtags(limit: 10) let request = Client.getTrends(limit: 10)
mastodonController.run(request) { (response) in mastodonController.run(request) { (response) in
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()

View File

@ -68,7 +68,6 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedHashtagsChanged), name: .savedHashtagsChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(savedInstancesChanged), name: .savedInstancesChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -139,9 +138,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections(Section.allCases.filter { $0 != .discover }) snapshot.appendSections(Section.allCases.filter { $0 != .discover })
snapshot.appendItems([.bookmarks], toSection: .bookmarks) snapshot.appendItems([.bookmarks], toSection: .bookmarks)
if mastodonController.instanceFeatures.instanceType.isMastodon, if mastodonController.instanceFeatures.instanceType.isMastodon {
!Preferences.shared.hideDiscover { snapshot.insertSections([.discover], afterSection: .bookmarks)
addDiscoverSection(to: &snapshot) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
} }
snapshot.appendItems([.addList], toSection: .lists) snapshot.appendItems([.addList], toSection: .lists)
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags) snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
@ -153,15 +152,6 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
reloadLists() reloadLists()
} }
private func addDiscoverSection(to snapshot: inout NSDiffableDataSourceSnapshot<Section, Item>) {
snapshot.insertSections([.discover], afterSection: .bookmarks)
snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
snapshot.insertItems([.trendingStatuses], beforeItem: .trendingTags)
snapshot.insertItems([.trendingLinks], afterItem: .trendingTags)
}
}
private func ownInstanceLoaded(_ instance: Instance) { private func ownInstanceLoaded(_ instance: Instance) {
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
if mastodonController.instanceFeatures.instanceType.isMastodon, if mastodonController.instanceFeatures.instanceType.isMastodon,
@ -208,20 +198,6 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
dataSource.apply(snapshot) dataSource.apply(snapshot)
} }
@objc private func preferencesChanged() {
var snapshot = dataSource.snapshot()
let hasSection = snapshot.sectionIdentifiers.contains(.discover)
let hide = Preferences.shared.hideDiscover
if hasSection && hide {
snapshot.deleteSections([.discover])
} else if !hasSection && !hide {
addDiscoverSection(to: &snapshot)
} else {
return
}
dataSource.apply(snapshot)
}
private func deleteList(_ list: List, completion: @escaping (Bool) -> Void) { private func deleteList(_ list: List, completion: @escaping (Bool) -> Void) {
let titleFormat = NSLocalizedString("Are you sure you want to delete the '%@' list?", comment: "delete list alert title") let titleFormat = NSLocalizedString("Are you sure you want to delete the '%@' list?", comment: "delete list alert title")
let title = String(format: titleFormat, list.title) let title = String(format: titleFormat, list.title)
@ -297,15 +273,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
case .bookmarks: case .bookmarks:
show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil) show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil)
case .trendingStatuses:
show(TrendingStatusesViewController(mastodonController: mastodonController), sender: nil)
case .trendingTags: case .trendingTags:
show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil) show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil)
case .trendingLinks:
show(TrendingLinksViewController(mastodonController: mastodonController), sender: nil)
case .profileDirectory: case .profileDirectory:
show(ProfileDirectoryViewController(mastodonController: mastodonController), sender: nil) show(ProfileDirectoryViewController(mastodonController: mastodonController), sender: nil)
@ -385,9 +355,7 @@ extension ExploreViewController {
enum Item: Hashable { enum Item: Hashable {
case bookmarks case bookmarks
case trendingStatuses
case trendingTags case trendingTags
case trendingLinks
case profileDirectory case profileDirectory
case list(List) case list(List)
case addList case addList
@ -400,12 +368,8 @@ extension ExploreViewController {
switch self { switch self {
case .bookmarks: case .bookmarks:
return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title") return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title")
case .trendingStatuses:
return NSLocalizedString("Trending Posts", comment: "trending statuses nav item title")
case .trendingTags: case .trendingTags:
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title") return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title")
case .trendingLinks:
return NSLocalizedString("Trending Links", comment: "trending links nav item title")
case .profileDirectory: case .profileDirectory:
return NSLocalizedString("Profile Directory", comment: "profile directory nav item title") return NSLocalizedString("Profile Directory", comment: "profile directory nav item title")
case let .list(list): case let .list(list):
@ -428,12 +392,8 @@ extension ExploreViewController {
switch self { switch self {
case .bookmarks: case .bookmarks:
name = "bookmark.fill" name = "bookmark.fill"
case .trendingStatuses:
name = "doc.text.image"
case .trendingTags: case .trendingTags:
name = "number" name = "arrow.up.arrow.down"
case .trendingLinks:
name = "link"
case .profileDirectory: case .profileDirectory:
name = "person.2.fill" name = "person.2.fill"
case .list(_): case .list(_):
@ -454,12 +414,8 @@ extension ExploreViewController {
switch (lhs, rhs) { switch (lhs, rhs) {
case (.bookmarks, .bookmarks): case (.bookmarks, .bookmarks):
return true return true
case (.trendingStatuses, .trendingStatuses):
return true
case (.trendingTags, .trendingTags): case (.trendingTags, .trendingTags):
return true return true
case (.trendingLinks, .trendingLinks):
return true
case (.profileDirectory, .profileDirectory): case (.profileDirectory, .profileDirectory):
return true return true
case let (.list(a), .list(b)): case let (.list(a), .list(b)):
@ -483,12 +439,8 @@ extension ExploreViewController {
switch self { switch self {
case .bookmarks: case .bookmarks:
hasher.combine("bookmarks") hasher.combine("bookmarks")
case .trendingStatuses:
hasher.combine("trendingStatuses")
case .trendingTags: case .trendingTags:
hasher.combine("trendingTags") hasher.combine("trendingTags")
case .trendingLinks:
hasher.combine("trendingLinks")
case .profileDirectory: case .profileDirectory:
hasher.combine("profileDirectory") hasher.combine("profileDirectory")
case let .list(list): case let .list(list):
@ -545,7 +497,7 @@ extension ExploreViewController: UICollectionViewDragDelegate {
case let .savedInstance(url): case let .savedInstance(url):
provider = NSItemProvider(object: url as NSURL) provider = NSItemProvider(object: url as NSURL)
// todo: should dragging public timelines into new windows be supported? // todo: should dragging public timelines into new windows be supported?
default: case .trendingTags, .profileDirectory, .addList, .addSavedHashtag, .findInstance:
return [] return []
} }
return [UIDragItem(itemProvider: provider)] return [UIDragItem(itemProvider: provider)]

View File

@ -55,19 +55,17 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
noteTextView.setEmojis(account.emojis) noteTextView.setEmojis(account.emojis)
avatarImageView.image = nil avatarImageView.image = nil
if let avatar = account.avatar { avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in defer {
defer { self?.avatarRequest = nil
self?.avatarRequest = nil }
} guard let self = self,
guard let self = self, let image = image,
let image = image, self.account?.id == account.id else {
self.account?.id == account.id else { return
return }
} DispatchQueue.main.async {
DispatchQueue.main.async { self.avatarImageView.image = image
self.avatarImageView.image = image
}
} }
} }

View File

@ -48,15 +48,19 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
let request = Client.getTrendingHashtags(limit: 10) let request = Client.getTrends(limit: 10)
Task { mastodonController.run(request) { (response) in
guard let (hashtags, _) = try? await mastodonController.run(request) else { var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
guard case let .success(hashtags, _) = response,
hashtags.count > 0 else {
self.dataSource.apply(snapshot)
return return
} }
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.trendingTags]) snapshot.appendSections([.trendingTags])
snapshot.appendItems(hashtags.map { .tag($0) }) snapshot.appendItems(hashtags.map { .tag($0) })
dataSource.apply(snapshot) self.dataSource.apply(snapshot)
} }
} }
@ -81,6 +85,7 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
} actionProvider: { (_) in } actionProvider: { (_) in
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath))) UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath)))
} }
} }
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {

View File

@ -1,165 +0,0 @@
//
// TrendingLinkTableViewCell.swift
// Tusker
//
// Created by Shadowfacts on 4/2/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import WebURLFoundationExtras
class TrendingLinkTableViewCell: UITableViewCell {
private var card: Card?
private var isGrayscale = false
private var thumbnailRequest: ImageCache.Request?
private let thumbnailView = UIImageView()
private let titleLabel = UILabel()
private let providerLabel = UILabel()
private let activityLabel = UILabel()
private let historyView = TrendHistoryView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
thumbnailView.contentMode = .scaleAspectFill
thumbnailView.clipsToBounds = true
titleLabel.font = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .headline).withSymbolicTraits(.traitBold)!, size: 0)
titleLabel.numberOfLines = 2
providerLabel.font = .preferredFont(forTextStyle: .subheadline)
activityLabel.font = .preferredFont(forTextStyle: .caption1)
let vStack = UIStackView(arrangedSubviews: [
titleLabel,
providerLabel,
activityLabel,
])
vStack.axis = .vertical
vStack.spacing = 4
let hStack = UIStackView(arrangedSubviews: [
thumbnailView,
vStack,
historyView,
])
hStack.axis = .horizontal
hStack.spacing = 4
hStack.alignment = .center
hStack.translatesAutoresizingMaskIntoConstraints = false
addSubview(hStack)
NSLayoutConstraint.activate([
thumbnailView.heightAnchor.constraint(equalToConstant: 75),
thumbnailView.widthAnchor.constraint(equalTo: thumbnailView.heightAnchor),
historyView.widthAnchor.constraint(equalToConstant: 75),
historyView.heightAnchor.constraint(equalToConstant: 44),
hStack.leadingAnchor.constraint(equalToSystemSpacingAfter: safeAreaLayoutGuide.leadingAnchor, multiplier: 1),
safeAreaLayoutGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: hStack.trailingAnchor, multiplier: 1),
hStack.topAnchor.constraint(equalToSystemSpacingBelow: topAnchor, multiplier: 1),
bottomAnchor.constraint(equalToSystemSpacingBelow: hStack.bottomAnchor, multiplier: 1),
])
NotificationCenter.default.addObserver(self, selector: #selector(updateUIForPreferences), name: .preferencesChanged, object: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
thumbnailView.layer.cornerRadius = 0.05 * thumbnailView.bounds.width
}
func updateUI(card: Card) {
self.card = card
self.thumbnailView.image = nil
updateGrayscaleableUI(card: card)
updateUIForPreferences()
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
titleLabel.text = title
titleLabel.isHidden = title.isEmpty
let provider = card.providerName?.trimmingCharacters(in: .whitespacesAndNewlines)
providerLabel.text = provider
providerLabel.isHidden = provider?.isEmpty ?? true
if let history = card.history {
let sorted = history.sorted(by: { $0.day < $1.day })
let lastTwo = sorted[(sorted.count - 2)...]
let accounts = lastTwo.map(\.accounts).reduce(0, +)
let uses = lastTwo.map(\.uses).reduce(0, +)
let format = NSLocalizedString("trending hashtag info", comment: "trending hashtag posts and people")
activityLabel.text = String.localizedStringWithFormat(format, accounts, uses)
activityLabel.isHidden = false
} else {
activityLabel.isHidden = true
}
historyView.setHistory(card.history)
historyView.isHidden = card.history == nil || card.history!.count < 2
}
@objc private func updateUIForPreferences() {
}
private func updateGrayscaleableUI(card: Card) {
isGrayscale = Preferences.shared.grayscaleImages
if let imageURL = card.image,
let url = URL(imageURL) {
thumbnailRequest = ImageCache.attachments.get(url, completion: { _, image in
guard let image = image,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: url, image: image) else {
return
}
DispatchQueue.main.async {
self.thumbnailView.image = transformedImage
}
})
if thumbnailRequest != nil {
loadBlurHash(card: card)
}
}
}
private func loadBlurHash(card: Card) {
guard let hash = card.blurhash else {
return
}
let imageViewSize = self.thumbnailView.bounds.size
AttachmentView.queue.async { [weak self] in
let size: CGSize
if let width = card.width, let height = card.height {
size = CGSize(width: width, height: height)
} else {
size = imageViewSize
}
guard let preview = UIImage(blurHash: hash, size: size) else {
return
}
DispatchQueue.main.async { [weak self] in
guard let self = self,
self.card?.url == card.url,
self.thumbnailView.image == nil else {
return
}
self.thumbnailView.image = preview
}
}
}
}

View File

@ -1,109 +0,0 @@
//
// TrendingLinksViewController.swift
// Tusker
//
// Created by Shadowfacts on 4/2/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
import WebURLFoundationExtras
import SafariServices
class TrendingLinksViewController: EnhancedTableViewController {
weak var mastodonController: MastodonController!
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(style: .grouped)
dragEnabled = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = NSLocalizedString("Trending Links", comment: "trending links screen title")
tableView.register(TrendingLinkTableViewCell.self, forCellReuseIdentifier: "trendingLinkCell")
tableView.estimatedRowHeight = 100
dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "trendingLinkCell", for: indexPath) as! TrendingLinkTableViewCell
cell.updateUI(card: item.card)
return cell
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let request = Client.getTrendingLinks()
Task {
guard let (links, _) = try? await mastodonController.run(request) else {
return
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.links])
snapshot.appendItems(links.map(Item.init))
dataSource.apply(snapshot)
}
}
// MARK: - Table View Delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let item = dataSource.itemIdentifier(for: indexPath),
let url = URL(item.card.url) else {
return
}
selected(url: url)
}
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard let item = dataSource.itemIdentifier(for: indexPath),
let url = URL(item.card.url) else {
return nil
}
return UIContextMenuConfiguration(identifier: nil) {
return SFSafariViewController(url: url)
} actionProvider: { _ in
return UIMenu(children: self.actionsForTrendingLink(card: item.card))
}
}
}
extension TrendingLinksViewController {
enum Section {
case links
}
struct Item: Hashable {
let card: Card
static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.card.url == rhs.card.url
}
func hash(into hasher: inout Hasher) {
hasher.combine(card.url)
}
}
}
extension TrendingLinksViewController: TuskerNavigationDelegate {
var apiController: MastodonController { mastodonController }
}
extension TrendingLinksViewController: MenuPreviewProvider {
var navigationDelegate: TuskerNavigationDelegate? { self }
}

View File

@ -1,93 +0,0 @@
//
// TrendingStatusesViewController.swift
// Tusker
//
// Created by Shadowfacts on 4/1/22.
// Copyright © 2022 Shadowfacts. All rights reserved.
//
import UIKit
import Pachyderm
class TrendingStatusesViewController: EnhancedTableViewController {
weak var mastodonController: MastodonController!
private var dataSource: UITableViewDiffableDataSource<Section, Item>!
init(mastodonController: MastodonController) {
self.mastodonController = mastodonController
super.init(style: .grouped)
dragEnabled = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = NSLocalizedString("Trending Posts", comment: "trending posts screen title")
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell")
tableView.estimatedRowHeight = 144
dataSource = UITableViewDiffableDataSource(tableView: tableView, cellProvider: { tableView, indexPath, item in
let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as! TimelineStatusTableViewCell
cell.delegate = self
cell.updateUI(statusID: item.id, state: item.state)
return cell
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let request = Client.getTrendingStatuses()
Task {
guard let (statuses, _) = try? await mastodonController.run(request) else {
return
}
mastodonController.persistentContainer.addAll(statuses: statuses) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.statuses])
snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) })
self.dataSource.apply(snapshot)
}
}
}
// MARK: - Table View Delegate
}
extension TrendingStatusesViewController {
enum Section {
case statuses
}
struct Item: Hashable {
let id: String
let state: StatusState
static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
}
extension TrendingStatusesViewController: TuskerNavigationDelegate {
var apiController: MastodonController { mastodonController }
}
extension TrendingStatusesViewController: StatusTableViewCellDelegate {
func statusCellCollapsedStateChanged(_ cell: BaseStatusTableViewCell) {
tableView.beginUpdates()
tableView.endUpdates()
}
}

View File

@ -86,10 +86,8 @@ class FastSwitchingAccountView: UIView {
let controller = MastodonController.getForAccount(account) let controller = MastodonController.getForAccount(account)
controller.getOwnAccount { [weak self] (result) in controller.getOwnAccount { [weak self] (result) in
guard let self = self, guard let self = self, case let .success(account) = result else { return }
case let .success(account) = result, self.avatarRequest = ImageCache.avatars.get(account.avatar) { [weak avatarImageView] (_, image) in
let avatar = account.avatar else { return }
self.avatarRequest = ImageCache.avatars.get(avatar) { [weak avatarImageView] (_, image) in
guard let avatarImageView = avatarImageView, let image = image else { return } guard let avatarImageView = avatarImageView, let image = image else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
avatarImageView.image = image avatarImageView.image = image

View File

@ -32,7 +32,7 @@ class MainSidebarViewController: UIViewController {
} }
var exploreTabItems: [Item] { var exploreTabItems: [Item] {
var items: [Item] = [.search, .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory] var items: [Item] = [.search, .bookmarks, .trendingTags, .profileDirectory]
let snapshot = dataSource.snapshot() let snapshot = dataSource.snapshot()
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) { for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
items.append(.list(list)) items.append(.list(list))
@ -94,7 +94,6 @@ class MainSidebarViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedHashtags), name: .savedHashtagsChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedHashtags), name: .savedHashtagsChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedInstances), name: .savedInstancesChanged, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(reloadSavedInstances), name: .savedInstancesChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(preferencesChanged), name: .preferencesChanged, object: nil)
} }
func select(item: Item, animated: Bool) { func select(item: Item, animated: Bool) {
@ -146,43 +145,34 @@ class MainSidebarViewController: UIViewController {
snapshot.appendItems([ snapshot.appendItems([
.tab(.compose) .tab(.compose)
], toSection: .compose) ], toSection: .compose)
if mastodonController.instanceFeatures.instanceType.isMastodon, if mastodonController.instanceFeatures.instanceType.isMastodon {
!Preferences.shared.hideDiscover {
snapshot.insertSections([.discover], afterSection: .compose) snapshot.insertSections([.discover], afterSection: .compose)
snapshot.appendItems([
.trendingTags,
.profileDirectory,
], toSection: .discover)
} }
dataSource.apply(snapshot, animatingDifferences: false) dataSource.apply(snapshot, animatingDifferences: false)
applyDiscoverSectionSnapshot()
reloadLists() reloadLists()
reloadSavedHashtags() reloadSavedHashtags()
reloadSavedInstances() reloadSavedInstances()
} }
private func applyDiscoverSectionSnapshot() {
var discoverSnapshot = NSDiffableDataSourceSectionSnapshot<Item>()
discoverSnapshot.append([.discoverHeader])
discoverSnapshot.append([
.trendingTags,
.profileDirectory,
], to: .discoverHeader)
if mastodonController.instanceFeatures.trendingStatusesAndLinks {
discoverSnapshot.insert([.trendingStatuses], before: .trendingTags)
discoverSnapshot.insert([.trendingLinks], after: .trendingTags)
}
dataSource.apply(discoverSnapshot, to: .discover)
}
private func ownInstanceLoaded(_ instance: Instance) { private func ownInstanceLoaded(_ instance: Instance) {
if mastodonController.instanceFeatures.instanceType.isMastodon { var snapshot = self.dataSource.snapshot()
var snapshot = self.dataSource.snapshot() if mastodonController.instanceFeatures.instanceType.isMastodon,
if !snapshot.sectionIdentifiers.contains(.discover) { !snapshot.sectionIdentifiers.contains(.discover) {
snapshot.appendSections([.discover]) snapshot.insertSections([.discover], afterSection: .compose)
dataSource.apply(snapshot, animatingDifferences: false) snapshot.appendItems([
} .trendingTags,
applyDiscoverSectionSnapshot() .profileDirectory,
], toSection: .discover)
} }
let prevSelected = collectionView.indexPathsForSelectedItems let prevSelected = collectionView.indexPathsForSelectedItems
dataSource.apply(snapshot, animatingDifferences: false)
if let prevSelected = prevSelected?.first { if let prevSelected = prevSelected?.first {
collectionView.selectItem(at: prevSelected, animated: false, scrollPosition: .top) collectionView.selectItem(at: prevSelected, animated: false, scrollPosition: .top)
} }
@ -241,22 +231,6 @@ class MainSidebarViewController: UIViewController {
} }
} }
} }
@objc private func preferencesChanged() {
var snapshot = dataSource.snapshot()
let hasSection = snapshot.sectionIdentifiers.contains(.discover)
let hide = Preferences.shared.hideDiscover
if hasSection && hide {
snapshot.deleteSections([.discover])
dataSource.apply(snapshot)
} else if !hasSection && !hide {
snapshot.insertSections([.discover], afterSection: .compose)
dataSource.apply(snapshot)
applyDiscoverSectionSnapshot()
} else {
return
}
}
// todo: deduplicate with ExploreViewController // todo: deduplicate with ExploreViewController
private func showAddList() { private func showAddList() {
@ -336,7 +310,7 @@ extension MainSidebarViewController {
enum Item: Hashable { enum Item: Hashable {
case tab(MainTabBarViewController.Tab) case tab(MainTabBarViewController.Tab)
case search, bookmarks case search, bookmarks
case discoverHeader, trendingStatuses, trendingTags, trendingLinks, profileDirectory case trendingTags, profileDirectory
case listsHeader, list(List), addList case listsHeader, list(List), addList
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
case savedInstancesHeader, savedInstance(URL), addSavedInstance case savedInstancesHeader, savedInstance(URL), addSavedInstance
@ -349,14 +323,8 @@ extension MainSidebarViewController {
return "Search" return "Search"
case .bookmarks: case .bookmarks:
return "Bookmarks" return "Bookmarks"
case .discoverHeader:
return "Discover"
case .trendingStatuses:
return "Trending Posts"
case .trendingTags: case .trendingTags:
return "Trending Hashtags" return "Trending Hashtags"
case .trendingLinks:
return "Trending Links"
case .profileDirectory: case .profileDirectory:
return "Profile Directory" return "Profile Directory"
case .listsHeader: case .listsHeader:
@ -388,12 +356,8 @@ extension MainSidebarViewController {
return "magnifyingglass" return "magnifyingglass"
case .bookmarks: case .bookmarks:
return "bookmark" return "bookmark"
case .trendingStatuses:
return "doc.text.image"
case .trendingTags: case .trendingTags:
return "number" return "arrow.up.arrow.down"
case .trendingLinks:
return "link"
case .profileDirectory: case .profileDirectory:
return "person.2.fill" return "person.2.fill"
case .list(_): case .list(_):
@ -402,7 +366,7 @@ extension MainSidebarViewController {
return "number" return "number"
case .savedInstance(_): case .savedInstance(_):
return "globe" return "globe"
case .discoverHeader, .listsHeader, .savedHashtagsHeader, .savedInstancesHeader: case .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
return nil return nil
case .addList, .addSavedHashtag, .addSavedInstance: case .addList, .addSavedHashtag, .addSavedInstance:
return "plus" return "plus"
@ -411,7 +375,7 @@ extension MainSidebarViewController {
var hasChildren: Bool { var hasChildren: Bool {
switch self { switch self {
case .discoverHeader, .listsHeader, .savedHashtagsHeader, .savedInstancesHeader: case .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
return true return true
default: default:
return false return false

View File

@ -207,7 +207,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
tabBarViewController.select(tab: .explore) tabBarViewController.select(tab: .explore)
case .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_): case .bookmarks, .trendingTags, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
tabBarViewController.select(tab: .explore) tabBarViewController.select(tab: .explore)
// Make sure the Explore VC doesn't show it's search bar when it appears, in case the user was previously // Make sure the Explore VC doesn't show it's search bar when it appears, in case the user was previously
// in compact mode and performing a search. // in compact mode and performing a search.
@ -215,7 +215,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
let explore = exploreNav.viewControllers.first as! ExploreViewController let explore = exploreNav.viewControllers.first as! ExploreViewController
explore.searchControllerStatusOnAppearance = false explore.searchControllerStatusOnAppearance = false
case .discoverHeader, .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance: case .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
// These items are not selectable in the sidebar collection view, so this code is unreachable. // These items are not selectable in the sidebar collection view, so this code is unreachable.
fatalError("unreachable") fatalError("unreachable")
} }
@ -273,27 +273,18 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
} }
// Insert the new search VC at the beginning of the new search nav stack // Insert the new search VC at the beginning of the new search nav stack
toPrepend = searchVC toPrepend = searchVC
} else { } else if tabNavigationStack[1] is BookmarksTableViewController {
switch tabNavigationStack[1] { exploreItem = .bookmarks
case is BookmarksTableViewController: } else if let listVC = tabNavigationStack[1] as? ListTimelineViewController {
exploreItem = .bookmarks exploreItem = .list(listVC.list)
case let listVC as ListTimelineViewController: } else if let hashtagVC = tabNavigationStack[1] as? HashtagTimelineViewController {
exploreItem = .list(listVC.list) exploreItem = .savedHashtag(hashtagVC.hashtag)
case let hashtagVC as HashtagTimelineViewController: } else if let instanceVC = tabNavigationStack[1] as? InstanceTimelineViewController {
exploreItem = .savedHashtag(hashtagVC.hashtag) exploreItem = .savedInstance(instanceVC.instanceURL)
case let instanceVC as InstanceTimelineViewController: } else if tabNavigationStack[1] is TrendingHashtagsViewController {
exploreItem = .savedInstance(instanceVC.instanceURL) exploreItem = .trendingTags
case is TrendingStatusesViewController: } else if tabNavigationStack[1] is ProfileDirectoryViewController {
exploreItem = .trendingStatuses exploreItem = .profileDirectory
case is TrendingHashtagsViewController:
exploreItem = .trendingTags
case is TrendingLinksViewController:
exploreItem = .trendingLinks
case is ProfileDirectoryViewController:
exploreItem = .profileDirectory
default:
fatalError("unhandled second-level explore screen")
}
} }
transferNavigationStack(from: tabNavController, to: exploreItem!, skipFirst: 1, prepend: toPrepend) transferNavigationStack(from: tabNavController, to: exploreItem!, skipFirst: 1, prepend: toPrepend)
@ -344,12 +335,8 @@ fileprivate extension MainSidebarViewController.Item {
return SearchViewController(mastodonController: mastodonController) return SearchViewController(mastodonController: mastodonController)
case .bookmarks: case .bookmarks:
return BookmarksTableViewController(mastodonController: mastodonController) return BookmarksTableViewController(mastodonController: mastodonController)
case .trendingStatuses:
return TrendingStatusesViewController(mastodonController: mastodonController)
case .trendingTags: case .trendingTags:
return TrendingHashtagsViewController(mastodonController: mastodonController) return TrendingHashtagsViewController(mastodonController: mastodonController)
case .trendingLinks:
return TrendingLinksViewController(mastodonController: mastodonController)
case .profileDirectory: case .profileDirectory:
return ProfileDirectoryViewController(mastodonController: mastodonController) return ProfileDirectoryViewController(mastodonController: mastodonController)
case let .list(list): case let .list(list):
@ -358,7 +345,7 @@ fileprivate extension MainSidebarViewController.Item {
return HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController) return HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController)
case let .savedInstance(url): case let .savedInstance(url):
return InstanceTimelineViewController(for: url, parentMastodonController: mastodonController) return InstanceTimelineViewController(for: url, parentMastodonController: mastodonController)
case .discoverHeader, .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance: case .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
return nil return nil
} }
} }

View File

@ -259,8 +259,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
for indexPath in indexPaths { for indexPath in indexPaths {
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue } guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
for notification in group.notifications { for notification in group.notifications {
guard let avatar = notification.account.avatar else { continue } ImageCache.avatars.fetchIfNotCached(notification.account.avatar)
ImageCache.avatars.fetchIfNotCached(avatar)
} }
} }
} }
@ -269,8 +268,7 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
for indexPath in indexPaths { for indexPath in indexPaths {
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue } guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
for notification in group.notifications { for notification in group.notifications {
guard let avatar = notification.account.avatar else { continue } ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
ImageCache.avatars.cancelWithoutCallback(avatar)
} }
} }
} }

View File

@ -167,7 +167,7 @@ class InstanceSelectorTableViewController: UITableViewController {
switch response { switch response {
case let .failure(error): case let .failure(error):
self.showRecommendationsError(error) self.showRecommendationsError(error)
case let .success(instances): case let .success(instances, _):
self.recommendedInstances = instances self.recommendedInstances = instances
self.filterRecommendedResults() self.filterRecommendedResults()
} }
@ -197,7 +197,7 @@ class InstanceSelectorTableViewController: UITableViewController {
tableView.tableHeaderView = header tableView.tableHeaderView = header
} }
private func showRecommendationsError(_ error: Client.ErrorType) { private func showRecommendationsError(_ error: Client.Error) {
let footer = UITableViewHeaderFooterView() let footer = UITableViewHeaderFooterView()
footer.translatesAutoresizingMaskIntoConstraints = false footer.translatesAutoresizingMaskIntoConstraints = false

View File

@ -8,10 +8,8 @@
import UIKit import UIKit
import AuthenticationServices import AuthenticationServices
import Pachyderm
protocol OnboardingViewControllerDelegate { protocol OnboardingViewControllerDelegate {
@MainActor
func didFinishOnboarding(account: LocalData.UserAccountInfo) func didFinishOnboarding(account: LocalData.UserAccountInfo)
} }
@ -42,119 +40,60 @@ class OnboardingViewController: UINavigationController {
instanceSelector.delegate = self instanceSelector.delegate = self
} }
@MainActor
private func tryLoginTo(instanceURL: URL) async throws {
let mastodonController = MastodonController(instanceURL: instanceURL)
let clientID: String
let clientSecret: String
do {
(clientID, clientSecret) = try await mastodonController.registerApp()
} catch {
throw Error.registeringApp(error)
}
let authCode = try await getAuthorizationCode(instanceURL: instanceURL, clientID: clientID)
let accessToken: String
do {
accessToken = try await mastodonController.authorize(authorizationCode: authCode)
} catch {
throw Error.gettingAccessToken(error)
}
// construct a temporary UserAccountInfo instance for the MastodonController to use to fetch its own account
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
mastodonController.accountInfo = tempAccountInfo
let ownAccount: Account
do {
ownAccount = try await mastodonController.getOwnAccount()
} catch {
throw Error.gettingOwnAccount(error)
}
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: ownAccount.username, accessToken: accessToken)
mastodonController.accountInfo = accountInfo
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
}
@MainActor
private func getAuthorizationCode(instanceURL: URL, clientID: String) async throws -> String {
var components = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
components.path = "/oauth/authorize"
components.queryItems = [
URLQueryItem(name: "client_id", value: clientID),
URLQueryItem(name: "response_type", value: "code"),
URLQueryItem(name: "scope", value: "read write follow"),
URLQueryItem(name: "redirect_uri", value: "tusker://oauth")
]
let authorizeURL = components.url!
return try await withCheckedThrowingContinuation({ continuation in
self.authenticationSession = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: "tusker", completionHandler: { url, error in
if let error = error {
if (error as? ASWebAuthenticationSessionError)?.code == .canceledLogin {
continuation.resume(throwing: Error.cancelled)
} else {
continuation.resume(throwing: Error.authenticationSessionError(error))
}
} else if let url = url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let item = components.queryItems?.first(where: { $0.name == "code" }),
let code = item.value {
continuation.resume(returning: code)
} else {
continuation.resume(throwing: Error.noAuthorizationCode)
}
})
// Prefer ephemeral sessions to make it easier to sign into multiple accounts on the same instance.
self.authenticationSession!.prefersEphemeralWebBrowserSession = true
self.authenticationSession!.presentationContextProvider = self
self.authenticationSession!.start()
})
}
} }
extension OnboardingViewController {
enum Error: Swift.Error {
case cancelled
case registeringApp(Swift.Error)
case authenticationSessionError(Swift.Error)
case noAuthorizationCode
case gettingAccessToken(Swift.Error)
case gettingOwnAccount(Swift.Error)
var localizedDescription: String {
switch self {
case .cancelled:
return "Login Cancelled"
case .registeringApp(let error):
return "Couldn't register app: \(error)"
case .authenticationSessionError(let error):
return error.localizedDescription
case .noAuthorizationCode:
return "No authorization code"
case .gettingAccessToken(let error):
return "Couldn't get access token: \(error)"
case .gettingOwnAccount(let error):
return "Couldn't fetch account: \(error)"
}
}
}
}
extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate { extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate {
func didSelectInstance(url instanceURL: URL) { func didSelectInstance(url instanceURL: URL) {
Task { let mastodonController = MastodonController(instanceURL: instanceURL)
do { mastodonController.registerApp { (clientID, clientSecret) in
try await self.tryLoginTo(instanceURL: instanceURL)
} catch Error.cancelled { var components = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
// no-op, don't show an error message components.path = "/oauth/authorize"
} catch let error as Error { components.queryItems = [
let alert = UIAlertController(title: "Error Logging In", message: error.localizedDescription, preferredStyle: .alert) URLQueryItem(name: "client_id", value: clientID),
alert.addAction(UIAlertAction(title: "Ok", style: .default)) URLQueryItem(name: "response_type", value: "code"),
self.present(alert, animated: true) URLQueryItem(name: "scope", value: "read write follow"),
URLQueryItem(name: "redirect_uri", value: "tusker://oauth")
]
let authorizeURL = components.url!
self.authenticationSession = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: "tusker") { url, error in
guard error == nil,
let url = url,
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let item = components.queryItems?.first(where: { $0.name == "code" }),
let authCode = item.value else { return }
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
// construct a temporary UserAccountInfo instance for the MastodonController to use to fetch it's own account
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
mastodonController.accountInfo = tempAccountInfo
mastodonController.getOwnAccount { (result) in
DispatchQueue.main.async {
switch result {
case let .failure(error):
let alert = UIAlertController(title: "Unable to Verify Credentials", message: "Your account could not be fetched at this time: \(error.localizedDescription)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
self.present(alert, animated: true)
case let .success(account):
// this needs to happen on the main thread because it publishes a new value for the ObservableObject
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
mastodonController.accountInfo = accountInfo
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
}
}
}
}
}
DispatchQueue.main.async {
// Prefer ephemeral sessions to make it easier to sign into multiple accounts on the same instance.
self.authenticationSession!.prefersEphemeralWebBrowserSession = true
self.authenticationSession!.presentationContextProvider = self
self.authenticationSession!.start()
} }
} }
} }

View File

@ -37,9 +37,8 @@ struct LocalAccountAvatarView: View {
func loadImage() { func loadImage() {
let controller = MastodonController.getForAccount(localAccountInfo) let controller = MastodonController.getForAccount(localAccountInfo)
controller.getOwnAccount { (result) in controller.getOwnAccount { (result) in
guard case let .success(account) = result, guard case let .success(account) = result else { return }
let avatar = account.avatar else { return } _ = ImageCache.avatars.get(account.avatar) { (_, image) in
_ = ImageCache.avatars.get(avatar) { (_, image) in
DispatchQueue.main.async { DispatchQueue.main.async {
self.avatarImage = image self.avatarImage = image
} }

View File

@ -19,7 +19,6 @@ struct WellnessPrefsView: View {
if #available(iOS 15.0, *) { if #available(iOS 15.0, *) {
disableInfiniteScrolling disableInfiniteScrolling
} }
hideDiscover
} }
.listStyle(InsetGroupedListStyle()) .listStyle(InsetGroupedListStyle())
.navigationBarTitle(Text("Digital Wellness")) .navigationBarTitle(Text("Digital Wellness"))
@ -58,14 +57,6 @@ struct WellnessPrefsView: View {
} }
} }
} }
private var hideDiscover: some View {
Section(footer: Text("Do not show the Discover section (Trends, Profile Directory) of the Explore screen or sidebar.")) {
Toggle(isOn: $preferences.hideDiscover) {
Text("Hide Discover Section")
}
}
}
} }
struct WellnessPrefsView_Previews: PreviewProvider { struct WellnessPrefsView_Previews: PreviewProvider {

View File

@ -42,7 +42,7 @@ class MyProfileViewController: ProfileViewController {
} }
private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) { private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) {
guard let avatarURL = account.avatar else { return } let avatarURL = account.avatar
_ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in _ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in
guard let self = self, guard let self = self,
let image = image, let image = image,

View File

@ -40,11 +40,6 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
super.viewDidLoad() super.viewDidLoad()
tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell") tableView.register(UINib(nibName: "TimelineStatusTableViewCell", bundle: .main), forCellReuseIdentifier: "statusCell")
// setup the initial snapshot with the sections in the right order, so we don't have to worry about order later
var snapshot = Snapshot()
snapshot.appendSections([.pinned, .statuses])
dataSource.apply(snapshot, animatingDifferences: false)
} }
func updateUI(account: AccountMO) { func updateUI(account: AccountMO) {
@ -77,10 +72,6 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
} }
getStatuses { (response) in getStatuses { (response) in
guard self.state == .loadingInitial else {
return
}
switch response { switch response {
case let .failure(error): case let .failure(error):
completion(.failure(.client(error))) completion(.failure(.client(error)))
@ -92,6 +83,7 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
self.mastodonController.persistentContainer.addAll(statuses: statuses) { self.mastodonController.persistentContainer.addAll(statuses: statuses) {
DispatchQueue.main.async { DispatchQueue.main.async {
var snapshot = self.dataSource.snapshot() var snapshot = self.dataSource.snapshot()
snapshot.appendSections([.statuses])
snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .statuses) snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .statuses)
if self.kind == .statuses { if self.kind == .statuses {
self.loadPinnedStatuses(snapshot: { snapshot }, completion: completion) self.loadPinnedStatuses(snapshot: { snapshot }, completion: completion)
@ -105,8 +97,7 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
} }
private func loadPinnedStatuses(snapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) { private func loadPinnedStatuses(snapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
guard kind == .statuses, guard kind == .statuses else {
mastodonController.instanceFeatures.profilePinnedStatuses else {
completion(.success(snapshot())) completion(.success(snapshot()))
return return
} }
@ -119,7 +110,10 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
self.mastodonController.persistentContainer.addAll(statuses: statuses) { self.mastodonController.persistentContainer.addAll(statuses: statuses) {
DispatchQueue.main.async { DispatchQueue.main.async {
var snapshot = snapshot() var snapshot = snapshot()
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .pinned)) if snapshot.indexOfSection(.pinned) != nil {
snapshot.deleteSections([.pinned])
}
snapshot.insertSections([.pinned], beforeSection: .statuses)
snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .pinned) snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .pinned)
completion(.success(snapshot)) completion(.success(snapshot))
} }
@ -215,9 +209,7 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
override func refresh() { override func refresh() {
super.refresh() super.refresh()
// only refresh pinned if the super call actually succeded (put the state into .loadingNewer) if kind == .statuses {
if state == .loadingNewer,
kind == .statuses {
loadPinnedStatuses(snapshot: dataSource.snapshot) { (result) in loadPinnedStatuses(snapshot: dataSource.snapshot) { (result) in
switch result { switch result {
case .failure(_): case .failure(_):
@ -248,14 +240,6 @@ extension ProfileStatusesViewController {
struct Item: Hashable { struct Item: Hashable {
let id: String let id: String
let state: StatusState let state: StatusState
static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
} }
} }

View File

@ -77,9 +77,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
let contentSections = snapshot.sectionIdentifiers.filter { timelineContentSections().contains($0) } let contentSections = snapshot.sectionIdentifiers.filter { timelineContentSections().contains($0) }
let contentSectionIndices = contentSections.compactMap(snapshot.indexOfSection(_:)) let contentSectionIndices = contentSections.compactMap(snapshot.indexOfSection(_:))
guard let maxContentSectionIndex = contentSectionIndices.max() else { let maxContentSectionIndex = contentSectionIndices.max()!
return
}
if lastVisibleRow.section < maxContentSectionIndex { if lastVisibleRow.section < maxContentSectionIndex {
return return
@ -116,7 +114,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
case let .failure(.client(error)): case let .failure(.client(error)):
self.state = .unloaded self.state = .unloaded
let config = ToastConfiguration(from: error, with: "Error Loading", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading") { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadInitial() self?.loadInitial()
} }
@ -148,7 +146,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
self.dataSource.apply(snapshot, animatingDifferences: false) self.dataSource.apply(snapshot, animatingDifferences: false)
case let .failure(.client(error)): case let .failure(.client(error)):
let config = ToastConfiguration(from: error, with: "Error Loading Older", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading Older") { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadOlder() self?.loadOlder()
} }
@ -197,12 +195,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
// MARK: - RefreshableViewController // MARK: - RefreshableViewController
func refresh() { func refresh() {
// if we're unloaded, there's nothing "newer" to load guard state != .loadingNewer else { return }
// if we're performing some other operation, we don't want to step on its toes
guard state == .loaded else {
self.refreshControl?.endRefreshing()
return
}
state = .loadingNewer state = .loadingNewer
@ -236,7 +229,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
} }
case let .failure(.client(error)): case let .failure(.client(error)):
let config = ToastConfiguration(from: error, with: "Error Loading Newer", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading Newer") { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.refresh() self?.refresh()
} }

View File

@ -48,23 +48,50 @@ extension MenuPreviewProvider {
] ]
} }
let actionsSection: [UIMenuElement] = [ var actionsSection: [UIMenuElement] = [
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in
guard let self = self else { return } guard let self = self else { return }
let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct) self.navigationDelegate?.compose(mentioningAcct: account.acct)
draft.visibility = .direct
self.navigationDelegate?.compose(editing: draft)
}), }),
UIDeferredMenuElement({ (elementHandler) in ]
Task { @MainActor in
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) { if accountID != mastodonController.account.id {
elementHandler([action]) actionsSection.append(UIDeferredMenuElement({ (elementHandler) in
} else { guard let mastodonController = self.mastodonController else {
elementHandler([]) elementHandler([])
return
}
let request = Client.getRelationships(accounts: [account.id])
// talk about callback hell :/
mastodonController.run(request) { [weak self] (response) in
guard let self = self,
case let .success(results, _) = response,
let relationship = results.first else {
DispatchQueue.main.async {
elementHandler([])
}
return
}
let following = relationship.following
DispatchQueue.main.async {
let action = self.createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus", handler: { (_) in
let request = (following ? Account.unfollow : Account.follow)(accountID)
mastodonController.run(request) { (response) in
switch response {
case .failure(_):
fatalError()
case let .success(relationship, _):
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
}
})
elementHandler([
action
])
} }
} }
}) }))
] }
var shareSection = [ var shareSection = [
openInSafariAction(url: account.url), openInSafariAction(url: account.url),
@ -165,8 +192,7 @@ extension MenuPreviewProvider {
} }
// only allowing pinning user's own statuses // only allowing pinning user's own statuses
if account.id == status.account.id, if account.id == status.account.id {
mastodonController.instanceFeatures.profilePinnedStatuses {
let pinned = status.pinned ?? false let pinned = status.pinned ?? false
actionsSection.append(createAction(identifier: "pin", title: pinned ? "Unpin from Profile" : "Pin to Profile", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in actionsSection.append(createAction(identifier: "pin", title: pinned ? "Unpin from Profile" : "Pin to Profile", systemImageName: pinned ? "pin.slash" : "pin", handler: { [weak self] (_) in
guard let self = self else { return } guard let self = self else { return }
@ -210,29 +236,6 @@ extension MenuPreviewProvider {
UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection), UIMenu(title: "", image: nil, identifier: nil, options: [.displayInline], children: actionsSection),
] ]
} }
func actionsForTrendingLink(card: Card) -> [UIMenuElement] {
guard let url = URL(card.url) else {
return []
}
return [
openInSafariAction(url: url),
createAction(identifier: "postlink", title: "Post this Link", systemImageName: "square.and.pencil", handler: { [weak self] _ in
guard let self = self else { return }
let draft = self.mastodonController!.createDraft()
let title = card.title.trimmingCharacters(in: .whitespacesAndNewlines)
if !title.isEmpty {
draft.text += title
draft.text += ":\n"
}
draft.text += url.absoluteString
// prevents the draft from being saved automatically until the user makes a change
// also prevents it from being posted without being changed
draft.initialText = draft.text
self.navigationDelegate?.compose(editing: draft)
})
]
}
private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping UIActionHandler) -> UIAction { private func createAction(identifier: String, title: String, systemImageName: String?, handler: @escaping UIActionHandler) -> UIAction {
let image: UIImage? let image: UIImage?
@ -264,30 +267,6 @@ extension MenuPreviewProvider {
} }
} }
private func followAction(for accountID: String, mastodonController: MastodonController) async -> UIMenuElement? {
guard let ownAccount = try? await mastodonController.getOwnAccount(),
accountID != ownAccount.id else {
return nil
}
let request = Client.getRelationships(accounts: [accountID])
guard let (relationships, _) = try? await mastodonController.run(request),
let relationship = relationships.first else {
return nil
}
let following = relationship.following
return createAction(identifier: "follow", title: following ? "Unfollow" : "Follow", systemImageName: following ? "person.badge.minus" : "person.badge.plus") { _ in
let request = (following ? Account.unfollow : Account.follow)(accountID)
mastodonController.run(request) { response in
switch response {
case .failure(_):
fatalError()
case .success(let relationship, _):
mastodonController.persistentContainer.addOrUpdate(relationship: relationship)
}
}
}
}
} }
extension LargeImageViewController: CustomPreviewPresenting { extension LargeImageViewController: CustomPreviewPresenting {

View File

@ -21,8 +21,7 @@ extension StatusTablePrefetching {
return return
} }
for status in statuses { for status in statuses {
guard let avatar = status.account.avatar else { continue } ImageCache.avatars.fetchIfNotCached(status.account.avatar)
ImageCache.avatars.fetchIfNotCached(avatar)
for attachment in status.attachments where attachment.kind == .image { for attachment in status.attachments where attachment.kind == .image {
ImageCache.attachments.fetchIfNotCached(attachment.url) ImageCache.attachments.fetchIfNotCached(attachment.url)
} }
@ -37,8 +36,7 @@ extension StatusTablePrefetching {
return return
} }
for status in statuses { for status in statuses {
guard let avatar = status.account.avatar else { continue } ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
ImageCache.avatars.cancelWithoutCallback(avatar)
for attachment in status.attachments where attachment.kind == .image { for attachment in status.attachments where attachment.kind == .image {
ImageCache.attachments.cancelWithoutCallback(attachment.url) ImageCache.attachments.cancelWithoutCallback(attachment.url)
} }

View File

@ -36,8 +36,7 @@ class AccountTableViewCell: UITableViewCell {
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView) avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
guard let account = mastodonController.persistentContainer.account(for: accountID) else { guard let account = mastodonController.persistentContainer.account(for: accountID) else {
// this table view cell could be cached in a table view (e.g., SearchResultsViewController) for an account that's since been purged fatalError("Missing cached account \(accountID!)")
return
} }
displayNameLabel.updateForAccountDisplayName(account: account) displayNameLabel.updateForAccountDisplayName(account: account)
@ -63,18 +62,17 @@ class AccountTableViewCell: UITableViewCell {
let accountID = self.accountID let accountID = self.accountID
if let avatarURL = account.avatar { let avatarURL = account.avatar
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
guard let self = self else { return } guard let self = self else { return }
self.avatarRequest = nil self.avatarRequest = nil
guard let image = image, guard let image = image,
self.accountID == accountID, self.accountID == accountID,
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return } let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
self.avatarImageView.image = transformedImage self.avatarImageView.image = transformedImage
}
} }
} }

View File

@ -69,13 +69,11 @@ class LargeAccountDetailView: UIView {
displayNameLabel.updateForAccountDisplayName(account: account) displayNameLabel.updateForAccountDisplayName(account: account)
usernameLabel.text = "@\(account.acct)" usernameLabel.text = "@\(account.acct)"
if let avatar = account.avatar { avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in guard let self = self, let image = image else { return }
guard let self = self, let image = image else { return } self.avatarRequest = nil
self.avatarRequest = nil DispatchQueue.main.async {
DispatchQueue.main.async { self.avatarImageView.image = image
self.avatarImageView.image = image
}
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More