This commit is contained in:
Justin Mazzocchi 2020-08-30 16:33:11 -07:00
parent 2ca92ed098
commit 9b59e2fbea
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
110 changed files with 770 additions and 896 deletions

View file

@ -3,12 +3,13 @@
import Foundation
import Combine
import GRDB
import Mastodon
// swiftlint:disable file_length
struct ContentDatabase {
private let databaseQueue: DatabaseQueue
init(identityID: UUID, environment: AppEnvironment) throws {
init(identityID: UUID, inMemory: Bool) throws {
guard
let documentsDirectory = NSSearchPathForDirectoriesInDomains(
.documentDirectory,
@ -16,7 +17,7 @@ struct ContentDatabase {
.first
else { throw DatabaseError.documentsDirectoryNotFound }
if environment.inMemoryContent {
if inMemory {
databaseQueue = DatabaseQueue()
} else {
databaseQueue = try DatabaseQueue(path: "\(documentsDirectory)/\(identityID.uuidString).sqlite3")
@ -24,7 +25,6 @@ struct ContentDatabase {
try Self.migrate(databaseQueue)
try Self.createTemporaryTables(databaseQueue)
Self.attributedStringCache = environment.attributedStringCache
}
}
@ -131,8 +131,6 @@ extension ContentDatabase {
}
private extension ContentDatabase {
static var attributedStringCache: AttributedStringCache?
// swiftlint:disable function_body_length
static func migrate(_ writer: DatabaseWriter) throws {
var migrator = DatabaseMigrator()
@ -244,21 +242,11 @@ private extension ContentDatabase {
}
extension Account: TableRecord, FetchableRecord, PersistableRecord {
static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] {
var userInfo = [CodingUserInfoKey: Any]()
if let attributedStringCache = ContentDatabase.attributedStringCache {
userInfo[.attributedStringCache] = attributedStringCache
}
return userInfo
}
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
MastodonDecoder()
}
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
public static func databaseJSONEncoder(for column: String) -> JSONEncoder {
MastodonEncoder()
}
}
@ -282,7 +270,7 @@ extension Timeline: StatusCollection {
case id, listTitle
}
init(row: Row) {
public init(row: Row) {
switch (row[Columns.id] as String, row[Columns.listTitle] as String?) {
case (Timeline.home.id, _):
self = .home
@ -297,7 +285,7 @@ extension Timeline: StatusCollection {
}
}
func encode(to container: inout PersistenceContainer) {
public func encode(to container: inout PersistenceContainer) {
container[Columns.id] = id
if case let .list(list) = self {
@ -336,11 +324,11 @@ private extension Timeline {
}
extension Filter: TableRecord, FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
MastodonDecoder()
}
static func databaseJSONEncoder(for column: String) -> JSONEncoder {
public static func databaseJSONEncoder(for column: String) -> JSONEncoder {
MastodonEncoder()
}
}
@ -464,16 +452,6 @@ private extension StoredStatus {
}
extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord {
static var databaseDecodingUserInfo: [CodingUserInfoKey: Any] {
var userInfo = [CodingUserInfoKey: Any]()
if let attributedStringCache = ContentDatabase.attributedStringCache {
userInfo[.attributedStringCache] = attributedStringCache
}
return userInfo
}
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
MastodonDecoder()
}

View file

@ -3,6 +3,7 @@
import Foundation
import Combine
import GRDB
import Mastodon
enum IdentityDatabaseError: Error {
case identityNotFound

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
// swiftlint:disable force_try
private let decoder = MastodonDecoder()
@ -119,7 +120,7 @@ extension FiltersViewModel {
}
extension EditFilterViewModel {
static let development = EditFilterViewModel(filter: .new, identityService: .development)
static let development = EditFilterViewModel(filter: Filter.new, identityService: .development)
}
extension StatusListViewModel {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
struct HTTPStubs {
static func stub(

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
extension AccessTokenEndpoint: Stubbing {
func dataString(url: URL) -> String? {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
// swiftlint:disable line_length
let officialAccountJSON = #"""

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
extension AppAuthorizationEndpoint: Stubbing {
func dataString(url: URL) -> String? {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
extension ContextEndpoint: Stubbing {
func dataString(url: URL) -> String? {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
// swiftlint:disable line_length
let officialInstanceJSON = #"""

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
extension MastodonTarget: Stubbing {
func stub(url: URL) -> HTTPStub? {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
extension PreferencesEndpoint: Stubbing {
func dataString(url: URL) -> String? {

View file

@ -2,6 +2,7 @@
import Foundation
import UIKit
import Mastodon
extension TimelinesEndpoint: Stubbing {
func data(url: URL) -> Data? {

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
class StubbingURLProtocol: URLProtocol {
private static var targetsForURLs = [URL: HTTPTarget]()

View file

@ -1,9 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
typealias AttributedStringCache = NSCache<NSString, NSAttributedString>
extension CodingUserInfoKey {
static let attributedStringCache = CodingUserInfoKey(rawValue: "com.metabolist.metatext.attributed-string-cache")!
}

View file

@ -2,6 +2,7 @@
import UIKit
import Kingfisher
import Mastodon
extension NSMutableAttributedString {
func insert(emoji: [Emoji], view: UIView) {

5
Mastodon/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

29
Mastodon/Package.swift Normal file
View file

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

3
Mastodon/README.md Normal file
View file

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

View file

@ -0,0 +1,5 @@
import Foundation
public enum Constants {
public static let dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
}

View file

@ -0,0 +1,9 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct AccessToken: Codable {
public let scope: String
public let tokenType: String
public let accessToken: String
}

View file

@ -0,0 +1,32 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Account: Codable, Hashable {
public struct Field: Codable, Hashable {
public let name: String
public let value: HTML
public let verifiedAt: Date?
}
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: HTML
public let url: URL
public let avatar: URL
public let avatarStatic: URL
public let header: URL
public let headerStatic: URL
public let fields: [Field]
public let emojis: [Emoji]
@DecodableDefault.False public private(set) var bot: Bool
@DecodableDefault.False public private(set) var moved: Bool
@DecodableDefault.False public private(set) var discoverable: Bool
}

View file

@ -0,0 +1,13 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct AppAuthorization: Codable {
public let id: String
public let clientId: String
public let clientSecret: String
public let name: String
public let redirectUri: String
public let website: String?
public let vapidKey: String?
}

View file

@ -0,0 +1,8 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Application: Codable, Hashable {
public let name: String
public let website: String?
}

View file

@ -0,0 +1,43 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Attachment: Codable, Hashable {
public enum AttachmentType: String, Codable, Hashable, Unknowable {
case image, video, gifv, audio, unknown
public static var unknownCase: Self { .unknown }
}
// swiftlint:disable nesting
public struct Meta: Codable, Hashable {
public struct Info: Codable, Hashable {
public let width: Int?
public let height: Int?
public let size: String?
public let aspect: Double?
public let frameRate: String?
public let duration: Double?
public let bitrate: Int?
}
public struct Focus: Codable, Hashable {
public let x: Double
public let y: Double
}
public let original: Info?
public let small: Info?
public let focus: Focus?
}
// swiftlint:enable nesting
public let id: String
public let type: AttachmentType
public let url: URL
public let remoteUrl: URL?
public let previewUrl: URL
public let textUrl: URL?
public let meta: Meta?
public let description: String?
}

View file

@ -0,0 +1,25 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Card: Codable, Hashable {
public enum CardType: String, Codable, Hashable, Unknowable {
case link, photo, video, rich, unknown
public static var unknownCase: Self { .unknown }
}
public let url: URL
public let title: String
public let description: String
public let type: CardType
public let authorName: String?
public let authorUrl: String?
public let providerName: String?
public let providerUrl: String?
public let html: String?
public let width: Int?
public let height: Int?
public let image: URL?
public let embedUrl: String?
}

View file

@ -0,0 +1,10 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Emoji: Codable, Hashable {
public let shortcode: String
public let staticUrl: URL
public let url: URL
public let visibleInPicker: Bool
}

View file

@ -2,8 +2,8 @@
import Foundation
struct Filter: Codable, Hashable, Identifiable {
enum Context: String, Codable, Unknowable {
public struct Filter: Codable, Hashable, Identifiable {
public enum Context: String, Codable, Unknowable {
case home
case notifications
case `public`
@ -11,18 +11,18 @@ struct Filter: Codable, Hashable, Identifiable {
case account
case unknown
static var unknownCase: Self { .unknown }
public static var unknownCase: Self { .unknown }
}
let id: String
var phrase: String
var context: [Context]
var expiresAt: Date?
var irreversible: Bool
var wholeWord: Bool
public let id: String
public var phrase: String
public var context: [Context]
public var expiresAt: Date?
public var irreversible: Bool
public var wholeWord: Bool
}
extension Filter {
public extension Filter {
static let newFilterID: String = "com.metabolist.metatext.new-filter-id"
static let new = Self(id: newFilterID,
phrase: "",
@ -36,7 +36,7 @@ extension Array where Element == Filter {
// swiftlint:disable line_length
// Adapted from https://github.com/tootsuite/mastodon/blob/bf477cee9f31036ebf3d164ddec1cebef5375513/app/javascript/mastodon/selectors/index.js#L43
// swiftlint:enable line_length
func regularExpression() -> String? {
public func regularExpression() -> String? {
guard !isEmpty else { return nil }
return map {
@ -59,24 +59,5 @@ extension Array where Element == Filter {
}
extension Filter.Context: Identifiable {
var id: Self { self }
}
extension Filter.Context {
var localized: String {
switch self {
case .home:
return NSLocalizedString("filter.context.home", comment: "")
case .notifications:
return NSLocalizedString("filter.context.notifications", comment: "")
case .public:
return NSLocalizedString("filter.context.public", comment: "")
case .thread:
return NSLocalizedString("filter.context.thread", comment: "")
case .account:
return NSLocalizedString("filter.context.account", comment: "")
case .unknown:
return NSLocalizedString("filter.context.unknown", comment: "")
}
}
public var id: Self { self }
}

View file

@ -1,30 +1,26 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
#if !os(macOS)
import UIKit
#else
import AppKit
#endif
struct HTML: Hashable {
let raw: String
let attributed: NSAttributedString
public struct HTML: Hashable {
public let raw: String
public let attributed: NSAttributedString
}
extension HTML: Codable {
init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let attributedStringCache = decoder.userInfo[.attributedStringCache] as? AttributedStringCache
raw = try container.decode(String.self)
if let attributed = attributedStringCache?.object(forKey: raw as NSString) {
self.attributed = attributed
return
}
attributed = HTMLParser(string: raw).parse()
attributedStringCache?.setObject(attributed, forKey: raw as NSString)
}
func encode(to encoder: Encoder) throws {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(raw)

View file

@ -0,0 +1,30 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Instance: Codable, Hashable {
public struct URLs: Codable, Hashable {
public let streamingApi: URL
}
public struct Stats: Codable, Hashable {
public let userCount: Int
public let statusCount: Int
public let domainCount: Int
}
public let uri: String
public let title: String
public let description: String
public let shortDescription: String?
public let email: String
public let version: String
@DecodableDefault.EmptyList public private(set) var languages: [String]
@DecodableDefault.False public private(set) var registrations: Bool
@DecodableDefault.False public private(set) var approvalRequired: Bool
@DecodableDefault.False public private(set) var invitesEnabled: Bool
public let urls: URLs
public let stats: Stats
public let thumbnail: URL?
public let contactAccount: Account?
}

View file

@ -0,0 +1,13 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct MastodonList: Codable, Hashable, Identifiable {
public let id: String
public let title: String
public init(id: String, title: String) {
self.id = id
self.title = title
}
}

View file

@ -0,0 +1,13 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct MastodonContext: Codable, Hashable {
public let ancestors: [Status]
public let descendants: [Status]
public init(ancestors: [Status], descendants: [Status]) {
self.ancestors = ancestors
self.descendants = descendants
}
}

View file

@ -0,0 +1,11 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct MastodonError: Error, Codable {
public let error: String
}
extension MastodonError: LocalizedError {
public var errorDescription: String? { error }
}

View file

@ -2,7 +2,7 @@
import Foundation
struct MastodonPreferences: Codable {
public struct MastodonPreferences: Codable {
enum CodingKeys: String, CodingKey {
case postingDefaultVisibility = "posting:default:visibility"
case postingDefaultSensitive = "posting:default:sensitive"
@ -11,20 +11,20 @@ struct MastodonPreferences: Codable {
case readingExpandSpoilers = "reading:expand:spoilers"
}
let postingDefaultVisibility: Status.Visibility
let postingDefaultSensitive: Bool
let postingDefaultLanguage: String?
let readingExpandMedia: ExpandMedia
let readingExpandSpoilers: Bool
public let postingDefaultVisibility: Status.Visibility
public let postingDefaultSensitive: Bool
public let postingDefaultLanguage: String?
public let readingExpandMedia: ExpandMedia
public let readingExpandSpoilers: Bool
}
extension MastodonPreferences {
public extension MastodonPreferences {
enum ExpandMedia: String, Codable, Unknowable {
case `default`
case showAll
case hideAll
case unknown
static var unknownCase: Self { .unknown }
public static var unknownCase: Self { .unknown }
}
}

View file

@ -0,0 +1,10 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Mention: Codable, Hashable {
public let url: URL
public let username: String
public let acct: String
public let id: String
}

View file

@ -0,0 +1,21 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Poll: Codable, Hashable {
public struct Option: Codable, Hashable {
public var title: String
public var votesCount: Int
}
public let id: String
public let expiresAt: Date
public let expired: Bool
public let multiple: Bool
public let votesCount: Int
public let votersCount: Int?
@DecodableDefault.False public private(set) var voted: Bool
@DecodableDefault.EmptyList public private(set) var ownVotes: [Int]
public let options: [Option]
public let emojis: [Emoji]
}

View file

@ -0,0 +1,23 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct PushNotification: Codable {
public enum NotificationType: String, Codable, Unknowable {
case mention
case reblog
case favourite
case follow
case unknown
public static var unknownCase: Self { .unknown }
}
public let accessToken: String
public let body: String
public let title: String
public let icon: URL
public let notificationId: Int
public let notificationType: NotificationType
public let preferredLocale: String
}

View file

@ -0,0 +1,26 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct PushSubscription: Codable {
public struct Alerts: Codable, Hashable {
public var follow: Bool
public var favourite: Bool
public var reblog: Bool
public var mention: Bool
@DecodableDefault.True public var poll: Bool
}
public let endpoint: URL
public let alerts: Alerts
public let serverKey: String
}
public extension PushSubscription.Alerts {
static let initial: Self = Self(
follow: true,
favourite: true,
reblog: true,
mention: true,
poll: DecodableDefault.True())
}

View file

@ -2,49 +2,49 @@
import Foundation
class Status: Codable, Identifiable {
enum Visibility: String, Codable, Unknowable {
public class Status: Codable, Identifiable {
public enum Visibility: String, Codable, Unknowable {
case `public`
case unlisted
case `private`
case direct
case unknown
static var unknownCase: Self { .unknown }
public static var unknownCase: Self { .unknown }
}
let id: String
let uri: String
let createdAt: Date
let account: Account
let content: HTML
let visibility: Visibility
let sensitive: Bool
let spoilerText: String
let mediaAttachments: [Attachment]
let mentions: [Mention]
let tags: [Tag]
let emojis: [Emoji]
let reblogsCount: Int
let favouritesCount: Int
@DecodableDefault.Zero private(set) var repliesCount: Int
let application: Application?
let url: URL?
let inReplyToId: String?
let inReplyToAccountId: String?
let reblog: Status?
let poll: Poll?
let card: Card?
let language: String?
let text: String?
@DecodableDefault.False private(set) var favourited: Bool
@DecodableDefault.False private(set) var reblogged: Bool
@DecodableDefault.False private(set) var muted: Bool
@DecodableDefault.False private(set) var bookmarked: Bool
let pinned: Bool?
public let id: String
public let uri: String
public let createdAt: Date
public let account: Account
public let content: HTML
public let visibility: Visibility
public let sensitive: Bool
public let spoilerText: String
public let mediaAttachments: [Attachment]
public let mentions: [Mention]
public let tags: [Tag]
public let emojis: [Emoji]
public let reblogsCount: Int
public let favouritesCount: Int
@DecodableDefault.Zero public private(set) var repliesCount: Int
public let application: Application?
public let url: URL?
public let inReplyToId: String?
public let inReplyToAccountId: String?
public let reblog: Status?
public let poll: Poll?
public let card: Card?
public let language: String?
public let text: String?
@DecodableDefault.False public private(set) var favourited: Bool
@DecodableDefault.False public private(set) var reblogged: Bool
@DecodableDefault.False public private(set) var muted: Bool
@DecodableDefault.False public private(set) var bookmarked: Bool
public let pinned: Bool?
// Xcode-generated memberwise initializer
init(
public init(
id: String,
uri: String,
createdAt: Date,
@ -88,7 +88,6 @@ class Status: Codable, Identifiable {
self.emojis = emojis
self.reblogsCount = reblogsCount
self.favouritesCount = favouritesCount
self.repliesCount = repliesCount
self.application = application
self.url = url
self.inReplyToId = inReplyToId
@ -98,15 +97,16 @@ class Status: Codable, Identifiable {
self.card = card
self.language = language
self.text = text
self.pinned = pinned
self.repliesCount = repliesCount
self.favourited = favourited
self.reblogged = reblogged
self.muted = muted
self.bookmarked = bookmarked
self.pinned = pinned
}
}
extension Status {
public extension Status {
var displayStatus: Status {
reblog ?? self
}
@ -121,7 +121,7 @@ extension Status {
}
extension Status: Hashable {
static func == (lhs: Status, rhs: Status) -> Bool {
public static func == (lhs: Status, rhs: Status) -> Bool {
lhs.id == rhs.id
&& lhs.uri == rhs.uri
&& lhs.createdAt == rhs.createdAt
@ -153,7 +153,7 @@ extension Status: Hashable {
&& lhs.pinned == rhs.pinned
}
func hash(into hasher: inout Hasher) {
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(uri)
hasher.combine(createdAt)

View file

@ -0,0 +1,8 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Tag: Codable, Hashable {
public let name: String
public let url: URL
}

View file

@ -2,7 +2,7 @@
import Foundation
enum Timeline: Hashable {
public enum Timeline: Hashable {
case home
case local
case federated
@ -10,7 +10,7 @@ enum Timeline: Hashable {
case tag(String)
}
extension Timeline {
public extension Timeline {
static let nonLists: [Timeline] = [.home, .local, .federated]
var endpoint: TimelinesEndpoint {
@ -30,7 +30,7 @@ extension Timeline {
}
extension Timeline: Identifiable {
var id: String {
public var id: String {
switch self {
case .home:
return "home"

View file

@ -4,22 +4,22 @@ import Foundation
import Combine
import Alamofire
typealias Session = Alamofire.Session
public typealias Session = Alamofire.Session
class HTTPClient {
public class HTTPClient {
private let session: Session
private let decoder: DataDecoder
init(session: Session, decoder: DataDecoder) {
public init(session: Session, decoder: DataDecoder) {
self.session = session
self.decoder = decoder
}
func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
public func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
requestPublisher(target).value().mapError { $0 as Error }.eraseToAnyPublisher()
}
func request<T: DecodableTarget, E: Error & Decodable>(
public func request<T: DecodableTarget, E: Error & Decodable>(
_ target: T,
decodeErrorsAs errorType: E.Type) -> AnyPublisher<T.ResultType, Error> {
let decoder = self.decoder
@ -43,12 +43,12 @@ class HTTPClient {
}
private extension HTTPClient {
private func requestPublisher<T: DecodableTarget>(_ target: T) -> DataResponsePublisher<T.ResultType> {
#if DEBUG
if let url = try? target.asURLRequest().url {
StubbingURLProtocol.setTarget(target, forURL: url)
}
#endif
func requestPublisher<T: DecodableTarget>(_ target: T) -> DataResponsePublisher<T.ResultType> {
// #if DEBUG
// if let url = try? target.asURLRequest().url {
// StubbingURLProtocol.setTarget(target, forURL: url)
// }
// #endif
return session.request(target)
.validate()

View file

@ -3,13 +3,13 @@
import Foundation
import Alamofire
typealias HTTPMethod = Alamofire.HTTPMethod
typealias HTTPHeaders = Alamofire.HTTPHeaders
typealias ParameterEncoding = Alamofire.ParameterEncoding
typealias URLEncoding = Alamofire.URLEncoding
typealias JSONEncoding = Alamofire.JSONEncoding
public typealias HTTPMethod = Alamofire.HTTPMethod
public typealias HTTPHeaders = Alamofire.HTTPHeaders
public typealias ParameterEncoding = Alamofire.ParameterEncoding
public typealias URLEncoding = Alamofire.URLEncoding
public typealias JSONEncoding = Alamofire.JSONEncoding
protocol HTTPTarget: URLRequestConvertible {
public protocol HTTPTarget: URLRequestConvertible {
var baseURL: URL { get }
var pathComponents: [String] { get }
var method: HTTPMethod { get }
@ -18,7 +18,7 @@ protocol HTTPTarget: URLRequestConvertible {
var headers: HTTPHeaders? { get }
}
extension HTTPTarget {
public extension HTTPTarget {
func asURLRequest() throws -> URLRequest {
var url = baseURL
@ -30,6 +30,6 @@ extension HTTPTarget {
}
}
protocol DecodableTarget: HTTPTarget {
public protocol DecodableTarget: HTTPTarget {
associatedtype ResultType: Decodable
}

View file

@ -2,7 +2,7 @@
import Foundation
enum AccessTokenEndpoint {
public enum AccessTokenEndpoint {
case oauthToken(
clientID: String,
clientSecret: String,
@ -14,21 +14,21 @@ enum AccessTokenEndpoint {
}
extension AccessTokenEndpoint: MastodonEndpoint {
typealias ResultType = AccessToken
public typealias ResultType = AccessToken
var context: [String] { [] }
public var context: [String] { [] }
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
["oauth", "token"]
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .oauthToken: return .post
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .oauthToken(clientID, clientSecret, code, grantType, scopes, redirectURI):
return [

View file

@ -2,24 +2,24 @@
import Foundation
enum AccountEndpoint {
public enum AccountEndpoint {
case verifyCredentials
}
extension AccountEndpoint: MastodonEndpoint {
typealias ResultType = Account
public typealias ResultType = Account
var context: [String] {
public var context: [String] {
defaultContext + ["accounts"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .verifyCredentials: return ["verify_credentials"]
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .verifyCredentials: return .get
}

View file

@ -2,26 +2,26 @@
import Foundation
enum AppAuthorizationEndpoint {
public enum AppAuthorizationEndpoint {
case apps(clientName: String, redirectURI: String, scopes: String, website: URL?)
}
extension AppAuthorizationEndpoint: MastodonEndpoint {
typealias ResultType = AppAuthorization
public typealias ResultType = AppAuthorization
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .apps: return ["apps"]
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .apps: return .post
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .apps(clientName, redirectURI, scopes, website):
var params = [

View file

@ -2,23 +2,23 @@
import Foundation
enum ContextEndpoint {
public enum ContextEndpoint {
case context(id: String)
}
extension ContextEndpoint: MastodonEndpoint {
typealias ResultType = MastodonContext
public typealias ResultType = MastodonContext
var context: [String] {
public var context: [String] {
defaultContext + ["statuses"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case let .context(id):
return [id, "context"]
}
}
var method: HTTPMethod { .get }
public var method: HTTPMethod { .get }
}

View file

@ -2,16 +2,16 @@
import Foundation
enum DeletionEndpoint {
public enum DeletionEndpoint {
case oauthRevoke(token: String, clientID: String, clientSecret: String)
case list(id: String)
case filter(id: String)
}
extension DeletionEndpoint: MastodonEndpoint {
typealias ResultType = [String: String]
public typealias ResultType = [String: String]
var context: [String] {
public var context: [String] {
switch self {
case .oauthRevoke:
return ["oauth"]
@ -22,7 +22,7 @@ extension DeletionEndpoint: MastodonEndpoint {
}
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .oauthRevoke:
return ["revoke"]
@ -31,7 +31,7 @@ extension DeletionEndpoint: MastodonEndpoint {
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .oauthRevoke:
return .post
@ -40,7 +40,7 @@ extension DeletionEndpoint: MastodonEndpoint {
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .oauthRevoke(token, clientID, clientSecret):
return ["token": token, "client_id": clientID, "client_secret": clientSecret]

View file

@ -2,7 +2,7 @@
import Foundation
enum FilterEndpoint {
public enum FilterEndpoint {
case create(
phrase: String,
context: [Filter.Context],
@ -19,13 +19,13 @@ enum FilterEndpoint {
}
extension FilterEndpoint: MastodonEndpoint {
typealias ResultType = Filter
public typealias ResultType = Filter
var context: [String] {
public var context: [String] {
defaultContext + ["filters"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .create:
return []
@ -34,7 +34,7 @@ extension FilterEndpoint: MastodonEndpoint {
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .create(phrase, context, irreversible, wholeWord, expiresIn):
return params(phrase: phrase,
@ -55,7 +55,7 @@ extension FilterEndpoint: MastodonEndpoint {
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .create:
return .post

View file

@ -2,25 +2,25 @@
import Foundation
enum FiltersEndpoint {
public enum FiltersEndpoint {
case filters
}
extension FiltersEndpoint: MastodonEndpoint {
typealias ResultType = [Filter]
public typealias ResultType = [Filter]
var context: [String] {
public var context: [String] {
defaultContext + ["filters"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .filters:
return []
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .filters:
return .get

View file

@ -2,20 +2,20 @@
import Foundation
enum InstanceEndpoint {
public enum InstanceEndpoint {
case instance
}
extension InstanceEndpoint: MastodonEndpoint {
typealias ResultType = Instance
public typealias ResultType = Instance
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .instance: return ["instance"]
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .instance: return .get
}

View file

@ -2,32 +2,32 @@
import Foundation
enum ListEndpoint {
public enum ListEndpoint {
case create(title: String)
}
extension ListEndpoint: MastodonEndpoint {
typealias ResultType = MastodonList
public typealias ResultType = MastodonList
var context: [String] {
public var context: [String] {
defaultContext + ["lists"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .create:
return []
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .create(title):
return ["title": title]
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .create:
return .post

View file

@ -2,18 +2,18 @@
import Foundation
enum ListsEndpoint {
public enum ListsEndpoint {
case lists
}
extension ListsEndpoint: MastodonEndpoint {
typealias ResultType = [MastodonList]
public typealias ResultType = [MastodonList]
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
["lists"]
}
var method: HTTPMethod {
public var method: HTTPMethod {
.get
}
}

View file

@ -0,0 +1,46 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct Paged<T: MastodonEndpoint> {
public let endpoint: T
public let maxID: String?
public let minID: String?
public let sinceID: String?
public let limit: Int?
public init(_ endpoint: T, maxID: String? = nil, minID: String? = nil, sinceID: String? = nil, limit: Int? = nil) {
self.endpoint = endpoint
self.maxID = maxID
self.minID = minID
self.sinceID = sinceID
self.limit = limit
}
}
extension Paged: MastodonEndpoint {
public typealias ResultType = T.ResultType
public var APIVersion: String { endpoint.APIVersion }
public var context: [String] { endpoint.context }
public var pathComponentsInContext: [String] { endpoint.pathComponentsInContext }
public var method: HTTPMethod { endpoint.method }
public var encoding: ParameterEncoding { endpoint.encoding }
public var parameters: [String: Any]? {
var parameters = endpoint.parameters ?? [String: Any]()
parameters["max_id"] = maxID
parameters["min_id"] = minID
parameters["since_id"] = sinceID
parameters["limit"] = limit
return parameters
}
public var headers: HTTPHeaders? { endpoint.headers }
}

View file

@ -2,20 +2,20 @@
import Foundation
enum PreferencesEndpoint {
public enum PreferencesEndpoint {
case preferences
}
extension PreferencesEndpoint: MastodonEndpoint {
typealias ResultType = MastodonPreferences
public typealias ResultType = MastodonPreferences
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .preferences: return ["preferences"]
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .preferences: return .get
}

View file

@ -2,7 +2,7 @@
import Foundation
enum PushSubscriptionEndpoint {
public enum PushSubscriptionEndpoint {
case create(
endpoint: URL,
publicKey: String,
@ -14,15 +14,15 @@ enum PushSubscriptionEndpoint {
}
extension PushSubscriptionEndpoint: MastodonEndpoint {
typealias ResultType = PushSubscription
public typealias ResultType = PushSubscription
var context: [String] {
public var context: [String] {
defaultContext + ["push", "subscription"]
}
var pathComponentsInContext: [String] { [] }
public var pathComponentsInContext: [String] { [] }
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .create: return .post
case .read: return .get
@ -31,7 +31,7 @@ extension PushSubscriptionEndpoint: MastodonEndpoint {
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .create(endpoint, publicKey, auth, alerts):
return ["subscription":

View file

@ -2,20 +2,20 @@
import Foundation
enum StatusEndpoint {
public enum StatusEndpoint {
case status(id: String)
case favourite(id: String)
case unfavourite(id: String)
}
extension StatusEndpoint: MastodonEndpoint {
typealias ResultType = Status
public typealias ResultType = Status
var context: [String] {
public var context: [String] {
defaultContext + ["statuses"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case let .status(id):
return [id]
@ -26,7 +26,7 @@ extension StatusEndpoint: MastodonEndpoint {
}
}
var method: HTTPMethod {
public var method: HTTPMethod {
switch self {
case .status:
return .get

View file

@ -2,7 +2,7 @@
import Foundation
enum TimelinesEndpoint {
public enum TimelinesEndpoint {
case `public`(local: Bool)
case tag(String)
case home
@ -10,13 +10,13 @@ enum TimelinesEndpoint {
}
extension TimelinesEndpoint: MastodonEndpoint {
typealias ResultType = [Status]
public typealias ResultType = [Status]
var context: [String] {
public var context: [String] {
defaultContext + ["timelines"]
}
var pathComponentsInContext: [String] {
public var pathComponentsInContext: [String] {
switch self {
case .public:
return ["public"]
@ -29,7 +29,7 @@ extension TimelinesEndpoint: MastodonEndpoint {
}
}
var parameters: [String: Any]? {
public var parameters: [String: Any]? {
switch self {
case let .public(local):
return ["local": local]
@ -38,5 +38,5 @@ extension TimelinesEndpoint: MastodonEndpoint {
}
}
var method: HTTPMethod { .get }
public var method: HTTPMethod { .get }
}

View file

@ -0,0 +1,29 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Combine
public class MastodonClient: HTTPClient {
public var instanceURL: URL?
public var accessToken: String?
public required init(session: Session) {
super.init(session: session, decoder: MastodonDecoder())
}
public override func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
super.request(target, decodeErrorsAs: MastodonError.self)
}
}
extension MastodonClient {
public func request<E: MastodonEndpoint>(_ endpoint: E) -> AnyPublisher<E.ResultType, Error> {
guard let instanceURL = instanceURL else {
return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
}
return super.request(
MastodonTarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
decodeErrorsAs: MastodonError.self)
}
}

View file

@ -2,13 +2,13 @@
import Foundation
class MastodonDecoder: JSONDecoder {
override init() {
public class MastodonDecoder: JSONDecoder {
public override init() {
super.init()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = MastodonAPI.dateFormat
dateFormatter.dateFormat = Constants.dateFormat
dateDecodingStrategy = .formatted(dateFormatter)
keyDecodingStrategy = .convertFromSnakeCase
}

View file

@ -2,13 +2,13 @@
import Foundation
class MastodonEncoder: JSONEncoder {
override init() {
public class MastodonEncoder: JSONEncoder {
public override init() {
super.init()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = MastodonAPI.dateFormat
dateFormatter.dateFormat = Constants.dateFormat
dateEncodingStrategy = .formatted(dateFormatter)
keyEncodingStrategy = .convertToSnakeCase
outputFormatting = .sortedKeys

View file

@ -2,7 +2,7 @@
import Foundation
protocol MastodonEndpoint {
public protocol MastodonEndpoint {
associatedtype ResultType: Decodable
var APIVersion: String { get }
var context: [String] { get }
@ -13,7 +13,7 @@ protocol MastodonEndpoint {
var headers: HTTPHeaders? { get }
}
extension MastodonEndpoint {
public extension MastodonEndpoint {
var defaultContext: [String] {
["api", APIVersion]
}

View file

@ -0,0 +1,41 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
public struct MastodonTarget<E: MastodonEndpoint> {
public let baseURL: URL
public let endpoint: E
public let accessToken: String?
public init(baseURL: URL, endpoint: E, accessToken: String?) {
self.baseURL = baseURL
self.endpoint = endpoint
self.accessToken = accessToken
}
}
extension MastodonTarget: DecodableTarget {
public typealias ResultType = E.ResultType
public var pathComponents: [String] { endpoint.pathComponents }
public var method: HTTPMethod { endpoint.method }
public var encoding: ParameterEncoding { endpoint.encoding }
public var parameters: [String: Any]? { endpoint.parameters }
public var headers: HTTPHeaders? {
var headers = endpoint.headers
if let accessToken = accessToken {
if headers == nil {
headers = HTTPHeaders()
}
headers?.add(.authorization(bearerToken: accessToken))
}
return headers
}
}

View file

@ -4,64 +4,66 @@ import Foundation
// Thank you https://www.swiftbysundell.com/tips/default-decoding-values/
protocol DecodableDefaultSource {
public protocol DecodableDefaultSource {
associatedtype Value: Decodable
static var defaultValue: Value { get }
}
enum DecodableDefault {}
public enum DecodableDefault {}
// swiftlint:disable nesting
extension DecodableDefault {
@propertyWrapper
struct Wrapper<Source: DecodableDefaultSource> {
typealias Value = Source.Value
var wrappedValue = Source.defaultValue
public struct Wrapper<Source: DecodableDefaultSource> {
public typealias Value = Source.Value
public var wrappedValue = Source.defaultValue
public init() {}
}
}
extension DecodableDefault {
public extension DecodableDefault {
typealias Source = DecodableDefaultSource
typealias List = Decodable & ExpressibleByArrayLiteral
typealias Map = Decodable & ExpressibleByDictionaryLiteral
enum Sources {
enum True: Source {
static var defaultValue: Bool { true }
public enum True: Source {
public static var defaultValue: Bool { true }
}
enum False: Source {
static var defaultValue: Bool { false }
public enum False: Source {
public static var defaultValue: Bool { false }
}
enum EmptyString: Source {
static var defaultValue: String { "" }
public enum EmptyString: Source {
public static var defaultValue: String { "" }
}
enum EmptyList<T: List>: Source {
static var defaultValue: T { [] }
public enum EmptyList<T: List>: Source {
public static var defaultValue: T { [] }
}
enum EmptyMap<T: Map>: Source {
static var defaultValue: T { [:] }
public enum EmptyMap<T: Map>: Source {
public static var defaultValue: T { [:] }
}
enum Zero: Source {
static var defaultValue: Int { 0 }
public enum Zero: Source {
public static var defaultValue: Int { 0 }
}
enum StatusVisibilityPublic: Source {
static var defaultValue: Status.Visibility { .public }
public enum StatusVisibilityPublic: Source {
public static var defaultValue: Status.Visibility { .public }
}
enum ExpandMediaDefault: Source {
static var defaultValue: MastodonPreferences.ExpandMedia { .default }
public enum ExpandMediaDefault: Source {
public static var defaultValue: MastodonPreferences.ExpandMedia { .default }
}
}
}
// swiftlint:enable nesting
extension DecodableDefault {
public extension DecodableDefault {
typealias True = Wrapper<Sources.True>
typealias False = Wrapper<Sources.False>
typealias EmptyString = Wrapper<Sources.EmptyString>
@ -73,7 +75,7 @@ extension DecodableDefault {
}
extension DecodableDefault.Wrapper: Decodable {
init(from decoder: Decoder) throws {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Value.self)
}
@ -83,13 +85,13 @@ extension DecodableDefault.Wrapper: Equatable where Value: Equatable {}
extension DecodableDefault.Wrapper: Hashable where Value: Hashable {}
extension DecodableDefault.Wrapper: Encodable where Value: Encodable {
func encode(to encoder: Encoder) throws {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
extension KeyedDecodingContainer {
public extension KeyedDecodingContainer {
func decode<T>(_ type: DecodableDefault.Wrapper<T>.Type,
forKey key: Key) throws -> DecodableDefault.Wrapper<T> {
try decodeIfPresent(type, forKey: key) ?? .init()

View file

@ -2,16 +2,14 @@
import Foundation
protocol Unknowable: RawRepresentable, CaseIterable where RawValue: Equatable {
public protocol Unknowable: RawRepresentable, CaseIterable where RawValue: Equatable {
static var unknownCase: Self { get }
}
extension Unknowable {
public extension Unknowable {
init(rawValue: RawValue) {
self = Self.allCases.first { $0.rawValue == rawValue } ?? Self.unknownCase
}
}
extension Unknowable {
static var allCasesExceptUnknown: [Self] { allCases.filter { $0 != unknownCase } }
}

View file

@ -0,0 +1,7 @@
import XCTest
import MastodonTests
var tests = [XCTestCaseEntry]()
tests += MastodonTests.allTests()
XCTMain(tests)

View file

@ -0,0 +1,15 @@
import XCTest
@testable import Mastodon
final class MastodonTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(Mastodon().text, "Hello, World!")
}
static var allTests = [
("testExample", testExample)
]
}

View file

@ -0,0 +1,9 @@
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(MastodonTests.allTests)
]
}
#endif

View file

@ -27,17 +27,11 @@
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; };
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */; };
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
D0BEB1F524F9A216001B0F04 /* Paged.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F424F9A216001B0F04 /* Paged.swift */; };
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
D0BEB1F924F9D627001B0F04 /* ListsEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F824F9D627001B0F04 /* ListsEndpoint.swift */; };
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */; };
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
D0BEB20124FA0220001B0F04 /* ListEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20024FA0220001B0F04 /* ListEndpoint.swift */; };
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */; };
D0BEB20924FA1136001B0F04 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20824FA1136001B0F04 /* Filter.swift */; };
D0BEB20B24FA12D8001B0F04 /* FiltersEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20A24FA12D8001B0F04 /* FiltersEndpoint.swift */; };
D0BEB20D24FA193A001B0F04 /* FilterEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20C24FA193A001B0F04 /* FilterEndpoint.swift */; };
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */; };
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */; };
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
@ -51,32 +45,10 @@
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; };
D0C7D4A624F7616A001EBDBB /* DecodableDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43324F76169001EBDBB /* DecodableDefault.swift */; };
D0C7D4A924F7616A001EBDBB /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43924F76169001EBDBB /* Mention.swift */; };
D0C7D4AA24F7616A001EBDBB /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43A24F76169001EBDBB /* Attachment.swift */; };
D0C7D4AB24F7616A001EBDBB /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43B24F76169001EBDBB /* Identity.swift */; };
D0C7D4AC24F7616A001EBDBB /* Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43C24F76169001EBDBB /* Poll.swift */; };
D0C7D4AD24F7616A001EBDBB /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43D24F76169001EBDBB /* AccessToken.swift */; };
D0C7D4AE24F7616A001EBDBB /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43E24F76169001EBDBB /* Timeline.swift */; };
D0C7D4AF24F7616A001EBDBB /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43F24F76169001EBDBB /* PushNotification.swift */; };
D0C7D4B024F7616A001EBDBB /* PushSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44024F76169001EBDBB /* PushSubscription.swift */; };
D0C7D4B124F7616A001EBDBB /* Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44124F76169001EBDBB /* Card.swift */; };
D0C7D4B224F7616A001EBDBB /* HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44224F76169001EBDBB /* HTML.swift */; };
D0C7D4B324F7616A001EBDBB /* MastodonError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44324F76169001EBDBB /* MastodonError.swift */; };
D0C7D4B424F7616A001EBDBB /* MastodonContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44424F76169001EBDBB /* MastodonContext.swift */; };
D0C7D4B524F7616A001EBDBB /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44524F76169001EBDBB /* Instance.swift */; };
D0C7D4B624F7616A001EBDBB /* ListTimeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44624F76169001EBDBB /* ListTimeline.swift */; };
D0C7D4B724F7616A001EBDBB /* TransientStatusCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */; };
D0C7D4B824F7616A001EBDBB /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44824F76169001EBDBB /* Application.swift */; };
D0C7D4B924F7616A001EBDBB /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44924F76169001EBDBB /* Status.swift */; };
D0C7D4BA24F7616A001EBDBB /* AppAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44A24F76169001EBDBB /* AppAuthorization.swift */; };
D0C7D4BB24F7616A001EBDBB /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44B24F76169001EBDBB /* Emoji.swift */; };
D0C7D4BC24F7616A001EBDBB /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44C24F76169001EBDBB /* Tag.swift */; };
D0C7D4BD24F7616A001EBDBB /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44D24F76169001EBDBB /* Account.swift */; };
D0C7D4BE24F7616A001EBDBB /* Unknowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44E24F76169001EBDBB /* Unknowable.swift */; };
D0C7D4BF24F7616A001EBDBB /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */; };
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45024F76169001EBDBB /* AlertItem.swift */; };
D0C7D4C124F7616A001EBDBB /* MastodonPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45124F76169001EBDBB /* MastodonPreferences.swift */; };
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45524F76169001EBDBB /* AppDelegate.swift */; };
@ -104,26 +76,7 @@
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47024F76169001EBDBB /* Date+Extensions.swift */; };
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47124F76169001EBDBB /* Data+Extensions.swift */; };
D0C7D4DD24F7616A001EBDBB /* CodingUserInfoKey+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47224F76169001EBDBB /* CodingUserInfoKey+Extensions.swift */; };
D0C7D4DE24F7616A001EBDBB /* HTTPTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47424F76169001EBDBB /* HTTPTarget.swift */; };
D0C7D4DF24F7616A001EBDBB /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47524F76169001EBDBB /* HTTPClient.swift */; };
D0C7D4E024F7616A001EBDBB /* WebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47624F76169001EBDBB /* WebAuthSession.swift */; };
D0C7D4E124F7616A001EBDBB /* MastodonDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47824F76169001EBDBB /* MastodonDecoder.swift */; };
D0C7D4E224F7616A001EBDBB /* MastodonEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47924F76169001EBDBB /* MastodonEndpoint.swift */; };
D0C7D4E324F7616A001EBDBB /* PushSubscriptionEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47B24F76169001EBDBB /* PushSubscriptionEndpoint.swift */; };
D0C7D4E424F7616A001EBDBB /* PreferencesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47C24F76169001EBDBB /* PreferencesEndpoint.swift */; };
D0C7D4E524F7616A001EBDBB /* InstanceEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47D24F76169001EBDBB /* InstanceEndpoint.swift */; };
D0C7D4E624F7616A001EBDBB /* TimelinesEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47E24F76169001EBDBB /* TimelinesEndpoint.swift */; };
D0C7D4E724F7616A001EBDBB /* AppAuthorizationEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47F24F76169001EBDBB /* AppAuthorizationEndpoint.swift */; };
D0C7D4E824F7616A001EBDBB /* AccountEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48024F76169001EBDBB /* AccountEndpoint.swift */; };
D0C7D4E924F7616A001EBDBB /* AccessTokenEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48124F76169001EBDBB /* AccessTokenEndpoint.swift */; };
D0C7D4EA24F7616A001EBDBB /* DeletionEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48224F76169001EBDBB /* DeletionEndpoint.swift */; };
D0C7D4EB24F7616A001EBDBB /* ContextEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48324F76169001EBDBB /* ContextEndpoint.swift */; };
D0C7D4EC24F7616A001EBDBB /* StatusEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48424F76169001EBDBB /* StatusEndpoint.swift */; };
D0C7D4ED24F7616A001EBDBB /* MastodonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48524F76169001EBDBB /* MastodonAPI.swift */; };
D0C7D4EE24F7616A001EBDBB /* MastodonEncoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48624F76169001EBDBB /* MastodonEncoder.swift */; };
D0C7D4EF24F7616A001EBDBB /* MastodonClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48724F76169001EBDBB /* MastodonClient.swift */; };
D0C7D4F024F7616A001EBDBB /* MastodonTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48824F76169001EBDBB /* MastodonTarget.swift */; };
D0C7D4F124F7616A001EBDBB /* IdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48A24F76169001EBDBB /* IdentityService.swift */; };
D0C7D4F224F7616A001EBDBB /* TimelineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48C24F76169001EBDBB /* TimelineService.swift */; };
D0C7D4F324F7616A001EBDBB /* ContextService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48D24F76169001EBDBB /* ContextService.swift */; };
@ -134,13 +87,9 @@
D0C7D4F824F7616A001EBDBB /* SecretsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49224F7616A001EBDBB /* SecretsService.swift */; };
D0C7D4F924F7616A001EBDBB /* UserNotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49324F7616A001EBDBB /* UserNotificationService.swift */; };
D0C7D4FA24F7616A001EBDBB /* AllIdentitiesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49424F7616A001EBDBB /* AllIdentitiesService.swift */; };
D0C7D4FB24F7619F001EBDBB /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43F24F76169001EBDBB /* PushNotification.swift */; };
D0C7D4FC24F761A8001EBDBB /* Unknowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44E24F76169001EBDBB /* Unknowable.swift */; };
D0C7D4FD24F761C1001EBDBB /* MastodonDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47824F76169001EBDBB /* MastodonDecoder.swift */; };
D0C7D4FE24F761C9001EBDBB /* SecretsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49224F7616A001EBDBB /* SecretsService.swift */; };
D0C7D4FF24F761D0001EBDBB /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49024F7616A001EBDBB /* KeychainService.swift */; };
D0C7D50024F761E0001EBDBB /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */; };
D0C7D50124F761EC001EBDBB /* MastodonAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48524F76169001EBDBB /* MastodonAPI.swift */; };
D0DC174624CFEC2000A75C65 /* StubbingURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC174524CFEC2000A75C65 /* StubbingURLProtocol.swift */; };
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC174924CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift */; };
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC174C24CFF1F100A75C65 /* Stubbing.swift */; };
@ -149,6 +98,8 @@
D0DC175824D0130800A75C65 /* HTTPStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC175724D0130800A75C65 /* HTTPStubs.swift */; };
D0DC175F24D016EA00A75C65 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = D0DC175E24D016EA00A75C65 /* Alamofire */; };
D0DC177724D0CF2600A75C65 /* MockKeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC177624D0CF2600A75C65 /* MockKeychainService.swift */; };
D0E0F1E624FC4B76002C04BF /* Mastodon in Frameworks */ = {isa = PBXBuildFile; productRef = D0E0F1E524FC4B76002C04BF /* Mastodon */; };
D0E0F1E824FC5A61002C04BF /* Mastodon in Frameworks */ = {isa = PBXBuildFile; productRef = D0E0F1E724FC5A61002C04BF /* Mastodon */; };
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */; };
D0E5362024E3EB4D00FB1CE1 /* Notification Service Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D0EC8DD424DFE38900A08489 /* AuthenticationServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */; };
@ -207,17 +158,11 @@
D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+Extensions.swift"; sourceTree = "<group>"; };
D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreferencesEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
D0BEB1F424F9A216001B0F04 /* Paged.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Paged.swift; sourceTree = "<group>"; };
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
D0BEB1F824F9D627001B0F04 /* ListsEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsEndpoint.swift; sourceTree = "<group>"; };
D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsViewModel.swift; sourceTree = "<group>"; };
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
D0BEB20024FA0220001B0F04 /* ListEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListEndpoint.swift; sourceTree = "<group>"; };
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
D0BEB20824FA1136001B0F04 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
D0BEB20A24FA12D8001B0F04 /* FiltersEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersEndpoint.swift; sourceTree = "<group>"; };
D0BEB20C24FA193A001B0F04 /* FilterEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterEndpoint.swift; sourceTree = "<group>"; };
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterViewModel.swift; sourceTree = "<group>"; };
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
@ -233,32 +178,10 @@
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; };
D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = "<group>"; };
D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = "<group>"; };
D0C7D43324F76169001EBDBB /* DecodableDefault.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DecodableDefault.swift; sourceTree = "<group>"; };
D0C7D43924F76169001EBDBB /* Mention.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mention.swift; sourceTree = "<group>"; };
D0C7D43A24F76169001EBDBB /* Attachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
D0C7D43B24F76169001EBDBB /* Identity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
D0C7D43C24F76169001EBDBB /* Poll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Poll.swift; sourceTree = "<group>"; };
D0C7D43D24F76169001EBDBB /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = "<group>"; };
D0C7D43E24F76169001EBDBB /* Timeline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
D0C7D43F24F76169001EBDBB /* PushNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = "<group>"; };
D0C7D44024F76169001EBDBB /* PushSubscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushSubscription.swift; sourceTree = "<group>"; };
D0C7D44124F76169001EBDBB /* Card.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Card.swift; sourceTree = "<group>"; };
D0C7D44224F76169001EBDBB /* HTML.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTML.swift; sourceTree = "<group>"; };
D0C7D44324F76169001EBDBB /* MastodonError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonError.swift; sourceTree = "<group>"; };
D0C7D44424F76169001EBDBB /* MastodonContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonContext.swift; sourceTree = "<group>"; };
D0C7D44524F76169001EBDBB /* Instance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
D0C7D44624F76169001EBDBB /* ListTimeline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListTimeline.swift; sourceTree = "<group>"; };
D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransientStatusCollection.swift; sourceTree = "<group>"; };
D0C7D44824F76169001EBDBB /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
D0C7D44924F76169001EBDBB /* Status.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
D0C7D44A24F76169001EBDBB /* AppAuthorization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAuthorization.swift; sourceTree = "<group>"; };
D0C7D44B24F76169001EBDBB /* Emoji.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = "<group>"; };
D0C7D44C24F76169001EBDBB /* Tag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
D0C7D44D24F76169001EBDBB /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
D0C7D44E24F76169001EBDBB /* Unknowable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unknowable.swift; sourceTree = "<group>"; };
D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = "<group>"; };
D0C7D45024F76169001EBDBB /* AlertItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = "<group>"; };
D0C7D45124F76169001EBDBB /* MastodonPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonPreferences.swift; sourceTree = "<group>"; };
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
D0C7D45524F76169001EBDBB /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -286,26 +209,7 @@
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47224F76169001EBDBB /* CodingUserInfoKey+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CodingUserInfoKey+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47424F76169001EBDBB /* HTTPTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPTarget.swift; sourceTree = "<group>"; };
D0C7D47524F76169001EBDBB /* HTTPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = "<group>"; };
D0C7D47624F76169001EBDBB /* WebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAuthSession.swift; sourceTree = "<group>"; };
D0C7D47824F76169001EBDBB /* MastodonDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonDecoder.swift; sourceTree = "<group>"; };
D0C7D47924F76169001EBDBB /* MastodonEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonEndpoint.swift; sourceTree = "<group>"; };
D0C7D47B24F76169001EBDBB /* PushSubscriptionEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushSubscriptionEndpoint.swift; sourceTree = "<group>"; };
D0C7D47C24F76169001EBDBB /* PreferencesEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesEndpoint.swift; sourceTree = "<group>"; };
D0C7D47D24F76169001EBDBB /* InstanceEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstanceEndpoint.swift; sourceTree = "<group>"; };
D0C7D47E24F76169001EBDBB /* TimelinesEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelinesEndpoint.swift; sourceTree = "<group>"; };
D0C7D47F24F76169001EBDBB /* AppAuthorizationEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppAuthorizationEndpoint.swift; sourceTree = "<group>"; };
D0C7D48024F76169001EBDBB /* AccountEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountEndpoint.swift; sourceTree = "<group>"; };
D0C7D48124F76169001EBDBB /* AccessTokenEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessTokenEndpoint.swift; sourceTree = "<group>"; };
D0C7D48224F76169001EBDBB /* DeletionEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeletionEndpoint.swift; sourceTree = "<group>"; };
D0C7D48324F76169001EBDBB /* ContextEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextEndpoint.swift; sourceTree = "<group>"; };
D0C7D48424F76169001EBDBB /* StatusEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusEndpoint.swift; sourceTree = "<group>"; };
D0C7D48524F76169001EBDBB /* MastodonAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonAPI.swift; sourceTree = "<group>"; };
D0C7D48624F76169001EBDBB /* MastodonEncoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonEncoder.swift; sourceTree = "<group>"; };
D0C7D48724F76169001EBDBB /* MastodonClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonClient.swift; sourceTree = "<group>"; };
D0C7D48824F76169001EBDBB /* MastodonTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonTarget.swift; sourceTree = "<group>"; };
D0C7D48A24F76169001EBDBB /* IdentityService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityService.swift; sourceTree = "<group>"; };
D0C7D48C24F76169001EBDBB /* TimelineService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineService.swift; sourceTree = "<group>"; };
D0C7D48D24F76169001EBDBB /* ContextService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextService.swift; sourceTree = "<group>"; };
@ -323,6 +227,7 @@
D0DC175424D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessTokenEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D0DC175724D0130800A75C65 /* HTTPStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStubs.swift; sourceTree = "<group>"; };
D0DC177624D0CF2600A75C65 /* MockKeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeychainService.swift; sourceTree = "<group>"; };
D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = "<group>"; };
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@ -337,6 +242,7 @@
buildActionMask = 2147483647;
files = (
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
D0E0F1E624FC4B76002C04BF /* Mastodon in Frameworks */,
D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */,
D0DC175F24D016EA00A75C65 /* Alamofire in Frameworks */,
);
@ -354,6 +260,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D0E0F1E824FC5A61002C04BF /* Mastodon in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -382,6 +289,7 @@
D047FA7F24C3E21000AF17C5 = {
isa = PBXGroup;
children = (
D0E0F1E424FC49FC002C04BF /* Mastodon */,
D0C7D45224F76169001EBDBB /* Assets.xcassets */,
D0C7D46424F76169001EBDBB /* Databases */,
D0ED1BB224CE3A1600B4899C /* Development Assets */,
@ -392,7 +300,6 @@
D0C7D47324F76169001EBDBB /* Networking */,
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
D047FA8D24C3E21200AF17C5 /* Products */,
D0C7D43224F76169001EBDBB /* Property Wrappers */,
D0C7D48924F76169001EBDBB /* Services */,
D0C7D41D24F76169001EBDBB /* Supporting Files */,
D0C7D45324F76169001EBDBB /* System */,
@ -470,43 +377,13 @@
path = "View Controllers";
sourceTree = "<group>";
};
D0C7D43224F76169001EBDBB /* Property Wrappers */ = {
isa = PBXGroup;
children = (
D0C7D43324F76169001EBDBB /* DecodableDefault.swift */,
);
path = "Property Wrappers";
sourceTree = "<group>";
};
D0C7D43824F76169001EBDBB /* Model */ = {
isa = PBXGroup;
children = (
D0C7D43D24F76169001EBDBB /* AccessToken.swift */,
D0C7D44D24F76169001EBDBB /* Account.swift */,
D0C7D45024F76169001EBDBB /* AlertItem.swift */,
D0C7D44A24F76169001EBDBB /* AppAuthorization.swift */,
D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */,
D0C7D44824F76169001EBDBB /* Application.swift */,
D0C7D43A24F76169001EBDBB /* Attachment.swift */,
D0C7D44124F76169001EBDBB /* Card.swift */,
D0C7D44B24F76169001EBDBB /* Emoji.swift */,
D0BEB20824FA1136001B0F04 /* Filter.swift */,
D0C7D44224F76169001EBDBB /* HTML.swift */,
D0C7D43B24F76169001EBDBB /* Identity.swift */,
D0C7D44524F76169001EBDBB /* Instance.swift */,
D0C7D44624F76169001EBDBB /* ListTimeline.swift */,
D0C7D44424F76169001EBDBB /* MastodonContext.swift */,
D0C7D44324F76169001EBDBB /* MastodonError.swift */,
D0C7D45124F76169001EBDBB /* MastodonPreferences.swift */,
D0C7D43924F76169001EBDBB /* Mention.swift */,
D0C7D43C24F76169001EBDBB /* Poll.swift */,
D0C7D43F24F76169001EBDBB /* PushNotification.swift */,
D0C7D44024F76169001EBDBB /* PushSubscription.swift */,
D0C7D44924F76169001EBDBB /* Status.swift */,
D0C7D44C24F76169001EBDBB /* Tag.swift */,
D0C7D43E24F76169001EBDBB /* Timeline.swift */,
D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */,
D0C7D44E24F76169001EBDBB /* Unknowable.swift */,
);
path = Model;
sourceTree = "<group>";
@ -563,7 +440,6 @@
D0C7D46824F76169001EBDBB /* Extensions */ = {
isa = PBXGroup;
children = (
D0C7D47224F76169001EBDBB /* CodingUserInfoKey+Extensions.swift */,
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */,
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
@ -580,50 +456,11 @@
D0C7D47324F76169001EBDBB /* Networking */ = {
isa = PBXGroup;
children = (
D0C7D47524F76169001EBDBB /* HTTPClient.swift */,
D0C7D47424F76169001EBDBB /* HTTPTarget.swift */,
D0C7D47724F76169001EBDBB /* Mastodon API */,
D0C7D47624F76169001EBDBB /* WebAuthSession.swift */,
);
path = Networking;
sourceTree = "<group>";
};
D0C7D47724F76169001EBDBB /* Mastodon API */ = {
isa = PBXGroup;
children = (
D0C7D47A24F76169001EBDBB /* Endpoints */,
D0C7D48524F76169001EBDBB /* MastodonAPI.swift */,
D0C7D48724F76169001EBDBB /* MastodonClient.swift */,
D0C7D47824F76169001EBDBB /* MastodonDecoder.swift */,
D0C7D48624F76169001EBDBB /* MastodonEncoder.swift */,
D0C7D47924F76169001EBDBB /* MastodonEndpoint.swift */,
D0C7D48824F76169001EBDBB /* MastodonTarget.swift */,
);
path = "Mastodon API";
sourceTree = "<group>";
};
D0C7D47A24F76169001EBDBB /* Endpoints */ = {
isa = PBXGroup;
children = (
D0C7D48124F76169001EBDBB /* AccessTokenEndpoint.swift */,
D0C7D48024F76169001EBDBB /* AccountEndpoint.swift */,
D0C7D47F24F76169001EBDBB /* AppAuthorizationEndpoint.swift */,
D0C7D48324F76169001EBDBB /* ContextEndpoint.swift */,
D0C7D48224F76169001EBDBB /* DeletionEndpoint.swift */,
D0BEB20C24FA193A001B0F04 /* FilterEndpoint.swift */,
D0BEB20A24FA12D8001B0F04 /* FiltersEndpoint.swift */,
D0C7D47D24F76169001EBDBB /* InstanceEndpoint.swift */,
D0BEB20024FA0220001B0F04 /* ListEndpoint.swift */,
D0BEB1F824F9D627001B0F04 /* ListsEndpoint.swift */,
D0BEB1F424F9A216001B0F04 /* Paged.swift */,
D0C7D47C24F76169001EBDBB /* PreferencesEndpoint.swift */,
D0C7D47B24F76169001EBDBB /* PushSubscriptionEndpoint.swift */,
D0C7D48424F76169001EBDBB /* StatusEndpoint.swift */,
D0C7D47E24F76169001EBDBB /* TimelinesEndpoint.swift */,
);
path = Endpoints;
sourceTree = "<group>";
};
D0C7D48924F76169001EBDBB /* Services */ = {
isa = PBXGroup;
children = (
@ -731,6 +568,7 @@
D0666A4824C6C1A300F3F04B /* GRDB */,
D0DC175E24D016EA00A75C65 /* Alamofire */,
D06B492224D4611300642749 /* KingfisherSwiftUI */,
D0E0F1E524FC4B76002C04BF /* Mastodon */,
);
productName = "Metatext (iOS)";
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
@ -771,6 +609,7 @@
);
name = "Notification Service Extension";
packageProductDependencies = (
D0E0F1E724FC5A61002C04BF /* Mastodon */,
);
productName = "Notification Service Extension";
productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */;
@ -880,16 +719,9 @@
buildActionMask = 2147483647;
files = (
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */,
D0C7D4EE24F7616A001EBDBB /* MastodonEncoder.swift in Sources */,
D0C7D4E624F7616A001EBDBB /* TimelinesEndpoint.swift in Sources */,
D0C7D4B924F7616A001EBDBB /* Status.swift in Sources */,
D0C7D4F424F7616A001EBDBB /* StatusListService.swift in Sources */,
D0C7D4B824F7616A001EBDBB /* Application.swift in Sources */,
D0C7D4E724F7616A001EBDBB /* AppAuthorizationEndpoint.swift in Sources */,
D0C7D4EA24F7616A001EBDBB /* DeletionEndpoint.swift in Sources */,
D0C7D4B724F7616A001EBDBB /* TransientStatusCollection.swift in Sources */,
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */,
D0C7D4E824F7616A001EBDBB /* AccountEndpoint.swift in Sources */,
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
D0C7D4D224F7616A001EBDBB /* ContentDatabase.swift in Sources */,
@ -900,55 +732,33 @@
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */,
D03658D124EDD80900AC17EC /* ContextEndpoint+Stubbing.swift in Sources */,
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
D0BEB1F524F9A216001B0F04 /* Paged.swift in Sources */,
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
D0BEB20D24FA193A001B0F04 /* FilterEndpoint.swift in Sources */,
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
D0BEB20924FA1136001B0F04 /* Filter.swift in Sources */,
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */,
D0C7D4E024F7616A001EBDBB /* WebAuthSession.swift in Sources */,
D0C7D4F024F7616A001EBDBB /* MastodonTarget.swift in Sources */,
D0C7D4A624F7616A001EBDBB /* DecodableDefault.swift in Sources */,
D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */,
D0C7D4BC24F7616A001EBDBB /* Tag.swift in Sources */,
D0C7D4E324F7616A001EBDBB /* PushSubscriptionEndpoint.swift in Sources */,
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */,
D0C7D4D124F7616A001EBDBB /* IdentityDatabase.swift in Sources */,
D0C7D4ED24F7616A001EBDBB /* MastodonAPI.swift in Sources */,
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
D05494FA24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */,
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */,
D0C7D4A924F7616A001EBDBB /* Mention.swift in Sources */,
D0DC175524D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift in Sources */,
D0C7D4B524F7616A001EBDBB /* Instance.swift in Sources */,
D0C7D4D324F7616A001EBDBB /* DatabaseError.swift in Sources */,
D0C7D4E224F7616A001EBDBB /* MastodonEndpoint.swift in Sources */,
D0C7D4F224F7616A001EBDBB /* TimelineService.swift in Sources */,
D0C7D4BF24F7616A001EBDBB /* AppEnvironment.swift in Sources */,
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */,
D0C7D4AC24F7616A001EBDBB /* Poll.swift in Sources */,
D0C7D4BD24F7616A001EBDBB /* Account.swift in Sources */,
D0C7D4EB24F7616A001EBDBB /* ContextEndpoint.swift in Sources */,
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
D0C7D4DF24F7616A001EBDBB /* HTTPClient.swift in Sources */,
D0C7D4D424F7616A001EBDBB /* NSError+Extensions.swift in Sources */,
D052BBCA24D74C9200A80A7A /* MockUserDefaults.swift in Sources */,
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */,
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */,
D0C7D4EF24F7616A001EBDBB /* MastodonClient.swift in Sources */,
D0C7D4B224F7616A001EBDBB /* HTML.swift in Sources */,
D0C7D4B324F7616A001EBDBB /* MastodonError.swift in Sources */,
D0C7D4E924F7616A001EBDBB /* AccessTokenEndpoint.swift in Sources */,
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
D0BEB20124FA0220001B0F04 /* ListEndpoint.swift in Sources */,
D0C7D4BA24F7616A001EBDBB /* AppAuthorization.swift in Sources */,
D0C7D4AB24F7616A001EBDBB /* Identity.swift in Sources */,
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */,
D0C7D4B424F7616A001EBDBB /* MastodonContext.swift in Sources */,
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */,
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */,
@ -964,46 +774,28 @@
D0DC174624CFEC2000A75C65 /* StubbingURLProtocol.swift in Sources */,
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
D0C7D4F824F7616A001EBDBB /* SecretsService.swift in Sources */,
D0C7D4DE24F7616A001EBDBB /* HTTPTarget.swift in Sources */,
D0C7D4F624F7616A001EBDBB /* KeychainService.swift in Sources */,
D0C7D4C124F7616A001EBDBB /* MastodonPreferences.swift in Sources */,
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */,
D0C7D4E524F7616A001EBDBB /* InstanceEndpoint.swift in Sources */,
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */,
D0C7D4F924F7616A001EBDBB /* UserNotificationService.swift in Sources */,
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
D0C7D4AA24F7616A001EBDBB /* Attachment.swift in Sources */,
D0C7D4AF24F7616A001EBDBB /* PushNotification.swift in Sources */,
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */,
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */,
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
D0C7D4B624F7616A001EBDBB /* ListTimeline.swift in Sources */,
D0C7D4E124F7616A001EBDBB /* MastodonDecoder.swift in Sources */,
D0C7D4B024F7616A001EBDBB /* PushSubscription.swift in Sources */,
D0BEB20B24FA12D8001B0F04 /* FiltersEndpoint.swift in Sources */,
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
D0C7D4E424F7616A001EBDBB /* PreferencesEndpoint.swift in Sources */,
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */,
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
D0C7D4B124F7616A001EBDBB /* Card.swift in Sources */,
D0C7D4F324F7616A001EBDBB /* ContextService.swift in Sources */,
D0C7D4DD24F7616A001EBDBB /* CodingUserInfoKey+Extensions.swift in Sources */,
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */,
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
D0C7D4BE24F7616A001EBDBB /* Unknowable.swift in Sources */,
D0C7D4AE24F7616A001EBDBB /* Timeline.swift in Sources */,
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
D0C7D4EC24F7616A001EBDBB /* StatusEndpoint.swift in Sources */,
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
D0C7D4BB24F7616A001EBDBB /* Emoji.swift in Sources */,
D0BEB1F924F9D627001B0F04 /* ListsEndpoint.swift in Sources */,
D0C7D4AD24F7616A001EBDBB /* AccessToken.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1021,12 +813,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0C7D4FC24F761A8001EBDBB /* Unknowable.swift in Sources */,
D0C7D4FB24F7619F001EBDBB /* PushNotification.swift in Sources */,
D0C7D4FE24F761C9001EBDBB /* SecretsService.swift in Sources */,
D0C7D50124F761EC001EBDBB /* MastodonAPI.swift in Sources */,
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */,
D0C7D4FD24F761C1001EBDBB /* MastodonDecoder.swift in Sources */,
D0C7D50024F761E0001EBDBB /* NSError+Extensions.swift in Sources */,
D0C7D4FF24F761D0001EBDBB /* KeychainService.swift in Sources */,
);
@ -1404,6 +1192,14 @@
package = D0DC175D24D016EA00A75C65 /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
D0E0F1E524FC4B76002C04BF /* Mastodon */ = {
isa = XCSwiftPackageProductDependency;
productName = Mastodon;
};
D0E0F1E724FC5A61002C04BF /* Mastodon */ = {
isa = XCSwiftPackageProductDependency;
productName = Mastodon;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = D047FA8024C3E21000AF17C5 /* Project object */;

View file

@ -1,9 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct AccessToken: Codable {
let scope: String
let tokenType: String
let accessToken: String
}

View file

@ -1,32 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Account: Codable, Hashable {
struct Field: Codable, Hashable {
let name: String
let value: HTML
let verifiedAt: Date?
}
let id: String
let username: String
let acct: String
let displayName: String
let locked: Bool
let createdAt: Date
let followersCount: Int
let followingCount: Int
let statusesCount: Int
let note: HTML
let url: URL
let avatar: URL
let avatarStatic: URL
let header: URL
let headerStatic: URL
let fields: [Field]
let emojis: [Emoji]
@DecodableDefault.False private(set) var bot: Bool
@DecodableDefault.False private(set) var moved: Bool
@DecodableDefault.False private(set) var discoverable: Bool
}

View file

@ -1,13 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct AppAuthorization: Codable {
let id: String
let clientId: String
let clientSecret: String
let name: String
let redirectUri: String
let website: String?
let vapidKey: String?
}

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
struct AppEnvironment {
let session: Session
@ -8,7 +9,6 @@ struct AppEnvironment {
let keychainServiceType: KeychainService.Type
let userDefaults: UserDefaults
let inMemoryContent: Bool
let attributedStringCache = AttributedStringCache()
}
extension AppEnvironment {

View file

@ -1,8 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Application: Codable, Hashable {
let name: String
let website: String?
}

View file

@ -1,43 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Attachment: Codable, Hashable {
enum AttachmentType: String, Codable, Hashable, Unknowable {
case image, video, gifv, audio, unknown
static var unknownCase: Self { .unknown }
}
// swiftlint:disable nesting
struct Meta: Codable, Hashable {
struct Info: Codable, Hashable {
let width: Int?
let height: Int?
let size: String?
let aspect: Double?
let frameRate: String?
let duration: Double?
let bitrate: Int?
}
struct Focus: Codable, Hashable {
let x: Double
let y: Double
}
let original: Info?
let small: Info?
let focus: Focus?
}
// swiftlint:enable nesting
let id: String
let type: AttachmentType
let url: URL
let remoteUrl: URL?
let previewUrl: URL
let textUrl: URL?
let meta: Meta?
let description: String?
}

View file

@ -1,25 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Card: Codable, Hashable {
enum CardType: String, Codable, Hashable, Unknowable {
case link, photo, video, rich, unknown
static var unknownCase: Self { .unknown }
}
let url: URL
let title: String
let description: String
let type: CardType
let authorName: String?
let authorUrl: String?
let providerName: String?
let providerUrl: String?
let html: String?
let width: Int?
let height: Int?
let image: URL?
let embedUrl: String?
}

View file

@ -1,10 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Emoji: Codable, Hashable {
let shortcode: String
let staticUrl: URL
let url: URL
let visibleInPicker: Bool
}

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
struct Identity: Codable, Hashable, Identifiable {
let id: UUID

View file

@ -1,30 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Instance: Codable, Hashable {
struct URLs: Codable, Hashable {
let streamingApi: URL
}
struct Stats: Codable, Hashable {
let userCount: Int
let statusCount: Int
let domainCount: Int
}
let uri: String
let title: String
let description: String
let shortDescription: String?
let email: String
let version: String
@DecodableDefault.EmptyList private(set) var languages: [String]
@DecodableDefault.False private(set) var registrations: Bool
@DecodableDefault.False private(set) var approvalRequired: Bool
@DecodableDefault.False private(set) var invitesEnabled: Bool
let urls: URLs
let stats: Stats
let thumbnail: URL?
let contactAccount: Account?
}

View file

@ -1,8 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct MastodonList: Codable, Hashable, Identifiable {
let id: String
let title: String
}

View file

@ -1,8 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct MastodonContext: Codable, Hashable {
let ancestors: [Status]
let descendants: [Status]
}

View file

@ -1,11 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct MastodonError: Error, Codable {
let error: String
}
extension MastodonError: LocalizedError {
var errorDescription: String? { error }
}

View file

@ -1,10 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Mention: Codable, Hashable {
let url: URL
let username: String
let acct: String
let id: String
}

View file

@ -1,21 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Poll: Codable, Hashable {
struct Option: Codable, Hashable {
var title: String
var votesCount: Int
}
let id: String
let expiresAt: Date
let expired: Bool
let multiple: Bool
let votesCount: Int
let votersCount: Int?
@DecodableDefault.False private(set) var voted: Bool
@DecodableDefault.EmptyList private(set) var ownVotes: [Int]
let options: [Option]
let emojis: [Emoji]
}

View file

@ -1,23 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct PushNotification: Codable {
enum NotificationType: String, Codable, Unknowable {
case mention
case reblog
case favourite
case follow
case unknown
static var unknownCase: Self { .unknown }
}
let accessToken: String
let body: String
let title: String
let icon: URL
let notificationId: Int
let notificationType: NotificationType
let preferredLocale: String
}

View file

@ -1,26 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct PushSubscription: Codable {
struct Alerts: Codable, Hashable {
var follow: Bool
var favourite: Bool
var reblog: Bool
var mention: Bool
@DecodableDefault.True var poll: Bool
}
let endpoint: URL
let alerts: Alerts
let serverKey: String
}
extension PushSubscription.Alerts {
static let initial: Self = Self(
follow: true,
favourite: true,
reblog: true,
mention: true,
poll: DecodableDefault.True())
}

View file

@ -1,8 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Tag: Codable, Hashable {
let name: String
let url: URL
}

View file

@ -1,46 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct Paged<T: MastodonEndpoint> {
let endpoint: T
let maxID: String?
let minID: String?
let sinceID: String?
let limit: Int?
init(_ endpoint: T, maxID: String? = nil, minID: String? = nil, sinceID: String? = nil, limit: Int? = nil) {
self.endpoint = endpoint
self.maxID = maxID
self.minID = minID
self.sinceID = sinceID
self.limit = limit
}
}
extension Paged: MastodonEndpoint {
typealias ResultType = T.ResultType
var APIVersion: String { endpoint.APIVersion }
var context: [String] { endpoint.context }
var pathComponentsInContext: [String] { endpoint.pathComponentsInContext }
var method: HTTPMethod { endpoint.method }
var encoding: ParameterEncoding { endpoint.encoding }
var parameters: [String: Any]? {
var parameters = endpoint.parameters ?? [String: Any]()
parameters["max_id"] = maxID
parameters["min_id"] = minID
parameters["since_id"] = sinceID
parameters["limit"] = limit
return parameters
}
var headers: HTTPHeaders? { endpoint.headers }
}

View file

@ -1,7 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct MastodonAPI {
static let dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
}

View file

@ -1,33 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Combine
class MastodonClient: HTTPClient {
var instanceURL: URL?
var accessToken: String?
required init(environment: AppEnvironment) {
let decoder = MastodonDecoder()
decoder.userInfo[.attributedStringCache] = environment.attributedStringCache
super.init(session: environment.session, decoder: decoder)
}
override func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
super.request(target, decodeErrorsAs: MastodonError.self)
}
}
extension MastodonClient {
func request<E: MastodonEndpoint>(_ endpoint: E) -> AnyPublisher<E.ResultType, Error> {
guard let instanceURL = instanceURL else {
return Fail(error: URLError(.badURL)).eraseToAnyPublisher()
}
return super.request(
MastodonTarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
decodeErrorsAs: MastodonError.self)
}
}

View file

@ -1,35 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct MastodonTarget<E: MastodonEndpoint> {
let baseURL: URL
let endpoint: E
let accessToken: String?
}
extension MastodonTarget: DecodableTarget {
typealias ResultType = E.ResultType
var pathComponents: [String] { endpoint.pathComponents }
var method: HTTPMethod { endpoint.method }
var encoding: ParameterEncoding { endpoint.encoding }
var parameters: [String: Any]? { endpoint.parameters }
var headers: HTTPHeaders? {
var headers = endpoint.headers
if let accessToken = accessToken {
if headers == nil {
headers = HTTPHeaders()
}
headers?.add(.authorization(bearerToken: accessToken))
}
return headers
}
}

View file

@ -2,6 +2,7 @@
import UserNotifications
import CryptoKit
import Mastodon
class NotificationService: UNNotificationServiceExtension {

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
struct AllIdentitiesService {
let mostRecentlyUsedIdentityID: AnyPublisher<UUID?, Never>
@ -49,7 +50,7 @@ extension AllIdentitiesService {
func deleteIdentity(_ identity: Identity) -> AnyPublisher<Never, Error> {
let secretsService = SecretsService(identityID: identity.id, keychainService: environment.keychainServiceType)
let networkClient = MastodonClient(environment: environment)
let networkClient = MastodonClient(session: environment.session)
networkClient.instanceURL = identity.url

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
struct AuthenticationService {
private let networkClient: MastodonClient
@ -9,7 +10,7 @@ struct AuthenticationService {
private let webAuthSessionContextProvider = WebAuthSessionContextProvider()
init(environment: AppEnvironment) {
networkClient = MastodonClient(environment: environment)
networkClient = MastodonClient(session: environment.session)
webAuthSessionType = environment.webAuthSessionType
}
}

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
class IdentityService {
@Published private(set) var identity: Identity
@ -34,11 +35,11 @@ class IdentityService {
secretsService = SecretsService(
identityID: identityID,
keychainService: environment.keychainServiceType)
networkClient = MastodonClient(environment: environment)
networkClient = MastodonClient(session: environment.session)
networkClient.instanceURL = identity.url
networkClient.accessToken = try? secretsService.item(.accessToken)
contentDatabase = try ContentDatabase(identityID: identityID, environment: environment)
contentDatabase = try ContentDatabase(identityID: identityID, inMemory: environment.inMemoryContent)
observation.catch { [weak self] error -> Empty<Identity, Never> in
self?.observationErrorsInput.send(error)

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
struct ContextService {
let statusSections: AnyPublisher<[[Status]], Error>

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
protocol StatusListService {
var statusSections: AnyPublisher<[[Status]], Error> { get }

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
struct TimelineService {
let statusSections: AnyPublisher<[[Status]], Error>

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
struct StatusService {
let status: Status

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
struct AttachmentViewModel {
let attachment: Attachment

View file

@ -2,6 +2,7 @@
import Foundation
import Combine
import Mastodon
class EditFilterViewModel: ObservableObject {
@Published var filter: Filter
@ -56,3 +57,22 @@ extension EditFilterViewModel {
.store(in: &cancellables)
}
}
extension Filter.Context {
var localized: String {
switch self {
case .home:
return NSLocalizedString("filter.context.home", comment: "")
case .notifications:
return NSLocalizedString("filter.context.notifications", comment: "")
case .public:
return NSLocalizedString("filter.context.public", comment: "")
case .thread:
return NSLocalizedString("filter.context.thread", comment: "")
case .account:
return NSLocalizedString("filter.context.account", comment: "")
case .unknown:
return NSLocalizedString("filter.context.unknown", comment: "")
}
}
}

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