Compare commits
24 Commits
80c79ded3b
...
0a7709526f
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 0a7709526f | |
Shadowfacts | 9ec821f6b3 | |
Shadowfacts | 5c4474dc87 | |
Shadowfacts | 829ecf06da | |
Shadowfacts | cb2bb215d3 | |
Shadowfacts | 916c6fba0d | |
Shadowfacts | 8473f32781 | |
Shadowfacts | 240ccf23a4 | |
Shadowfacts | e49859e5ea | |
Shadowfacts | c6d158a8a3 | |
Shadowfacts | 7e90fe2401 | |
Shadowfacts | cab78a4aa4 | |
Shadowfacts | 7da139be4d | |
Shadowfacts | 2444783edf | |
Shadowfacts | 727615a818 | |
Shadowfacts | 6e3089f025 | |
Shadowfacts | e09b0ff4e3 | |
Shadowfacts | 830eea5e95 | |
Shadowfacts | 705fbbe343 | |
Shadowfacts | 12bcf52764 | |
Shadowfacts | f31c909517 | |
Shadowfacts | 781c37fbae | |
Shadowfacts | 930ec7ccff | |
Shadowfacts | de93d6e171 |
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
|||
# 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)
|
||||
Features/Improvements:
|
||||
- Local only posts (Glitch/Hometown)
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?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>
|
|
@ -1,84 +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
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// 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>
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
# Pachyderm
|
||||
|
||||
A description of this package.
|
|
@ -68,29 +68,32 @@ public class Client {
|
|||
|
||||
@discardableResult
|
||||
public func run<Result>(_ request: Request<Result>, completion: @escaping Callback<Result>) -> URLSessionTask? {
|
||||
guard let request = createURLRequest(request: request) else {
|
||||
completion(.failure(Error.invalidRequest))
|
||||
guard let urlRequest = createURLRequest(request: request) else {
|
||||
completion(.failure(Error(request: request, type: .invalidRequest)))
|
||||
return nil
|
||||
}
|
||||
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
let task = session.dataTask(with: urlRequest) { data, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(.networkError(error)))
|
||||
completion(.failure(Error(request: request, type: .networkError(error))))
|
||||
return
|
||||
}
|
||||
guard let data = data,
|
||||
let response = response as? HTTPURLResponse else {
|
||||
completion(.failure(.invalidResponse))
|
||||
completion(.failure(Error(request: request, type: .invalidResponse)))
|
||||
return
|
||||
}
|
||||
guard response.statusCode == 200 else {
|
||||
let mastodonError = try? Client.decoder.decode(MastodonError.self, from: data)
|
||||
let error: Error = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode)
|
||||
completion(.failure(error))
|
||||
let type: ErrorType = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode)
|
||||
completion(.failure(Error(request: request, type: type)))
|
||||
return
|
||||
}
|
||||
guard let result = try? Client.decoder.decode(Result.self, from: data) else {
|
||||
completion(.failure(.invalidModel))
|
||||
let result: Result
|
||||
do {
|
||||
result = try Client.decoder.decode(Result.self, from: data)
|
||||
} catch {
|
||||
completion(.failure(Error(request: request, type: .invalidModel(error))))
|
||||
return
|
||||
}
|
||||
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
|
||||
|
@ -103,7 +106,7 @@ public class Client {
|
|||
|
||||
func createURLRequest<Result>(request: Request<Result>) -> URLRequest? {
|
||||
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil }
|
||||
components.path = request.path
|
||||
components.path = request.endpoint.path
|
||||
components.queryItems = request.queryParameters.isEmpty ? nil : request.queryParameters.queryItems
|
||||
guard let url = components.url else { return nil }
|
||||
var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval)
|
||||
|
@ -163,7 +166,7 @@ public class Client {
|
|||
if let url = wellKnown.links.first(where: { $0.rel == "http://nodeinfo.diaspora.software/ns/schema/2.0" }),
|
||||
let components = URLComponents(string: url.href),
|
||||
components.host == self.baseURL.host {
|
||||
let nodeInfo = Request<NodeInfo>(method: .get, path: components.path)
|
||||
let nodeInfo = Request<NodeInfo>(method: .get, path: Endpoint(stringLiteral: components.path))
|
||||
self.run(nodeInfo, completion: completion)
|
||||
}
|
||||
}
|
||||
|
@ -365,7 +368,7 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Instance
|
||||
public static func getTrends(limit: Int? = nil) -> Request<[Hashtag]> {
|
||||
public static func getTrendingHashtags(limit: Int? = nil) -> Request<[Hashtag]> {
|
||||
let parameters: [Parameter]
|
||||
if let limit = limit {
|
||||
parameters = ["limit" => limit]
|
||||
|
@ -375,6 +378,26 @@ public class Client {
|
|||
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]> {
|
||||
var parameters = [
|
||||
"order" => order.rawValue,
|
||||
|
@ -392,16 +415,19 @@ public class Client {
|
|||
}
|
||||
|
||||
extension Client {
|
||||
public enum Error: LocalizedError {
|
||||
case networkError(Swift.Error)
|
||||
case unexpectedStatus(Int)
|
||||
case invalidRequest
|
||||
case invalidResponse
|
||||
case invalidModel
|
||||
case mastodonError(String)
|
||||
public struct Error: LocalizedError {
|
||||
public let requestMethod: Method
|
||||
public let requestEndpoint: Endpoint
|
||||
public let type: ErrorType
|
||||
|
||||
init<ResultType: Decodable>(request: Request<ResultType>, type: ErrorType) {
|
||||
self.requestMethod = request.method
|
||||
self.requestEndpoint = request.endpoint
|
||||
self.type = type
|
||||
}
|
||||
|
||||
public var localizedDescription: String {
|
||||
switch self {
|
||||
switch type {
|
||||
case .networkError(let error):
|
||||
return "Network Error: \(error.localizedDescription)"
|
||||
// todo: support more status codes
|
||||
|
@ -413,11 +439,19 @@ extension Client {
|
|||
return "Invalid Request"
|
||||
case .invalidResponse:
|
||||
return "Invalid Response"
|
||||
case .invalidModel:
|
||||
case .invalidModel(_):
|
||||
return "Invalid Model"
|
||||
case .mastodonError(let 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)
|
||||
}
|
||||
}
|
|
@ -20,8 +20,9 @@ public final class Account: AccountProtocol, Decodable {
|
|||
public let statusesCount: Int
|
||||
public let note: String
|
||||
public let url: URL
|
||||
public let avatar: URL
|
||||
public let avatarStatic: URL
|
||||
// required on mastodon, but optional on gotosocial
|
||||
public let avatar: URL?
|
||||
public let avatarStatic: URL?
|
||||
public let header: URL?
|
||||
public let headerStatic: URL?
|
||||
public private(set) var emojis: [Emoji]
|
||||
|
@ -44,8 +45,8 @@ public final class Account: AccountProtocol, Decodable {
|
|||
self.statusesCount = try container.decode(Int.self, forKey: .statusesCount)
|
||||
self.note = try container.decode(String.self, forKey: .note)
|
||||
self.url = try container.decode(URL.self, forKey: .url)
|
||||
self.avatar = try container.decode(URL.self, forKey: .avatar)
|
||||
self.avatarStatic = try container.decode(URL.self, forKey: .avatarStatic)
|
||||
self.avatar = try? container.decode(URL.self, forKey: .avatar)
|
||||
self.avatarStatic = try? container.decode(URL.self, forKey: .avatarStatic)
|
||||
self.header = try? container.decode(URL.self, forKey: .header)
|
||||
self.headerStatic = try? container.decode(URL.self, forKey: .headerStatic)
|
||||
self.emojis = try container.decode([Emoji].self, forKey: .emojis)
|
|
@ -56,6 +56,23 @@ extension Attachment {
|
|||
case gifv
|
||||
case audio
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,8 @@ public class Card: Codable {
|
|||
public let width: Int?
|
||||
public let height: Int?
|
||||
public let blurhash: String?
|
||||
/// Only present when returned from the trending links endpoint
|
||||
public let history: [History]?
|
||||
|
||||
public required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
@ -40,6 +42,7 @@ public class Card: Codable {
|
|||
self.width = try? container.decodeIfPresent(Int.self, forKey: .width)
|
||||
self.height = try? container.decodeIfPresent(Int.self, forKey: .height)
|
||||
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 {
|
||||
|
@ -67,6 +70,7 @@ public class Card: Codable {
|
|||
case width
|
||||
case height
|
||||
case blurhash
|
||||
case history
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ public protocol AccountProtocol {
|
|||
var statusesCount: Int { get }
|
||||
var note: String { get }
|
||||
var url: URL { get }
|
||||
var avatar: URL { get }
|
||||
var avatar: URL? { get }
|
||||
var header: URL? { get }
|
||||
var moved: Bool? { get }
|
||||
var bot: Bool? { get }
|
|
@ -17,7 +17,7 @@ public enum Timeline {
|
|||
}
|
||||
|
||||
extension Timeline {
|
||||
var endpoint: String {
|
||||
var endpoint: Endpoint {
|
||||
switch self {
|
||||
case .home:
|
||||
return "/api/v1/timelines/home"
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
|
@ -8,12 +8,12 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum Method {
|
||||
public enum Method {
|
||||
case get, post, put, patch, delete
|
||||
}
|
||||
|
||||
extension Method {
|
||||
var name: String {
|
||||
public var name: String {
|
||||
switch self {
|
||||
case .get:
|
||||
return "GET"
|
|
@ -10,13 +10,13 @@ import Foundation
|
|||
|
||||
public struct Request<ResultType: Decodable> {
|
||||
let method: Method
|
||||
let path: String
|
||||
let endpoint: Endpoint
|
||||
let body: Body
|
||||
var queryParameters: [Parameter]
|
||||
|
||||
init(method: Method, path: String, body: Body = EmptyBody(), queryParameters: [Parameter] = []) {
|
||||
init(method: Method, path: Endpoint, body: Body = EmptyBody(), queryParameters: [Parameter] = []) {
|
||||
self.method = method
|
||||
self.path = path
|
||||
self.endpoint = path
|
||||
self.body = body
|
||||
self.queryParameters = queryParameters
|
||||
}
|
|
@ -12,7 +12,7 @@ public class InstanceSelector {
|
|||
|
||||
private static let decoder = JSONDecoder()
|
||||
|
||||
public static func getInstances(category: String?, completion: @escaping Client.Callback<[Instance]>) {
|
||||
public static func getInstances(category: String?, completion: @escaping (Result<[Instance], Client.ErrorType>) -> Void) {
|
||||
let url: URL
|
||||
if let category = category {
|
||||
url = URL(string: "https://api.joinmastodon.org/servers?category=\(category)")!
|
||||
|
@ -34,11 +34,14 @@ public class InstanceSelector {
|
|||
completion(.failure(.unexpectedStatus(response.statusCode)))
|
||||
return
|
||||
}
|
||||
guard let result = try? decoder.decode([Instance].self, from: data) else {
|
||||
completion(.failure(Client.Error.invalidModel))
|
||||
let result: [Instance]
|
||||
do {
|
||||
result = try decoder.decode([Instance].self, from: data)
|
||||
} catch {
|
||||
completion(.failure(.invalidModel(error)))
|
||||
return
|
||||
}
|
||||
completion(.success(result, nil))
|
||||
completion(.success(result))
|
||||
}
|
||||
task.resume()
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
<?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>
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objectVersion = 55;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
|
@ -22,56 +22,20 @@
|
|||
D6093F9B25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */; };
|
||||
D6093FB025BE0B01004811E6 /* TrendingHashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */; };
|
||||
D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */; };
|
||||
D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */; };
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */; };
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = D60CFFDA24A290BA00D00083 /* SwiftSoup */; };
|
||||
D60D2B8223844C71001B87A3 /* BaseStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60D2B8123844C71001B87A3 /* BaseStatusTableViewCell.swift */; };
|
||||
D60E2F272442372B005F8713 /* StatusMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F232442372B005F8713 /* StatusMO.swift */; };
|
||||
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F252442372B005F8713 /* AccountMO.swift */; };
|
||||
D60E2F2C24423EAD005F8713 /* LazilyDecoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */; };
|
||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */; };
|
||||
D60E2F3124424F1A005F8713 /* StatusProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3024424F1A005F8713 /* StatusProtocol.swift */; };
|
||||
D60E2F3324425374005F8713 /* AccountProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E2F3224425374005F8713 /* AccountProtocol.swift */; };
|
||||
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */; };
|
||||
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61099BA2144B0CC00432DC2 /* PachydermTests.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 */; };
|
||||
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */; };
|
||||
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */; };
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */; };
|
||||
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */; };
|
||||
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6114E1627F8BB210080E273 /* VersionTests.swift */; };
|
||||
D611C2CF232DC61100C86A49 /* HashtagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D611C2CD232DC61100C86A49 /* HashtagTableViewCell.swift */; };
|
||||
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 */; };
|
||||
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
|
||||
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
|
||||
|
@ -86,7 +50,6 @@
|
|||
D62275A824F1CA2800B82A16 /* ComposeReplyContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A724F1CA2800B82A16 /* ComposeReplyContentView.swift */; };
|
||||
D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62275A924F1E01C00B82A16 /* ComposeTextView.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 */; };
|
||||
D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; };
|
||||
D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; };
|
||||
|
@ -110,14 +73,11 @@
|
|||
D627FF7B217E951500CC0648 /* DraftsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D627FF7A217E951500CC0648 /* DraftsTableViewController.swift */; };
|
||||
D627FF7D217E958900CC0648 /* DraftTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D627FF7C217E958900CC0648 /* DraftTableViewCell.xib */; };
|
||||
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 */; };
|
||||
D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6289E83217B795D0003D1D7 /* LargeImageViewController.xib */; };
|
||||
D62D2422217AA7E1005076CC /* UserActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62D2421217AA7E1005076CC /* UserActivityManager.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 */; };
|
||||
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 */; };
|
||||
D62E9987279D094F00C26176 /* StatusMetaIndicatorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9986279D094F00C26176 /* StatusMetaIndicatorsView.swift */; };
|
||||
D62E9989279DB2D100C26176 /* InstanceFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = D62E9988279DB2D100C26176 /* InstanceFeatures.swift */; };
|
||||
|
@ -125,7 +85,6 @@
|
|||
D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6311C4F25B3765B00B27539 /* ImageDataCache.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 */; };
|
||||
D63569E023908A8D003DD353 /* StatusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60A4FFB238B726A008AC647 /* StatusState.swift */; };
|
||||
D63661C02381C144004B9E16 /* PreferencesNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63661BF2381C144004B9E16 /* PreferencesNavigationController.swift */; };
|
||||
D6370B9C24421FF30092A7FF /* Tusker.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6370B9A24421FF30092A7FF /* Tusker.xcdatamodeld */; };
|
||||
D63A8D0B2561C27F00D9DFFF /* ProfileStatusesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63A8D0A2561C27F00D9DFFF /* ProfileStatusesViewController.swift */; };
|
||||
|
@ -184,6 +143,7 @@
|
|||
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 */; };
|
||||
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 */; };
|
||||
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A7D2157E02600721E32 /* XCBRequestSpec.swift */; };
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6757A812157E8FA00721E32 /* XCBSession.swift */; };
|
||||
|
@ -217,7 +177,6 @@
|
|||
D690797324A4EF9700023A34 /* UIBezierPath+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D690797224A4EF9700023A34 /* UIBezierPath+Helpers.swift */; };
|
||||
D693A72825CF282E003A14E2 /* TrendingHashtagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.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 */; };
|
||||
D693A73025CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */; };
|
||||
D693DE5723FE1A6A0061E07D /* EnhancedNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D693DE5623FE1A6A0061E07D /* EnhancedNavigationViewController.swift */; };
|
||||
|
@ -233,8 +192,6 @@
|
|||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6969E9F240C8384002843CE /* EmojiLabel.swift */; };
|
||||
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = D69CCBBE249E6EFD000AF167 /* CrashReporter */; };
|
||||
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 */; };
|
||||
D6A3BC7D232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6A3BC7B232195C600FD64D5 /* ActionNotificationGroupTableViewCell.xib */; };
|
||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A3BC7E2321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift */; };
|
||||
|
@ -329,45 +286,20 @@
|
|||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */; };
|
||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
D6EE63FB2551F7F60065485C /* StatusCollapseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EE63FA2551F7F60065485C /* StatusCollapseButton.swift */; };
|
||||
D6F0B12B24A3071C001E48C3 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B12A24A3071C001E48C3 /* MainSplitViewController.swift */; };
|
||||
D6F0B17524A3A1AA001E48C3 /* MainSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */; };
|
||||
D6F1F9DF27B0613300CB7D88 /* WebURL in Frameworks */ = {isa = PBXBuildFile; productRef = D6F1F9DE27B0613300CB7D88 /* WebURL */; settings = {ATTRIBUTES = (Required, ); }; };
|
||||
D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */; };
|
||||
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */; };
|
||||
D6F2E965249E8BFD005846BB /* IssueReporterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */; };
|
||||
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */; };
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FF985F255C717400845181 /* AccountSwitchingContainerViewController.swift */; };
|
||||
/* End PBXBuildFile 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 */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
|
@ -409,7 +341,6 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -432,57 +363,19 @@
|
|||
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>"; };
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrendingHashtagTableViewCell.xib; 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>"; };
|
||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendHistoryView.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>"; };
|
||||
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>"; };
|
||||
D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonCachePersistentStore.swift; sourceTree = "<group>"; };
|
||||
D60E2F3024424F1A005F8713 /* StatusProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusProtocol.swift; sourceTree = "<group>"; };
|
||||
D60E2F3224425374005F8713 /* AccountProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountProtocol.swift; sourceTree = "<group>"; };
|
||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pachyderm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D61099AD2144B0CC00432DC2 /* Pachyderm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Pachyderm.h; 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>"; };
|
||||
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusesViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinksViewController.swift; sourceTree = "<group>"; };
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingLinkTableViewCell.swift; sourceTree = "<group>"; };
|
||||
D6114E1627F8BB210080E273 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -497,7 +390,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -521,14 +413,11 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -597,6 +486,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -630,7 +520,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -645,8 +534,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -746,45 +633,26 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
D6F0B17424A3A1AA001E48C3 /* MainSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSidebarViewController.swift; sourceTree = "<group>"; };
|
||||
D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporterViewController.swift; sourceTree = "<group>"; };
|
||||
D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CrashReporterViewController.xib; sourceTree = "<group>"; };
|
||||
D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueReporterViewController.swift; sourceTree = "<group>"; };
|
||||
D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IssueReporterViewController.xib; 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>"; };
|
||||
/* End PBXFileReference 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 */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||
D674A50927F9128D00BA03AC /* Pachyderm in Frameworks */,
|
||||
D6B0539F23BD2BA300A066FA /* SheetController in Frameworks */,
|
||||
D69CCBBF249E6EFD000AF167 /* CrashReporter in Frameworks */,
|
||||
D60CFFDB24A290BA00D00083 /* SwiftSoup in Frameworks */,
|
||||
|
@ -829,107 +697,6 @@
|
|||
path = "Attachment Gallery";
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -937,7 +704,6 @@
|
|||
D611C2CE232DC61100C86A49 /* HashtagTableViewCell.xib */,
|
||||
D6093FAE25BE0B01004811E6 /* TrendingHashtagTableViewCell.swift */,
|
||||
D6093FAF25BE0B01004811E6 /* TrendingHashtagTableViewCell.xib */,
|
||||
D6093FB625BE0CF3004811E6 /* HashtagHistoryView.swift */,
|
||||
);
|
||||
path = "Hashtag Cell";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1018,7 +784,10 @@
|
|||
D6945C3323AC6431005C403C /* AddSavedHashtagViewController.swift */,
|
||||
D6093F9A25BDD4B9004811E6 /* HashtagSearchResultsViewController.swift */,
|
||||
D6945C3923AC75E2005C403C /* FindInstanceViewController.swift */,
|
||||
D6114E0C27F7FEB30080E273 /* TrendingStatusesViewController.swift */,
|
||||
D693A72725CF282E003A14E2 /* TrendingHashtagsViewController.swift */,
|
||||
D6114E1027F899B30080E273 /* TrendingLinksViewController.swift */,
|
||||
D6114E1227F89B440080E273 /* TrendingLinkTableViewCell.swift */,
|
||||
D693A72925CF8C1E003A14E2 /* ProfileDirectoryViewController.swift */,
|
||||
D693A72D25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.swift */,
|
||||
D693A72E25CF91C6003A14E2 /* FeaturedProfileCollectionViewCell.xib */,
|
||||
|
@ -1369,18 +1138,6 @@
|
|||
path = "Account Detail";
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1498,6 +1255,7 @@
|
|||
D627943123A5466600D38C68 /* SelectableTableViewCell.swift */,
|
||||
D620483723D38190008A63EF /* StatusContentTextView.swift */,
|
||||
04ED00B021481ED800567C53 /* SteppedProgressView.swift */,
|
||||
D6093FB625BE0CF3004811E6 /* TrendHistoryView.swift */,
|
||||
D6403CC124A6B72D00E81C55 /* VisualEffectImageButton.swift */,
|
||||
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */,
|
||||
D627944623A6AC9300D38C68 /* BasicTableViewCell.xib */,
|
||||
|
@ -1556,8 +1314,7 @@
|
|||
D6D4DDC3212518A000E1C4BB = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
||||
D674A50727F910F300BA03AC /* Pachyderm */,
|
||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||
|
@ -1573,8 +1330,6 @@
|
|||
D6D4DDCC212518A000E1C4BB /* Tusker.app */,
|
||||
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
|
||||
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
|
||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
||||
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
||||
D6E343A8265AAD6B00C4AA01 /* OpenInTusker.appex */,
|
||||
);
|
||||
name = Products;
|
||||
|
@ -1625,6 +1380,7 @@
|
|||
D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */,
|
||||
D62FF04723D7CDD700909D6E /* AttributedStringHelperTests.swift */,
|
||||
D6E426AC25334DA500C02E1C /* FuzzyMatcherTests.swift */,
|
||||
D6114E1627F8BB210080E273 /* VersionTests.swift */,
|
||||
D6D4DDE6212518A200E1C4BB /* Info.plist */,
|
||||
);
|
||||
path = TuskerTests;
|
||||
|
@ -1681,8 +1437,9 @@
|
|||
D6F2E960249E772F005846BB /* Crash Reporter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D6F2E963249E8BFD005846BB /* CrashReporterViewController.swift */,
|
||||
D6F2E964249E8BFD005846BB /* CrashReporterViewController.xib */,
|
||||
D6114E0827F3EA3D0080E273 /* CrashReporterViewController.swift */,
|
||||
D6F2E963249E8BFD005846BB /* IssueReporterViewController.swift */,
|
||||
D6F2E964249E8BFD005846BB /* IssueReporterViewController.xib */,
|
||||
);
|
||||
path = "Crash Reporter";
|
||||
sourceTree = "<group>";
|
||||
|
@ -1698,58 +1455,7 @@
|
|||
};
|
||||
/* 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 */
|
||||
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 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
|
||||
|
@ -1765,7 +1471,6 @@
|
|||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
D6E343B3265AAD6B00C4AA01 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tusker;
|
||||
|
@ -1774,6 +1479,7 @@
|
|||
D69CCBBE249E6EFD000AF167 /* CrashReporter */,
|
||||
D60CFFDA24A290BA00D00083 /* SwiftSoup */,
|
||||
D6676CA427A8D0020052936B /* WebURLFoundationExtras */,
|
||||
D674A50827F9128D00BA03AC /* Pachyderm */,
|
||||
);
|
||||
productName = Tusker;
|
||||
productReference = D6D4DDCC212518A000E1C4BB /* Tusker.app */;
|
||||
|
@ -1844,15 +1550,6 @@
|
|||
LastUpgradeCheck = 1250;
|
||||
ORGANIZATIONNAME = Shadowfacts;
|
||||
TargetAttributes = {
|
||||
D61099AA2144B0CC00432DC2 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
D61099B22144B0CC00432DC2 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1020;
|
||||
TestTargetID = D6D4DDCB212518A000E1C4BB;
|
||||
};
|
||||
D6D4DDCB212518A000E1C4BB = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1200;
|
||||
|
@ -1873,7 +1570,7 @@
|
|||
};
|
||||
};
|
||||
buildConfigurationList = D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
compatibilityVersion = "Xcode 13.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
|
@ -1900,28 +1597,12 @@
|
|||
D6D4DDCB212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDDF212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
|
||||
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
||||
D6E343A7265AAD6B00C4AA01 /* OpenInTusker */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D61099A92144B0CC00432DC2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099B12144B0CC00432DC2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D6D4DDCA212518A000E1C4BB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -1950,7 +1631,7 @@
|
|||
D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */,
|
||||
D6A3BC812321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.xib in Resources */,
|
||||
D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */,
|
||||
D6F2E966249E8BFD005846BB /* CrashReporterViewController.xib in Resources */,
|
||||
D6F2E966249E8BFD005846BB /* IssueReporterViewController.xib in Resources */,
|
||||
D662AEF0263A3B880082A153 /* PollFinishedTableViewCell.xib in Resources */,
|
||||
D6A4DCCD2553667800D9DE31 /* FastAccountSwitcherViewController.xib in Resources */,
|
||||
D6DEA0DF268400C300FE896A /* ConfirmLoadMoreTableViewCell.xib in Resources */,
|
||||
|
@ -2027,68 +1708,6 @@
|
|||
/* End PBXShellScriptBuildPhase 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 */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -2105,7 +1724,7 @@
|
|||
D60E2F292442372B005F8713 /* AccountMO.swift in Sources */,
|
||||
D6412B0324AFF6A600F5412E /* TabBarScrollableViewController.swift in Sources */,
|
||||
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
|
||||
D6093FB725BE0CF3004811E6 /* HashtagHistoryView.swift in Sources */,
|
||||
D6093FB725BE0CF3004811E6 /* TrendHistoryView.swift in Sources */,
|
||||
D6DEA0DE268400C300FE896A /* ConfirmLoadMoreTableViewCell.swift in Sources */,
|
||||
D6EBF01723C55E0D00AE061B /* UISceneSession+MastodonController.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
|
@ -2144,7 +1763,7 @@
|
|||
D6BC9DD7232D7811002CA326 /* TimelinesPageViewController.swift in Sources */,
|
||||
D60E2F2E244248BF005F8713 /* MastodonCachePersistentStore.swift in Sources */,
|
||||
D620483623D38075008A63EF /* ContentTextView.swift in Sources */,
|
||||
D6F2E965249E8BFD005846BB /* CrashReporterViewController.swift in Sources */,
|
||||
D6F2E965249E8BFD005846BB /* IssueReporterViewController.swift in Sources */,
|
||||
D6A57408255C53EC00674551 /* ComposeTextViewCaretScrolling.swift in Sources */,
|
||||
D6CA6A94249FADE700AD45C1 /* GalleryPlayerViewController.swift in Sources */,
|
||||
D6A3BC802321B7E600FD64D5 /* FollowNotificationGroupTableViewCell.swift in Sources */,
|
||||
|
@ -2187,6 +1806,7 @@
|
|||
04ED00B121481ED800567C53 /* SteppedProgressView.swift in Sources */,
|
||||
D677284C24ECBE9100C732D3 /* ComposeAvatarImageView.swift in Sources */,
|
||||
D6A6C11B25B63CEE00298D0F /* MemoryCache.swift in Sources */,
|
||||
D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */,
|
||||
D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */,
|
||||
D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */,
|
||||
D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */,
|
||||
|
@ -2264,6 +1884,7 @@
|
|||
D6D3F4C424FDB6B700EC4A6A /* View+ConditionalModifier.swift in Sources */,
|
||||
D6B8DB342182A59300424AF7 /* UIAlertController+Visibility.swift in Sources */,
|
||||
D67C57AD21E265FC00C3118B /* LargeAccountDetailView.swift in Sources */,
|
||||
D6114E1327F89B440080E273 /* TrendingLinkTableViewCell.swift in Sources */,
|
||||
D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */,
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */,
|
||||
D6C693FE2162FEEA007D6A6D /* UIViewController+Children.swift in Sources */,
|
||||
|
@ -2273,6 +1894,7 @@
|
|||
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
|
||||
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
|
||||
D6FF9860255C717400845181 /* AccountSwitchingContainerViewController.swift in Sources */,
|
||||
D6114E1127F899B30080E273 /* TrendingLinksViewController.swift in Sources */,
|
||||
D670F8B62537DC890046588A /* EmojiPickerWrapper.swift in Sources */,
|
||||
D6B81F442560390300F6E31D /* MenuController.swift in Sources */,
|
||||
D65234E12561AA68001AF9CF /* NotificationsTableViewController.swift in Sources */,
|
||||
|
@ -2318,6 +1940,7 @@
|
|||
D677284E24ECC01D00C732D3 /* Draft.swift in Sources */,
|
||||
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
|
||||
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,
|
||||
D6114E0927F3EA3D0080E273 /* CrashReporterViewController.swift in Sources */,
|
||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */,
|
||||
D6E426B325337C7000C02E1C /* CustomEmojiImageView.swift in Sources */,
|
||||
D6D4DDD0212518A000E1C4BB /* AppDelegate.swift in Sources */,
|
||||
|
@ -2337,6 +1960,7 @@
|
|||
files = (
|
||||
D62FF04823D7CDD700909D6E /* AttributedStringHelperTests.swift in Sources */,
|
||||
D6E426AD25334DA500C02E1C /* FuzzyMatcherTests.swift in Sources */,
|
||||
D6114E1727F8BB210080E273 /* VersionTests.swift in Sources */,
|
||||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -2366,21 +1990,6 @@
|
|||
/* End PBXSourcesBuildPhase 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 */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
||||
|
@ -2426,111 +2035,6 @@
|
|||
/* End PBXVariantGroup 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 */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -2661,7 +2165,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -2692,7 +2196,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -2802,7 +2306,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -2829,7 +2333,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 24;
|
||||
CURRENT_PROJECT_VERSION = 25;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = OpenInTusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
|
@ -2852,24 +2356,6 @@
|
|||
/* End XCBuildConfiguration 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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -2963,6 +2449,10 @@
|
|||
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
|
||||
productName = WebURLFoundationExtras;
|
||||
};
|
||||
D674A50827F9128D00BA03AC /* Pachyderm */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Pachyderm;
|
||||
};
|
||||
D69CCBBE249E6EFD000AF167 /* CrashReporter */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D69CCBBD249E6EFD000AF167 /* XCRemoteSwiftPackageReference "plcrashreporter" */;
|
||||
|
@ -2973,11 +2463,6 @@
|
|||
package = D6B0539D23BD2BA300A066FA /* XCRemoteSwiftPackageReference "SheetController" */;
|
||||
productName = SheetController;
|
||||
};
|
||||
D6F1F9DE27B0613300CB7D88 /* WebURL */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D6676CA127A8D0020052936B /* XCRemoteSwiftPackageReference "swift-url" */;
|
||||
productName = WebURL;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:Tusker.xcodeproj">
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<?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>
|
|
@ -1,8 +0,0 @@
|
|||
<?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>
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"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
|
||||
}
|
|
@ -29,8 +29,9 @@ class AccountActivityItemSource: NSObject, UIActivityItemSource {
|
|||
metadata.originalURL = account.url
|
||||
metadata.url = account.url
|
||||
metadata.title = "\(account.displayName) (@\(account.username)@\(account.url.host!)"
|
||||
if let data = ImageCache.avatars.getData(account.avatar),
|
||||
let image = UIImage(data: data) {
|
||||
if let avatar = account.avatar,
|
||||
let data = ImageCache.avatars.getData(avatar),
|
||||
let image = UIImage(data: data) {
|
||||
metadata.iconProvider = NSItemProvider(object: image)
|
||||
}
|
||||
return metadata
|
||||
|
|
|
@ -32,8 +32,9 @@ class StatusActivityItemSource: NSObject, UIActivityItemSource {
|
|||
let doc = try! SwiftSoup.parse(status.content)
|
||||
let content = try! doc.text()
|
||||
metadata.title = "\(status.account.displayName): \"\(content)\""
|
||||
if let data = ImageCache.avatars.getData(status.account.avatar),
|
||||
let image = UIImage(data: data) {
|
||||
if let avatar = status.account.avatar,
|
||||
let data = ImageCache.avatars.getData(avatar),
|
||||
let image = UIImage(data: data) {
|
||||
metadata.iconProvider = NSItemProvider(object: image)
|
||||
}
|
||||
return metadata
|
||||
|
|
|
@ -67,28 +67,54 @@ class MastodonController: ObservableObject {
|
|||
return client.run(request, completion: completion)
|
||||
}
|
||||
|
||||
func registerApp(completion: @escaping (_ clientID: String, _ clientSecret: String) -> Void) {
|
||||
guard client.clientID == nil,
|
||||
client.clientSecret == nil else {
|
||||
|
||||
completion(client.clientID!, client.clientSecret!)
|
||||
return
|
||||
}
|
||||
|
||||
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
|
||||
guard case let .success(app, _) = response else { fatalError() }
|
||||
func run<Result>(_ request: Request<Result>) async throws -> (Result, Pagination?) {
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
client.run(request) { response in
|
||||
switch response {
|
||||
case .failure(let error):
|
||||
continuation.resume(throwing: error)
|
||||
case .success(let result, let pagination):
|
||||
continuation.resume(returning: (result, pagination))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// - 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.clientSecret = app.clientSecret
|
||||
completion(app.clientID, app.clientSecret)
|
||||
return (app.clientID, app.clientSecret)
|
||||
}
|
||||
}
|
||||
|
||||
func authorize(authorizationCode: String, completion: @escaping (_ accessToken: String) -> Void) {
|
||||
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||
guard case let .success(settings, _) = response else { fatalError() }
|
||||
self.client.accessToken = settings.accessToken
|
||||
completion(settings.accessToken)
|
||||
}
|
||||
/// - Returns: The access token
|
||||
func authorize(authorizationCode: String) async throws -> String {
|
||||
return try await withCheckedThrowingContinuation({ continuation in
|
||||
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||
switch response {
|
||||
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) {
|
||||
|
@ -120,6 +146,18 @@ 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) {
|
||||
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public final class AccountMO: NSManagedObject, AccountProtocol {
|
|||
}
|
||||
|
||||
@NSManaged public var acct: String
|
||||
@NSManaged public var avatar: URL
|
||||
@NSManaged public var avatar: URL?
|
||||
@NSManaged public var botCD: Bool
|
||||
@NSManaged public var createdAt: Date
|
||||
@NSManaged public var displayName: String
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21C52" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="19574" systemVersion="21D49" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Account" representedClassName="AccountMO" syncable="YES">
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" attributeType="URI"/>
|
||||
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="botCD" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="displayName" attributeType="String"/>
|
||||
|
|
|
@ -11,6 +11,7 @@ import Pachyderm
|
|||
|
||||
struct InstanceFeatures {
|
||||
private(set) var instanceType = InstanceType.mastodon
|
||||
private(set) var version: Version?
|
||||
private(set) var maxStatusChars = 500
|
||||
|
||||
var localOnlyPosts: Bool {
|
||||
|
@ -29,6 +30,14 @@ struct InstanceFeatures {
|
|||
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?) {
|
||||
let ver = instance.version.lowercased()
|
||||
if ver.contains("glitch") {
|
||||
|
@ -37,10 +46,14 @@ struct InstanceFeatures {
|
|||
instanceType = .hometown
|
||||
} else if ver.contains("pleroma") {
|
||||
instanceType = .pleroma
|
||||
} else if ver.contains("pixelfed") {
|
||||
instanceType = .pixelfed
|
||||
} else {
|
||||
instanceType = .mastodon
|
||||
}
|
||||
|
||||
version = Version(string: ver)
|
||||
|
||||
maxStatusChars = instance.maxStatusCharacters ?? 500
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +64,7 @@ extension InstanceFeatures {
|
|||
case pleroma
|
||||
case hometown
|
||||
case glitch
|
||||
case pixelfed
|
||||
|
||||
var isMastodon: Bool {
|
||||
switch self {
|
||||
|
@ -62,3 +76,56 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,9 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
return
|
||||
}
|
||||
|
||||
window!.rootViewController = CrashReporterViewController.create(report: report)
|
||||
window!.rootViewController = CrashReporterViewController.create(report: report, dismiss: {
|
||||
self.showAppOrOnboardingUI()
|
||||
})
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -207,11 +209,3 @@ extension MainSceneDelegate: OnboardingViewControllerDelegate {
|
|||
activateAccount(account, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension MainSceneDelegate: MFMailComposeViewControllerDelegate {
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
controller.dismiss(animated: true) {
|
||||
self.showAppOrOnboardingUI()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2019 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
|
||||
enum StatusFormat: CaseIterable {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Pachyderm
|
||||
import Combine
|
||||
|
||||
|
@ -64,6 +64,7 @@ class Preferences: Codable, ObservableObject {
|
|||
self.defaultNotificationsMode = try container.decode(NotificationsMode.self, forKey: .defaultNotificationsType)
|
||||
self.grayscaleImages = try container.decodeIfPresent(Bool.self, forKey: .grayscaleImages) ?? 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.statusContentType = try container.decode(StatusContentType.self, forKey: .statusContentType)
|
||||
|
@ -102,6 +103,7 @@ class Preferences: Codable, ObservableObject {
|
|||
try container.encode(defaultNotificationsMode, forKey: .defaultNotificationsType)
|
||||
try container.encode(grayscaleImages, forKey: .grayscaleImages)
|
||||
try container.encode(disableInfiniteScrolling, forKey: .disableInfiniteScrolling)
|
||||
try container.encode(hideDiscover, forKey: .hideDiscover)
|
||||
|
||||
try container.encode(silentActions, forKey: .silentActions)
|
||||
try container.encode(statusContentType, forKey: .statusContentType)
|
||||
|
@ -142,6 +144,7 @@ class Preferences: Codable, ObservableObject {
|
|||
@Published var defaultNotificationsMode = NotificationsMode.allNotifications
|
||||
@Published var grayscaleImages = false
|
||||
@Published var disableInfiniteScrolling = false
|
||||
@Published var hideDiscover = false
|
||||
|
||||
// MARK: Advanced
|
||||
@Published var silentActions: [String: Permission] = [:]
|
||||
|
@ -179,6 +182,7 @@ class Preferences: Codable, ObservableObject {
|
|||
case defaultNotificationsType
|
||||
case grayscaleImages
|
||||
case disableInfiniteScrolling
|
||||
case hideDiscover
|
||||
|
||||
case silentActions
|
||||
case statusContentType
|
||||
|
|
|
@ -143,7 +143,9 @@ class AssetCollectionViewController: UIViewController, UICollectionViewDelegate
|
|||
switch PHPhotoLibrary.authorizationStatus(for: .readWrite) {
|
||||
case .notDetermined:
|
||||
PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in
|
||||
self.loadAssets()
|
||||
DispatchQueue.main.async {
|
||||
self.loadAssets()
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ struct ComposeAutocompleteMentionsView: View {
|
|||
}
|
||||
}
|
||||
|
||||
var avatar: URL {
|
||||
var avatar: URL? {
|
||||
switch self {
|
||||
case let .pachyderm(account):
|
||||
return account.avatar
|
||||
|
@ -346,7 +346,7 @@ struct ComposeAutocompleteHashtagsView: View {
|
|||
let group = DispatchGroup()
|
||||
|
||||
group.enter()
|
||||
trendingRequest = mastodonController.run(Client.getTrends()) { (response) in
|
||||
trendingRequest = mastodonController.run(Client.getTrendingHashtags()) { (response) in
|
||||
defer { group.leave() }
|
||||
guard case let .success(trends, _) = response else { return }
|
||||
trendingTags = trends
|
||||
|
|
|
@ -157,7 +157,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
DispatchQueue.main.async {
|
||||
self.loadingState = .unloaded
|
||||
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Status") { [weak self] (toast) in
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
self?.loadMainStatus()
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ class ConversationTableViewController: EnhancedTableViewController {
|
|||
DispatchQueue.main.async {
|
||||
self.loadingState = .loadedMain
|
||||
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Content") { [weak self] (toast) in
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
self?.loadContext(for: mainStatus)
|
||||
}
|
||||
|
|
|
@ -86,13 +86,15 @@ class ExpandThreadTableViewCell: UITableViewCell {
|
|||
xConstraint
|
||||
])
|
||||
|
||||
let req = ImageCache.avatars.get(account.avatar) { [weak accountImageView] (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
accountImageView?.image = image
|
||||
if let avatar = account.avatar {
|
||||
let req = ImageCache.avatars.get(avatar) { [weak accountImageView] (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
accountImageView?.image = image
|
||||
}
|
||||
}
|
||||
if let req = req {
|
||||
avatarRequests.append(req)
|
||||
}
|
||||
}
|
||||
if let req = req {
|
||||
avatarRequests.append(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,132 +2,46 @@
|
|||
// CrashReporterViewController.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 6/20/20.
|
||||
// Copyright © 2020 Shadowfacts. All rights reserved.
|
||||
// Created by Shadowfacts on 3/29/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CrashReporter
|
||||
import MessageUI
|
||||
|
||||
class CrashReporterViewController: UIViewController {
|
||||
|
||||
class CrashReporterViewController: IssueReporterViewController {
|
||||
|
||||
private let report: PLCrashReport
|
||||
private var reportText: String!
|
||||
|
||||
private var reportFilename: String {
|
||||
let timestamp = ISO8601DateFormatter().string(from: report.systemInfo.timestamp)
|
||||
return "Tusker-crash-\(timestamp).crash"
|
||||
override var preamble: 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."
|
||||
}
|
||||
|
||||
@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
|
||||
override var subject: String {
|
||||
"Tusker Crash Report"
|
||||
}
|
||||
|
||||
private init(report: PLCrashReport){
|
||||
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 reportFilename = "Tusker-crash-\(timestamp).crash"
|
||||
|
||||
super.init(nibName: "CrashReporterViewController", bundle: .main)
|
||||
super.init(reportText: reportText, reportFilename: reportFilename, dismiss: dismiss)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||
<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"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CrashReporterViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="IssueReporterViewController" customModule="Tusker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="crashReportTextView" destination="hxN-7J-Usc" id="TGd-yq-Ds5"/>
|
||||
<outlet property="sendReportButton" destination="Ofm-5l-nAp" id="6xM-hz-uvw"/>
|
||||
|
@ -27,9 +29,9 @@
|
|||
<subviews>
|
||||
<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"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<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" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<color key="textColor" systemColor="labelColor"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
|
@ -43,13 +45,13 @@
|
|||
<viewLayoutGuide key="contentLayoutGuide" id="LRh-7Z-mV1"/>
|
||||
<viewLayoutGuide key="frameLayoutGuide" id="Rgd-t7-8QN"/>
|
||||
</scrollView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ofm-5l-nAp">
|
||||
<rect key="frame" x="52" y="730" width="310.5" height="50"/>
|
||||
<color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" systemColor="systemBlueColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="50" id="jHf-W0-qQn"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Send Crash Report">
|
||||
<state key="normal" title="Send Report">
|
||||
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</state>
|
||||
<connections>
|
||||
|
@ -59,8 +61,8 @@
|
|||
<action selector="sendReportTouchUpInside:" destination="-1" eventType="touchUpInside" id="ggd-fm-Orq"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz">
|
||||
<rect key="frame" x="169" y="788" width="76" height="30"/>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JiJ-Ng-jOz">
|
||||
<rect key="frame" x="168.5" y="788" width="77" height="30"/>
|
||||
<state key="normal" title="Don't Send"/>
|
||||
<connections>
|
||||
<action selector="cancelPressed:" destination="-1" eventType="touchUpInside" id="o4R-0Q-STS"/>
|
||||
|
@ -69,7 +71,8 @@
|
|||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<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"/>
|
||||
|
@ -79,8 +82,18 @@
|
|||
<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"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||
<point key="canvasLocation" x="133" y="154"/>
|
||||
</view>
|
||||
</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>
|
|
@ -69,7 +69,7 @@ class AddSavedHashtagViewController: EnhancedTableViewController {
|
|||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let request = Client.getTrends(limit: 10)
|
||||
let request = Client.getTrendingHashtags(limit: 10)
|
||||
mastodonController.run(request) { (response) in
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
|
||||
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(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -138,9 +139,9 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections(Section.allCases.filter { $0 != .discover })
|
||||
snapshot.appendItems([.bookmarks], toSection: .bookmarks)
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon {
|
||||
snapshot.insertSections([.discover], afterSection: .bookmarks)
|
||||
snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover)
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon,
|
||||
!Preferences.shared.hideDiscover {
|
||||
addDiscoverSection(to: &snapshot)
|
||||
}
|
||||
snapshot.appendItems([.addList], toSection: .lists)
|
||||
snapshot.appendItems(SavedDataManager.shared.sortedHashtags(for: account).map { .savedHashtag($0) }, toSection: .savedHashtags)
|
||||
|
@ -152,6 +153,15 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
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) {
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon,
|
||||
|
@ -198,6 +208,20 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
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) {
|
||||
let titleFormat = NSLocalizedString("Are you sure you want to delete the '%@' list?", comment: "delete list alert title")
|
||||
let title = String(format: titleFormat, list.title)
|
||||
|
@ -273,9 +297,15 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate {
|
|||
case .bookmarks:
|
||||
show(BookmarksTableViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .trendingStatuses:
|
||||
show(TrendingStatusesViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .trendingTags:
|
||||
show(TrendingHashtagsViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .trendingLinks:
|
||||
show(TrendingLinksViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
case .profileDirectory:
|
||||
show(ProfileDirectoryViewController(mastodonController: mastodonController), sender: nil)
|
||||
|
||||
|
@ -355,7 +385,9 @@ extension ExploreViewController {
|
|||
|
||||
enum Item: Hashable {
|
||||
case bookmarks
|
||||
case trendingStatuses
|
||||
case trendingTags
|
||||
case trendingLinks
|
||||
case profileDirectory
|
||||
case list(List)
|
||||
case addList
|
||||
|
@ -368,8 +400,12 @@ extension ExploreViewController {
|
|||
switch self {
|
||||
case .bookmarks:
|
||||
return NSLocalizedString("Bookmarks", comment: "bookmarks nav item title")
|
||||
case .trendingStatuses:
|
||||
return NSLocalizedString("Trending Posts", comment: "trending statuses nav item title")
|
||||
case .trendingTags:
|
||||
return NSLocalizedString("Trending Hashtags", comment: "trending hashtags nav item title")
|
||||
case .trendingLinks:
|
||||
return NSLocalizedString("Trending Links", comment: "trending links nav item title")
|
||||
case .profileDirectory:
|
||||
return NSLocalizedString("Profile Directory", comment: "profile directory nav item title")
|
||||
case let .list(list):
|
||||
|
@ -392,8 +428,12 @@ extension ExploreViewController {
|
|||
switch self {
|
||||
case .bookmarks:
|
||||
name = "bookmark.fill"
|
||||
case .trendingStatuses:
|
||||
name = "doc.text.image"
|
||||
case .trendingTags:
|
||||
name = "arrow.up.arrow.down"
|
||||
name = "number"
|
||||
case .trendingLinks:
|
||||
name = "link"
|
||||
case .profileDirectory:
|
||||
name = "person.2.fill"
|
||||
case .list(_):
|
||||
|
@ -414,8 +454,12 @@ extension ExploreViewController {
|
|||
switch (lhs, rhs) {
|
||||
case (.bookmarks, .bookmarks):
|
||||
return true
|
||||
case (.trendingStatuses, .trendingStatuses):
|
||||
return true
|
||||
case (.trendingTags, .trendingTags):
|
||||
return true
|
||||
case (.trendingLinks, .trendingLinks):
|
||||
return true
|
||||
case (.profileDirectory, .profileDirectory):
|
||||
return true
|
||||
case let (.list(a), .list(b)):
|
||||
|
@ -439,8 +483,12 @@ extension ExploreViewController {
|
|||
switch self {
|
||||
case .bookmarks:
|
||||
hasher.combine("bookmarks")
|
||||
case .trendingStatuses:
|
||||
hasher.combine("trendingStatuses")
|
||||
case .trendingTags:
|
||||
hasher.combine("trendingTags")
|
||||
case .trendingLinks:
|
||||
hasher.combine("trendingLinks")
|
||||
case .profileDirectory:
|
||||
hasher.combine("profileDirectory")
|
||||
case let .list(list):
|
||||
|
@ -497,7 +545,7 @@ extension ExploreViewController: UICollectionViewDragDelegate {
|
|||
case let .savedInstance(url):
|
||||
provider = NSItemProvider(object: url as NSURL)
|
||||
// todo: should dragging public timelines into new windows be supported?
|
||||
case .trendingTags, .profileDirectory, .addList, .addSavedHashtag, .findInstance:
|
||||
default:
|
||||
return []
|
||||
}
|
||||
return [UIDragItem(itemProvider: provider)]
|
||||
|
|
|
@ -55,17 +55,19 @@ class FeaturedProfileCollectionViewCell: UICollectionViewCell {
|
|||
noteTextView.setEmojis(account.emojis)
|
||||
|
||||
avatarImageView.image = nil
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
|
||||
defer {
|
||||
self?.avatarRequest = nil
|
||||
}
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
self.account?.id == account.id else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
if let avatar = account.avatar {
|
||||
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in
|
||||
defer {
|
||||
self?.avatarRequest = nil
|
||||
}
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
self.account?.id == account.id else {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,19 +48,15 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
let request = Client.getTrends(limit: 10)
|
||||
mastodonController.run(request) { (response) in
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
|
||||
guard case let .success(hashtags, _) = response,
|
||||
hashtags.count > 0 else {
|
||||
self.dataSource.apply(snapshot)
|
||||
let request = Client.getTrendingHashtags(limit: 10)
|
||||
Task {
|
||||
guard let (hashtags, _) = try? await mastodonController.run(request) else {
|
||||
return
|
||||
}
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
snapshot.appendSections([.trendingTags])
|
||||
snapshot.appendItems(hashtags.map { .tag($0) })
|
||||
self.dataSource.apply(snapshot)
|
||||
dataSource.apply(snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,7 +81,6 @@ class TrendingHashtagsViewController: EnhancedTableViewController {
|
|||
} actionProvider: { (_) in
|
||||
UIMenu(children: self.actionsForHashtag(hashtag, sourceView: self.tableView.cellForRow(at: indexPath)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// 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 }
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
|
@ -86,8 +86,10 @@ class FastSwitchingAccountView: UIView {
|
|||
|
||||
let controller = MastodonController.getForAccount(account)
|
||||
controller.getOwnAccount { [weak self] (result) in
|
||||
guard let self = self, case let .success(account) = result else { return }
|
||||
self.avatarRequest = ImageCache.avatars.get(account.avatar) { [weak avatarImageView] (_, image) in
|
||||
guard let self = self,
|
||||
case let .success(account) = result,
|
||||
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 }
|
||||
DispatchQueue.main.async {
|
||||
avatarImageView.image = image
|
||||
|
|
|
@ -32,7 +32,7 @@ class MainSidebarViewController: UIViewController {
|
|||
}
|
||||
|
||||
var exploreTabItems: [Item] {
|
||||
var items: [Item] = [.search, .bookmarks, .trendingTags, .profileDirectory]
|
||||
var items: [Item] = [.search, .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory]
|
||||
let snapshot = dataSource.snapshot()
|
||||
for case let .list(list) in snapshot.itemIdentifiers(inSection: .lists) {
|
||||
items.append(.list(list))
|
||||
|
@ -94,6 +94,7 @@ class MainSidebarViewController: UIViewController {
|
|||
|
||||
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(preferencesChanged), name: .preferencesChanged, object: nil)
|
||||
}
|
||||
|
||||
func select(item: Item, animated: Bool) {
|
||||
|
@ -145,34 +146,43 @@ class MainSidebarViewController: UIViewController {
|
|||
snapshot.appendItems([
|
||||
.tab(.compose)
|
||||
], toSection: .compose)
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon {
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon,
|
||||
!Preferences.shared.hideDiscover {
|
||||
snapshot.insertSections([.discover], afterSection: .compose)
|
||||
snapshot.appendItems([
|
||||
.trendingTags,
|
||||
.profileDirectory,
|
||||
], toSection: .discover)
|
||||
}
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
applyDiscoverSectionSnapshot()
|
||||
reloadLists()
|
||||
reloadSavedHashtags()
|
||||
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) {
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon,
|
||||
!snapshot.sectionIdentifiers.contains(.discover) {
|
||||
snapshot.insertSections([.discover], afterSection: .compose)
|
||||
snapshot.appendItems([
|
||||
.trendingTags,
|
||||
.profileDirectory,
|
||||
], toSection: .discover)
|
||||
if mastodonController.instanceFeatures.instanceType.isMastodon {
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
if !snapshot.sectionIdentifiers.contains(.discover) {
|
||||
snapshot.appendSections([.discover])
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
applyDiscoverSectionSnapshot()
|
||||
}
|
||||
let prevSelected = collectionView.indexPathsForSelectedItems
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
if let prevSelected = prevSelected?.first {
|
||||
collectionView.selectItem(at: prevSelected, animated: false, scrollPosition: .top)
|
||||
}
|
||||
|
@ -231,6 +241,22 @@ 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
|
||||
private func showAddList() {
|
||||
|
@ -310,7 +336,7 @@ extension MainSidebarViewController {
|
|||
enum Item: Hashable {
|
||||
case tab(MainTabBarViewController.Tab)
|
||||
case search, bookmarks
|
||||
case trendingTags, profileDirectory
|
||||
case discoverHeader, trendingStatuses, trendingTags, trendingLinks, profileDirectory
|
||||
case listsHeader, list(List), addList
|
||||
case savedHashtagsHeader, savedHashtag(Hashtag), addSavedHashtag
|
||||
case savedInstancesHeader, savedInstance(URL), addSavedInstance
|
||||
|
@ -323,8 +349,14 @@ extension MainSidebarViewController {
|
|||
return "Search"
|
||||
case .bookmarks:
|
||||
return "Bookmarks"
|
||||
case .discoverHeader:
|
||||
return "Discover"
|
||||
case .trendingStatuses:
|
||||
return "Trending Posts"
|
||||
case .trendingTags:
|
||||
return "Trending Hashtags"
|
||||
case .trendingLinks:
|
||||
return "Trending Links"
|
||||
case .profileDirectory:
|
||||
return "Profile Directory"
|
||||
case .listsHeader:
|
||||
|
@ -356,8 +388,12 @@ extension MainSidebarViewController {
|
|||
return "magnifyingglass"
|
||||
case .bookmarks:
|
||||
return "bookmark"
|
||||
case .trendingStatuses:
|
||||
return "doc.text.image"
|
||||
case .trendingTags:
|
||||
return "arrow.up.arrow.down"
|
||||
return "number"
|
||||
case .trendingLinks:
|
||||
return "link"
|
||||
case .profileDirectory:
|
||||
return "person.2.fill"
|
||||
case .list(_):
|
||||
|
@ -366,7 +402,7 @@ extension MainSidebarViewController {
|
|||
return "number"
|
||||
case .savedInstance(_):
|
||||
return "globe"
|
||||
case .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
|
||||
case .discoverHeader, .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
|
||||
return nil
|
||||
case .addList, .addSavedHashtag, .addSavedInstance:
|
||||
return "plus"
|
||||
|
@ -375,7 +411,7 @@ extension MainSidebarViewController {
|
|||
|
||||
var hasChildren: Bool {
|
||||
switch self {
|
||||
case .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
|
||||
case .discoverHeader, .listsHeader, .savedHashtagsHeader, .savedInstancesHeader:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
@ -207,7 +207,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
|
||||
tabBarViewController.select(tab: .explore)
|
||||
|
||||
case .bookmarks, .trendingTags, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
case .bookmarks, .trendingStatuses, .trendingTags, .trendingLinks, .profileDirectory, .list(_), .savedHashtag(_), .savedInstance(_):
|
||||
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
|
||||
// in compact mode and performing a search.
|
||||
|
@ -215,7 +215,7 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
let explore = exploreNav.viewControllers.first as! ExploreViewController
|
||||
explore.searchControllerStatusOnAppearance = false
|
||||
|
||||
case .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
|
||||
case .discoverHeader, .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
|
||||
// These items are not selectable in the sidebar collection view, so this code is unreachable.
|
||||
fatalError("unreachable")
|
||||
}
|
||||
|
@ -273,18 +273,27 @@ extension MainSplitViewController: UISplitViewControllerDelegate {
|
|||
}
|
||||
// Insert the new search VC at the beginning of the new search nav stack
|
||||
toPrepend = searchVC
|
||||
} else if tabNavigationStack[1] is BookmarksTableViewController {
|
||||
exploreItem = .bookmarks
|
||||
} else if let listVC = tabNavigationStack[1] as? ListTimelineViewController {
|
||||
exploreItem = .list(listVC.list)
|
||||
} else if let hashtagVC = tabNavigationStack[1] as? HashtagTimelineViewController {
|
||||
exploreItem = .savedHashtag(hashtagVC.hashtag)
|
||||
} else if let instanceVC = tabNavigationStack[1] as? InstanceTimelineViewController {
|
||||
exploreItem = .savedInstance(instanceVC.instanceURL)
|
||||
} else if tabNavigationStack[1] is TrendingHashtagsViewController {
|
||||
exploreItem = .trendingTags
|
||||
} else if tabNavigationStack[1] is ProfileDirectoryViewController {
|
||||
exploreItem = .profileDirectory
|
||||
} else {
|
||||
switch tabNavigationStack[1] {
|
||||
case is BookmarksTableViewController:
|
||||
exploreItem = .bookmarks
|
||||
case let listVC as ListTimelineViewController:
|
||||
exploreItem = .list(listVC.list)
|
||||
case let hashtagVC as HashtagTimelineViewController:
|
||||
exploreItem = .savedHashtag(hashtagVC.hashtag)
|
||||
case let instanceVC as InstanceTimelineViewController:
|
||||
exploreItem = .savedInstance(instanceVC.instanceURL)
|
||||
case is TrendingStatusesViewController:
|
||||
exploreItem = .trendingStatuses
|
||||
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)
|
||||
|
||||
|
@ -335,8 +344,12 @@ fileprivate extension MainSidebarViewController.Item {
|
|||
return SearchViewController(mastodonController: mastodonController)
|
||||
case .bookmarks:
|
||||
return BookmarksTableViewController(mastodonController: mastodonController)
|
||||
case .trendingStatuses:
|
||||
return TrendingStatusesViewController(mastodonController: mastodonController)
|
||||
case .trendingTags:
|
||||
return TrendingHashtagsViewController(mastodonController: mastodonController)
|
||||
case .trendingLinks:
|
||||
return TrendingLinksViewController(mastodonController: mastodonController)
|
||||
case .profileDirectory:
|
||||
return ProfileDirectoryViewController(mastodonController: mastodonController)
|
||||
case let .list(list):
|
||||
|
@ -345,7 +358,7 @@ fileprivate extension MainSidebarViewController.Item {
|
|||
return HashtagTimelineViewController(for: hashtag, mastodonController: mastodonController)
|
||||
case let .savedInstance(url):
|
||||
return InstanceTimelineViewController(for: url, parentMastodonController: mastodonController)
|
||||
case .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
|
||||
case .discoverHeader, .listsHeader, .addList, .savedHashtagsHeader, .addSavedHashtag, .savedInstancesHeader, .addSavedInstance:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,7 +259,8 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths {
|
||||
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
|
||||
for notification in group.notifications {
|
||||
ImageCache.avatars.fetchIfNotCached(notification.account.avatar)
|
||||
guard let avatar = notification.account.avatar else { continue }
|
||||
ImageCache.avatars.fetchIfNotCached(avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +269,8 @@ extension NotificationsTableViewController: UITableViewDataSourcePrefetching {
|
|||
for indexPath in indexPaths {
|
||||
guard let group = dataSource.itemIdentifier(for: indexPath) else { continue }
|
||||
for notification in group.notifications {
|
||||
ImageCache.avatars.cancelWithoutCallback(notification.account.avatar)
|
||||
guard let avatar = notification.account.avatar else { continue }
|
||||
ImageCache.avatars.cancelWithoutCallback(avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
|||
switch response {
|
||||
case let .failure(error):
|
||||
self.showRecommendationsError(error)
|
||||
case let .success(instances, _):
|
||||
case let .success(instances):
|
||||
self.recommendedInstances = instances
|
||||
self.filterRecommendedResults()
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class InstanceSelectorTableViewController: UITableViewController {
|
|||
tableView.tableHeaderView = header
|
||||
}
|
||||
|
||||
private func showRecommendationsError(_ error: Client.Error) {
|
||||
private func showRecommendationsError(_ error: Client.ErrorType) {
|
||||
let footer = UITableViewHeaderFooterView()
|
||||
footer.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
import UIKit
|
||||
import AuthenticationServices
|
||||
import Pachyderm
|
||||
|
||||
protocol OnboardingViewControllerDelegate {
|
||||
@MainActor
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo)
|
||||
}
|
||||
|
||||
|
@ -40,60 +42,119 @@ class OnboardingViewController: UINavigationController {
|
|||
|
||||
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 {
|
||||
func didSelectInstance(url instanceURL: URL) {
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL)
|
||||
mastodonController.registerApp { (clientID, clientSecret) in
|
||||
|
||||
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!
|
||||
|
||||
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()
|
||||
Task {
|
||||
do {
|
||||
try await self.tryLoginTo(instanceURL: instanceURL)
|
||||
} catch Error.cancelled {
|
||||
// no-op, don't show an error message
|
||||
} catch let error as Error {
|
||||
let alert = UIAlertController(title: "Error Logging In", message: error.localizedDescription, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default))
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,9 @@ struct LocalAccountAvatarView: View {
|
|||
func loadImage() {
|
||||
let controller = MastodonController.getForAccount(localAccountInfo)
|
||||
controller.getOwnAccount { (result) in
|
||||
guard case let .success(account) = result else { return }
|
||||
_ = ImageCache.avatars.get(account.avatar) { (_, image) in
|
||||
guard case let .success(account) = result,
|
||||
let avatar = account.avatar else { return }
|
||||
_ = ImageCache.avatars.get(avatar) { (_, image) in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImage = image
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ struct WellnessPrefsView: View {
|
|||
if #available(iOS 15.0, *) {
|
||||
disableInfiniteScrolling
|
||||
}
|
||||
hideDiscover
|
||||
}
|
||||
.listStyle(InsetGroupedListStyle())
|
||||
.navigationBarTitle(Text("Digital Wellness"))
|
||||
|
@ -57,6 +58,14 @@ 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 {
|
||||
|
|
|
@ -42,7 +42,7 @@ class MyProfileViewController: ProfileViewController {
|
|||
}
|
||||
|
||||
private func setAvatarTabBarImage<Account: AccountProtocol>(account: Account) {
|
||||
let avatarURL = account.avatar
|
||||
guard let avatarURL = account.avatar else { return }
|
||||
_ = ImageCache.avatars.get(avatarURL, completion: { [weak self] (_, image) in
|
||||
guard let self = self,
|
||||
let image = image,
|
||||
|
|
|
@ -40,6 +40,11 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
super.viewDidLoad()
|
||||
|
||||
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) {
|
||||
|
@ -72,6 +77,10 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
}
|
||||
|
||||
getStatuses { (response) in
|
||||
guard self.state == .loadingInitial else {
|
||||
return
|
||||
}
|
||||
|
||||
switch response {
|
||||
case let .failure(error):
|
||||
completion(.failure(.client(error)))
|
||||
|
@ -83,7 +92,6 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||
DispatchQueue.main.async {
|
||||
var snapshot = self.dataSource.snapshot()
|
||||
snapshot.appendSections([.statuses])
|
||||
snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .statuses)
|
||||
if self.kind == .statuses {
|
||||
self.loadPinnedStatuses(snapshot: { snapshot }, completion: completion)
|
||||
|
@ -97,7 +105,8 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
}
|
||||
|
||||
private func loadPinnedStatuses(snapshot: @escaping () -> Snapshot, completion: @escaping (LoadResult) -> Void) {
|
||||
guard kind == .statuses else {
|
||||
guard kind == .statuses,
|
||||
mastodonController.instanceFeatures.profilePinnedStatuses else {
|
||||
completion(.success(snapshot()))
|
||||
return
|
||||
}
|
||||
|
@ -110,10 +119,7 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
self.mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||
DispatchQueue.main.async {
|
||||
var snapshot = snapshot()
|
||||
if snapshot.indexOfSection(.pinned) != nil {
|
||||
snapshot.deleteSections([.pinned])
|
||||
}
|
||||
snapshot.insertSections([.pinned], beforeSection: .statuses)
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .pinned))
|
||||
snapshot.appendItems(statuses.map { Item(id: $0.id, state: .unknown) }, toSection: .pinned)
|
||||
completion(.success(snapshot))
|
||||
}
|
||||
|
@ -209,7 +215,9 @@ class ProfileStatusesViewController: DiffableTimelineLikeTableViewController<Pro
|
|||
override func refresh() {
|
||||
super.refresh()
|
||||
|
||||
if kind == .statuses {
|
||||
// only refresh pinned if the super call actually succeded (put the state into .loadingNewer)
|
||||
if state == .loadingNewer,
|
||||
kind == .statuses {
|
||||
loadPinnedStatuses(snapshot: dataSource.snapshot) { (result) in
|
||||
switch result {
|
||||
case .failure(_):
|
||||
|
@ -240,6 +248,14 @@ extension ProfileStatusesViewController {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,9 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
|||
|
||||
let contentSections = snapshot.sectionIdentifiers.filter { timelineContentSections().contains($0) }
|
||||
let contentSectionIndices = contentSections.compactMap(snapshot.indexOfSection(_:))
|
||||
let maxContentSectionIndex = contentSectionIndices.max()!
|
||||
guard let maxContentSectionIndex = contentSectionIndices.max() else {
|
||||
return
|
||||
}
|
||||
|
||||
if lastVisibleRow.section < maxContentSectionIndex {
|
||||
return
|
||||
|
@ -114,7 +116,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
|||
|
||||
case let .failure(.client(error)):
|
||||
self.state = .unloaded
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading") { [weak self] (toast) in
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading", in: self) { [weak self] (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
self?.loadInitial()
|
||||
}
|
||||
|
@ -146,7 +148,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
|||
self.dataSource.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
case let .failure(.client(error)):
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Older") { [weak self] (toast) in
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Older", in: self) { [weak self] (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
self?.loadOlder()
|
||||
}
|
||||
|
@ -195,7 +197,12 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
|||
// MARK: - RefreshableViewController
|
||||
|
||||
func refresh() {
|
||||
guard state != .loadingNewer else { return }
|
||||
// if we're unloaded, there's nothing "newer" to load
|
||||
// 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
|
||||
|
||||
|
@ -229,7 +236,7 @@ class DiffableTimelineLikeTableViewController<Section: Hashable & CaseIterable,
|
|||
}
|
||||
|
||||
case let .failure(.client(error)):
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Newer") { [weak self] (toast) in
|
||||
let config = ToastConfiguration(from: error, with: "Error Loading Newer", in: self) { [weak self] (toast) in
|
||||
toast.dismissToast(animated: true)
|
||||
self?.refresh()
|
||||
}
|
||||
|
|
|
@ -48,50 +48,23 @@ extension MenuPreviewProvider {
|
|||
]
|
||||
}
|
||||
|
||||
var actionsSection: [UIMenuElement] = [
|
||||
let actionsSection: [UIMenuElement] = [
|
||||
createAction(identifier: "sendmessage", title: "Send Message", systemImageName: "envelope", handler: { [weak self] (_) in
|
||||
guard let self = self else { return }
|
||||
self.navigationDelegate?.compose(mentioningAcct: account.acct)
|
||||
let draft = self.mastodonController!.createDraft(mentioningAcct: account.acct)
|
||||
draft.visibility = .direct
|
||||
self.navigationDelegate?.compose(editing: draft)
|
||||
}),
|
||||
]
|
||||
|
||||
if accountID != mastodonController.account.id {
|
||||
actionsSection.append(UIDeferredMenuElement({ (elementHandler) in
|
||||
guard let mastodonController = self.mastodonController else {
|
||||
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
|
||||
])
|
||||
UIDeferredMenuElement({ (elementHandler) in
|
||||
Task { @MainActor in
|
||||
if let action = await self.followAction(for: accountID, mastodonController: mastodonController) {
|
||||
elementHandler([action])
|
||||
} else {
|
||||
elementHandler([])
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
var shareSection = [
|
||||
openInSafariAction(url: account.url),
|
||||
|
@ -192,7 +165,8 @@ extension MenuPreviewProvider {
|
|||
}
|
||||
|
||||
// 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
|
||||
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 }
|
||||
|
@ -236,6 +210,29 @@ extension MenuPreviewProvider {
|
|||
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 {
|
||||
let image: UIImage?
|
||||
|
@ -267,6 +264,30 @@ 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 {
|
||||
|
|
|
@ -21,7 +21,8 @@ extension StatusTablePrefetching {
|
|||
return
|
||||
}
|
||||
for status in statuses {
|
||||
ImageCache.avatars.fetchIfNotCached(status.account.avatar)
|
||||
guard let avatar = status.account.avatar else { continue }
|
||||
ImageCache.avatars.fetchIfNotCached(avatar)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.fetchIfNotCached(attachment.url)
|
||||
}
|
||||
|
@ -36,7 +37,8 @@ extension StatusTablePrefetching {
|
|||
return
|
||||
}
|
||||
for status in statuses {
|
||||
ImageCache.avatars.cancelWithoutCallback(status.account.avatar)
|
||||
guard let avatar = status.account.avatar else { continue }
|
||||
ImageCache.avatars.cancelWithoutCallback(avatar)
|
||||
for attachment in status.attachments where attachment.kind == .image {
|
||||
ImageCache.attachments.cancelWithoutCallback(attachment.url)
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@ class AccountTableViewCell: UITableViewCell {
|
|||
avatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: avatarImageView)
|
||||
|
||||
guard let account = mastodonController.persistentContainer.account(for: accountID) else {
|
||||
fatalError("Missing cached account \(accountID!)")
|
||||
// this table view cell could be cached in a table view (e.g., SearchResultsViewController) for an account that's since been purged
|
||||
return
|
||||
}
|
||||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
|
||||
|
@ -62,17 +63,18 @@ class AccountTableViewCell: UITableViewCell {
|
|||
|
||||
let accountID = self.accountID
|
||||
|
||||
let avatarURL = account.avatar
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
self.avatarRequest = nil
|
||||
|
||||
guard let image = image,
|
||||
self.accountID == accountID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = transformedImage
|
||||
if let avatarURL = account.avatar {
|
||||
avatarRequest = ImageCache.avatars.get(avatarURL) { [weak self] (_, image) in
|
||||
guard let self = self else { return }
|
||||
self.avatarRequest = nil
|
||||
|
||||
guard let image = image,
|
||||
self.accountID == accountID,
|
||||
let transformedImage = ImageGrayscalifier.convertIfNecessary(url: avatarURL, image: image) else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = transformedImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,11 +69,13 @@ class LargeAccountDetailView: UIView {
|
|||
displayNameLabel.updateForAccountDisplayName(account: account)
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
avatarRequest = ImageCache.avatars.get(account.avatar) { [weak self] (_, image) in
|
||||
guard let self = self, let image = image else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
if let avatar = account.avatar {
|
||||
avatarRequest = ImageCache.avatars.get(avatar) { [weak self] (_, image) in
|
||||
guard let self = self, let image = image else { return }
|
||||
self.avatarRequest = nil
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue