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"]
|
||||
path = SwiftSoup
|
||||
url = git://github.com/scinfu/SwiftSoup.git
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit cfece3083acfeda2f124a84dc35f268682681d49
|
|
@ -1,13 +1,58 @@
|
|||
import UIKit
|
||||
|
||||
func test(_ nillable: String?) {
|
||||
defer {
|
||||
print("defer")
|
||||
class Client {
|
||||
func test<A>(_ thing: A) {
|
||||
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")
|
||||
print("------")
|
||||
test(nil)
|
||||
protocol ClientModel {
|
||||
var client: Client! { get set }
|
||||
}
|
||||
|
||||
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 = {
|
||||
|
||||
/* 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 */; };
|
||||
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 */; };
|
||||
D6333B772138D94E00CE884A /* ComposeMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6333B762138D94E00CE884A /* ComposeMediaView.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 */; };
|
||||
D64D0AAF2128D954005A6F37 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D64D0AAE2128D954005A6F37 /* Onboarding.storyboard */; };
|
||||
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 */; };
|
||||
D663625F2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663625E2135C75500C9CBA2 /* ConversationMainStatusTableViewCell.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 */; };
|
||||
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F42135BCD50057A976 /* ConversationViewController.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, ); }; };
|
||||
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BED173212667E900F02DA0 /* StatusTableViewCell.swift */; };
|
||||
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 */; };
|
||||
D6D4DDE5212518A200E1C4BB /* TuskerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D4DDE4212518A200E1C4BB /* TuskerTests.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 */; };
|
||||
D6F953EE21251A0700CF0F2B /* Timeline.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6F953ED21251A0700CF0F2B /* Timeline.storyboard */; };
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F953EF21251A2900CF0F2B /* MastodonController.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
|
||||
remoteInfo = Pachyderm;
|
||||
};
|
||||
D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D6D4DDCB212518A000E1C4BB;
|
||||
remoteInfo = Tusker;
|
||||
};
|
||||
D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = D61099AA2144B0CC00432DC2;
|
||||
remoteInfo = Pachyderm;
|
||||
};
|
||||
D6D4DDE1212518A200E1C4BB /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = D6D4DDC4212518A000E1C4BB /* Project object */;
|
||||
|
@ -93,7 +150,7 @@
|
|||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
D6F953E8212519A400CF0F2B /* MastodonKit.framework in Embed Frameworks */,
|
||||
D61099C12144B0CC00432DC2 /* Pachyderm.framework in Embed Frameworks */,
|
||||
D6BED170212663DA00F02DA0 /* SwiftSoup.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
|
@ -102,9 +159,48 @@
|
|||
/* End PBXCopyFilesBuildPhase 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -169,12 +265,27 @@
|
|||
/* End PBXFileReference 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 */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D6F953E7212519A400CF0F2B /* MastodonKit.framework in Frameworks */,
|
||||
D6BED16F212663DA00F02DA0 /* SwiftSoup.framework in Frameworks */,
|
||||
D61099C02144B0CC00432DC2 /* Pachyderm.framework in Frameworks */,
|
||||
D65A37F321472F300087646E /* SwiftSoup.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -195,6 +306,90 @@
|
|||
/* End PBXFrameworksBuildPhase 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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -335,6 +530,13 @@
|
|||
path = Transitions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D65A37F221472F300087646E /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D663626021360A9600C9CBA2 /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -379,10 +581,13 @@
|
|||
children = (
|
||||
D6BED16E212663DA00F02DA0 /* SwiftSoup.framework */,
|
||||
D6F953E6212519A400CF0F2B /* MastodonKit.framework */,
|
||||
D61099AC2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B92144B0CC00432DC2 /* PachydermTests */,
|
||||
D6D4DDCE212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDE3212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEE212518A200E1C4BB /* TuskerUITests */,
|
||||
D6D4DDCD212518A000E1C4BB /* Products */,
|
||||
D65A37F221472F300087646E /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -392,6 +597,8 @@
|
|||
D6D4DDCC212518A000E1C4BB /* Tusker.app */,
|
||||
D6D4DDE0212518A200E1C4BB /* TuskerTests.xctest */,
|
||||
D6D4DDEB212518A200E1C4BB /* TuskerUITests.xctest */,
|
||||
D61099AB2144B0CC00432DC2 /* Pachyderm.framework */,
|
||||
D61099B32144B0CC00432DC2 /* PachydermTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
|
@ -400,7 +607,6 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||
04DACE89212CA6B7009840C4 /* Timeline.swift */,
|
||||
D64D0AAC2128D88B005A6F37 /* LocalData.swift */,
|
||||
04DACE8D212CC7CC009840C4 /* AvatarCache.swift */,
|
||||
D663626021360A9600C9CBA2 /* Preferences */,
|
||||
|
@ -443,7 +649,55 @@
|
|||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
D61099A62144B0CC00432DC2 /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D61099BD2144B0CC00432DC2 /* Pachyderm.h in Headers */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
D61099AA2144B0CC00432DC2 /* Pachyderm */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */;
|
||||
buildPhases = (
|
||||
D61099A62144B0CC00432DC2 /* Headers */,
|
||||
D61099A72144B0CC00432DC2 /* Sources */,
|
||||
D61099A82144B0CC00432DC2 /* Frameworks */,
|
||||
D61099A92144B0CC00432DC2 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Pachyderm;
|
||||
productName = Pachyderm;
|
||||
productReference = D61099AB2144B0CC00432DC2 /* Pachyderm.framework */;
|
||||
productType = "com.apple.product-type.framework";
|
||||
};
|
||||
D61099B22144B0CC00432DC2 /* PachydermTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */;
|
||||
buildPhases = (
|
||||
D61099AF2144B0CC00432DC2 /* Sources */,
|
||||
D61099B02144B0CC00432DC2 /* Frameworks */,
|
||||
D61099B12144B0CC00432DC2 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
D61099B82144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
);
|
||||
name = PachydermTests;
|
||||
productName = PachydermTests;
|
||||
productReference = D61099B32144B0CC00432DC2 /* PachydermTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
D6D4DDCB212518A000E1C4BB /* Tusker */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = D6D4DDF4212518A200E1C4BB /* Build configuration list for PBXNativeTarget "Tusker" */;
|
||||
|
@ -456,6 +710,7 @@
|
|||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */,
|
||||
);
|
||||
name = Tusker;
|
||||
productName = Tusker;
|
||||
|
@ -508,6 +763,14 @@
|
|||
LastUpgradeCheck = 1000;
|
||||
ORGANIZATIONNAME = Shadowfacts;
|
||||
TargetAttributes = {
|
||||
D61099AA2144B0CC00432DC2 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1000;
|
||||
};
|
||||
D61099B22144B0CC00432DC2 = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
TestTargetID = D6D4DDCB212518A000E1C4BB;
|
||||
};
|
||||
D6D4DDCB212518A000E1C4BB = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
};
|
||||
|
@ -537,11 +800,27 @@
|
|||
D6D4DDCB212518A000E1C4BB /* Tusker */,
|
||||
D6D4DDDF212518A200E1C4BB /* TuskerTests */,
|
||||
D6D4DDEA212518A200E1C4BB /* TuskerUITests */,
|
||||
D61099AA2144B0CC00432DC2 /* Pachyderm */,
|
||||
D61099B22144B0CC00432DC2 /* PachydermTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
D61099A92144B0CC00432DC2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D61099B12144B0CC00432DC2 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
D6D4DDCA212518A000E1C4BB /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
|
@ -582,11 +861,59 @@
|
|||
/* End PBXResourcesBuildPhase 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 */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
04DACE8A212CA6B7009840C4 /* Timeline.swift in Sources */,
|
||||
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
|
||||
D667E5F52135BCD50057A976 /* ConversationViewController.swift in Sources */,
|
||||
D6F953F021251A2900CF0F2B /* MastodonController.swift in Sources */,
|
||||
|
@ -646,6 +973,21 @@
|
|||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
D61099B62144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||
targetProxy = D61099B52144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D61099B82144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
||||
targetProxy = D61099B72144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D61099BF2144B0CC00432DC2 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D61099AA2144B0CC00432DC2 /* Pachyderm */;
|
||||
targetProxy = D61099BE2144B0CC00432DC2 /* PBXContainerItemProxy */;
|
||||
};
|
||||
D6D4DDE2212518A200E1C4BB /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = D6D4DDCB212518A000E1C4BB /* Tusker */;
|
||||
|
@ -678,6 +1020,105 @@
|
|||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
D61099C32144B0CC00432DC2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 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 */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
|
@ -797,6 +1238,7 @@
|
|||
D6D4DDF5212518A200E1C4BB /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
|
@ -817,6 +1259,7 @@
|
|||
D6D4DDF6212518A200E1C4BB /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
|
@ -919,6 +1362,24 @@
|
|||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
D61099C22144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "Pachyderm" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D61099C32144B0CC00432DC2 /* Debug */,
|
||||
D61099C42144B0CC00432DC2 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D61099C52144B0CC00432DC2 /* Build configuration list for PBXNativeTarget "PachydermTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
D61099C62144B0CC00432DC2 /* Debug */,
|
||||
D61099C72144B0CC00432DC2 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
D6D4DDC7212518A000E1C4BB /* Build configuration list for PBXProject "Tusker" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>Pachyderm.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>Tusker.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
<FileRef
|
||||
location = "container:Tusker.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:MastodonKit/MastodonKit.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:SwiftSoup/SwiftSoup.xcodeproj">
|
||||
</FileRef>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class MastodonController {
|
||||
|
||||
|
@ -37,30 +37,25 @@ class MastodonController {
|
|||
return
|
||||
}
|
||||
|
||||
let registerRequest = Clients.register(clientName: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow])
|
||||
|
||||
client.run(registerRequest) { result in
|
||||
guard case let .success(application, _) = result else { fatalError() }
|
||||
LocalData.shared.clientID = application.clientID
|
||||
LocalData.shared.clientSecret = application.clientSecret
|
||||
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
|
||||
guard case let .success(app, _) = response else { fatalError() }
|
||||
LocalData.shared.clientID = app.clientID
|
||||
LocalData.shared.clientSecret = app.clientSecret
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
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.run(authorizeRequest) { result in
|
||||
guard case let .success(settings, _) = result else { fatalError() }
|
||||
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||
guard case let .success(settings, _) = response else { fatalError() }
|
||||
LocalData.shared.accessToken = settings.accessToken
|
||||
self.client.accessToken = settings.accessToken
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
func getOwnAccount() {
|
||||
let req = Accounts.currentUser()
|
||||
client.run(req) { result in
|
||||
guard case let .success(account, _) = result else { fatalError() }
|
||||
client.getSelfAccount { response in
|
||||
guard case let .success(account, _) = response else { fatalError() }
|
||||
self.account = account
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
extension Account {
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright © 2018 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
extension Status: Equatable {
|
||||
public static func ==(lhs: Status, rhs: Status) -> Bool {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
|
||||
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.
|
||||
//
|
||||
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
extension Visibility {
|
||||
|
||||
static var allCases: [Visibility] {
|
||||
return [.public, .unlisted, .private, .direct]
|
||||
}
|
||||
extension Status.Visibility {
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
|
|
|
@ -25,9 +25,9 @@ class LocalData {
|
|||
}
|
||||
|
||||
private let instanceURLKey = "instanceURL"
|
||||
var instanceURL: String? {
|
||||
var instanceURL: URL? {
|
||||
get {
|
||||
return defaults.string(forKey: instanceURLKey)
|
||||
return defaults.url(forKey: instanceURLKey)
|
||||
}
|
||||
set {
|
||||
defaults.set(newValue, forKey: instanceURLKey)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class Preferences: Codable {
|
||||
|
||||
|
@ -37,6 +37,6 @@ class Preferences: Codable {
|
|||
|
||||
var hideCustomEmojiInUsernames = false
|
||||
|
||||
var defaultPostVisibility = Visibility.public
|
||||
var defaultPostVisibility = Status.Visibility.public
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class ComposeViewController: UIViewController {
|
||||
|
||||
|
@ -73,20 +73,18 @@ class ComposeViewController: UIViewController {
|
|||
inReplyToAvatarImageView.layer.cornerRadius = Preferences.shared.avatarStyle.cornerRadius(for: inReplyToAvatarImageView)
|
||||
inReplyToAvatarImageView.layer.masksToBounds = true
|
||||
inReplyToAvatarImageView.image = nil
|
||||
if let url = URL(string: inReplyTo.account.avatar) {
|
||||
AvatarCache.shared.get(url) { image in
|
||||
AvatarCache.shared.get(inReplyTo.account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.inReplyToAvatarImageView.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
|
||||
if inReplyTo.account != MastodonController.shared.account {
|
||||
statusTextView.text = "@\(inReplyTo.account.acct) "
|
||||
}
|
||||
statusTextView.text += inReplyTo.mentions.filter({ $0.id != MastodonController.shared.account.id }).map({ "@\($0.acct) " }).joined()
|
||||
statusTextView.textViewDidChange(statusTextView)
|
||||
contentWarning = inReplyTo.sensitive ?? false
|
||||
contentWarning = inReplyTo.sensitive
|
||||
contentWarningTextField.text = inReplyTo.spoilerText
|
||||
visibility = inReplyTo.visibility
|
||||
} else {
|
||||
|
@ -132,7 +130,7 @@ class ComposeViewController: UIViewController {
|
|||
|
||||
@IBAction func visibilityPressed(_ sender: Any) {
|
||||
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
|
||||
UIView.performWithoutAnimation {
|
||||
self.visibility = visibility
|
||||
|
@ -177,8 +175,6 @@ class ComposeViewController: UIViewController {
|
|||
guard let text = statusTextView.text,
|
||||
!text.isEmpty else { return }
|
||||
|
||||
let inReplyToID = inReplyTo?.id
|
||||
|
||||
let contentWarning: String?
|
||||
if self.contentWarning,
|
||||
let text = contentWarningTextField.text,
|
||||
|
@ -200,26 +196,23 @@ class ComposeViewController: UIViewController {
|
|||
let index = attachments.count
|
||||
attachments.append(nil)
|
||||
group.enter()
|
||||
let req = Media.upload(media: .png(data), description: mediaView.mediaDescription)
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
guard case let .success(attachment, _) = result else { fatalError() }
|
||||
MastodonController.shared.client.upload(attachment: FormAttachment(pngData: data), description: mediaView.mediaDescription) { response in
|
||||
guard case let .success(attachment, _) = response else { fatalError() }
|
||||
attachments[index] = attachment
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
group.notify(queue: .main) {
|
||||
let mediaIDs = attachments.map { $0!.id }
|
||||
let attachments = attachments.compactMap { $0 }
|
||||
|
||||
let req = Statuses.create(status: text,
|
||||
replyToID: inReplyToID,
|
||||
mediaIDs: mediaIDs,
|
||||
MastodonController.shared.client.createStatus(text: text,
|
||||
inReplyTo: self.inReplyTo,
|
||||
media: attachments,
|
||||
sensitive: sensitive,
|
||||
spoilerText: contentWarning,
|
||||
visibility: visibility)
|
||||
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
guard case let .success(status, _) = result else { fatalError() }
|
||||
visiblity: visibility) { response in
|
||||
guard case let .success(status, _) = response else { fatalError() }
|
||||
self.status = status
|
||||
DispatchQueue.main.async {
|
||||
self.performSegue(withIdentifier: "postComplete", sender: self)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class ConversationViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
|
@ -40,9 +40,8 @@ class ConversationViewController: UIViewController, UITableViewDataSource, UITab
|
|||
|
||||
statuses = [mainStatus]
|
||||
|
||||
let req = Statuses.context(id: mainStatus.id)
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
guard case let .success(context, _) = result else { fatalError() }
|
||||
mainStatus.getContext { response in
|
||||
guard case let .success(context, _) = response else { fatalError() }
|
||||
var statuses = self.getDirectParents(of: self.mainStatus, from: context.ancestors)
|
||||
statuses.append(self.mainStatus)
|
||||
statuses.append(contentsOf: context.descendants)
|
||||
|
|
|
@ -15,8 +15,8 @@ class MainTabBarViewController: UITabBarController {
|
|||
|
||||
viewControllers = [
|
||||
TimelineTableViewController.create(for: .home),
|
||||
TimelineTableViewController.create(for: .federated),
|
||||
TimelineTableViewController.create(for: .local),
|
||||
TimelineTableViewController.create(for: .public(local: false)),
|
||||
TimelineTableViewController.create(for: .public(local: true)),
|
||||
NotificationsTableViewController.create(),
|
||||
PreferencesTableViewController.create()
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class NotificationsTableViewController: UITableViewController {
|
||||
|
||||
|
@ -16,7 +16,7 @@ class NotificationsTableViewController: UITableViewController {
|
|||
return navigationController
|
||||
}
|
||||
|
||||
var notifications: [MastodonKit.Notification] = [] {
|
||||
var notifications: [Pachyderm.Notification] = [] {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.tableView.reloadData()
|
||||
|
@ -43,12 +43,11 @@ class NotificationsTableViewController: UITableViewController {
|
|||
tableView.register(UINib(nibName: "ActionNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "actionCell")
|
||||
tableView.register(UINib(nibName: "FollowNotificationTableViewCell", bundle: nil), forCellReuseIdentifier: "followCell")
|
||||
|
||||
let req = Notifications.all()
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
MastodonController.shared.client.getNotifications() { result in
|
||||
guard case let .success(notifications, pagination) = result else { fatalError() }
|
||||
self.notifications = notifications
|
||||
self.newer = pagination?.previous
|
||||
self.older = pagination?.next
|
||||
self.newer = pagination?.newer
|
||||
self.older = pagination?.older
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +85,7 @@ class NotificationsTableViewController: UITableViewController {
|
|||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let notification = notifications[indexPath.row]
|
||||
|
||||
switch notification.type {
|
||||
switch notification.kind {
|
||||
case .mention:
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
|
||||
let status = notification.status!
|
||||
|
@ -111,10 +110,9 @@ class NotificationsTableViewController: UITableViewController {
|
|||
if indexPath.row == notifications.count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
let req = Notifications.all(range: older)
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
MastodonController.shared.client.getNotifications(range: older) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
self.older = pagination?.next
|
||||
self.older = pagination?.older
|
||||
self.notifications.append(contentsOf: newNotifications)
|
||||
}
|
||||
}
|
||||
|
@ -123,10 +121,9 @@ class NotificationsTableViewController: UITableViewController {
|
|||
@IBAction func refreshNotifications(_ sender: Any) {
|
||||
guard let newer = newer else { return }
|
||||
|
||||
let req = Notifications.all(range: newer)
|
||||
MastodonController.shared.client.run(req) { result in
|
||||
MastodonController.shared.client.getNotifications(range: newer) { result in
|
||||
guard case let .success(newNotifications, pagination) = result else { fatalError() }
|
||||
self.newer = pagination?.previous
|
||||
self.newer = pagination?.newer
|
||||
self.notifications.insert(contentsOf: newNotifications, at: 0)
|
||||
DispatchQueue.main.async {
|
||||
self.refreshControl?.endRefreshing()
|
||||
|
|
|
@ -29,9 +29,10 @@ class OnboardingViewController: UIViewController {
|
|||
|
||||
@IBAction func loginPressed(_ sender: Any) {
|
||||
guard let text = urlTextField.text,
|
||||
let url = URL(string: text),
|
||||
var components = URLComponents(string: text) else { return }
|
||||
|
||||
LocalData.shared.instanceURL = text
|
||||
LocalData.shared.instanceURL = url
|
||||
MastodonController.shared.createClient()
|
||||
MastodonController.shared.registerApp {
|
||||
let clientID = LocalData.shared.clientID!
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class VisibilityTableViewController: UITableViewController {
|
||||
|
||||
|
@ -24,11 +24,11 @@ class VisibilityTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
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 {
|
||||
let visibility = Visibility.allCases[indexPath.row]
|
||||
let visibility = Status.Visibility.allCases[indexPath.row]
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "visibilityCell", for: indexPath)
|
||||
cell.textLabel!.text = visibility.displayName
|
||||
|
@ -38,9 +38,9 @@ class VisibilityTableViewController: UITableViewController {
|
|||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
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
|
||||
|
||||
tableView.reloadRows(at: [indexPath], with: .automatic)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
import SafariServices
|
||||
|
||||
class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
||||
|
@ -28,12 +28,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
|||
}
|
||||
}
|
||||
|
||||
var newer: RequestRange?
|
||||
var older: RequestRange?
|
||||
var newer: RequestRange?
|
||||
|
||||
func request(for range: RequestRange? = .default) -> Request<[Status]> {
|
||||
let range = range ?? .default
|
||||
return Accounts.statuses(id: account.id, mediaOnly: false, pinnedOnly: false, excludeReplies: !Preferences.shared.showRepliesInProfiles, range: range)
|
||||
func getStatuses(for range: RequestRange = .default, completion: @escaping Client.Callback<[Status]>) {
|
||||
account.getStatuses(range: range, onlyMedia: false, pinned: false, excludeReplies: !Preferences.shared.showRepliesInProfiles, completion: completion)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
@ -47,13 +46,11 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
|||
|
||||
updateUIForPreferences()
|
||||
|
||||
|
||||
|
||||
MastodonController.shared.client.run(request()) { result in
|
||||
guard case let .success(statuses, pagination) = result else { fatalError() }
|
||||
getStatuses { response in
|
||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||
self.statuses = statuses
|
||||
self.newer = pagination?.previous
|
||||
self.older = pagination?.next
|
||||
self.older = pagination?.older
|
||||
self.newer = pagination?.newer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,9 +124,9 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
|||
if indexPath.section == 1 && indexPath.row == statuses.count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
MastodonController.shared.client.run(request(for: older)) { result in
|
||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
||||
self.older = pagination?.next
|
||||
getStatuses(for: older) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.older = pagination?.older
|
||||
self.statuses.append(contentsOf: newStatuses)
|
||||
}
|
||||
}
|
||||
|
@ -138,9 +135,9 @@ class ProfileTableViewController: UITableViewController, PreferencesAdaptive {
|
|||
@IBAction func refreshStatuses(_ sender: Any) {
|
||||
guard let newer = newer else { return }
|
||||
|
||||
MastodonController.shared.client.run(request(for: newer)) { result in
|
||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
||||
self.newer = pagination?.previous
|
||||
getStatuses(for: newer) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.newer = pagination?.newer
|
||||
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
||||
DispatchQueue.main.async {
|
||||
self.refreshControl?.endRefreshing()
|
||||
|
@ -159,11 +156,11 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
|
|||
func showMoreOptions() {
|
||||
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
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)
|
||||
}))
|
||||
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)
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Send Message...", style: .default, handler: { _ in
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class TimelineTableViewController: UITableViewController {
|
||||
|
||||
|
@ -15,17 +15,22 @@ class TimelineTableViewController: UITableViewController {
|
|||
guard let navigationController = UIStoryboard(name: "Timeline", bundle: nil).instantiateInitialViewController() as? UINavigationController,
|
||||
let timelineController = navigationController.topViewController as? TimelineTableViewController else { fatalError() }
|
||||
timelineController.timeline = timeline
|
||||
|
||||
let title: String
|
||||
switch timeline {
|
||||
case .home:
|
||||
navigationController.tabBarItem.title = "Home"
|
||||
timelineController.navigationItem.title = "Home"
|
||||
case .local:
|
||||
navigationController.tabBarItem.title = "Local"
|
||||
timelineController.navigationItem.title = "Local"
|
||||
case .federated:
|
||||
navigationController.tabBarItem.title = "Federated"
|
||||
timelineController.navigationItem.title = "Federated"
|
||||
title = "Home"
|
||||
case let .public(local):
|
||||
title = local ? "Local" : "Federated"
|
||||
case let .tag(hashtag):
|
||||
title = "#\(hashtag)"
|
||||
case .list:
|
||||
title = "List"
|
||||
case .direct:
|
||||
title = "Direct"
|
||||
}
|
||||
navigationController.tabBarItem.title = title
|
||||
timelineController.navigationItem.title = title
|
||||
|
||||
return navigationController
|
||||
}
|
||||
|
@ -52,11 +57,11 @@ class TimelineTableViewController: UITableViewController {
|
|||
tableView.register(UINib(nibName: "StatusTableViewCell", bundle: nil), forCellReuseIdentifier: "statusCell")
|
||||
|
||||
guard MastodonController.shared.client?.accessToken != nil else { return }
|
||||
MastodonController.shared.client.run(timeline.request()) { result in
|
||||
guard case let .success(statuses, pagination) = result else { fatalError() }
|
||||
MastodonController.shared.client.getStatuses(timeline: timeline) { response in
|
||||
guard case let .success(statuses, pagination) = response else { fatalError() }
|
||||
self.statuses = statuses
|
||||
self.newer = pagination?.previous
|
||||
self.older = pagination?.next
|
||||
self.newer = pagination?.newer
|
||||
self.older = pagination?.older
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,9 +112,9 @@ class TimelineTableViewController: UITableViewController {
|
|||
if indexPath.row == statuses.count - 1 {
|
||||
guard let older = older else { return }
|
||||
|
||||
MastodonController.shared.client.run(timeline.request(range: older)) { result in
|
||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
||||
self.older = pagination?.next
|
||||
MastodonController.shared.client.getStatuses(timeline: timeline, range: older) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.older = pagination?.older
|
||||
self.statuses.append(contentsOf: newStatuses)
|
||||
}
|
||||
}
|
||||
|
@ -118,9 +123,9 @@ class TimelineTableViewController: UITableViewController {
|
|||
@IBAction func refreshStatuses(_ sender: Any) {
|
||||
guard let newer = newer else { return }
|
||||
|
||||
MastodonController.shared.client.run(timeline.request(range: newer)) { result in
|
||||
guard case let .success(newStatuses, pagination) = result else { fatalError() }
|
||||
self.newer = pagination?.previous
|
||||
MastodonController.shared.client.getStatuses(timeline: timeline, range: newer) { response in
|
||||
guard case let .success(newStatuses, pagination) = response else { fatalError() }
|
||||
self.newer = pagination?.newer
|
||||
self.statuses.insert(contentsOf: newStatuses, at: 0)
|
||||
DispatchQueue.main.async {
|
||||
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 MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
protocol AttachmentViewDelegate {
|
||||
func showLargeAttachment(for attachmentView: AttachmentView)
|
||||
|
@ -45,8 +45,7 @@ class AttachmentView: UIImageView {
|
|||
}
|
||||
|
||||
func loadImage() {
|
||||
guard let url = URL(string: attachment.url) else { fatalError("Invalid URL: \(attachment.url)") }
|
||||
task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
task = URLSession.shared.dataTask(with: attachment.url) { data, response, error in
|
||||
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.image = image
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
import SwiftSoup
|
||||
|
||||
protocol HTMLContentLabelDelegate {
|
||||
|
||||
func selected(mention: Mention)
|
||||
|
||||
func selected(tag: MastodonKit.Tag)
|
||||
func selected(tag: Hashtag)
|
||||
|
||||
func selected(url: URL)
|
||||
|
||||
|
@ -192,10 +192,10 @@ class HTMLContentLabel: UILabel {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getTag(for url: URL, text: String) -> MastodonKit.Tag? {
|
||||
func getTag(for url: URL, text: String) -> Hashtag? {
|
||||
if text.starts(with: "#") {
|
||||
let tag = String(text.dropFirst())
|
||||
return MastodonKit.Tag(name: tag, url: url.absoluteString)
|
||||
return Hashtag(name: tag, url: url)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||
|
||||
|
@ -22,7 +22,7 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
@IBOutlet weak var timestampLabel: UILabel!
|
||||
@IBOutlet weak var attachmentsView: UIStackView!
|
||||
|
||||
var notification: MastodonKit.Notification!
|
||||
var notification: Pachyderm.Notification!
|
||||
var status: Status!
|
||||
|
||||
var opAvatarURL: URL?
|
||||
|
@ -49,20 +49,20 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
displayNameLabel.text = status.account.realDisplayName
|
||||
|
||||
let verb: String
|
||||
switch notification.type {
|
||||
switch notification.kind {
|
||||
case .favourite:
|
||||
verb = "Liked"
|
||||
case .reblog:
|
||||
verb = "Reblogged"
|
||||
default:
|
||||
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
|
||||
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||
}
|
||||
actionLabel.text = "\(verb) by \(notification.account.realDisplayName)"
|
||||
}
|
||||
|
||||
func updateUI(for notification: MastodonKit.Notification) {
|
||||
guard notification.type == .favourite || notification.type == .reblog else {
|
||||
fatalError("Invalid notification type \(notification.type) for ActionNotificationTableViewCell")
|
||||
func updateUI(for notification: Pachyderm.Notification) {
|
||||
guard notification.kind == .favourite || notification.kind == .reblog else {
|
||||
fatalError("Invalid notification type \(notification.kind) for ActionNotificationTableViewCell")
|
||||
}
|
||||
self.notification = notification
|
||||
self.status = notification.status!
|
||||
|
@ -71,31 +71,27 @@ class ActionNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
|
||||
usernameLabel.text = "@\(status.account.acct)"
|
||||
opAvatarImageView.image = nil
|
||||
if let url = URL(string: status.account.avatar) {
|
||||
opAvatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
opAvatarURL = status.account.avatar
|
||||
AvatarCache.shared.get(status.account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.opAvatarImageView.image = image
|
||||
self.opAvatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
actionAvatarImageView.image = nil
|
||||
if let url = URL(string: notification.account.avatar) {
|
||||
actionAvatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
actionAvatarURL = notification.account.avatar
|
||||
AvatarCache.shared.get(notification.account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.actionAvatarImageView.image = image
|
||||
self.actionAvatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
||||
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||
if attachments.count > 0 {
|
||||
attachmentsView.isHidden = false
|
||||
for attachment in attachments {
|
||||
guard let url = URL(string: attachment.textURL ?? attachment.url) else { continue }
|
||||
let url = attachment.textURL ?? attachment.url
|
||||
let label = UILabel()
|
||||
label.textColor = .darkGray
|
||||
|
||||
|
@ -190,7 +186,7 @@ extension ActionNotificationTableViewCell: HTMLContentLabelDelegate {
|
|||
delegate?.selected(mention: mention)
|
||||
}
|
||||
|
||||
func selected(tag: Tag) {
|
||||
func selected(tag: Hashtag) {
|
||||
delegate?.selected(tag: tag)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||
|
||||
|
@ -19,7 +19,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
@IBOutlet weak var displayNameLabel: UILabel!
|
||||
@IBOutlet weak var usernameLabel: UILabel!
|
||||
|
||||
var notification: MastodonKit.Notification!
|
||||
var notification: Pachyderm.Notification!
|
||||
var account: Account!
|
||||
|
||||
var avatarURL: URL?
|
||||
|
@ -37,7 +37,7 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
displayNameLabel.text = account.realDisplayName
|
||||
}
|
||||
|
||||
func updateUI(for notification: MastodonKit.Notification) {
|
||||
func updateUI(for notification: Pachyderm.Notification) {
|
||||
self.notification = notification
|
||||
self.account = notification.account
|
||||
|
||||
|
@ -45,15 +45,13 @@ class FollowNotificationTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.image = nil
|
||||
if let url = URL(string: account.avatar) {
|
||||
avatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
avatarURL = account.avatar
|
||||
AvatarCache.shared.get(account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
protocol ProfileHeaderTableViewCellDelegate: StatusTableViewCellDelegate {
|
||||
|
||||
|
@ -55,17 +55,14 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
usernameLabel.text = "@\(account.acct)"
|
||||
|
||||
avatarImageView.image = nil
|
||||
if let url = URL(string: account.avatar) {
|
||||
avatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
avatarURL = account.avatar
|
||||
AvatarCache.shared.get(account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if let url = URL(string: account.header) {
|
||||
headerImageDownloadTask = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
headerImageDownloadTask = URLSession.shared.dataTask(with: account.header) { data, response, error in
|
||||
guard error == nil, let data = data, let image = UIImage(data: data) else { return }
|
||||
DispatchQueue.main.async {
|
||||
self.headerImageView.image = image
|
||||
|
@ -73,7 +70,6 @@ class ProfileHeaderTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
}
|
||||
}
|
||||
headerImageDownloadTask!.resume()
|
||||
}
|
||||
|
||||
// todo: HTML parsing
|
||||
noteLabel.text = account.note
|
||||
|
@ -106,7 +102,7 @@ extension ProfileHeaderTableViewCell: HTMLContentLabelDelegate {
|
|||
delegate?.selected(mention: mention)
|
||||
}
|
||||
|
||||
func selected(tag: Tag) {
|
||||
func selected(tag: Hashtag) {
|
||||
delegate?.selected(tag: tag)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
||||
|
||||
|
@ -72,18 +72,16 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
|||
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.image = nil
|
||||
if let url = URL(string: account.avatar) {
|
||||
avatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
avatarURL = account.avatar
|
||||
AvatarCache.shared.get(account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
attachmentsView.isHidden = false
|
||||
let width = attachmentsView.bounds.width
|
||||
|
@ -164,55 +162,40 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive
|
|||
}
|
||||
|
||||
@IBAction func favoritePressed(_ sender: Any) {
|
||||
let oldValue = favorited
|
||||
favorited = !favorited
|
||||
|
||||
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
|
||||
guard case .success = result else {
|
||||
(favorited ? realStatus.favourite : realStatus.unfavourite)() { response in
|
||||
self.favorited = realStatus.favourited ?? false
|
||||
DispatchQueue.main.async {
|
||||
if case .success = response {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
print("Couldn't favorite status \(realStatus.id)")
|
||||
// todo: display error message
|
||||
DispatchQueue.main.async {
|
||||
self.favorited = oldValue
|
||||
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.error)
|
||||
}
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func reblogPressed(_ sender: Any) {
|
||||
let oldValue = reblogged
|
||||
reblogged = !reblogged
|
||||
|
||||
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
|
||||
guard case .success = result else {
|
||||
(reblogged ? realStatus.reblog : realStatus.unreblog)() { response in
|
||||
self.reblogged = realStatus.reblogged ?? false
|
||||
DispatchQueue.main.async {
|
||||
if case .success = response {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
print("Couldn't reblog status \(realStatus.id)")
|
||||
// todo: display error message
|
||||
DispatchQueue.main.async {
|
||||
self.reblogged = oldValue
|
||||
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.error)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +208,7 @@ extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate {
|
|||
delegate?.selected(mention: mention)
|
||||
}
|
||||
|
||||
func selected(tag: MastodonKit.Tag) {
|
||||
func selected(tag: Hashtag) {
|
||||
delegate?.selected(tag: tag)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
protocol StatusTableViewCellDelegate {
|
||||
|
||||
|
@ -15,7 +15,7 @@ protocol StatusTableViewCellDelegate {
|
|||
|
||||
func selected(mention: Mention)
|
||||
|
||||
func selected(tag: MastodonKit.Tag)
|
||||
func selected(tag: Hashtag)
|
||||
|
||||
func selected(url: URL)
|
||||
|
||||
|
@ -105,18 +105,16 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
|
||||
usernameLabel.text = "@\(account.acct)"
|
||||
avatarImageView.image = nil
|
||||
if let url = URL(string: account.avatar) {
|
||||
avatarURL = url
|
||||
AvatarCache.shared.get(url) { image in
|
||||
avatarURL = account.avatar
|
||||
AvatarCache.shared.get(account.avatar) { image in
|
||||
DispatchQueue.main.async {
|
||||
self.avatarImageView.image = image
|
||||
self.avatarURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTimestamp()
|
||||
|
||||
let attachments = status.mediaAttachments.filter({ $0.type == .image })
|
||||
let attachments = status.attachments.filter({ $0.kind == .image })
|
||||
if attachments.count > 0 {
|
||||
attachmentsView.isHidden = false
|
||||
let width = attachmentsView.bounds.width
|
||||
|
@ -212,55 +210,40 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive {
|
|||
}
|
||||
|
||||
@IBAction func favoritePressed(_ sender: Any) {
|
||||
let oldValue = favorited
|
||||
favorited = !favorited
|
||||
|
||||
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
|
||||
guard case .success = result else {
|
||||
(favorited ? realStatus.favourite : realStatus.unfavourite)() { response in
|
||||
self.favorited = realStatus.favourited ?? false
|
||||
DispatchQueue.main.async {
|
||||
if case .success = response {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
print("Couldn't favorite status \(realStatus.id)")
|
||||
// todo: display error message
|
||||
DispatchQueue.main.async {
|
||||
self.favorited = oldValue
|
||||
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.error)
|
||||
}
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func reblogPressed(_ sender: Any) {
|
||||
let oldValue = reblogged
|
||||
reblogged = !reblogged
|
||||
|
||||
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
|
||||
guard case .success = result else {
|
||||
(reblogged ? realStatus.reblog : realStatus.unreblog)() { response in
|
||||
self.reblogged = realStatus.reblogged ?? false
|
||||
DispatchQueue.main.async {
|
||||
if case .success = response {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
} else {
|
||||
print("Couldn't reblog status \(realStatus.id)")
|
||||
// todo: display error message
|
||||
DispatchQueue.main.async {
|
||||
self.reblogged = oldValue
|
||||
|
||||
let generator = UINotificationFeedbackGenerator()
|
||||
generator.notificationOccurred(.error)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
generator.impactOccurred()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +256,7 @@ extension StatusTableViewCell: HTMLContentLabelDelegate {
|
|||
delegate?.selected(mention: mention)
|
||||
}
|
||||
|
||||
func selected(tag: MastodonKit.Tag) {
|
||||
func selected(tag: Hashtag) {
|
||||
delegate?.selected(tag: tag)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonKit
|
||||
import Pachyderm
|
||||
|
||||
class StatusContentLabel: HTMLContentLabel {
|
||||
|
||||
|
@ -19,14 +19,12 @@ class StatusContentLabel: HTMLContentLabel {
|
|||
|
||||
override func getMention(for url: URL, text: String) -> Mention? {
|
||||
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)
|
||||
}
|
||||
|
||||
override func getTag(for url: URL, text: String) -> MastodonKit.Tag? {
|
||||
if let tag = status.tags.first(where: { tag -> Bool in
|
||||
tag.url == url.absoluteString
|
||||
}) {
|
||||
override func getTag(for url: URL, text: String) -> Hashtag? {
|
||||
if let tag = status.hashtags.first(where: { $0.url == url }) {
|
||||
return tag
|
||||
} else {
|
||||
return super.getTag(for: url, text: text)
|
||||
|
|
Loading…
Reference in New Issue