Replace MastodonKit with Pachyderm
This commit is contained in:
parent
eb0f272c0b
commit
b46ceb30ed
|
@ -1,6 +1,3 @@
|
||||||
[submodule "MastodonKit"]
|
|
||||||
path = MastodonKit
|
|
||||||
url = git://github.com/shadowfacts/MastodonKit.git
|
|
||||||
[submodule "SwiftSoup"]
|
[submodule "SwiftSoup"]
|
||||||
path = SwiftSoup
|
path = SwiftSoup
|
||||||
url = git://github.com/scinfu/SwiftSoup.git
|
url = git://github.com/scinfu/SwiftSoup.git
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit cfece3083acfeda2f124a84dc35f268682681d49
|
|
|
@ -1,13 +1,58 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
func test(_ nillable: String?) {
|
class Client {
|
||||||
defer {
|
func test<A>(_ thing: A) {
|
||||||
print("defer")
|
if var thing = thing as? ClientModel {
|
||||||
|
thing.client = self
|
||||||
|
} else if var arr = thing as? [ClientModel] {
|
||||||
|
arr.client = self
|
||||||
|
}
|
||||||
|
// } else if let arr = thing as? Array<Any> {
|
||||||
|
// for el in arr {
|
||||||
|
// if var el = el as? ClientModel {
|
||||||
|
// el.client = self
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
guard let value = nillable else { return }
|
|
||||||
print(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test("test")
|
protocol ClientModel {
|
||||||
print("------")
|
var client: Client! { get set }
|
||||||
test(nil)
|
}
|
||||||
|
|
||||||
|
class Something: ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array: ClientModel where Element: ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
get {
|
||||||
|
return first?.client
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
for var el in self {
|
||||||
|
el.client = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//extension Array: ClientModel where Element == ClientModel {
|
||||||
|
// var client: Client! {
|
||||||
|
// get {
|
||||||
|
// return first?.client
|
||||||
|
// }
|
||||||
|
// set {
|
||||||
|
// for var el in self {
|
||||||
|
// el.client = newValue
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
var array = [Something(), Something()]
|
||||||
|
|
||||||
|
let client = Client()
|
||||||
|
client.test(array)
|
||||||
|
array[0].client
|
||||||
|
array[1].client
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
//
|
||||||
|
// Client.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
The base Mastodon API client.
|
||||||
|
*/
|
||||||
|
public class Client {
|
||||||
|
|
||||||
|
public typealias Callback<Result: Decodable> = (Response<Result>) -> Void
|
||||||
|
|
||||||
|
let baseURL: URL
|
||||||
|
let session: URLSession
|
||||||
|
|
||||||
|
public var accessToken: String?
|
||||||
|
|
||||||
|
public var appID: String?
|
||||||
|
public var clientID: String?
|
||||||
|
public var clientSecret: String?
|
||||||
|
|
||||||
|
public var timeoutInterval: TimeInterval = 60
|
||||||
|
|
||||||
|
lazy var decoder: JSONDecoder = {
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
|
||||||
|
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
||||||
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||||
|
decoder.dateDecodingStrategy = .formatted(formatter)
|
||||||
|
return decoder
|
||||||
|
}()
|
||||||
|
|
||||||
|
public init(baseURL: URL, accessToken: String? = nil, session: URLSession = .shared) {
|
||||||
|
self.baseURL = baseURL
|
||||||
|
self.accessToken = accessToken
|
||||||
|
self.session = session
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Internal Helpers
|
||||||
|
func run<Result>(_ request: Request<Result>, completion: @escaping Callback<Result>) {
|
||||||
|
guard let request = createURLRequest(request: request) else {
|
||||||
|
completion(.failure(Error.invalidRequest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = session.dataTask(with: request) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let data = data,
|
||||||
|
let response = response as? HTTPURLResponse else {
|
||||||
|
completion(.failure(Error.invalidResponse))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard response.statusCode == 200 else {
|
||||||
|
let mastodonError = try? self.decoder.decode(MastodonError.self, from: data)
|
||||||
|
let error = mastodonError.flatMap { Error.mastodonError($0.description) } ?? Error.unknownError
|
||||||
|
completion(.failure(error))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let result = try? self.decoder.decode(Result.self, from: data) else {
|
||||||
|
completion(.failure(Error.invalidModel))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if var result = result as? ClientModel {
|
||||||
|
result.client = self
|
||||||
|
} else if var result = result as? [ClientModel] {
|
||||||
|
result.client = self
|
||||||
|
}
|
||||||
|
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
|
||||||
|
|
||||||
|
completion(.success(result, pagination))
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createURLRequest<Result>(request: Request<Result>) -> URLRequest? {
|
||||||
|
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil }
|
||||||
|
components.path = request.path
|
||||||
|
components.queryItems = request.queryParameters.queryItems
|
||||||
|
guard let url = components.url else { return nil }
|
||||||
|
var urlRequest = URLRequest(url: url, timeoutInterval: timeoutInterval)
|
||||||
|
urlRequest.httpMethod = request.method.name
|
||||||
|
urlRequest.httpBody = request.body.data
|
||||||
|
urlRequest.setValue(request.body.mimeType, forHTTPHeaderField: "Content-Type")
|
||||||
|
if let accessToken = accessToken {
|
||||||
|
urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
|
||||||
|
}
|
||||||
|
return urlRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Authorization
|
||||||
|
public func registerApp(name: String, redirectURI: String, scopes: [Scope], website: URL? = nil, completion: @escaping Callback<RegisteredApplication>) {
|
||||||
|
let request = Request<RegisteredApplication>(method: .post, path: "/api/v1/apps", body: .parameters([
|
||||||
|
"client_name" => name,
|
||||||
|
"redirect_uris" => redirectURI,
|
||||||
|
"scopes" => scopes.scopeString,
|
||||||
|
"website" => website?.absoluteString
|
||||||
|
]))
|
||||||
|
run(request) { result in
|
||||||
|
defer { completion(result) }
|
||||||
|
guard case let .success(application, _) = result else { return }
|
||||||
|
|
||||||
|
self.appID = application.id
|
||||||
|
self.clientID = application.clientID
|
||||||
|
self.clientSecret = application.clientSecret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getAccessToken(authorizationCode: String, redirectURI: String, completion: @escaping Callback<LoginSettings>) {
|
||||||
|
let request = Request<LoginSettings>(method: .post, path: "/oauth/token", body: .parameters([
|
||||||
|
"client_id" => clientID,
|
||||||
|
"client_secret" => clientSecret,
|
||||||
|
"grant_type" => "authorization_code",
|
||||||
|
"code" => authorizationCode,
|
||||||
|
"redirect_uri" => redirectURI
|
||||||
|
]))
|
||||||
|
run(request) { result in
|
||||||
|
defer { completion(result) }
|
||||||
|
guard case let .success(loginSettings, _) = result else { return }
|
||||||
|
|
||||||
|
self.accessToken = loginSettings.accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Self
|
||||||
|
public func getSelfAccount(completion: @escaping Callback<Account>) {
|
||||||
|
let request = Request<Account>(method: .get, path: "/api/v1/accounts/verify_credentials")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFavourites(completion: @escaping Callback<[Status]>) {
|
||||||
|
let request = Request<[Status]>(method: .get, path: "/api/v1/favourites")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getRelationships(accounts: [Account]? = nil, completion: @escaping Callback<[Relationship]>) {
|
||||||
|
let request = Request<[Relationship]>(method: .get, path: "/api/v1/accounts/relationships", queryParameters: "id" => accounts?.map { $0.id })
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getInstance(completion: @escaping Callback<Instance>) {
|
||||||
|
let request = Request<Instance>(method: .get, path: "/api/v1/instance")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getCustomEmoji(completion: @escaping Callback<[Emoji]>) {
|
||||||
|
let request = Request<[Emoji]>(method: .get, path: "/api/v1/custom_emojis")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Accounts
|
||||||
|
public func getAccount(id: String, completion: @escaping Callback<Account>) {
|
||||||
|
let request = Request<Account>(method: .get, path: "/api/v1/accounts/\(id)")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func searchForAccount(query: String, limit: Int? = nil, following: Bool? = nil, completion: @escaping Callback<[Account]>) {
|
||||||
|
let request = Request<[Account]>(method: .get, path: "/api/v1/accounts/search", queryParameters: [
|
||||||
|
"q" => query,
|
||||||
|
"limit" => limit,
|
||||||
|
"following" => following
|
||||||
|
])
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Blocks
|
||||||
|
public func getBlocks(completion: @escaping Callback<[Account]>) {
|
||||||
|
let request = Request<[Account]>(method: .get, path: "/api/v1/blocks")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getDomainBlocks(completion: @escaping Callback<[String]>) {
|
||||||
|
let request = Request<[String]>(method: .get, path: "api/v1/domain_blocks")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func block(domain: String, completion: @escaping Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/domain_blocks", body: .parameters([
|
||||||
|
"domain" => domain
|
||||||
|
]))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unblock(domain: String, completion: @escaping Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/domain_blocks", body: .parameters([
|
||||||
|
"domain" => domain
|
||||||
|
]))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Filters
|
||||||
|
public func getFilters(completion: @escaping Callback<[Filter]>) {
|
||||||
|
let request = Request<[Filter]>(method: .get, path: "/api/v1/filters")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createFilter(phrase: String, context: [Filter.Context], irreversible: Bool? = nil, wholeWord: Bool? = nil, expiresAt: Date? = nil, completion: @escaping Callback<Filter>) {
|
||||||
|
let request = Request<Filter>(method: .post, path: "/api/v1/filters", body: .parameters([
|
||||||
|
"phrase" => phrase,
|
||||||
|
"irreversible" => irreversible,
|
||||||
|
"whole_word" => wholeWord,
|
||||||
|
"expires_at" => expiresAt
|
||||||
|
] + "context" => context.contextStrings))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFilter(id: String, completion: @escaping Callback<Filter>) {
|
||||||
|
let request = Request<Filter>(method: .get, path: "/api/v1/filters/\(id)")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Follows
|
||||||
|
public func getFollowRequests(range: RequestRange = .default, completion: @escaping Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/follow_requests")
|
||||||
|
request.range = range
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFollowSuggestions(completion: @escaping Callback<[Account]>) {
|
||||||
|
let request = Request<[Account]>(method: .get, path: "/api/v1/suggestions")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func followRemote(acct: String, completion: @escaping Callback<Account>) {
|
||||||
|
let request = Request<Account>(method: .post, path: "/api/v1/follows", body: .parameters(["uri" => acct]))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Lists
|
||||||
|
public func getLists(completion: @escaping Callback<[List]>) {
|
||||||
|
let request = Request<[List]>(method: .get, path: "/api/v1/lists")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getList(id: String, completion: @escaping Callback<List>) {
|
||||||
|
let request = Request<List>(method: .get, path: "/api/v1/lists/\(id)")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createList(title: String, completion: @escaping Callback<List>) {
|
||||||
|
let request = Request<List>(method: .post, path: "/api/v1/lists", body: .parameters(["title" => title]))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Media
|
||||||
|
public func upload(attachment: FormAttachment, description: String? = nil, focus: (Float, Float)? = nil, completion: @escaping Callback<Attachment>) {
|
||||||
|
let request = Request<Attachment>(method: .post, path: "/api/v1/media", body: .formData([
|
||||||
|
"description" => description,
|
||||||
|
"focus" => focus
|
||||||
|
], attachment))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Mutes
|
||||||
|
public func getMutes(range: RequestRange, completion: @escaping Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/mutes")
|
||||||
|
request.range = range
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Notifications
|
||||||
|
public func getNotifications(range: RequestRange = .default, completion: @escaping Callback<[Notification]>) {
|
||||||
|
var request = Request<[Notification]>(method: .get, path: "/api/v1/notifications")
|
||||||
|
request.range = range
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func clearNotifications(completion: @escaping Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/notifications/clear")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Reports
|
||||||
|
public func getReports(completion: @escaping Callback<[Report]>) {
|
||||||
|
let request = Request<[Report]>(method: .get, path: "/api/v1/reports")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func report(account: Account, statuses: [Status], comment: String, completion: @escaping Callback<Report>) {
|
||||||
|
let request = Request<Report>(method: .post, path: "/api/v1/reports", body: .parameters([
|
||||||
|
"account_id" => account.id,
|
||||||
|
"comment" => comment
|
||||||
|
] + "status_ids" => statuses.map { $0.id }))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Search
|
||||||
|
public func search(query: String, resolve: Bool? = nil, completion: @escaping Callback<SearchResults>) {
|
||||||
|
let request = Request<SearchResults>(method: .get, path: "/api/v2/search", queryParameters: [
|
||||||
|
"q" => query,
|
||||||
|
"resolve" => resolve
|
||||||
|
])
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Statuses
|
||||||
|
public func getStatus(id: String, completion: @escaping Callback<Status>) {
|
||||||
|
let request = Request<Status>(method: .get, path: "/api/v1/statuses/\(id)")
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func createStatus(text: String,
|
||||||
|
inReplyTo: Status? = nil,
|
||||||
|
media: [Attachment]? = nil,
|
||||||
|
sensitive: Bool? = nil,
|
||||||
|
spoilerText: String? = nil,
|
||||||
|
visiblity: Status.Visibility? = nil,
|
||||||
|
language: String? = nil,
|
||||||
|
completion: @escaping Callback<Status>) {
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses", body: .parameters([
|
||||||
|
"status" => text,
|
||||||
|
"in_reply_to_id" => inReplyTo?.id,
|
||||||
|
"sensitive" => sensitive,
|
||||||
|
"spoiler_text" => spoilerText,
|
||||||
|
"visibility" => visiblity?.rawValue,
|
||||||
|
"language" => language
|
||||||
|
] + "media" => media?.map { $0.id }))
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Timelines
|
||||||
|
public func getStatuses(timeline: Timeline, range: RequestRange = .default, completion: @escaping Callback<[Status]>) {
|
||||||
|
let request = timeline.request(range: range)
|
||||||
|
run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Client {
|
||||||
|
public enum Error: Swift.Error {
|
||||||
|
case unknownError
|
||||||
|
case invalidRequest
|
||||||
|
case invalidResponse
|
||||||
|
case invalidModel
|
||||||
|
case mastodonError(String)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// ClientModel.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
protocol ClientModel {
|
||||||
|
var client: Client! { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
get {
|
||||||
|
return first?.client
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
for var el in self {
|
||||||
|
el.client = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element: ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
get {
|
||||||
|
return first?.client
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
for var el in self {
|
||||||
|
el.client = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// Data.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Data {
|
||||||
|
mutating func append(_ string: String, encoding: String.Encoding = .utf8) {
|
||||||
|
guard let data = string.data(using: encoding) else { return }
|
||||||
|
append(data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,146 @@
|
||||||
|
//
|
||||||
|
// Account.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Account: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
emojis.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let username: String
|
||||||
|
public let acct: String
|
||||||
|
public let displayName: String
|
||||||
|
public let locked: Bool
|
||||||
|
public let createdAt: Date
|
||||||
|
public let followersCount: Int
|
||||||
|
public let followingCount: Int
|
||||||
|
public let statusesCount: Int
|
||||||
|
public let note: String
|
||||||
|
public let url: URL
|
||||||
|
public let avatar: URL
|
||||||
|
public let avatarStatic: URL
|
||||||
|
public let header: URL
|
||||||
|
public let headerStatic: URL
|
||||||
|
public private(set) var emojis: [Emoji]
|
||||||
|
public let moved: Bool?
|
||||||
|
public let fields: [Field]?
|
||||||
|
public let bot: Bool?
|
||||||
|
|
||||||
|
public func authorizeFollowRequest(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/follow_requests/\(id)/authorize")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func rejectFollowRequest(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/follow_requests/\(id)/reject")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeFromFollowRequests(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/suggestions/\(id)")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFollowers(range: RequestRange = .default, completion: @escaping Client.Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/accounts/\(id)/followers")
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFollowing(range: RequestRange = .default, completion: @escaping Client.Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/accounts/\(id)/following")
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getStatuses(range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil, completion: @escaping Client.Callback<[Status]>) {
|
||||||
|
var request = Request<[Status]>(method: .get, path: "/api/v1/accounts/\(id)/statuses", queryParameters: [
|
||||||
|
"only_media" => onlyMedia,
|
||||||
|
"pinned" => pinned,
|
||||||
|
"exclude_replies" => excludeReplies
|
||||||
|
])
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func follow(completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/follow")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unfollow(completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/unfollow")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func block(completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/block")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unblock(completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/unblock")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func mute(notifications: Bool? = nil, completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/mute", body: .parameters([
|
||||||
|
"notifications" => notifications
|
||||||
|
]))
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unmute(completion: @escaping Client.Callback<Relationship>) {
|
||||||
|
let request = Request<Relationship>(method: .post, path: "/api/v1/accounts/\(id)/unmute")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getLists(completion: @escaping Client.Callback<[List]>) {
|
||||||
|
let request = Request<[List]>(method: .get, path: "/api/v1/accounts/\(id)/lists")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case username
|
||||||
|
case acct
|
||||||
|
case displayName = "display_name"
|
||||||
|
case locked
|
||||||
|
case createdAt = "created_at"
|
||||||
|
case followersCount = "followers_count"
|
||||||
|
case followingCount = "following_count"
|
||||||
|
case statusesCount = "statuses_count"
|
||||||
|
case note
|
||||||
|
case url
|
||||||
|
case avatar
|
||||||
|
case avatarStatic = "avatar_static"
|
||||||
|
case header
|
||||||
|
case headerStatic = "header_static"
|
||||||
|
case emojis
|
||||||
|
case moved
|
||||||
|
case fields
|
||||||
|
case bot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Account: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
return "Account(\(id), \(acct))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Account {
|
||||||
|
public struct Field: Codable {
|
||||||
|
let name: String
|
||||||
|
let value: String
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Application.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Application: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let name: String
|
||||||
|
public let website: URL?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case name
|
||||||
|
case website
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
//
|
||||||
|
// Attachment.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Attachment: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let kind: Kind
|
||||||
|
public let url: URL
|
||||||
|
public let remoteURL: URL?
|
||||||
|
public let previewURL: URL
|
||||||
|
public let textURL: URL?
|
||||||
|
public let meta: Metadata?
|
||||||
|
public var description: String?
|
||||||
|
|
||||||
|
public func update(focus: (Float, Float)?, completion: Client.Callback<Attachment>?) {
|
||||||
|
let request = Request<Attachment>(method: .put, path: "/api/v1/media/\(id)", body: .formData([
|
||||||
|
"description" => description,
|
||||||
|
"focus" => focus
|
||||||
|
], nil))
|
||||||
|
client.run(request) { result in
|
||||||
|
completion?(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case kind = "type"
|
||||||
|
case url
|
||||||
|
case remoteURL = "remote_url"
|
||||||
|
case previewURL = "preview_url"
|
||||||
|
case textURL = "text_url"
|
||||||
|
case meta
|
||||||
|
case description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attachment {
|
||||||
|
public enum Kind: String, Decodable {
|
||||||
|
case image
|
||||||
|
case video
|
||||||
|
case gifv
|
||||||
|
case audio
|
||||||
|
case unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Attachment {
|
||||||
|
public class Metadata: Decodable {
|
||||||
|
public let length: String?
|
||||||
|
public let duration: Float?
|
||||||
|
public let audioEncoding: String?
|
||||||
|
public let audioBitrate: String?
|
||||||
|
public let audioChannels: String?
|
||||||
|
public let fps: Float?
|
||||||
|
public let width: Int?
|
||||||
|
public let height: Int?
|
||||||
|
public let size: String?
|
||||||
|
public let aspect: Float?
|
||||||
|
|
||||||
|
public let small: ImageMetadata?
|
||||||
|
public let original: ImageMetadata?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case length
|
||||||
|
case duration
|
||||||
|
case audioEncoding = "audio_encode"
|
||||||
|
case audioBitrate = "audio_bitrate"
|
||||||
|
case audioChannels = "audio_channels"
|
||||||
|
case fps
|
||||||
|
case width
|
||||||
|
case height
|
||||||
|
case size
|
||||||
|
case aspect
|
||||||
|
case small
|
||||||
|
case original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImageMetadata: Decodable {
|
||||||
|
public let width: Int?
|
||||||
|
public let height: Int?
|
||||||
|
public let size: String?
|
||||||
|
public let aspect: Float?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case width
|
||||||
|
case height
|
||||||
|
case size
|
||||||
|
case aspect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// Card.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Card: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let url: URL
|
||||||
|
public let title: String
|
||||||
|
public let description: String
|
||||||
|
public let image: URL?
|
||||||
|
public let kind: Kind
|
||||||
|
public let authorName: String?
|
||||||
|
public let authorURL: URL?
|
||||||
|
public let providerName: String?
|
||||||
|
public let providerURL: URL?
|
||||||
|
public let html: String?
|
||||||
|
public let width: Int?
|
||||||
|
public let height: Int?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case url
|
||||||
|
case title
|
||||||
|
case description
|
||||||
|
case image
|
||||||
|
case kind = "type"
|
||||||
|
case authorName = "author_name"
|
||||||
|
case authorURL = "author_url"
|
||||||
|
case providerName = "provider_name"
|
||||||
|
case providerURL = "provider_url"
|
||||||
|
case html
|
||||||
|
case width
|
||||||
|
case height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Card {
|
||||||
|
public enum Kind: String, Decodable {
|
||||||
|
case link
|
||||||
|
case photo
|
||||||
|
case video
|
||||||
|
case rich
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// Context.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class ConversationContext: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
ancestors.client = client
|
||||||
|
descendants.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var ancestors: [Status]
|
||||||
|
public private(set) var descendants: [Status]
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ancestors
|
||||||
|
case descendants
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
//
|
||||||
|
// Emoji.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Emoji: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
let shortcode: String
|
||||||
|
let url: URL
|
||||||
|
let staticURL: URL
|
||||||
|
// TODO: missing in pleroma
|
||||||
|
// let visibleInPicker: Bool
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case shortcode
|
||||||
|
case url
|
||||||
|
case staticURL = "static_url"
|
||||||
|
// case visibleInPicker = "visible_in_picker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Emoji: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
return ":\(shortcode):"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// Filter.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Filter: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public var phrase: String
|
||||||
|
private var context: [String]
|
||||||
|
public var expiresAt: Date?
|
||||||
|
public var irreversible: Bool
|
||||||
|
public var wholeWord: Bool
|
||||||
|
|
||||||
|
public var contexts: [Context] {
|
||||||
|
get {
|
||||||
|
return context.compactMap(Context.init)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
context = contexts.contextStrings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(completion: Client.Callback<Filter>?) {
|
||||||
|
let request = Request<Filter>(method: .put, path: "/api/v1/filters/\(id)", body: .parameters([
|
||||||
|
"phrase" => phrase,
|
||||||
|
"irreversible" => irreversible,
|
||||||
|
"whole_word" => wholeWord,
|
||||||
|
"expires_at" => expiresAt
|
||||||
|
] + "context" => context))
|
||||||
|
client.run(request) { result in
|
||||||
|
completion?(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func delete(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/filters/\(id)")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case phrase
|
||||||
|
case context
|
||||||
|
case expiresAt = "expires_at"
|
||||||
|
case irreversible
|
||||||
|
case wholeWord = "whole_word"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Filter {
|
||||||
|
public enum Context: String, Decodable {
|
||||||
|
case home
|
||||||
|
case notifications
|
||||||
|
case `public`
|
||||||
|
case thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == Filter.Context {
|
||||||
|
var contextStrings: [String] {
|
||||||
|
return map { $0.rawValue }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// Hashtag.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Hashtag: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
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: Decodable {
|
||||||
|
public let day: Date
|
||||||
|
public let uses: Int
|
||||||
|
public let accounts: Int
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case day
|
||||||
|
case uses
|
||||||
|
case accounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// Instance.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Instance: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
contactAccount.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let uri: String
|
||||||
|
public let title: String
|
||||||
|
public let description: String
|
||||||
|
public let email: String
|
||||||
|
public let version: String
|
||||||
|
public let urls: [String: URL]
|
||||||
|
public let languages: [String]
|
||||||
|
public let contactAccount: Account
|
||||||
|
|
||||||
|
// MARK: Unofficial additions to the Mastodon API.
|
||||||
|
public let stats: Stats?
|
||||||
|
public let thumbnail: URL?
|
||||||
|
public let maxStatusCharacters: Int?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case uri
|
||||||
|
case title
|
||||||
|
case description
|
||||||
|
case email
|
||||||
|
case version
|
||||||
|
case urls
|
||||||
|
case languages
|
||||||
|
case contactAccount = "contact_account"
|
||||||
|
|
||||||
|
case stats
|
||||||
|
case thumbnail
|
||||||
|
case maxStatusCharacters = "max_toot_chars"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Instance {
|
||||||
|
public class Stats: Decodable {
|
||||||
|
public let domainCount: Int?
|
||||||
|
public let statusCount: Int?
|
||||||
|
public let userCount: Int?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case domainCount = "domain_count"
|
||||||
|
case statusCount = "status_count"
|
||||||
|
case userCount = "user_count"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// List.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class List: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public var title: String
|
||||||
|
|
||||||
|
public func getAccounts(range: RequestRange = .default, completion: @escaping Client.Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/lists/\(id)/accounts")
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(completion: Client.Callback<List>?) {
|
||||||
|
let request = Request<List>(method: .put, path: "/api/v1/lists/\(id)", body: .parameters(["title" => title]))
|
||||||
|
client.run(request) { result in
|
||||||
|
completion?(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func delete(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/lists/\(id)")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func add(accounts: [Account], completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/lists/\(id)/accounts", body: .parameters(
|
||||||
|
"account_ids" => accounts.map { $0.id }
|
||||||
|
))
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func remove(accounts: [Account], completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/lists/\(id)/accounts", body: .parameters(
|
||||||
|
"account_ids" => accounts.map { $0.id }
|
||||||
|
))
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case title
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// LoginSettings.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class LoginSettings: Decodable {
|
||||||
|
public let accessToken: String
|
||||||
|
private let scope: String
|
||||||
|
|
||||||
|
public var scopes: [Scope] {
|
||||||
|
return scope.components(separatedBy: .whitespaces).compactMap(Scope.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case accessToken = "access_token"
|
||||||
|
case scope
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// MastodonError.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct MastodonError: Decodable, CustomStringConvertible {
|
||||||
|
var description: String
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case description = "error"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Mention.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Mention: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let url: URL
|
||||||
|
public let username: String
|
||||||
|
public let acct: String
|
||||||
|
public let id: String
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case url
|
||||||
|
case username
|
||||||
|
case acct
|
||||||
|
case id
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
// Notification.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Notification: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
account.client = client
|
||||||
|
status?.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let kind: Kind
|
||||||
|
public let createdAt: Date
|
||||||
|
public let account: Account
|
||||||
|
public let status: Status?
|
||||||
|
|
||||||
|
public func dismiss(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .post, path: "/api/v1/notifications/dismiss", body: .parameters([
|
||||||
|
"id" => id
|
||||||
|
]))
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case kind = "type"
|
||||||
|
case createdAt = "created_at"
|
||||||
|
case account
|
||||||
|
case status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Notification {
|
||||||
|
public enum Kind: String, Decodable {
|
||||||
|
case mention
|
||||||
|
case reblog
|
||||||
|
case favourite
|
||||||
|
case follow
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
//
|
||||||
|
// PushSubscription.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class PushSubscription: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let endpoint: URL
|
||||||
|
public let serverKey: String
|
||||||
|
// TODO: WTF is this?
|
||||||
|
// public let alerts
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case endpoint
|
||||||
|
case serverKey = "server_key"
|
||||||
|
// case alerts
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// RegisteredApplication.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class RegisteredApplication: Decodable {
|
||||||
|
public let id: String
|
||||||
|
public let clientID: String
|
||||||
|
public let clientSecret: String
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case clientID = "client_id"
|
||||||
|
case clientSecret = "client_secret"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// Relationship.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Relationship: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let following: Bool
|
||||||
|
public let followedBy: Bool
|
||||||
|
public let blocked: Bool
|
||||||
|
public let muting: Bool
|
||||||
|
public let mutingNotifications: Bool
|
||||||
|
public let followRequested: Bool
|
||||||
|
public let domainBlocking: Bool
|
||||||
|
public let showingReblogs: Bool
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case following
|
||||||
|
case followedBy = "followed_by"
|
||||||
|
case blocked
|
||||||
|
case muting
|
||||||
|
case mutingNotifications = "muting_notifications"
|
||||||
|
case followRequested = "requested"
|
||||||
|
case domainBlocking = "domain_blocking"
|
||||||
|
case showingReblogs = "showing_reblogs"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Report.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Report: Decodable, ClientModel {
|
||||||
|
var client: Client!
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let actionTaken: Bool
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case actionTaken = "action_taken"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Scope.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Scope: String {
|
||||||
|
case read
|
||||||
|
case write
|
||||||
|
case follow
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == Scope {
|
||||||
|
var scopeString: String {
|
||||||
|
return map { $0.rawValue }.joined(separator: " ")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// SearchResults.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class SearchResults: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
accounts.client = client
|
||||||
|
statuses.client = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public private(set) var accounts: [Account]
|
||||||
|
public private(set) var statuses: [Status]
|
||||||
|
public let hashtags: [String]
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case accounts
|
||||||
|
case statuses
|
||||||
|
case hashtags
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
//
|
||||||
|
// Status.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class Status: Decodable, ClientModel {
|
||||||
|
var client: Client! {
|
||||||
|
didSet {
|
||||||
|
didSetClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// when reblog.client is set directly from self.client didSet, reblog.client didSet is never called
|
||||||
|
private func didSetClient() {
|
||||||
|
account.client = client
|
||||||
|
reblog?.client = client
|
||||||
|
emojis.client = client
|
||||||
|
attachments.client = client
|
||||||
|
mentions.client = client
|
||||||
|
hashtags.client = client
|
||||||
|
application?.client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
public let id: String
|
||||||
|
public let uri: String
|
||||||
|
public let url: URL?
|
||||||
|
public let account: Account
|
||||||
|
public let inReplyToID: String?
|
||||||
|
public let inReplyToAccountID: String?
|
||||||
|
public private(set) var reblog: Status?
|
||||||
|
public let content: String
|
||||||
|
public let createdAt: Date
|
||||||
|
public private(set) var emojis: [Emoji]
|
||||||
|
// TODO: missing from pleroma
|
||||||
|
// public let repliesCount: Int
|
||||||
|
public let reblogsCount: Int
|
||||||
|
public let favouritesCount: Int
|
||||||
|
public var reblogged: Bool?
|
||||||
|
public var favourited: Bool?
|
||||||
|
public var muted: Bool?
|
||||||
|
public let sensitive: Bool
|
||||||
|
public let spoilerText: String
|
||||||
|
public let visibility: Visibility
|
||||||
|
public private(set) var attachments: [Attachment]
|
||||||
|
public private(set) var mentions: [Mention]
|
||||||
|
public private(set) var hashtags: [Hashtag]
|
||||||
|
public private(set) var application: Application?
|
||||||
|
public let language: String?
|
||||||
|
public var pinned: Bool?
|
||||||
|
|
||||||
|
public func getContext(completion: @escaping Client.Callback<ConversationContext>) {
|
||||||
|
let request = Request<ConversationContext>(method: .get, path: "/api/v1/statuses/\(id)/context")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getCard(completion: @escaping Client.Callback<Card>) {
|
||||||
|
let request = Request<Card>(method: .get, path: "/api/v1/statuses/\(id)/card")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getFavourites(range: RequestRange = .default, completion: @escaping Client.Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(id)/favourited_by")
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getReblogs(range: RequestRange = .default, completion: @escaping Client.Callback<[Account]>) {
|
||||||
|
var request = Request<[Account]>(method: .get, path: "/api/v1/statuses/\(id)/reblogged_by")
|
||||||
|
request.range = range
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func delete(completion: @escaping Client.Callback<Empty>) {
|
||||||
|
let request = Request<Empty>(method: .delete, path: "/api/v1/statuses/\(id)")
|
||||||
|
client.run(request, completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reblog(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = reblogged
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/reblog")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.reblogged = true
|
||||||
|
} else {
|
||||||
|
self.reblogged = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unreblog(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = reblogged
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/unreblog")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.reblogged = false
|
||||||
|
} else {
|
||||||
|
self.reblogged = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func favourite(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = favourited
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/favourite")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.favourited = true
|
||||||
|
} else {
|
||||||
|
self.favourited = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unfavourite(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = favourited
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/unfavourite")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.favourited = false
|
||||||
|
} else {
|
||||||
|
self.favourited = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pin(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = pinned
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/pin")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.pinned = true
|
||||||
|
} else {
|
||||||
|
self.pinned = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unpin(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = pinned
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/unpin")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.pinned = false
|
||||||
|
} else {
|
||||||
|
self.pinned = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func muteConversation(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = muted
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/mute")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.muted = true
|
||||||
|
} else {
|
||||||
|
self.muted = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func unmuteConversation(completion: @escaping Client.Callback<Status>) {
|
||||||
|
let oldValue = muted
|
||||||
|
let request = Request<Status>(method: .post, path: "/api/v1/statuses/\(id)/unmute")
|
||||||
|
client.run(request) { response in
|
||||||
|
if case .success = response {
|
||||||
|
self.muted = false
|
||||||
|
} else {
|
||||||
|
self.muted = oldValue
|
||||||
|
}
|
||||||
|
completion(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case id
|
||||||
|
case uri
|
||||||
|
case url
|
||||||
|
case account
|
||||||
|
case inReplyToID = "in_reply_to_id"
|
||||||
|
case inReplyToAccountID = "in_reply_to_account_id"
|
||||||
|
case reblog
|
||||||
|
case content
|
||||||
|
case createdAt = "created_at"
|
||||||
|
case emojis
|
||||||
|
// case repliesCount = "replies_count"
|
||||||
|
case reblogsCount = "reblogs_count"
|
||||||
|
case favouritesCount = "favourites_count"
|
||||||
|
case reblogged
|
||||||
|
case favourited
|
||||||
|
case muted
|
||||||
|
case sensitive
|
||||||
|
case spoilerText = "spoiler_text"
|
||||||
|
case visibility
|
||||||
|
case attachments = "media_attachments"
|
||||||
|
case mentions
|
||||||
|
case hashtags = "tags"
|
||||||
|
case application
|
||||||
|
case language
|
||||||
|
case pinned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Status {
|
||||||
|
public enum Visibility: String, Codable, CaseIterable {
|
||||||
|
case `public`
|
||||||
|
case unlisted
|
||||||
|
case `private`
|
||||||
|
case direct
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// Timeline.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Timeline {
|
||||||
|
case home
|
||||||
|
case `public`(local: Bool)
|
||||||
|
case tag(hashtag: String)
|
||||||
|
case list(id: String)
|
||||||
|
case direct
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Timeline {
|
||||||
|
func request(range: RequestRange) -> Request<[Status]> {
|
||||||
|
var request: Request<[Status]>
|
||||||
|
switch self {
|
||||||
|
case .home:
|
||||||
|
request = Request(method: .get, path: "/api/v1/timelines/home")
|
||||||
|
case let .public(local):
|
||||||
|
request = Request(method: .get, path: "/api/v1/timelines/public")
|
||||||
|
if local {
|
||||||
|
request.queryParameters.append("local" => true)
|
||||||
|
}
|
||||||
|
case let .tag(hashtag):
|
||||||
|
request = Request(method: .get, path: "/api/v1/timeliens/tag/\(hashtag)")
|
||||||
|
case let .list(id):
|
||||||
|
request = Request(method: .get, path: "/api/v1/timelines/list/\(id)")
|
||||||
|
case .direct:
|
||||||
|
request = Request(method: .get, path: "/api/v1/timelines/direct")
|
||||||
|
}
|
||||||
|
request.range = range
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Pachyderm.h
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
//! Project version number for Pachyderm.
|
||||||
|
FOUNDATION_EXPORT double PachydermVersionNumber;
|
||||||
|
|
||||||
|
//! Project version string for Pachyderm.
|
||||||
|
FOUNDATION_EXPORT const unsigned char PachydermVersionString[];
|
||||||
|
|
||||||
|
// In this header, you should import all the public headers of your framework using statements like #import <Pachyderm/PublicHeader.h>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// Body.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum Body {
|
||||||
|
case parameters([Parameter]?)
|
||||||
|
case formData([Parameter]?, FormAttachment?)
|
||||||
|
case empty
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Body {
|
||||||
|
private static let boundary: String = "PachydermBoundary"
|
||||||
|
|
||||||
|
var data: Data? {
|
||||||
|
switch self {
|
||||||
|
case let .parameters(parameters):
|
||||||
|
return parameters?.urlEncoded.data(using: .utf8)
|
||||||
|
case let .formData(parameters, attachment):
|
||||||
|
var data = Data()
|
||||||
|
parameters?.forEach { param in
|
||||||
|
guard let value = param.value else { return }
|
||||||
|
data.append("--\(Body.boundary)\r\n")
|
||||||
|
data.append("Content-Disposition: form-data; name=\"\(param.name)\"\r\n\r\n")
|
||||||
|
data.append("\(value)\r\n")
|
||||||
|
}
|
||||||
|
if let attachment = attachment {
|
||||||
|
data.append("--\(Body.boundary)\r\n")
|
||||||
|
data.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(attachment.fileName)\"\r\n")
|
||||||
|
data.append("Content-Type: \(attachment.mimeType)\r\n\r\n")
|
||||||
|
data.append(attachment.data)
|
||||||
|
data.append("\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append("--\(Body.boundary)--\r\n")
|
||||||
|
return data
|
||||||
|
case .empty:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimeType: String? {
|
||||||
|
switch self {
|
||||||
|
case let .parameters(parameters):
|
||||||
|
if parameters == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return "application/x-www-form-urlencoded; charset=utf-8"
|
||||||
|
case let .formData(parameters, attachment):
|
||||||
|
if parameters == nil && attachment == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return "multipart/form-data; boundary=\(Body.boundary)"
|
||||||
|
case .empty:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// Attachment.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct FormAttachment {
|
||||||
|
let mimeType: String
|
||||||
|
let data: Data
|
||||||
|
let fileName: String
|
||||||
|
|
||||||
|
public init(mimeType: String, data: Data, fileName: String) {
|
||||||
|
self.mimeType = mimeType
|
||||||
|
self.data = data
|
||||||
|
self.fileName = fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FormAttachment {
|
||||||
|
public init(jepgData data: Data, fileName: String = "file.jpg") {
|
||||||
|
self.init(mimeType: "image/jpg", data: data, fileName: fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(pngData data: Data, fileName: String = "file.png") {
|
||||||
|
self.init(mimeType: "image/png", data: data, fileName: fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(gifData data: Data, fileName: String = "file.gif") {
|
||||||
|
self.init(mimeType: "image/gif", data: data, fileName: fileName)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
//
|
||||||
|
// Method.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum Method {
|
||||||
|
case get, post, put, patch, delete
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Method {
|
||||||
|
var name: String {
|
||||||
|
switch self {
|
||||||
|
case .get:
|
||||||
|
return "GET"
|
||||||
|
case .post:
|
||||||
|
return "POST"
|
||||||
|
case .put:
|
||||||
|
return "PUT"
|
||||||
|
case .patch:
|
||||||
|
return "PATCH"
|
||||||
|
case .delete:
|
||||||
|
return "DELETE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
//
|
||||||
|
// Parameter.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Parameter {
|
||||||
|
let name: String
|
||||||
|
let value: String?
|
||||||
|
}
|
||||||
|
precedencegroup ParameterizationPrecedence {
|
||||||
|
associativity: left
|
||||||
|
higherThan: AdditionPrecedence
|
||||||
|
}
|
||||||
|
infix operator => : ParameterizationPrecedence
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
static func =>(name: String, value: String?) -> Parameter {
|
||||||
|
return Parameter(name: name, value: value)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func =>(name: String, value: Bool?) -> Parameter {
|
||||||
|
return Parameter(name: name, value: value?.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func =>(name: String, value: Int?) -> Parameter {
|
||||||
|
return Parameter(name: name, value: value?.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func =>(name: String, value: Date?) -> Parameter {
|
||||||
|
if let value = value {
|
||||||
|
let formatter = ISO8601DateFormatter()
|
||||||
|
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||||
|
let string = formatter.string(from: value)
|
||||||
|
return Parameter(name: name, value: string)
|
||||||
|
} else {
|
||||||
|
return Parameter(name: name, value: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func =>(name: String, focus: (Float, Float)?) -> Parameter {
|
||||||
|
guard let focus = focus else { return Parameter(name: name, value: nil) }
|
||||||
|
return Parameter(name: name, value: "\(focus.0),\(focus.1)")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func =>(name: String, values: [String]?) -> [Parameter] {
|
||||||
|
guard let values = values else { return [] }
|
||||||
|
let name = "\(name)[]"
|
||||||
|
return values.map { Parameter(name: name, value: $0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Parameter: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
if let value = value {
|
||||||
|
return "\(name)=\(value)"
|
||||||
|
} else {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == Parameter {
|
||||||
|
var urlEncoded: String {
|
||||||
|
return compactMap {
|
||||||
|
guard let value = $0.value else { return nil }
|
||||||
|
return "\($0.name)=\(value)"
|
||||||
|
}.joined(separator: "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryItems: [URLQueryItem] {
|
||||||
|
return compactMap {
|
||||||
|
URLQueryItem(name: $0.name, value: $0.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
//
|
||||||
|
// Request.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct Request<ResultType: Decodable> {
|
||||||
|
let method: Method
|
||||||
|
let path: String
|
||||||
|
let body: Body
|
||||||
|
var queryParameters: [Parameter]
|
||||||
|
|
||||||
|
init(method: Method, path: String, body: Body = .empty, queryParameters: [Parameter] = []) {
|
||||||
|
self.method = method
|
||||||
|
self.path = path
|
||||||
|
self.body = body
|
||||||
|
self.queryParameters = queryParameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Request {
|
||||||
|
var range: RequestRange {
|
||||||
|
get {
|
||||||
|
let max = queryParameters.first { $0.name == "max_id" }
|
||||||
|
let since = queryParameters.first { $0.name == "since_id" }
|
||||||
|
let count = queryParameters.first { $0.name == "count" }
|
||||||
|
if let max = max, let count = count {
|
||||||
|
return .before(id: max.value!, count: Int(count.value!)!)
|
||||||
|
} else if let since = since, let count = count {
|
||||||
|
return .after(id: since.value!, count: Int(count.value!)!)
|
||||||
|
} else if let count = count {
|
||||||
|
return .count(Int(count.value!)!)
|
||||||
|
} else {
|
||||||
|
return .default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
let rangeParams = newValue.queryParameters
|
||||||
|
let max = rangeParams.first { $0.name == "max_id" }
|
||||||
|
let since = rangeParams.first { $0.name == "since_id" }
|
||||||
|
let count = rangeParams.first { $0.name == "count" }
|
||||||
|
if let max = max, let i = queryParameters.firstIndex(where: { $0.name == "max_id" }) {
|
||||||
|
queryParameters[i] = max
|
||||||
|
}
|
||||||
|
if let since = since, let i = queryParameters.firstIndex(where: { $0.name == "since_id" }) {
|
||||||
|
queryParameters[i] = since
|
||||||
|
}
|
||||||
|
if let count = count, let i = queryParameters.firstIndex(where: { $0.name == "count" }) {
|
||||||
|
queryParameters[i] = count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// RequestRange.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum RequestRange {
|
||||||
|
case `default`
|
||||||
|
case count(Int)
|
||||||
|
case before(id: String, count: Int?)
|
||||||
|
case after(id: String, count: Int?)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RequestRange {
|
||||||
|
var queryParameters: [Parameter] {
|
||||||
|
switch self {
|
||||||
|
case .default:
|
||||||
|
return []
|
||||||
|
case let .count(count):
|
||||||
|
return ["limit" => count]
|
||||||
|
case let .before(id, count):
|
||||||
|
return ["max_id" => id, "count" => count]
|
||||||
|
case let .after(id, count):
|
||||||
|
return ["since_id" => id, "count" => count]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Empty.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Empty: Decodable {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
//
|
||||||
|
// Pagination.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/9/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Pagination {
|
||||||
|
public let older: RequestRange?
|
||||||
|
public let newer: RequestRange?
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Pagination {
|
||||||
|
init(string: String) {
|
||||||
|
let links = string.components(separatedBy: ",").compactMap(Item.init)
|
||||||
|
self.older = links.first(where: { $0.kind == .next })?.range
|
||||||
|
self.newer = links.first(where: { $0.kind == .prev })?.range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Pagination {
|
||||||
|
struct Item {
|
||||||
|
let kind: Kind
|
||||||
|
let id: String
|
||||||
|
let limit: Int?
|
||||||
|
|
||||||
|
var range: RequestRange {
|
||||||
|
switch kind {
|
||||||
|
case .next:
|
||||||
|
return .after(id: id, count: limit)
|
||||||
|
case .prev:
|
||||||
|
return .before(id: id, count: limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(string: String) {
|
||||||
|
let segments = string.components(separatedBy: .whitespaces).filter { !$0.isEmpty }.joined().components(separatedBy: ";")
|
||||||
|
|
||||||
|
let url = segments.first.flatMap { str in
|
||||||
|
String(str[str.index(after: str.startIndex)..<str.index(before: str.endIndex)])
|
||||||
|
}
|
||||||
|
let rel = segments.last?.replacingOccurrences(of: "\"", with: "").trimmingCharacters(in: .whitespaces).components(separatedBy: "=")
|
||||||
|
|
||||||
|
guard let validURL = url,
|
||||||
|
let key = rel?.first,
|
||||||
|
key == "rel",
|
||||||
|
let value = rel?.last,
|
||||||
|
let kind = Kind(rawValue: value),
|
||||||
|
let components = URLComponents(string: validURL),
|
||||||
|
let queryItems = components.queryItems else { return nil }
|
||||||
|
|
||||||
|
let since = queryItems.first { $0.name == "since_id" }?.value
|
||||||
|
let max = queryItems.first { $0.name == "max_id" }?.value
|
||||||
|
|
||||||
|
guard let id = since ?? max else { return nil }
|
||||||
|
|
||||||
|
let limit = queryItems.first { $0.name == "limit" }.flatMap { $0.value }.flatMap { Int($0) }
|
||||||
|
|
||||||
|
self.kind = kind
|
||||||
|
self.id = id
|
||||||
|
self.limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Pagination.Item {
|
||||||
|
enum Kind: String {
|
||||||
|
case next, prev
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// Response.swift
|
||||||
|
// Pachyderm
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum Response<Result: Decodable> {
|
||||||
|
case success(Result, Pagination?)
|
||||||
|
case failure(Error)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>BNDL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// PachydermTests.swift
|
||||||
|
// PachydermTests
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 9/8/18.
|
||||||
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import Pachyderm
|
||||||
|
|
||||||
|
class PachydermTests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPerformanceExample() {
|
||||||
|
// This is an example of a performance test case.
|
||||||
|
self.measure {
|
||||||
|
// Put the code you want to measure the time of here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,9 +7,47 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE89212CA6B7009840C4 /* Timeline.swift */; };
|
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
|
||||||
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.swift */; };
|
04DACE8E212CC7CC009840C4 /* AvatarCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8D212CC7CC009840C4 /* AvatarCache.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 */; };
|
||||||
|
D6109A0B2145953C00432DC2 /* ClientModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6109A0A2145953C00432DC2 /* ClientModel.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 */; };
|
||||||
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
|
D6333B372137838300CE884A /* AttributedString+Trim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B362137838300CE884A /* AttributedString+Trim.swift */; };
|
||||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.swift */; };
|
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.swift */; };
|
||||||
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */; };
|
||||||
|
@ -28,6 +66,7 @@
|
||||||
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AAC2128D88B005A6F37 /* LocalData.swift */; };
|
||||||
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
||||||
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
D64D0AB12128D9AE005A6F37 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64D0AB02128D9AE005A6F37 /* OnboardingViewController.swift */; };
|
||||||
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
||||||
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; };
|
D663625D2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D663625C2135C74800C9CBA2 /* ConversationMainStatusTableViewCell.xib */; };
|
||||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; };
|
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift */; };
|
||||||
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626121360B1900C9CBA2 /* Preferences.swift */; };
|
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626121360B1900C9CBA2 /* Preferences.swift */; };
|
||||||
|
@ -50,7 +89,6 @@
|
||||||
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5F22135BC260057A976 /* Conversation.storyboard */; };
|
D667E5F32135BC260057A976 /* Conversation.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D667E5F22135BC260057A976 /* Conversation.storyboard */; };
|
||||||
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationViewController.swift */; };
|
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationViewController.swift */; };
|
||||||
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; };
|
D667E5F82135C3040057A976 /* Mastodon+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F72135C3040057A976 /* Mastodon+Equatable.swift */; };
|
||||||
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; };
|
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
||||||
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C94D842139DFD800CB5196 /* LargeImage.storyboard */; };
|
D6C94D852139DFD800CB5196 /* LargeImage.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C94D842139DFD800CB5196 /* LargeImage.storyboard */; };
|
||||||
|
@ -62,14 +100,33 @@
|
||||||
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; };
|
D6D4DDDA212518A200E1C4BB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6D4DDD8212518A200E1C4BB /* LaunchScreen.storyboard */; };
|
||||||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */; };
|
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.swift */; };
|
||||||
D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */; };
|
D6D4DDF0212518A200E1C4BB /* TuskerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDEF212518A200E1C4BB /* TuskerUITests.swift */; };
|
||||||
D6F953E7212519A400CF0F2B /* MastodonKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6F953E6212519A400CF0F2B /* MastodonKit.framework */; };
|
|
||||||
D6F953E8212519A400CF0F2B /* MastodonKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D6F953E6212519A400CF0F2B /* MastodonKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
|
||||||
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; };
|
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EB212519E700CF0F2B /* TimelineTableViewController.swift */; };
|
||||||
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6F953ED21251A0700CF0F2B /* Timeline.storyboard */; };
|
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6F953ED21251A0700CF0F2B /* Timeline.storyboard */; };
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
|
||||||
|
remoteInfo = Pachyderm;
|
||||||
|
};
|
||||||
|
D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = D6D4DDCB212518A000E1C4BB;
|
||||||
|
remoteInfo = Tusker;
|
||||||
|
};
|
||||||
|
D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
|
||||||
|
remoteInfo = Pachyderm;
|
||||||
|
};
|
||||||
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
|
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||||
|
@ -93,7 +150,7 @@
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
D6F953E8212519A400CF0F2B /* MastodonKit.framework in Embed Frameworks */,
|
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||||
);
|
);
|
||||||
name = "Embed Frameworks";
|
name = "Embed Frameworks";
|
||||||
|
@ -102,9 +159,48 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
04DACE89212CA6B7009840C4 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
|
||||||
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
|
||||||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.swift; sourceTree = "<group>"; };
|
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarCache.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>"; };
|
||||||
|
D6109A0A2145953C00432DC2 /* ClientModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientModel.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>"; };
|
||||||
D6333B362137838300CE884A /* AttributedString+Trim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Trim.swift"; sourceTree = "<group>"; };
|
D6333B362137838300CE884A /* AttributedString+Trim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Trim.swift"; sourceTree = "<group>"; };
|
||||||
D6333B762138D94E00CE884A /* ComposeMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMediaView.swift; sourceTree = "<group>"; };
|
D6333B762138D94E00CE884A /* ComposeMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeMediaView.swift; sourceTree = "<group>"; };
|
||||||
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
D6333B782139AEFD00CE884A /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -169,12 +265,27 @@
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
D61099A82144B0CC00432DC2 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
D61099B02144B0CC00432DC2 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
D61099B42144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
D6D4DDC9212518A000E1C4BB /* Frameworks */ = {
|
D6D4DDC9212518A000E1C4BB /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
D6F953E7212519A400CF0F2B /* MastodonKit.framework in Frameworks */,
|
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||||
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */,
|
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -195,6 +306,90 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
D61099AC2144B0CC00432DC2 /* Pachyderm */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D61099AD2144B0CC00432DC2 /* Pachyderm.h */,
|
||||||
|
D61099AE2144B0CC00432DC2 /* Info.plist */,
|
||||||
|
D61099C82144B13C00432DC2 /* Client.swift */,
|
||||||
|
D6109A0A2145953C00432DC2 /* ClientModel.swift */,
|
||||||
|
D61099D72144B74500432DC2 /* Extensions */,
|
||||||
|
D61099CC2144B2C300432DC2 /* Request */,
|
||||||
|
D61099DA2144BDB600432DC2 /* Response */,
|
||||||
|
D61099DD2144C10C00432DC2 /* Model */,
|
||||||
|
);
|
||||||
|
path = Pachyderm;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
D61099B92144B0CC00432DC2 /* PachydermTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D61099BA2144B0CC00432DC2 /* PachydermTests.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 = (
|
||||||
|
D61099DE2144C11400432DC2 /* MastodonError.swift */,
|
||||||
|
D6109A04214572BF00432DC2 /* Scope.swift */,
|
||||||
|
D61099E02144C1DC00432DC2 /* Account.swift */,
|
||||||
|
D61099E4214561AB00432DC2 /* Application.swift */,
|
||||||
|
D61099E6214561FF00432DC2 /* Attachment.swift */,
|
||||||
|
D61099E82145658300432DC2 /* Card.swift */,
|
||||||
|
D61099EA2145661700432DC2 /* ConversationContext.swift */,
|
||||||
|
D61099E22144C38900432DC2 /* Emoji.swift */,
|
||||||
|
D61099EC2145664800432DC2 /* Filter.swift */,
|
||||||
|
D6109A0021456B0800432DC2 /* Hashtag.swift */,
|
||||||
|
D61099EE214566C000432DC2 /* Instance.swift */,
|
||||||
|
D61099F02145686D00432DC2 /* List.swift */,
|
||||||
|
D6109A062145756700432DC2 /* LoginSettings.swift */,
|
||||||
|
D61099F22145688600432DC2 /* Mention.swift */,
|
||||||
|
D61099F4214568C300432DC2 /* Notification.swift */,
|
||||||
|
D61099F62145693500432DC2 /* PushSubscription.swift */,
|
||||||
|
D6109A022145722C00432DC2 /* RegisteredApplication.swift */,
|
||||||
|
D61099F82145698900432DC2 /* Relationship.swift */,
|
||||||
|
D61099FA214569F600432DC2 /* Report.swift */,
|
||||||
|
D61099FC21456A1D00432DC2 /* SearchResults.swift */,
|
||||||
|
D61099FE21456A4C00432DC2 /* Status.swift */,
|
||||||
|
D6109A10214607D500432DC2 /* Timeline.swift */,
|
||||||
|
);
|
||||||
|
path = Model;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D641C780213DD7C4004B4513 /* Screens */ = {
|
D641C780213DD7C4004B4513 /* Screens */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -335,6 +530,13 @@
|
||||||
path = Transitions;
|
path = Transitions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D65A37F221472F300087646E /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D663626021360A9600C9CBA2 /* Preferences */ = {
|
D663626021360A9600C9CBA2 /* Preferences */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -379,10 +581,13 @@
|
||||||
children = (
|
children = (
|
||||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
||||||
D6F953E6212519A400CF0F2B /* MastodonKit.framework */,
|
D6F953E6212519A400CF0F2B /* MastodonKit.framework */,
|
||||||
|
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||||
|
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
||||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||||
D6D4DDCD212518A000E1C4BB /* Products */,
|
D6D4DDCD212518A000E1C4BB /* Products */,
|
||||||
|
D65A37F221472F300087646E /* Frameworks */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
@ -392,6 +597,8 @@
|
||||||
D6D4DDCC212518A000E1C4BB /* Tusker.app */,
|
D6D4DDCC212518A000E1C4BB /* Tusker.app */,
|
||||||
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
|
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
|
||||||
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
|
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
|
||||||
|
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
||||||
|
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -400,7 +607,6 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
04DACE89212CA6B7009840C4 /* Timeline.swift */,
|
|
||||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
|
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
|
||||||
D663626021360A9600C9CBA2 /* Preferences */,
|
D663626021360A9600C9CBA2 /* Preferences */,
|
||||||
|
@ -443,7 +649,55 @@
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXHeadersBuildPhase section */
|
||||||
|
D61099A62144B0CC00432DC2 /* Headers */ = {
|
||||||
|
isa = PBXHeadersBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXHeadersBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
|
||||||
|
buildPhases = (
|
||||||
|
D61099A62144B0CC00432DC2 /* Headers */,
|
||||||
|
D61099A72144B0CC00432DC2 /* Sources */,
|
||||||
|
D61099A82144B0CC00432DC2 /* Frameworks */,
|
||||||
|
D61099A92144B0CC00432DC2 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Pachyderm;
|
||||||
|
productName = Pachyderm;
|
||||||
|
productReference = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */;
|
||||||
|
productType = "com.apple.product-type.framework";
|
||||||
|
};
|
||||||
|
D61099B22144B0CC00432DC2 /* PachydermTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
D61099AF2144B0CC00432DC2 /* Sources */,
|
||||||
|
D61099B02144B0CC00432DC2 /* Frameworks */,
|
||||||
|
D61099B12144B0CC00432DC2 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
D61099B62144B0CC00432DC2 /* PBXTargetDependency */,
|
||||||
|
D61099B82144B0CC00432DC2 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = PachydermTests;
|
||||||
|
productName = PachydermTests;
|
||||||
|
productReference = D61099B32144B0CC00432DC2 /* PachydermTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
D6D4DDCB212518A000E1C4BB /* Tusker */ = {
|
D6D4DDCB212518A000E1C4BB /* Tusker */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
|
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
|
||||||
|
@ -456,6 +710,7 @@
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
name = Tusker;
|
name = Tusker;
|
||||||
productName = Tusker;
|
productName = Tusker;
|
||||||
|
@ -508,6 +763,14 @@
|
||||||
LastUpgradeCheck = 1000;
|
LastUpgradeCheck = 1000;
|
||||||
ORGANIZATIONNAME = Shadowfacts;
|
ORGANIZATIONNAME = Shadowfacts;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
|
D61099AA2144B0CC00432DC2 = {
|
||||||
|
CreatedOnToolsVersion = 10.0;
|
||||||
|
LastSwiftMigration = 1000;
|
||||||
|
};
|
||||||
|
D61099B22144B0CC00432DC2 = {
|
||||||
|
CreatedOnToolsVersion = 10.0;
|
||||||
|
TestTargetID = D6D4DDCB212518A000E1C4BB;
|
||||||
|
};
|
||||||
D6D4DDCB212518A000E1C4BB = {
|
D6D4DDCB212518A000E1C4BB = {
|
||||||
CreatedOnToolsVersion = 10.0;
|
CreatedOnToolsVersion = 10.0;
|
||||||
};
|
};
|
||||||
|
@ -537,11 +800,27 @@
|
||||||
D6D4DDCB212518A000E1C4BB /* Tusker */,
|
D6D4DDCB212518A000E1C4BB /* Tusker */,
|
||||||
D6D4DDDF212518A200E1C4BB /* TuskerTests */,
|
D6D4DDDF212518A200E1C4BB /* TuskerTests */,
|
||||||
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
|
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
|
||||||
|
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
||||||
|
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
D61099A92144B0CC00432DC2 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
D61099B12144B0CC00432DC2 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
D6D4DDCA212518A000E1C4BB /* Resources */ = {
|
D6D4DDCA212518A000E1C4BB /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -582,11 +861,59 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
D61099A72144B0CC00432DC2 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
D61099E5214561AB00432DC2 /* Application.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 */,
|
||||||
|
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 */,
|
||||||
|
D61099E7214561FF00432DC2 /* Attachment.swift in Sources */,
|
||||||
|
D61099D02144B2D700432DC2 /* Method.swift in Sources */,
|
||||||
|
D61099FB214569F600432DC2 /* Report.swift in Sources */,
|
||||||
|
D61099F92145698900432DC2 /* Relationship.swift in Sources */,
|
||||||
|
D61099E12144C1DC00432DC2 /* Account.swift in Sources */,
|
||||||
|
D6109A0B2145953C00432DC2 /* ClientModel.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 */,
|
||||||
|
D61099DF2144C11400432DC2 /* MastodonError.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 */,
|
||||||
|
D6109A0121456B0800432DC2 /* Hashtag.swift in Sources */,
|
||||||
|
D61099FD21456A1D00432DC2 /* SearchResults.swift in Sources */,
|
||||||
|
D61099F12145686D00432DC2 /* List.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
D61099AF2144B0CC00432DC2 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
D61099BB2144B0CC00432DC2 /* PachydermTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
D6D4DDC8212518A000E1C4BB /* Sources */ = {
|
D6D4DDC8212518A000E1C4BB /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */,
|
|
||||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||||
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */,
|
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */,
|
||||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||||
|
@ -646,6 +973,21 @@
|
||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||||
|
targetProxy = D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
D61099B82144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
||||||
|
targetProxy = D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||||
|
targetProxy = D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
|
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
||||||
|
@ -678,6 +1020,105 @@
|
||||||
/* End PBXVariantGroup section */
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
|
D61099C32144B0CC00432DC2 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEFINES_MODULE = YES;
|
||||||
|
DEVELOPMENT_TEAM = HGYVAQA9FW;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = Pachyderm/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker.Pachyderm;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 4.2;
|
||||||
|
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 = HGYVAQA9FW;
|
||||||
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
|
DYLIB_CURRENT_VERSION = 1;
|
||||||
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||||
|
INFOPLIST_FILE = Pachyderm/Info.plist;
|
||||||
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@loader_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker.Pachyderm;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 4.2;
|
||||||
|
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 = 4.2;
|
||||||
|
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 = 4.2;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Tusker.app/Tusker";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
D6D4DDF2212518A200E1C4BB /* Debug */ = {
|
D6D4DDF2212518A200E1C4BB /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
@ -797,6 +1238,7 @@
|
||||||
D6D4DDF5212518A200E1C4BB /* Debug */ = {
|
D6D4DDF5212518A200E1C4BB /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
@ -817,6 +1259,7 @@
|
||||||
D6D4DDF6212518A200E1C4BB /* Release */ = {
|
D6D4DDF6212518A200E1C4BB /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
@ -919,6 +1362,24 @@
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
D61099C32144B0CC00432DC2 /* Debug */,
|
||||||
|
D61099C42144B0CC00432DC2 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
D61099C62144B0CC00432DC2 /* Debug */,
|
||||||
|
D61099C72144B0CC00432DC2 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = {
|
D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>SchemeUserState</key>
|
<key>SchemeUserState</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>Pachyderm.xcscheme</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>9</integer>
|
||||||
|
</dict>
|
||||||
<key>Tusker.xcscheme</key>
|
<key>Tusker.xcscheme</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "container:Tusker.xcodeproj">
|
location = "container:Tusker.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
<FileRef
|
|
||||||
location = "group:MastodonKit/MastodonKit.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:SwiftSoup/SwiftSoup.xcodeproj">
|
location = "group:SwiftSoup/SwiftSoup.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class MastodonController {
|
class MastodonController {
|
||||||
|
|
||||||
|
@ -37,30 +37,25 @@ class MastodonController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let registerRequest = Clients.register(clientName: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow])
|
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
|
||||||
|
guard case let .success(app, _) = response else { fatalError() }
|
||||||
client.run(registerRequest) { result in
|
LocalData.shared.clientID = app.clientID
|
||||||
guard case let .success(application, _) = result else { fatalError() }
|
LocalData.shared.clientSecret = app.clientSecret
|
||||||
LocalData.shared.clientID = application.clientID
|
|
||||||
LocalData.shared.clientSecret = application.clientSecret
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func authorize(authorizationCode: String, completion: @escaping () -> Void) {
|
func authorize(authorizationCode: String, completion: @escaping () -> Void) {
|
||||||
let authorizeRequest = Login.authorize(code: authorizationCode, clientID: LocalData.shared.clientID!, clientSecret: LocalData.shared.clientSecret!, redirectURI: "tusker://oauth")
|
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||||
client.run(authorizeRequest) { result in
|
guard case let .success(settings, _) = response else { fatalError() }
|
||||||
guard case let .success(settings, _) = result else { fatalError() }
|
|
||||||
LocalData.shared.accessToken = settings.accessToken
|
LocalData.shared.accessToken = settings.accessToken
|
||||||
self.client.accessToken = settings.accessToken
|
|
||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOwnAccount() {
|
func getOwnAccount() {
|
||||||
let req = Accounts.currentUser()
|
client.getSelfAccount { response in
|
||||||
client.run(req) { result in
|
guard case let .success(account, _) = response else { fatalError() }
|
||||||
guard case let .success(account, _) = result else { fatalError() }
|
|
||||||
self.account = account
|
self.account = account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
extension Account {
|
extension Account {
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
extension Status: Equatable {
|
extension Status: Equatable {
|
||||||
public static func ==(lhs: Status, rhs: Status) -> Bool {
|
public static func ==(lhs: Status, rhs: Status) -> Bool {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
|
||||||
extension StatusTableViewCellDelegate where Self: UIViewController {
|
extension StatusTableViewCellDelegate where Self: UIViewController {
|
||||||
|
@ -30,7 +30,7 @@ extension StatusTableViewCellDelegate where Self: UIViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: Tag) {
|
func selected(tag: Hashtag) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,9 @@
|
||||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
extension Visibility {
|
extension Status.Visibility {
|
||||||
|
|
||||||
static var allCases: [Visibility] {
|
|
||||||
return [.public, .unlisted, .private, .direct]
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
|
|
@ -25,9 +25,9 @@ class LocalData {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let instanceURLKey = "instanceURL"
|
private let instanceURLKey = "instanceURL"
|
||||||
var instanceURL: String? {
|
var instanceURL: URL? {
|
||||||
get {
|
get {
|
||||||
return defaults.string(forKey: instanceURLKey)
|
return defaults.url(forKey: instanceURLKey)
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
defaults.set(newValue, forKey: instanceURLKey)
|
defaults.set(newValue, forKey: instanceURLKey)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class Preferences: Codable {
|
class Preferences: Codable {
|
||||||
|
|
||||||
|
@ -37,6 +37,6 @@ class Preferences: Codable {
|
||||||
|
|
||||||
var hideCustomEmojiInUsernames = false
|
var hideCustomEmojiInUsernames = false
|
||||||
|
|
||||||
var defaultPostVisibility = Visibility.public
|
var defaultPostVisibility = Status.Visibility.public
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class ComposeViewController: UIViewController {
|
class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
|
@ -73,20 +73,18 @@ class ComposeViewController: UIViewController {
|
||||||
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
||||||
inReplyToAvatarImageView.layer.masksToBounds = true
|
inReplyToAvatarImageView.layer.masksToBounds = true
|
||||||
inReplyToAvatarImageView.image = nil
|
inReplyToAvatarImageView.image = nil
|
||||||
if let url = URL(string: inReplyTo.account.avatar) {
|
AvatarCache.shared.get(inReplyTo.account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.inReplyToAvatarImageView.image = image
|
self.inReplyToAvatarImageView.image = image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||||
if inReplyTo.account != MastodonController.shared.account {
|
if inReplyTo.account != MastodonController.shared.account {
|
||||||
statusTextView.text = "@\(inReplyTo.account.acct) "
|
statusTextView.text = "@\(inReplyTo.account.acct) "
|
||||||
}
|
}
|
||||||
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.shared.account.id }).map({ "@\($0.acct) " }).joined()
|
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.shared.account.id }).map({ "@\($0.acct) " }).joined()
|
||||||
statusTextView.textViewDidChange(statusTextView)
|
statusTextView.textViewDidChange(statusTextView)
|
||||||
contentWarning = inReplyTo.sensitive ?? false
|
contentWarning = inReplyTo.sensitive
|
||||||
contentWarningTextField.text = inReplyTo.spoilerText
|
contentWarningTextField.text = inReplyTo.spoilerText
|
||||||
visibility = inReplyTo.visibility
|
visibility = inReplyTo.visibility
|
||||||
} else {
|
} else {
|
||||||
|
@ -132,7 +130,7 @@ class ComposeViewController: UIViewController {
|
||||||
|
|
||||||
@IBAction func visibilityPressed(_ sender: Any) {
|
@IBAction func visibilityPressed(_ sender: Any) {
|
||||||
let alertController = UIAlertController(title: "Post Visibility", message: nil, preferredStyle: .actionSheet)
|
let alertController = UIAlertController(title: "Post Visibility", message: nil, preferredStyle: .actionSheet)
|
||||||
for visibility in Visibility.allCases {
|
for visibility in Status.Visibility.allCases {
|
||||||
let action = UIAlertAction(title: visibility.displayName, style: .default, handler: { _ in
|
let action = UIAlertAction(title: visibility.displayName, style: .default, handler: { _ in
|
||||||
UIView.performWithoutAnimation {
|
UIView.performWithoutAnimation {
|
||||||
self.visibility = visibility
|
self.visibility = visibility
|
||||||
|
@ -177,8 +175,6 @@ class ComposeViewController: UIViewController {
|
||||||
guard let text = statusTextView.text,
|
guard let text = statusTextView.text,
|
||||||
!text.isEmpty else { return }
|
!text.isEmpty else { return }
|
||||||
|
|
||||||
let inReplyToID = inReplyTo?.id
|
|
||||||
|
|
||||||
let contentWarning: String?
|
let contentWarning: String?
|
||||||
if self.contentWarning,
|
if self.contentWarning,
|
||||||
let text = contentWarningTextField.text,
|
let text = contentWarningTextField.text,
|
||||||
|
@ -200,26 +196,23 @@ class ComposeViewController: UIViewController {
|
||||||
let index = attachments.count
|
let index = attachments.count
|
||||||
attachments.append(nil)
|
attachments.append(nil)
|
||||||
group.enter()
|
group.enter()
|
||||||
let req = Media.upload(media: .png(data), description: mediaView.mediaDescription)
|
MastodonController.shared.client.upload(attachment: FormAttachment(pngData: data), description: mediaView.mediaDescription) { response in
|
||||||
MastodonController.shared.client.run(req) { result in
|
guard case let .success(attachment, _) = response else { fatalError() }
|
||||||
guard case let .success(attachment, _) = result else { fatalError() }
|
|
||||||
attachments[index] = attachment
|
attachments[index] = attachment
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group.notify(queue: .main) {
|
group.notify(queue: .main) {
|
||||||
let mediaIDs = attachments.map { $0!.id }
|
let attachments = attachments.compactMap { $0 }
|
||||||
|
|
||||||
let req = Statuses.create(status: text,
|
MastodonController.shared.client.createStatus(text: text,
|
||||||
replyToID: inReplyToID,
|
inReplyTo: self.inReplyTo,
|
||||||
mediaIDs: mediaIDs,
|
media: attachments,
|
||||||
sensitive: sensitive,
|
sensitive: sensitive,
|
||||||
spoilerText: contentWarning,
|
spoilerText: contentWarning,
|
||||||
visibility: visibility)
|
visiblity: visibility) { response in
|
||||||
|
guard case let .success(status, _) = response else { fatalError() }
|
||||||
MastodonController.shared.client.run(req) { result in
|
|
||||||
guard case let .success(status, _) = result else { fatalError() }
|
|
||||||
self.status = status
|
self.status = status
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.performSegue(withIdentifier: "postComplete", sender: self)
|
self.performSegue(withIdentifier: "postComplete", sender: self)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class ConversationViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
class ConversationViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||||
|
|
||||||
|
@ -40,9 +40,8 @@ class ConversationViewController: UIViewController, UITableViewDataSource, UITab
|
||||||
|
|
||||||
statuses = [mainStatus]
|
statuses = [mainStatus]
|
||||||
|
|
||||||
let req = Statuses.context(id: mainStatus.id)
|
mainStatus.getContext { response in
|
||||||
MastodonController.shared.client.run(req) { result in
|
guard case let .success(context, _) = response else { fatalError() }
|
||||||
guard case let .success(context, _) = result else { fatalError() }
|
|
||||||
var statuses = self.getDirectParents(of: self.mainStatus, from: context.ancestors)
|
var statuses = self.getDirectParents(of: self.mainStatus, from: context.ancestors)
|
||||||
statuses.append(self.mainStatus)
|
statuses.append(self.mainStatus)
|
||||||
statuses.append(contentsOf: context.descendants)
|
statuses.append(contentsOf: context.descendants)
|
||||||
|
|
|
@ -15,8 +15,8 @@ class MainTabBarViewController: UITabBarController {
|
||||||
|
|
||||||
viewControllers = [
|
viewControllers = [
|
||||||
TimelineTableViewController.create(for: .home),
|
TimelineTableViewController.create(for: .home),
|
||||||
TimelineTableViewController.create(for: .federated),
|
TimelineTableViewController.create(for: .public(local: false)),
|
||||||
TimelineTableViewController.create(for: .local),
|
TimelineTableViewController.create(for: .public(local: true)),
|
||||||
NotificationsTableViewController.create(),
|
NotificationsTableViewController.create(),
|
||||||
PreferencesTableViewController.create()
|
PreferencesTableViewController.create()
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class NotificationsTableViewController: UITableViewController {
|
class NotificationsTableViewController: UITableViewController {
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class NotificationsTableViewController: UITableViewController {
|
||||||
return navigationController
|
return navigationController
|
||||||
}
|
}
|
||||||
|
|
||||||
var notifications: [MastodonKit.Notification] = [] {
|
var notifications: [Pachyderm.Notification] = [] {
|
||||||
didSet {
|
didSet {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
|
@ -43,12 +43,11 @@ class NotificationsTableViewController: UITableViewController {
|
||||||
tableView.register(UINib(nibName: "ActionNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "actionCell")
|
tableView.register(UINib(nibName: "ActionNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "actionCell")
|
||||||
tableView.register(UINib(nibName: "FollowNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "followCell")
|
tableView.register(UINib(nibName: "FollowNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "followCell")
|
||||||
|
|
||||||
let req = Notifications.all()
|
MastodonController.shared.client.getNotifications() { result in
|
||||||
MastodonController.shared.client.run(req) { result in
|
|
||||||
guard case let .success(notifications, pagination) = result else { fatalError() }
|
guard case let .success(notifications, pagination) = result else { fatalError() }
|
||||||
self.notifications = notifications
|
self.notifications = notifications
|
||||||
self.newer = pagination?.previous
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.next
|
self.older = pagination?.older
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ class NotificationsTableViewController: UITableViewController {
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let notification = notifications[indexPath.row]
|
let notification = notifications[indexPath.row]
|
||||||
|
|
||||||
switch notification.type {
|
switch notification.kind {
|
||||||
case .mention:
|
case .mention:
|
||||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
|
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
|
||||||
let status = notification.status!
|
let status = notification.status!
|
||||||
|
@ -111,10 +110,9 @@ class NotificationsTableViewController: UITableViewController {
|
||||||
if indexPath.row == notifications.count - 1 {
|
if indexPath.row == notifications.count - 1 {
|
||||||
guard let older = older else { return }
|
guard let older = older else { return }
|
||||||
|
|
||||||
let req = Notifications.all(range: older)
|
MastodonController.shared.client.getNotifications(range: older) { result in
|
||||||
MastodonController.shared.client.run(req) { result in
|
|
||||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||||
self.older = pagination?.next
|
self.older = pagination?.older
|
||||||
self.notifications.append(contentsOf: newNotifications)
|
self.notifications.append(contentsOf: newNotifications)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,10 +121,9 @@ class NotificationsTableViewController: UITableViewController {
|
||||||
@IBAction func refreshNotifications(_ sender: Any) {
|
@IBAction func refreshNotifications(_ sender: Any) {
|
||||||
guard let newer = newer else { return }
|
guard let newer = newer else { return }
|
||||||
|
|
||||||
let req = Notifications.all(range: newer)
|
MastodonController.shared.client.getNotifications(range: newer) { result in
|
||||||
MastodonController.shared.client.run(req) { result in
|
|
||||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||||
self.newer = pagination?.previous
|
self.newer = pagination?.newer
|
||||||
self.notifications.insert(contentsOf: newNotifications, at: 0)
|
self.notifications.insert(contentsOf: newNotifications, at: 0)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
|
|
|
@ -29,9 +29,10 @@ class OnboardingViewController: UIViewController {
|
||||||
|
|
||||||
@IBAction func loginPressed(_ sender: Any) {
|
@IBAction func loginPressed(_ sender: Any) {
|
||||||
guard let text = urlTextField.text,
|
guard let text = urlTextField.text,
|
||||||
|
let url = URL(string: text),
|
||||||
var components = URLComponents(string: text) else { return }
|
var components = URLComponents(string: text) else { return }
|
||||||
|
|
||||||
LocalData.shared.instanceURL = text
|
LocalData.shared.instanceURL = url
|
||||||
MastodonController.shared.createClient()
|
MastodonController.shared.createClient()
|
||||||
MastodonController.shared.registerApp {
|
MastodonController.shared.registerApp {
|
||||||
let clientID = LocalData.shared.clientID!
|
let clientID = LocalData.shared.clientID!
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class VisibilityTableViewController: UITableViewController {
|
class VisibilityTableViewController: UITableViewController {
|
||||||
|
|
||||||
|
@ -24,11 +24,11 @@ class VisibilityTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return Visibility.allCases.count
|
return Status.Visibility.allCases.count
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let visibility = Visibility.allCases[indexPath.row]
|
let visibility = Status.Visibility.allCases[indexPath.row]
|
||||||
|
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "visibilityCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "visibilityCell", for: indexPath)
|
||||||
cell.textLabel!.text = visibility.displayName
|
cell.textLabel!.text = visibility.displayName
|
||||||
|
@ -38,9 +38,9 @@ class VisibilityTableViewController: UITableViewController {
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let oldVisibility = Preferences.shared.defaultPostVisibility
|
let oldVisibility = Preferences.shared.defaultPostVisibility
|
||||||
let oldIndexPath = IndexPath(row: Visibility.allCases.firstIndex(of: oldVisibility)!, section: 0)
|
let oldIndexPath = IndexPath(row: Status.Visibility.allCases.firstIndex(of: oldVisibility)!, section: 0)
|
||||||
|
|
||||||
let visibility = Visibility.allCases[indexPath.row]
|
let visibility = Status.Visibility.allCases[indexPath.row]
|
||||||
Preferences.shared.defaultPostVisibility = visibility
|
Preferences.shared.defaultPostVisibility = visibility
|
||||||
|
|
||||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
|
||||||
class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||||
|
@ -28,12 +28,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newer: RequestRange?
|
|
||||||
var older: RequestRange?
|
var older: RequestRange?
|
||||||
|
var newer: RequestRange?
|
||||||
|
|
||||||
func request(for range: RequestRange? = .default) -> Request<[Status]> {
|
func getStatuses(for range: RequestRange = .default, completion: @escaping Client.Callback<[Status]>) {
|
||||||
let range = range ?? .default
|
account.getStatuses(range: range, onlyMedia: false, pinned: false, excludeReplies: !Preferences.shared.showRepliesInProfiles, completion: completion)
|
||||||
return Accounts.statuses(id: account.id, mediaOnly: false, pinnedOnly: false, excludeReplies: !Preferences.shared.showRepliesInProfiles, range: range)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
@ -47,13 +46,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||||
|
|
||||||
updateUIForPreferences()
|
updateUIForPreferences()
|
||||||
|
|
||||||
|
getStatuses { response in
|
||||||
|
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||||
MastodonController.shared.client.run(request()) { result in
|
|
||||||
guard case let .success(statuses, pagination) = result else { fatalError() }
|
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
self.newer = pagination?.previous
|
self.older = pagination?.older
|
||||||
self.older = pagination?.next
|
self.newer = pagination?.newer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,9 +124,9 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||||
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
|
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
|
||||||
guard let older = older else { return }
|
guard let older = older else { return }
|
||||||
|
|
||||||
MastodonController.shared.client.run(request(for: older)) { result in
|
getStatuses(for: older) { response in
|
||||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.older = pagination?.next
|
self.older = pagination?.older
|
||||||
self.statuses.append(contentsOf: newStatuses)
|
self.statuses.append(contentsOf: newStatuses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,9 +135,9 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||||
@IBAction func refreshStatuses(_ sender: Any) {
|
@IBAction func refreshStatuses(_ sender: Any) {
|
||||||
guard let newer = newer else { return }
|
guard let newer = newer else { return }
|
||||||
|
|
||||||
MastodonController.shared.client.run(request(for: newer)) { result in
|
getStatuses(for: newer) { response in
|
||||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.newer = pagination?.previous
|
self.newer = pagination?.newer
|
||||||
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
|
@ -159,11 +156,11 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
||||||
func showMoreOptions() {
|
func showMoreOptions() {
|
||||||
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
alert.addAction(UIAlertAction(title: "Open in Safari...", style: .default, handler: { _ in
|
alert.addAction(UIAlertAction(title: "Open in Safari...", style: .default, handler: { _ in
|
||||||
let vc = SFSafariViewController(url: URL(string: self.account.url)!)
|
let vc = SFSafariViewController(url: self.account.url)
|
||||||
self.present(vc, animated: true)
|
self.present(vc, animated: true)
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { _ in
|
alert.addAction(UIAlertAction(title: "Share...", style: .default, handler: { _ in
|
||||||
let vc = UIActivityViewController(activityItems: [URL(string: self.account.url)!], applicationActivities: nil)
|
let vc = UIActivityViewController(activityItems: [self.account.url], applicationActivities: nil)
|
||||||
self.present(vc, animated: true)
|
self.present(vc, animated: true)
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Send Message...", style: .default, handler: { _ in
|
alert.addAction(UIAlertAction(title: "Send Message...", style: .default, handler: { _ in
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class TimelineTableViewController: UITableViewController {
|
class TimelineTableViewController: UITableViewController {
|
||||||
|
|
||||||
|
@ -15,17 +15,22 @@ class TimelineTableViewController: UITableViewController {
|
||||||
guard let navigationController = UIStoryboard(name: "Timeline", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
guard let navigationController = UIStoryboard(name: "Timeline", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
||||||
let timelineController = navigationController.topViewController as? TimelineTableViewController else { fatalError() }
|
let timelineController = navigationController.topViewController as? TimelineTableViewController else { fatalError() }
|
||||||
timelineController.timeline = timeline
|
timelineController.timeline = timeline
|
||||||
|
|
||||||
|
let title: String
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case .home:
|
case .home:
|
||||||
navigationController.tabBarItem.title = "Home"
|
title = "Home"
|
||||||
timelineController.navigationItem.title = "Home"
|
case let .public(local):
|
||||||
case .local:
|
title = local ? "Local" : "Federated"
|
||||||
navigationController.tabBarItem.title = "Local"
|
case let .tag(hashtag):
|
||||||
timelineController.navigationItem.title = "Local"
|
title = "#\(hashtag)"
|
||||||
case .federated:
|
case .list:
|
||||||
navigationController.tabBarItem.title = "Federated"
|
title = "List"
|
||||||
timelineController.navigationItem.title = "Federated"
|
case .direct:
|
||||||
|
title = "Direct"
|
||||||
}
|
}
|
||||||
|
navigationController.tabBarItem.title = title
|
||||||
|
timelineController.navigationItem.title = title
|
||||||
|
|
||||||
return navigationController
|
return navigationController
|
||||||
}
|
}
|
||||||
|
@ -52,11 +57,11 @@ class TimelineTableViewController: UITableViewController {
|
||||||
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
||||||
|
|
||||||
guard MastodonController.shared.client?.accessToken != nil else { return }
|
guard MastodonController.shared.client?.accessToken != nil else { return }
|
||||||
MastodonController.shared.client.run(timeline.request()) { result in
|
MastodonController.shared.client.getStatuses(timeline: timeline) { response in
|
||||||
guard case let .success(statuses, pagination) = result else { fatalError() }
|
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||||
self.statuses = statuses
|
self.statuses = statuses
|
||||||
self.newer = pagination?.previous
|
self.newer = pagination?.newer
|
||||||
self.older = pagination?.next
|
self.older = pagination?.older
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +112,9 @@ class TimelineTableViewController: UITableViewController {
|
||||||
if indexPath.row == statuses.count - 1 {
|
if indexPath.row == statuses.count - 1 {
|
||||||
guard let older = older else { return }
|
guard let older = older else { return }
|
||||||
|
|
||||||
MastodonController.shared.client.run(timeline.request(range: older)) { result in
|
MastodonController.shared.client.getStatuses(timeline: timeline, range: older) { response in
|
||||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.older = pagination?.next
|
self.older = pagination?.older
|
||||||
self.statuses.append(contentsOf: newStatuses)
|
self.statuses.append(contentsOf: newStatuses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,9 +123,9 @@ class TimelineTableViewController: UITableViewController {
|
||||||
@IBAction func refreshStatuses(_ sender: Any) {
|
@IBAction func refreshStatuses(_ sender: Any) {
|
||||||
guard let newer = newer else { return }
|
guard let newer = newer else { return }
|
||||||
|
|
||||||
MastodonController.shared.client.run(timeline.request(range: newer)) { result in
|
MastodonController.shared.client.getStatuses(timeline: timeline, range: newer) { response in
|
||||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||||
self.newer = pagination?.previous
|
self.newer = pagination?.newer
|
||||||
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.refreshControl?.endRefreshing()
|
self.refreshControl?.endRefreshing()
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// Timeline.swift
|
|
||||||
// Tusker
|
|
||||||
//
|
|
||||||
// Created by Shadowfactson 8/21/18.
|
|
||||||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import MastodonKit
|
|
||||||
|
|
||||||
enum Timeline {
|
|
||||||
|
|
||||||
case home, local, federated
|
|
||||||
|
|
||||||
func request(range: RequestRange = .default) -> Request<[Status]> {
|
|
||||||
switch self {
|
|
||||||
case .home:
|
|
||||||
return Timelines.home(range: range)
|
|
||||||
case .local:
|
|
||||||
return Timelines.public(local: true, range: range)
|
|
||||||
case .federated:
|
|
||||||
return Timelines.public(local: false, range: range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
protocol AttachmentViewDelegate {
|
protocol AttachmentViewDelegate {
|
||||||
func showLargeAttachment(for attachmentView: AttachmentView)
|
func showLargeAttachment(for attachmentView: AttachmentView)
|
||||||
|
@ -45,8 +45,7 @@ class AttachmentView: UIImageView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadImage() {
|
func loadImage() {
|
||||||
guard let url = URL(string: attachment.url) else { fatalError("Invalid URL: \(attachment.url)") }
|
task = URLSession.shared.dataTask(with: attachment.url) { data, response, error in
|
||||||
task = URLSession.shared.dataTask(with: url) { data, response, error in
|
|
||||||
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.image = image
|
self.image = image
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
import SwiftSoup
|
import SwiftSoup
|
||||||
|
|
||||||
protocol HTMLContentLabelDelegate {
|
protocol HTMLContentLabelDelegate {
|
||||||
|
|
||||||
func selected(mention: Mention)
|
func selected(mention: Mention)
|
||||||
|
|
||||||
func selected(tag: MastodonKit.Tag)
|
func selected(tag: Hashtag)
|
||||||
|
|
||||||
func selected(url: URL)
|
func selected(url: URL)
|
||||||
|
|
||||||
|
@ -192,10 +192,10 @@ class HTMLContentLabel: UILabel {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTag(for url: URL, text: String) -> MastodonKit.Tag? {
|
func getTag(for url: URL, text: String) -> Hashtag? {
|
||||||
if text.starts(with: "#") {
|
if text.starts(with: "#") {
|
||||||
let tag = String(text.dropFirst())
|
let tag = String(text.dropFirst())
|
||||||
return MastodonKit.Tag(name: tag, url: url.absoluteString)
|
return Hashtag(name: tag, url: url)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
@IBOutlet weak var timestampLabel: UILabel!
|
@IBOutlet weak var timestampLabel: UILabel!
|
||||||
@IBOutlet weak var attachmentsView: UIStackView!
|
@IBOutlet weak var attachmentsView: UIStackView!
|
||||||
|
|
||||||
var notification: MastodonKit.Notification!
|
var notification: Pachyderm.Notification!
|
||||||
var status: Status!
|
var status: Status!
|
||||||
|
|
||||||
var opAvatarURL: URL?
|
var opAvatarURL: URL?
|
||||||
|
@ -49,20 +49,20 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
displayNameLabel.text = status.account.realDisplayName
|
displayNameLabel.text = status.account.realDisplayName
|
||||||
|
|
||||||
let verb: String
|
let verb: String
|
||||||
switch notification.type {
|
switch notification.kind {
|
||||||
case .favourite:
|
case .favourite:
|
||||||
verb = "Liked"
|
verb = "Liked"
|
||||||
case .reblog:
|
case .reblog:
|
||||||
verb = "Reblogged"
|
verb = "Reblogged"
|
||||||
default:
|
default:
|
||||||
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
|
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||||
}
|
}
|
||||||
actionLabel.text = "\(verb) by \(notification.account.realDisplayName)"
|
actionLabel.text = "\(verb) by \(notification.account.realDisplayName)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(for notification: MastodonKit.Notification) {
|
func updateUI(for notification: Pachyderm.Notification) {
|
||||||
guard notification.type == .favourite || notification.type == .reblog else {
|
guard notification.kind == .favourite || notification.kind == .reblog else {
|
||||||
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
|
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||||
}
|
}
|
||||||
self.notification = notification
|
self.notification = notification
|
||||||
self.status = notification.status!
|
self.status = notification.status!
|
||||||
|
@ -71,31 +71,27 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
usernameLabel.text = "@\(status.account.acct)"
|
usernameLabel.text = "@\(status.account.acct)"
|
||||||
opAvatarImageView.image = nil
|
opAvatarImageView.image = nil
|
||||||
if let url = URL(string: status.account.avatar) {
|
opAvatarURL = status.account.avatar
|
||||||
opAvatarURL = url
|
AvatarCache.shared.get(status.account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.opAvatarImageView.image = image
|
self.opAvatarImageView.image = image
|
||||||
self.opAvatarURL = nil
|
self.opAvatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
actionAvatarImageView.image = nil
|
actionAvatarImageView.image = nil
|
||||||
if let url = URL(string: notification.account.avatar) {
|
actionAvatarURL = notification.account.avatar
|
||||||
actionAvatarURL = url
|
AvatarCache.shared.get(notification.account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.actionAvatarImageView.image = image
|
self.actionAvatarImageView.image = image
|
||||||
self.actionAvatarURL = nil
|
self.actionAvatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||||
if attachments.count > 0 {
|
if attachments.count > 0 {
|
||||||
attachmentsView.isHidden = false
|
attachmentsView.isHidden = false
|
||||||
for attachment in attachments {
|
for attachment in attachments {
|
||||||
guard let url = URL(string: attachment.textURL ?? attachment.url) else { continue }
|
let url = attachment.textURL ?? attachment.url
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.textColor = .darkGray
|
label.textColor = .darkGray
|
||||||
|
|
||||||
|
@ -190,7 +186,7 @@ extension ActionNotificationTableViewCell: HTMLContentLabelDelegate {
|
||||||
delegate?.selected(mention: mention)
|
delegate?.selected(mention: mention)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: Tag) {
|
func selected(tag: Hashtag) {
|
||||||
delegate?.selected(tag: tag)
|
delegate?.selected(tag: tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
@IBOutlet weak var displayNameLabel: UILabel!
|
@IBOutlet weak var displayNameLabel: UILabel!
|
||||||
@IBOutlet weak var usernameLabel: UILabel!
|
@IBOutlet weak var usernameLabel: UILabel!
|
||||||
|
|
||||||
var notification: MastodonKit.Notification!
|
var notification: Pachyderm.Notification!
|
||||||
var account: Account!
|
var account: Account!
|
||||||
|
|
||||||
var avatarURL: URL?
|
var avatarURL: URL?
|
||||||
|
@ -37,7 +37,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
displayNameLabel.text = account.realDisplayName
|
displayNameLabel.text = account.realDisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(for notification: MastodonKit.Notification) {
|
func updateUI(for notification: Pachyderm.Notification) {
|
||||||
self.notification = notification
|
self.notification = notification
|
||||||
self.account = notification.account
|
self.account = notification.account
|
||||||
|
|
||||||
|
@ -45,15 +45,13 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
if let url = URL(string: account.avatar) {
|
avatarURL = account.avatar
|
||||||
avatarURL = url
|
AvatarCache.shared.get(account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = image
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
protocol ProfileHeaderTableViewCellDelegate: StatusTableViewCellDelegate {
|
protocol ProfileHeaderTableViewCellDelegate: StatusTableViewCellDelegate {
|
||||||
|
|
||||||
|
@ -55,17 +55,14 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
|
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
if let url = URL(string: account.avatar) {
|
avatarURL = account.avatar
|
||||||
avatarURL = url
|
AvatarCache.shared.get(account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = image
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
headerImageDownloadTask = URLSession.shared.dataTask(with: account.header) { data, response, error in
|
||||||
if let url = URL(string: account.header) {
|
|
||||||
headerImageDownloadTask = URLSession.shared.dataTask(with: url) { data, response, error in
|
|
||||||
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.headerImageView.image = image
|
self.headerImageView.image = image
|
||||||
|
@ -73,7 +70,6 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headerImageDownloadTask!.resume()
|
headerImageDownloadTask!.resume()
|
||||||
}
|
|
||||||
|
|
||||||
// todo: HTML parsing
|
// todo: HTML parsing
|
||||||
noteLabel.text = account.note
|
noteLabel.text = account.note
|
||||||
|
@ -106,7 +102,7 @@ extension ProfileHeaderTableViewCell: HTMLContentLabelDelegate {
|
||||||
delegate?.selected(mention: mention)
|
delegate?.selected(mention: mention)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: Tag) {
|
func selected(tag: Hashtag) {
|
||||||
delegate?.selected(tag: tag)
|
delegate?.selected(tag: tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
|
@ -72,18 +72,16 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
||||||
|
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
if let url = URL(string: account.avatar) {
|
avatarURL = account.avatar
|
||||||
avatarURL = url
|
AvatarCache.shared.get(account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = image
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
|
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
|
||||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||||
if attachments.count > 0 {
|
if attachments.count > 0 {
|
||||||
attachmentsView.isHidden = false
|
attachmentsView.isHidden = false
|
||||||
let width = attachmentsView.bounds.width
|
let width = attachmentsView.bounds.width
|
||||||
|
@ -164,55 +162,40 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func favoritePressed(_ sender: Any) {
|
@IBAction func favoritePressed(_ sender: Any) {
|
||||||
let oldValue = favorited
|
|
||||||
favorited = !favorited
|
favorited = !favorited
|
||||||
|
|
||||||
let realStatus: Status = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let req = favorited ? Statuses.favourite(id: realStatus.id) : Statuses.unfavourite(id: realStatus.id)
|
|
||||||
|
|
||||||
MastodonController.shared.client.run(req) { result in
|
(favorited ? realStatus.favourite : realStatus.unfavourite)() { response in
|
||||||
guard case .success = result else {
|
self.favorited = realStatus.favourited ?? false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case .success = response {
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
print("Couldn't favorite status \(realStatus.id)")
|
print("Couldn't favorite status \(realStatus.id)")
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
DispatchQueue.main.async {
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
self.favorited = oldValue
|
|
||||||
|
|
||||||
let generator = UINotificationFeedbackGenerator()
|
|
||||||
generator.notificationOccurred(.error)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
generator.impactOccurred()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func reblogPressed(_ sender: Any) {
|
@IBAction func reblogPressed(_ sender: Any) {
|
||||||
let oldValue = reblogged
|
|
||||||
reblogged = !reblogged
|
reblogged = !reblogged
|
||||||
|
|
||||||
let realStatus: Status = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let req = reblogged ? Statuses.reblog(id: realStatus.id) : Statuses.unreblog(id: realStatus.id)
|
|
||||||
|
|
||||||
MastodonController.shared.client.run(req) { result in
|
(reblogged ? realStatus.reblog : realStatus.unreblog)() { response in
|
||||||
guard case .success = result else {
|
self.reblogged = realStatus.reblogged ?? false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case .success = response {
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
print("Couldn't reblog status \(realStatus.id)")
|
print("Couldn't reblog status \(realStatus.id)")
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
DispatchQueue.main.async {
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
self.reblogged = oldValue
|
|
||||||
|
|
||||||
let generator = UINotificationFeedbackGenerator()
|
|
||||||
generator.notificationOccurred(.error)
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
generator.impactOccurred()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,7 +208,7 @@ extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate {
|
||||||
delegate?.selected(mention: mention)
|
delegate?.selected(mention: mention)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: MastodonKit.Tag) {
|
func selected(tag: Hashtag) {
|
||||||
delegate?.selected(tag: tag)
|
delegate?.selected(tag: tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
protocol StatusTableViewCellDelegate {
|
protocol StatusTableViewCellDelegate {
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ protocol StatusTableViewCellDelegate {
|
||||||
|
|
||||||
func selected(mention: Mention)
|
func selected(mention: Mention)
|
||||||
|
|
||||||
func selected(tag: MastodonKit.Tag)
|
func selected(tag: Hashtag)
|
||||||
|
|
||||||
func selected(url: URL)
|
func selected(url: URL)
|
||||||
|
|
||||||
|
@ -105,18 +105,16 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
|
|
||||||
usernameLabel.text = "@\(account.acct)"
|
usernameLabel.text = "@\(account.acct)"
|
||||||
avatarImageView.image = nil
|
avatarImageView.image = nil
|
||||||
if let url = URL(string: account.avatar) {
|
avatarURL = account.avatar
|
||||||
avatarURL = url
|
AvatarCache.shared.get(account.avatar) { image in
|
||||||
AvatarCache.shared.get(url) { image in
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.avatarImageView.image = image
|
self.avatarImageView.image = image
|
||||||
self.avatarURL = nil
|
self.avatarURL = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updateTimestamp()
|
updateTimestamp()
|
||||||
|
|
||||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||||
if attachments.count > 0 {
|
if attachments.count > 0 {
|
||||||
attachmentsView.isHidden = false
|
attachmentsView.isHidden = false
|
||||||
let width = attachmentsView.bounds.width
|
let width = attachmentsView.bounds.width
|
||||||
|
@ -212,55 +210,40 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func favoritePressed(_ sender: Any) {
|
@IBAction func favoritePressed(_ sender: Any) {
|
||||||
let oldValue = favorited
|
|
||||||
favorited = !favorited
|
favorited = !favorited
|
||||||
|
|
||||||
let realStatus: Status = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let req = favorited ? Statuses.favourite(id: realStatus.id) : Statuses.unfavourite(id: realStatus.id)
|
|
||||||
|
|
||||||
MastodonController.shared.client.run(req) { result in
|
(favorited ? realStatus.favourite : realStatus.unfavourite)() { response in
|
||||||
guard case .success = result else {
|
self.favorited = realStatus.favourited ?? false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case .success = response {
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
print("Couldn't favorite status \(realStatus.id)")
|
print("Couldn't favorite status \(realStatus.id)")
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
DispatchQueue.main.async {
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
self.favorited = oldValue
|
|
||||||
|
|
||||||
let generator = UINotificationFeedbackGenerator()
|
|
||||||
generator.notificationOccurred(.error)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
generator.impactOccurred()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func reblogPressed(_ sender: Any) {
|
@IBAction func reblogPressed(_ sender: Any) {
|
||||||
let oldValue = reblogged
|
|
||||||
reblogged = !reblogged
|
reblogged = !reblogged
|
||||||
|
|
||||||
let realStatus: Status = status.reblog ?? status
|
let realStatus: Status = status.reblog ?? status
|
||||||
let req = reblogged ? Statuses.reblog(id: realStatus.id) : Statuses.unreblog(id: realStatus.id)
|
|
||||||
|
|
||||||
MastodonController.shared.client.run(req) { result in
|
(reblogged ? realStatus.reblog : realStatus.unreblog)() { response in
|
||||||
guard case .success = result else {
|
self.reblogged = realStatus.reblogged ?? false
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if case .success = response {
|
||||||
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||||
|
} else {
|
||||||
print("Couldn't reblog status \(realStatus.id)")
|
print("Couldn't reblog status \(realStatus.id)")
|
||||||
// todo: display error message
|
// todo: display error message
|
||||||
DispatchQueue.main.async {
|
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||||
self.reblogged = oldValue
|
|
||||||
|
|
||||||
let generator = UINotificationFeedbackGenerator()
|
|
||||||
generator.notificationOccurred(.error)
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
|
||||||
generator.impactOccurred()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +256,7 @@ extension StatusTableViewCell: HTMLContentLabelDelegate {
|
||||||
delegate?.selected(mention: mention)
|
delegate?.selected(mention: mention)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selected(tag: MastodonKit.Tag) {
|
func selected(tag: Hashtag) {
|
||||||
delegate?.selected(tag: tag)
|
delegate?.selected(tag: tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
import MastodonKit
|
import Pachyderm
|
||||||
|
|
||||||
class StatusContentLabel: HTMLContentLabel {
|
class StatusContentLabel: HTMLContentLabel {
|
||||||
|
|
||||||
|
@ -19,14 +19,12 @@ class StatusContentLabel: HTMLContentLabel {
|
||||||
|
|
||||||
override func getMention(for url: URL, text: String) -> Mention? {
|
override func getMention(for url: URL, text: String) -> Mention? {
|
||||||
return status.mentions.first(where: { mention -> Bool in
|
return status.mentions.first(where: { mention -> Bool in
|
||||||
(text.dropFirst() == mention.username || text == mention.username) && url.host == URL(string: mention.url)!.host
|
(text.dropFirst() == mention.username || text == mention.username) && url.host == mention.url.host
|
||||||
}) ?? super.getMention(for: url, text: text)
|
}) ?? super.getMention(for: url, text: text)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func getTag(for url: URL, text: String) -> MastodonKit.Tag? {
|
override func getTag(for url: URL, text: String) -> Hashtag? {
|
||||||
if let tag = status.tags.first(where: { tag -> Bool in
|
if let tag = status.hashtags.first(where: { $0.url == url }) {
|
||||||
tag.url == url.absoluteString
|
|
||||||
}) {
|
|
||||||
return tag
|
return tag
|
||||||
} else {
|
} else {
|
||||||
return super.getTag(for: url, text: text)
|
return super.getTag(for: url, text: text)
|
||||||
|
|
Loading…
Reference in New Issue