This commit is contained in:
Justin Mazzocchi 2020-08-31 03:21:01 -07:00
parent 71c8861600
commit 5f96f59ac3
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
69 changed files with 393 additions and 564 deletions

View File

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -1,13 +0,0 @@
{
"data" : [
{
"filename" : "timeline.json",
"idiom" : "universal",
"universal-type-identifier" : "public.json"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -4,6 +4,8 @@ import Foundation
import Combine import Combine
import HTTP import HTTP
import Mastodon import Mastodon
import Services
import ServiceMocks
// swiftlint:disable force_try // swiftlint:disable force_try
private let decoder = APIDecoder() private let decoder = APIDecoder()
@ -20,31 +22,6 @@ extension Instance {
static let development = try! decoder.decode(Instance.self, from: Data(officialInstanceJSON.utf8)) static let development = try! decoder.decode(Instance.self, from: Data(officialInstanceJSON.utf8))
} }
extension IdentityDatabase {
static func fresh() -> IdentityDatabase { try! IdentityDatabase(inMemory: true) }
static var development: IdentityDatabase = {
let db = IdentityDatabase.fresh()
db.createIdentity(id: devIdentityID, url: devInstanceURL)
.receive(on: ImmediateScheduler.shared)
.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
.store(in: &cancellables)
db.updateAccount(.development, forIdentityID: devIdentityID)
.receive(on: ImmediateScheduler.shared)
.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
.store(in: &cancellables)
db.updateInstance(.development, forIdentityID: devIdentityID)
.receive(on: ImmediateScheduler.shared)
.sink(receiveCompletion: { _ in }, receiveValue: { _ in })
.store(in: &cancellables)
return db
}()
}
extension AppEnvironment { extension AppEnvironment {
static let development = AppEnvironment( static let development = AppEnvironment(
session: Session(configuration: .stubbing), session: Session(configuration: .stubbing),
@ -55,18 +32,30 @@ extension AppEnvironment {
} }
extension AllIdentitiesService { extension AllIdentitiesService {
static func fresh( static let fresh = try! AllIdentitiesService(environment: .development)
identityDatabase: IdentityDatabase = .fresh(),
keychainService: KeychainService = MockKeychainService(),
environment: AppEnvironment = .development) -> AllIdentitiesService {
AllIdentitiesService(
identityDatabase: identityDatabase,
environment: environment)
}
static let development = AllIdentitiesService( static var development: Self = {
identityDatabase: .development, let allIdentitiesService = try! AllIdentitiesService(environment: .development)
environment: .development)
allIdentitiesService.authorizeIdentity(id: devIdentityID, instanceURL: devInstanceURL)
.receive(on: ImmediateScheduler.shared)
.sink { _ in } receiveValue: { _ in }
.store(in: &cancellables)
// let identityService = try! allIdentitiesService.identityService(id: devIdentityID)
//
// identityService.verifyCredentials()
// .receive(on: ImmediateScheduler.shared)
// .sink { _ in } receiveValue: { _ in }
// .store(in: &cancellables)
//
// identityService.refreshInstance()
// .receive(on: ImmediateScheduler.shared)
// .sink { _ in } receiveValue: { _ in }
// .store(in: &cancellables)
return allIdentitiesService
} ()
} }
extension IdentityService { extension IdentityService {

View File

@ -1,21 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
class MockKeychainService {
private var guts = [String: Data]()
}
extension MockKeychainService: KeychainServiceType {
func set(data: Data, forKey key: String) throws {
guts[key] = data
}
func deleteData(key: String) throws {
guts[key] = nil
}
func getData(key: String) throws -> Data? {
guts[key]
}
}

View File

@ -1,17 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import HTTP
struct HTTPStubs {
static func stub(
request: URLRequest,
target: Target? = nil,
userInfo: [String: Any] = [:]) -> HTTPStub? {
guard let url = request.url else {
return nil
}
return (target as? Stubbing)?.stub(url: url)
}
}

View File

@ -1,11 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import UIKit
import Mastodon
extension TimelinesEndpoint: Stubbing {
func data(url: URL) -> Data? {
NSDataAsset(name: "timelineJSON")!.data
}
}

View File

@ -11,7 +11,10 @@ let package = Package(
products: [ products: [
.library( .library(
name: "HTTP", name: "HTTP",
targets: ["HTTP"]) targets: ["HTTP"]),
.library(
name: "Stubbing",
targets: ["Stubbing"])
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.2")) .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.2"))
@ -20,6 +23,9 @@ let package = Package(
.target( .target(
name: "HTTP", name: "HTTP",
dependencies: ["Alamofire"]), dependencies: ["Alamofire"]),
.target(
name: "Stubbing",
dependencies: ["HTTP"]),
.testTarget( .testTarget(
name: "HTTPTests", name: "HTTPTests",
dependencies: ["HTTP"]) dependencies: ["HTTP"])

View File

@ -2,16 +2,16 @@
import Foundation import Foundation
typealias HTTPStub = Result<(URLResponse, Data), Error> public typealias HTTPStub = Result<(URLResponse, Data), Error>
protocol Stubbing { public protocol Stubbing {
func stub(url: URL) -> HTTPStub? func stub(url: URL) -> HTTPStub?
func data(url: URL) -> Data? func data(url: URL) -> Data?
func dataString(url: URL) -> String? func dataString(url: URL) -> String?
func statusCode(url: URL) -> Int? func statusCode(url: URL) -> Int?
} }
extension Stubbing { public extension Stubbing {
func stub(url: URL) -> HTTPStub? { func stub(url: URL) -> HTTPStub? {
if let data = data(url: url), if let data = data(url: url),
let statusCode = statusCode(url: url), let statusCode = statusCode(url: url),

View File

@ -3,25 +3,25 @@
import Foundation import Foundation
import HTTP import HTTP
class StubbingURLProtocol: URLProtocol { public class StubbingURLProtocol: URLProtocol {
private static var targetsForURLs = [URL: Target]() private static var targetsForURLs = [URL: Target]()
override class func canInit(with task: URLSessionTask) -> Bool { override public class func canInit(with task: URLSessionTask) -> Bool {
true true
} }
override class func canInit(with request: URLRequest) -> Bool { override public class func canInit(with request: URLRequest) -> Bool {
true true
} }
override class func canonicalRequest(for request: URLRequest) -> URLRequest { override public class func canonicalRequest(for request: URLRequest) -> URLRequest {
request request
} }
override func startLoading() { override public func startLoading() {
guard guard
let url = request.url, let url = request.url,
let stub = HTTPStubs.stub(request: request, target: Self.targetsForURLs[url]) else { let stub = Self.stub(request: request, target: Self.targetsForURLs[url]) else {
preconditionFailure("Stub for request not found") preconditionFailure("Stub for request not found")
} }
@ -35,11 +35,24 @@ class StubbingURLProtocol: URLProtocol {
} }
} }
override func stopLoading() {} override public func stopLoading() {}
}
private extension StubbingURLProtocol {
class func stub(
request: URLRequest,
target: Target? = nil,
userInfo: [String: Any] = [:]) -> HTTPStub? {
guard let url = request.url else {
return nil
}
return (target as? Stubbing)?.stub(url: url)
}
} }
extension StubbingURLProtocol: TargetProcessing { extension StubbingURLProtocol: TargetProcessing {
static func process(target: Target) { public static func process(target: Target) {
if let url = try? target.asURLRequest().url { if let url = try? target.asURLRequest().url {
targetsForURLs[url] = target targetsForURLs[url] = target
} }

View File

@ -2,7 +2,7 @@
import Foundation import Foundation
extension URLSessionConfiguration { public extension URLSessionConfiguration {
static var stubbing: URLSessionConfiguration { static var stubbing: URLSessionConfiguration {
let configuration = Self.default let configuration = Self.default

View File

@ -11,7 +11,10 @@ let package = Package(
products: [ products: [
.library( .library(
name: "Mastodon", name: "Mastodon",
targets: ["Mastodon"]) targets: ["Mastodon"]),
.library(
name: "MastodonStubs",
targets: ["MastodonStubs"])
], ],
dependencies: [ dependencies: [
.package(path: "HTTP") .package(path: "HTTP")
@ -20,8 +23,12 @@ let package = Package(
.target( .target(
name: "Mastodon", name: "Mastodon",
dependencies: ["HTTP"]), dependencies: ["HTTP"]),
.target(
name: "MastodonStubs",
dependencies: ["Mastodon", .product(name: "Stubbing", package: "HTTP")],
resources: [.process("Resources")]),
.testTarget( .testTarget(
name: "MastodonTests", name: "MastodonTests",
dependencies: ["Mastodon"]) dependencies: ["MastodonStubs"])
] ]
) )

View File

@ -2,9 +2,10 @@
import Foundation import Foundation
import Mastodon import Mastodon
import Stubbing
extension AccessTokenEndpoint: Stubbing { extension AccessTokenEndpoint: Stubbing {
func dataString(url: URL) -> String? { public func dataString(url: URL) -> String? {
switch self { switch self {
case let .oauthToken(_, _, _, _, scopes, _): case let .oauthToken(_, _, _, _, scopes, _):
return """ return """

View File

@ -0,0 +1,13 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import Stubbing
extension AccountEndpoint: Stubbing {
public func data(url: URL) -> Data? {
switch self {
case .verifyCredentials: return try? Data(contentsOf: Bundle.module.url(forResource: "account", withExtension: "json")!)
}
}
}

View File

@ -2,9 +2,10 @@
import Foundation import Foundation
import Mastodon import Mastodon
import Stubbing
extension AppAuthorizationEndpoint: Stubbing { extension AppAuthorizationEndpoint: Stubbing {
func dataString(url: URL) -> String? { public func dataString(url: URL) -> String? {
switch self { switch self {
case let .apps(clientName, redirectURI, _, _): case let .apps(clientName, redirectURI, _, _):
return """ return """

View File

@ -2,9 +2,10 @@
import Foundation import Foundation
import Mastodon import Mastodon
import Stubbing
extension ContextEndpoint: Stubbing { extension ContextEndpoint: Stubbing {
func dataString(url: URL) -> String? { public func dataString(url: URL) -> String? {
switch self { switch self {
case .context: case .context:
return """ return """

View File

@ -0,0 +1,13 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import Stubbing
extension InstanceEndpoint: Stubbing {
public func data(url: URL) -> Data? {
switch self {
case .instance: return try? Data(contentsOf: Bundle.module.url(forResource: "instance", withExtension: "json")!)
}
}
}

View File

@ -2,21 +2,22 @@
import Foundation import Foundation
import Mastodon import Mastodon
import Stubbing
extension APITarget: Stubbing { extension APITarget: Stubbing {
func stub(url: URL) -> HTTPStub? { public func stub(url: URL) -> HTTPStub? {
(endpoint as? Stubbing)?.stub(url: url) (endpoint as? Stubbing)?.stub(url: url)
} }
func data(url: URL) -> Data? { public func data(url: URL) -> Data? {
(endpoint as? Stubbing)?.data(url: url) (endpoint as? Stubbing)?.data(url: url)
} }
func dataString(url: URL) -> String? { public func dataString(url: URL) -> String? {
(endpoint as? Stubbing)?.dataString(url: url) (endpoint as? Stubbing)?.dataString(url: url)
} }
func statusCode(url: URL) -> Int? { public func statusCode(url: URL) -> Int? {
(endpoint as? Stubbing)?.statusCode(url: url) (endpoint as? Stubbing)?.statusCode(url: url)
} }
} }

View File

@ -2,9 +2,10 @@
import Foundation import Foundation
import Mastodon import Mastodon
import Stubbing
extension PreferencesEndpoint: Stubbing { extension PreferencesEndpoint: Stubbing {
func dataString(url: URL) -> String? { public func dataString(url: URL) -> String? {
switch self { switch self {
case .preferences: case .preferences:
return """ return """

View File

@ -1,10 +1,3 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
// swiftlint:disable line_length
let officialAccountJSON = #"""
{ {
"id": "13179", "id": "13179",
"username": "Mastodon", "username": "Mastodon",
@ -39,13 +32,3 @@ let officialAccountJSON = #"""
} }
] ]
} }
"""#
extension AccountEndpoint: Stubbing {
func dataString(url: URL) -> String? {
switch self {
case .verifyCredentials: return officialAccountJSON
}
}
}
// swiftlint:enable line_length

View File

@ -1,10 +1,3 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
// swiftlint:disable line_length
let officialInstanceJSON = #"""
{ {
"uri": "mastodon.social", "uri": "mastodon.social",
"title": "Mastodon", "title": "Mastodon",
@ -62,14 +55,3 @@ let officialInstanceJSON = #"""
] ]
} }
} }
"""#
extension InstanceEndpoint: Stubbing {
func dataString(url: URL) -> String? {
switch self {
case .instance: return officialInstanceJSON
}
}
}
// swiftlint:enable line_length

View File

@ -0,0 +1,11 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
import Stubbing
extension TimelinesEndpoint: Stubbing {
public func data(url: URL) -> Data? {
try? Data(contentsOf: Bundle.module.url(forResource: "timeline", withExtension: "json")!)
}
}

View File

@ -12,20 +12,13 @@
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; }; D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */; }; D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */; };
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; }; D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
D03658D124EDD80900AC17EC /* ContextEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03658D024EDD80900AC17EC /* ContextEndpoint+Stubbing.swift */; };
D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */; };
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */; };
D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD74124D4AA34007D572D /* DevelopmentModels.swift */; }; D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04FD74124D4AA34007D572D /* DevelopmentModels.swift */; };
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC624D749C800A80A7A /* RootViewModelTests.swift */; }; D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC624D749C800A80A7A /* RootViewModelTests.swift */; };
D052BBCA24D74C9200A80A7A /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */; };
D05494FA24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */; };
D054950124EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */; };
D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; }; D065F53924D37E5100741304 /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = D065F53824D37E5100741304 /* CombineExpectations */; };
D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = D0666A4824C6C1A300F3F04B /* GRDB */; };
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; }; D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577624D29006004758DB /* MockWebAuthSession.swift */; }; D075C28524FCD41D00D35112 /* Services in Frameworks */ = {isa = PBXBuildFile; productRef = D075C28424FCD41D00D35112 /* Services */; };
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; }; D075C28724FCD92400D35112 /* Services in Frameworks */ = {isa = PBXBuildFile; productRef = D075C28624FCD92400D35112 /* Services */; };
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */; }; D0ADCBF124FD05510062ACCE /* ServiceMocks in Frameworks */ = {isa = PBXBuildFile; productRef = D0ADCBF024FD05510062ACCE /* ServiceMocks */; };
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; }; D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; }; D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */; }; D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */; };
@ -45,9 +38,6 @@
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; }; D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */; };
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; }; D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */; };
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; }; D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43124F76169001EBDBB /* StatusListViewController.swift */; };
D0C7D4AB24F7616A001EBDBB /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D43B24F76169001EBDBB /* Identity.swift */; };
D0C7D4B724F7616A001EBDBB /* TransientStatusCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */; };
D0C7D4BF24F7616A001EBDBB /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */; };
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45024F76169001EBDBB /* AlertItem.swift */; }; D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45024F76169001EBDBB /* AlertItem.swift */; };
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; }; D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; }; D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D45424F76169001EBDBB /* MetatextApp.swift */; };
@ -64,10 +54,6 @@
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */; }; D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */; };
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46224F76169001EBDBB /* StatusViewModel.swift */; }; D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46224F76169001EBDBB /* StatusViewModel.swift */; };
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */; }; D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */; };
D0C7D4D124F7616A001EBDBB /* IdentityDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */; };
D0C7D4D224F7616A001EBDBB /* ContentDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46624F76169001EBDBB /* ContentDatabase.swift */; };
D0C7D4D324F7616A001EBDBB /* DatabaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46724F76169001EBDBB /* DatabaseError.swift */; };
D0C7D4D424F7616A001EBDBB /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */; };
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46A24F76169001EBDBB /* String+Extensions.swift */; }; D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46A24F76169001EBDBB /* String+Extensions.swift */; };
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */; }; D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */; };
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; }; D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */; };
@ -76,32 +62,8 @@
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; }; 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 */; }; 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 */; }; D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47124F76169001EBDBB /* Data+Extensions.swift */; };
D0C7D4E024F7616A001EBDBB /* WebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47624F76169001EBDBB /* WebAuthSession.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 */; };
D0C7D4F424F7616A001EBDBB /* StatusListService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48E24F7616A001EBDBB /* StatusListService.swift */; };
D0C7D4F524F7616A001EBDBB /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D48F24F7616A001EBDBB /* AuthenticationService.swift */; };
D0C7D4F624F7616A001EBDBB /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49024F7616A001EBDBB /* KeychainService.swift */; };
D0C7D4F724F7616A001EBDBB /* StatusService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D49124F7616A001EBDBB /* StatusService.swift */; };
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 */; };
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 */; };
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 */; };
D0DC175224D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */; };
D0DC175524D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC175424D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift */; };
D0DC175824D0130800A75C65 /* HTTPStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC175724D0130800A75C65 /* HTTPStubs.swift */; };
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 */; }; 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, ); }; }; 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 */; };
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */; }; D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -142,20 +104,12 @@
D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; }; D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchFallthroughTextView.swift; sourceTree = "<group>"; };
D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = "<group>"; }; D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = "<group>"; };
D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; }; D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = "<group>"; };
D03658D024EDD80900AC17EC /* ContextEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; }; D047FA8C24C3E21200AF17C5 /* Metatext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Metatext.app; sourceTree = BUILT_PRODUCTS_DIR; };
D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InstanceEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D04FD74124D4AA34007D572D /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = "<group>"; }; D04FD74124D4AA34007D572D /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = "<group>"; };
D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.swift; sourceTree = "<group>"; }; D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.swift; sourceTree = "<group>"; };
D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelinesEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DevelopmentAssets.xcassets; sourceTree = "<group>"; };
D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D0666A2124C677B400F3F04B /* Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; D0666A2524C677B400F3F04B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D074577624D29006004758DB /* MockWebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockWebAuthSession.swift; sourceTree = "<group>"; }; D075C28324FCD27300D35112 /* Services */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Services; sourceTree = "<group>"; };
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>"; }; D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = "<group>"; };
D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; }; D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = "<group>"; };
D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsViewModel.swift; sourceTree = "<group>"; }; D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsViewModel.swift; sourceTree = "<group>"; };
@ -178,9 +132,6 @@
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = "<group>"; };
D0C7D43B24F76169001EBDBB /* Identity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransientStatusCollection.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>"; }; D0C7D45024F76169001EBDBB /* AlertItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = "<group>"; };
D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; 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>"; }; D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = "<group>"; };
@ -197,10 +148,6 @@
D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewModel.swift; sourceTree = "<group>"; }; D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewModel.swift; sourceTree = "<group>"; };
D0C7D46224F76169001EBDBB /* StatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = "<group>"; }; D0C7D46224F76169001EBDBB /* StatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = "<group>"; };
D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewModel.swift; sourceTree = "<group>"; }; D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewModel.swift; sourceTree = "<group>"; };
D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityDatabase.swift; sourceTree = "<group>"; };
D0C7D46624F76169001EBDBB /* ContentDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentDatabase.swift; sourceTree = "<group>"; };
D0C7D46724F76169001EBDBB /* DatabaseError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseError.swift; sourceTree = "<group>"; };
D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = "<group>"; };
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; }; D0C7D46A24F76169001EBDBB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = "<group>"; }; D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = "<group>"; };
D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; }; D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = "<group>"; };
@ -209,30 +156,11 @@
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; }; 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>"; }; 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>"; }; D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47624F76169001EBDBB /* WebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAuthSession.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>"; };
D0C7D48E24F7616A001EBDBB /* StatusListService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListService.swift; sourceTree = "<group>"; };
D0C7D48F24F7616A001EBDBB /* AuthenticationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = "<group>"; };
D0C7D49024F7616A001EBDBB /* KeychainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
D0C7D49124F7616A001EBDBB /* StatusService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusService.swift; sourceTree = "<group>"; };
D0C7D49224F7616A001EBDBB /* SecretsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsService.swift; sourceTree = "<group>"; };
D0C7D49324F7616A001EBDBB /* UserNotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationService.swift; sourceTree = "<group>"; };
D0C7D49424F7616A001EBDBB /* AllIdentitiesService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllIdentitiesService.swift; sourceTree = "<group>"; };
D0DC174524CFEC2000A75C65 /* StubbingURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubbingURLProtocol.swift; sourceTree = "<group>"; };
D0DC174924CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppAuthorizationEndpoint+Stubbing.swift"; sourceTree = "<group>"; };
D0DC174C24CFF1F100A75C65 /* Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubbing.swift; sourceTree = "<group>"; };
D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonTarget+Stubbing.swift"; sourceTree = "<group>"; };
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>"; }; 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; }; 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>"; }; 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>"; }; D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; }; D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = "<group>"; };
D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceTests.swift; sourceTree = "<group>"; };
D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModelTests.swift; sourceTree = "<group>"; }; D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModelTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -241,9 +169,9 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D075C28524FCD41D00D35112 /* Services in Frameworks */,
D0ADCBF124FD05510062ACCE /* ServiceMocks in Frameworks */,
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */, D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
D0E0F1E624FC4B76002C04BF /* Mastodon in Frameworks */,
D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -259,7 +187,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0E0F1E824FC5A61002C04BF /* Mastodon in Frameworks */, D075C28724FCD92400D35112 /* Services in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -289,7 +217,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0C7D45224F76169001EBDBB /* Assets.xcassets */, D0C7D45224F76169001EBDBB /* Assets.xcassets */,
D0C7D46424F76169001EBDBB /* Databases */,
D0ED1BB224CE3A1600B4899C /* Development Assets */, D0ED1BB224CE3A1600B4899C /* Development Assets */,
D0C7D46824F76169001EBDBB /* Extensions */, D0C7D46824F76169001EBDBB /* Extensions */,
D0666A7924C7745A00F3F04B /* Frameworks */, D0666A7924C7745A00F3F04B /* Frameworks */,
@ -297,10 +224,9 @@
D0C7D45624F76169001EBDBB /* Localizations */, D0C7D45624F76169001EBDBB /* Localizations */,
D0E0F1E424FC49FC002C04BF /* Mastodon */, D0E0F1E424FC49FC002C04BF /* Mastodon */,
D0C7D43824F76169001EBDBB /* Model */, D0C7D43824F76169001EBDBB /* Model */,
D0C7D47324F76169001EBDBB /* Networking */,
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */, D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
D047FA8D24C3E21200AF17C5 /* Products */, D047FA8D24C3E21200AF17C5 /* Products */,
D0C7D48924F76169001EBDBB /* Services */, D075C28324FCD27300D35112 /* Services */,
D0C7D41D24F76169001EBDBB /* Supporting Files */, D0C7D41D24F76169001EBDBB /* Supporting Files */,
D0C7D45324F76169001EBDBB /* System */, D0C7D45324F76169001EBDBB /* System */,
D0666A2224C677B400F3F04B /* Tests */, D0666A2224C677B400F3F04B /* Tests */,
@ -324,7 +250,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0666A2524C677B400F3F04B /* Info.plist */, D0666A2524C677B400F3F04B /* Info.plist */,
D0EC8DD024DFE34F00A08489 /* Services */,
D0ED1B6C24CE0EED00B4899C /* View Models */, D0ED1B6C24CE0EED00B4899C /* View Models */,
); );
path = Tests; path = Tests;
@ -381,9 +306,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0C7D45024F76169001EBDBB /* AlertItem.swift */, D0C7D45024F76169001EBDBB /* AlertItem.swift */,
D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */,
D0C7D43B24F76169001EBDBB /* Identity.swift */,
D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@ -427,23 +349,12 @@
path = "View Models"; path = "View Models";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D0C7D46424F76169001EBDBB /* Databases */ = {
isa = PBXGroup;
children = (
D0C7D46624F76169001EBDBB /* ContentDatabase.swift */,
D0C7D46724F76169001EBDBB /* DatabaseError.swift */,
D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */,
);
path = Databases;
sourceTree = "<group>";
};
D0C7D46824F76169001EBDBB /* Extensions */ = { D0C7D46824F76169001EBDBB /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */, D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */, D0C7D47024F76169001EBDBB /* Date+Extensions.swift */,
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */, D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */,
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */, D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */, D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */,
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */, D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
@ -453,54 +364,6 @@
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D0C7D47324F76169001EBDBB /* Networking */ = {
isa = PBXGroup;
children = (
D0C7D47624F76169001EBDBB /* WebAuthSession.swift */,
);
path = Networking;
sourceTree = "<group>";
};
D0C7D48924F76169001EBDBB /* Services */ = {
isa = PBXGroup;
children = (
D0C7D49424F7616A001EBDBB /* AllIdentitiesService.swift */,
D0C7D48F24F7616A001EBDBB /* AuthenticationService.swift */,
D0C7D48A24F76169001EBDBB /* IdentityService.swift */,
D0C7D49024F7616A001EBDBB /* KeychainService.swift */,
D0C7D49224F7616A001EBDBB /* SecretsService.swift */,
D0C7D48B24F76169001EBDBB /* Status List Services */,
D0C7D49124F7616A001EBDBB /* StatusService.swift */,
D0C7D49324F7616A001EBDBB /* UserNotificationService.swift */,
);
path = Services;
sourceTree = "<group>";
};
D0C7D48B24F76169001EBDBB /* Status List Services */ = {
isa = PBXGroup;
children = (
D0C7D48D24F76169001EBDBB /* ContextService.swift */,
D0C7D48E24F7616A001EBDBB /* StatusListService.swift */,
D0C7D48C24F76169001EBDBB /* TimelineService.swift */,
);
path = "Status List Services";
sourceTree = "<group>";
};
D0DC174824CFF13700A75C65 /* Mastodon API Stubs */ = {
isa = PBXGroup;
children = (
D0DC175424D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift */,
D04FD73824D4A7B4007D572D /* AccountEndpoint+Stubbing.swift */,
D0DC174924CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift */,
D03658D024EDD80900AC17EC /* ContextEndpoint+Stubbing.swift */,
D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */,
D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */,
D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */,
D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */,
);
path = "Mastodon API Stubs";
sourceTree = "<group>";
};
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */ = { D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -511,14 +374,6 @@
path = "Notification Service Extension"; path = "Notification Service Extension";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D0EC8DD024DFE34F00A08489 /* Services */ = {
isa = PBXGroup;
children = (
D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */,
);
path = Services;
sourceTree = "<group>";
};
D0ED1B6C24CE0EED00B4899C /* View Models */ = { D0ED1B6C24CE0EED00B4899C /* View Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -531,16 +386,7 @@
D0ED1BB224CE3A1600B4899C /* Development Assets */ = { D0ED1BB224CE3A1600B4899C /* Development Assets */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */,
D04FD74124D4AA34007D572D /* DevelopmentModels.swift */, D04FD74124D4AA34007D572D /* DevelopmentModels.swift */,
D0DC175724D0130800A75C65 /* HTTPStubs.swift */,
D0DC174824CFF13700A75C65 /* Mastodon API Stubs */,
D0DC177624D0CF2600A75C65 /* MockKeychainService.swift */,
D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */,
D074577624D29006004758DB /* MockWebAuthSession.swift */,
D0DC174C24CFF1F100A75C65 /* Stubbing.swift */,
D0DC174524CFEC2000A75C65 /* StubbingURLProtocol.swift */,
D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */,
); );
path = "Development Assets"; path = "Development Assets";
sourceTree = "<group>"; sourceTree = "<group>";
@ -565,9 +411,9 @@
); );
name = Metatext; name = Metatext;
packageProductDependencies = ( packageProductDependencies = (
D0666A4824C6C1A300F3F04B /* GRDB */,
D06B492224D4611300642749 /* KingfisherSwiftUI */, D06B492224D4611300642749 /* KingfisherSwiftUI */,
D0E0F1E524FC4B76002C04BF /* Mastodon */, D075C28424FCD41D00D35112 /* Services */,
D0ADCBF024FD05510062ACCE /* ServiceMocks */,
); );
productName = "Metatext (iOS)"; productName = "Metatext (iOS)";
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */; productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
@ -608,7 +454,7 @@
); );
name = "Notification Service Extension"; name = "Notification Service Extension";
packageProductDependencies = ( packageProductDependencies = (
D0E0F1E724FC5A61002C04BF /* Mastodon */, D075C28624FCD92400D35112 /* Services */,
); );
productName = "Notification Service Extension"; productName = "Notification Service Extension";
productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */; productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */;
@ -647,7 +493,6 @@
); );
mainGroup = D047FA7F24C3E21000AF17C5; mainGroup = D047FA7F24C3E21000AF17C5;
packageReferences = ( packageReferences = (
D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */,
D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */, D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */,
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */, D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */,
); );
@ -669,7 +514,6 @@
files = ( files = (
D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */, D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */,
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */, D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */,
D054950124EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */,
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */, D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */,
D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */, D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */,
); );
@ -717,81 +561,48 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */, D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */,
D0C7D4F424F7616A001EBDBB /* StatusListService.swift in Sources */,
D0C7D4B724F7616A001EBDBB /* TransientStatusCollection.swift in Sources */,
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */, D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */,
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */, D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */,
D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */, D0C7D49C24F7616A001EBDBB /* RootView.swift in Sources */,
D0C7D4D224F7616A001EBDBB /* ContentDatabase.swift in Sources */,
D0C7D4F724F7616A001EBDBB /* StatusService.swift in Sources */,
D04FD73924D4A7B4007D572D /* AccountEndpoint+Stubbing.swift in Sources */,
D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */, D0BEB21324FA2C0A001B0F04 /* EditFilterViewModel.swift in Sources */,
D0C7D4FA24F7616A001EBDBB /* AllIdentitiesService.swift in Sources */,
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */, D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */,
D03658D124EDD80900AC17EC /* ContextEndpoint+Stubbing.swift in Sources */,
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */, D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */, D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */, D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */, D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */, D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */,
D0C7D4E024F7616A001EBDBB /* WebAuthSession.swift in Sources */,
D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */, D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */,
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */, D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */,
D0C7D4D124F7616A001EBDBB /* IdentityDatabase.swift in Sources */,
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */, D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
D05494FA24EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift in Sources */,
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */,
D0DC175524D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift in Sources */,
D0C7D4D324F7616A001EBDBB /* DatabaseError.swift in Sources */,
D0C7D4F224F7616A001EBDBB /* TimelineService.swift in Sources */,
D0C7D4BF24F7616A001EBDBB /* AppEnvironment.swift in Sources */,
D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */, D0C7D49D24F7616A001EBDBB /* PostingReadingPreferencesView.swift in Sources */,
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */, D0C7D4D024F7616A001EBDBB /* StatusListViewModel.swift in Sources */,
D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */, D0C7D49E24F7616A001EBDBB /* SecondaryNavigationView.swift in Sources */,
D0C7D4D424F7616A001EBDBB /* NSError+Extensions.swift in Sources */,
D052BBCA24D74C9200A80A7A /* MockUserDefaults.swift in Sources */,
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */, D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */,
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */, D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */, D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */,
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */, D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
D0C7D4AB24F7616A001EBDBB /* Identity.swift in Sources */,
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */, D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */,
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */, D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */, D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */,
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */, D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */,
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */, D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
D0DC175224D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
D0C7D4F124F7616A001EBDBB /* IdentityService.swift in Sources */,
D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */, D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */,
D0C7D4F524F7616A001EBDBB /* AuthenticationService.swift in Sources */,
D0DC175824D0130800A75C65 /* HTTPStubs.swift in Sources */,
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */, D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */, D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */,
D0DC177724D0CF2600A75C65 /* MockKeychainService.swift in Sources */,
D0DC174624CFEC2000A75C65 /* StubbingURLProtocol.swift in Sources */,
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */, D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */,
D0C7D4F824F7616A001EBDBB /* SecretsService.swift in Sources */,
D0C7D4F624F7616A001EBDBB /* KeychainService.swift in Sources */,
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */,
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */, D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */,
D0C7D4F924F7616A001EBDBB /* UserNotificationService.swift in Sources */,
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */, D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */, D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */, D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */,
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */, D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */,
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */, D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */, D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */, D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */, D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */,
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */, D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
D0C7D4F324F7616A001EBDBB /* ContextService.swift in Sources */,
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */, D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */,
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */, D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */, D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */, D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */, D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
); );
@ -801,7 +612,6 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0EC8DD424DFE38900A08489 /* AuthenticationServiceTests.swift in Sources */,
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */, D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */,
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */, D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */,
); );
@ -811,10 +621,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
D0C7D4FE24F761C9001EBDBB /* SecretsService.swift in Sources */,
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */, D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */,
D0C7D50024F761E0001EBDBB /* NSError+Extensions.swift in Sources */,
D0C7D4FF24F761D0001EBDBB /* KeychainService.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -953,7 +760,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements"; CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_ASSET_PATHS = "Development\\ Assets";
DEVELOPMENT_TEAM = 82HL67AXQ2; DEVELOPMENT_TEAM = 82HL67AXQ2;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "Supporting Files/Info.plist"; INFOPLIST_FILE = "Supporting Files/Info.plist";
@ -979,7 +786,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements"; CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs"; DEVELOPMENT_ASSET_PATHS = "Development\\ Assets";
DEVELOPMENT_TEAM = 82HL67AXQ2; DEVELOPMENT_TEAM = 82HL67AXQ2;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "Supporting Files/Info.plist"; INFOPLIST_FILE = "Supporting Files/Info.plist";
@ -1143,14 +950,6 @@
minimumVersion = 0.5.0; minimumVersion = 0.5.0;
}; };
}; };
D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/groue/GRDB.swift";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = "5.0.0-beta.10";
};
};
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher"; repositoryURL = "https://github.com/onevcat/Kingfisher";
@ -1167,23 +966,22 @@
package = D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */; package = D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */;
productName = CombineExpectations; productName = CombineExpectations;
}; };
D0666A4824C6C1A300F3F04B /* GRDB */ = {
isa = XCSwiftPackageProductDependency;
package = D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */;
productName = GRDB;
};
D06B492224D4611300642749 /* KingfisherSwiftUI */ = { D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */; package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = KingfisherSwiftUI; productName = KingfisherSwiftUI;
}; };
D0E0F1E524FC4B76002C04BF /* Mastodon */ = { D075C28424FCD41D00D35112 /* Services */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Mastodon; productName = Services;
}; };
D0E0F1E724FC5A61002C04BF /* Mastodon */ = { D075C28624FCD92400D35112 /* Services */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
productName = Mastodon; productName = Services;
};
D0ADCBF024FD05510062ACCE /* ServiceMocks */ = {
isa = XCSwiftPackageProductDependency;
productName = ServiceMocks;
}; };
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };

View File

@ -21,7 +21,7 @@
}, },
{ {
"package": "GRDB", "package": "GRDB",
"repositoryURL": "https://github.com/groue/GRDB.swift", "repositoryURL": "https://github.com/groue/GRDB.swift.git",
"state": { "state": {
"branch": null, "branch": null,
"revision": "ededd8668abd5a3c4c43cc9ebcfd611082b47f65", "revision": "ededd8668abd5a3c4c43cc9ebcfd611082b47f65",

View File

@ -1,73 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
struct Identity: Codable, Hashable, Identifiable {
let id: UUID
let url: URL
let lastUsedAt: Date
let preferences: Identity.Preferences
let instance: Identity.Instance?
let account: Identity.Account?
let lastRegisteredDeviceToken: String?
let pushSubscriptionAlerts: PushSubscription.Alerts
}
extension Identity {
struct Instance: Codable, Hashable {
let uri: String
let streamingAPI: URL
let title: String
let thumbnail: URL?
}
struct Account: Codable, Hashable {
let id: String
let identityID: UUID
let username: String
let displayName: String
let url: URL
let avatar: URL
let avatarStatic: URL
let header: URL
let headerStatic: URL
let emojis: [Emoji]
}
struct Preferences: Codable, Hashable {
@DecodableDefault.True var useServerPostingReadingPreferences
@DecodableDefault.StatusVisibilityPublic var postingDefaultVisibility: Status.Visibility
@DecodableDefault.False var postingDefaultSensitive
var postingDefaultLanguage: String?
@DecodableDefault.ExpandMediaDefault var readingExpandMedia: Mastodon.Preferences.ExpandMedia
@DecodableDefault.False var readingExpandSpoilers
}
}
extension Identity {
var handle: String {
if let account = account, let host = account.url.host {
return account.url.lastPathComponent + "@" + host
}
return instance?.title ?? url.host ?? url.absoluteString
}
var image: URL? { account?.avatar ?? instance?.thumbnail }
}
extension Identity.Preferences {
func updated(from serverPreferences: Preferences) -> Self {
var mutable = self
if useServerPostingReadingPreferences {
mutable.postingDefaultVisibility = serverPreferences.postingDefaultVisibility
mutable.postingDefaultSensitive = serverPreferences.postingDefaultSensitive
mutable.readingExpandMedia = serverPreferences.readingExpandMedia
mutable.readingExpandSpoilers = serverPreferences.readingExpandSpoilers
}
return mutable
}
}

View File

@ -1,7 +0,0 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
struct TransientStatusCollection: Codable {
let id: String
}

View File

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

5
Services/.gitignore vendored Normal file
View File

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

35
Services/Package.swift Normal file
View File

@ -0,0 +1,35 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "Services",
platforms: [
.iOS(.v14),
.macOS(.v11)
],
products: [
.library(
name: "Services",
targets: ["Services"]),
.library(
name: "ServiceMocks",
targets: ["ServiceMocks"])
],
dependencies: [
.package(url: "https://github.com/groue/CombineExpectations.git", .upToNextMajor(from: "0.5.0")),
.package(name: "GRDB", url: "https://github.com/groue/GRDB.swift.git", .upToNextMajor(from: "5.0.0-beta.10")),
.package(path: "Mastodon")
],
targets: [
.target(
name: "Services",
dependencies: ["GRDB", "Mastodon"]),
.target(
name: "ServiceMocks",
dependencies: ["Services", .product(name: "MastodonStubs", package: "Mastodon")]),
.testTarget(
name: "ServicesTests",
dependencies: ["ServiceMocks", "CombineExpectations"])
]
)

View File

@ -0,0 +1,13 @@
import Foundation
import HTTP
import Services
import Stubbing
extension AppEnvironment {
static let mock = AppEnvironment(
session: Session(configuration: .stubbing),
webAuthSessionType: SuccessfulMockWebAuthSession.self,
keychainServiceType: MockKeychainService.self,
userDefaults: MockUserDefaults(),
inMemoryContent: true)
}

View File

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import Foundation import Foundation
import Services
struct MockKeychainService {} struct MockKeychainService {}

View File

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import Foundation import Foundation
import Services
class MockWebAuthSession: WebAuthSession { class MockWebAuthSession: WebAuthSession {
let completionHandler: WebAuthSessionCompletionHandler let completionHandler: WebAuthSessionCompletionHandler

View File

@ -4,14 +4,14 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
struct AllIdentitiesService { public struct AllIdentitiesService {
let mostRecentlyUsedIdentityID: AnyPublisher<UUID?, Never> public let mostRecentlyUsedIdentityID: AnyPublisher<UUID?, Never>
private let identityDatabase: IdentityDatabase private let identityDatabase: IdentityDatabase
private let environment: AppEnvironment private let environment: AppEnvironment
init(identityDatabase: IdentityDatabase, environment: AppEnvironment) { public init(environment: AppEnvironment) throws {
self.identityDatabase = identityDatabase self.identityDatabase = try IdentityDatabase(inMemory: environment.inMemoryContent)
self.environment = environment self.environment = environment
mostRecentlyUsedIdentityID = identityDatabase.mostRecentlyUsedIdentityIDObservation() mostRecentlyUsedIdentityID = identityDatabase.mostRecentlyUsedIdentityIDObservation()
@ -20,7 +20,7 @@ struct AllIdentitiesService {
} }
} }
extension AllIdentitiesService { public extension AllIdentitiesService {
func identityService(id: UUID) throws -> IdentityService { func identityService(id: UUID) throws -> IdentityService {
try IdentityService(identityID: id, try IdentityService(identityID: id,
identityDatabase: identityDatabase, identityDatabase: identityDatabase,

View File

@ -4,18 +4,18 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
struct AuthenticationService { public struct AuthenticationService {
private let networkClient: APIClient private let networkClient: APIClient
private let webAuthSessionType: WebAuthSession.Type private let webAuthSessionType: WebAuthSession.Type
private let webAuthSessionContextProvider = WebAuthSessionContextProvider() private let webAuthSessionContextProvider = WebAuthSessionContextProvider()
init(environment: AppEnvironment) { public init(environment: AppEnvironment) {
networkClient = APIClient(session: environment.session) networkClient = APIClient(session: environment.session)
webAuthSessionType = environment.webAuthSessionType webAuthSessionType = environment.webAuthSessionType
} }
} }
extension AuthenticationService { public extension AuthenticationService {
func authorizeApp(instanceURL: URL) -> AnyPublisher<AppAuthorization, Error> { func authorizeApp(instanceURL: URL) -> AnyPublisher<AppAuthorization, Error> {
let endpoint = AppAuthorizationEndpoint.apps( let endpoint = AppAuthorizationEndpoint.apps(
clientName: OAuth.clientName, clientName: OAuth.clientName,

View File

@ -241,7 +241,7 @@ private extension ContentDatabase {
} }
} }
extension Account: TableRecord, FetchableRecord, PersistableRecord { extension Account: FetchableRecord, PersistableRecord {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder { public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder() APIDecoder()
} }
@ -251,14 +251,14 @@ extension Account: TableRecord, FetchableRecord, PersistableRecord {
} }
} }
protocol StatusCollection: FetchableRecord, PersistableRecord { public protocol StatusCollection: FetchableRecord, PersistableRecord {
var id: String { get } var id: String { get }
var fetch: (Database) throws -> [StatusResult] { get } var fetch: (Database) throws -> [StatusResult] { get }
func joinRecord(status: Status) -> PersistableRecord func joinRecord(status: Status) -> PersistableRecord
} }
private struct TimelineStatusJoin: Codable, TableRecord, FetchableRecord, PersistableRecord { private struct TimelineStatusJoin: Codable, FetchableRecord, PersistableRecord {
let timelineId: String let timelineId: String
let statusId: String let statusId: String
@ -293,7 +293,7 @@ extension Timeline: StatusCollection {
} }
} }
var fetch: (Database) throws -> [StatusResult] { public var fetch: (Database) throws -> [StatusResult] {
statuses statuses
.including(required: StoredStatus.account) .including(required: StoredStatus.account)
.including(optional: StoredStatus.reblogAccount) .including(optional: StoredStatus.reblogAccount)
@ -302,7 +302,7 @@ extension Timeline: StatusCollection {
.fetchAll .fetchAll
} }
func joinRecord(status: Status) -> PersistableRecord { public func joinRecord(status: Status) -> PersistableRecord {
TimelineStatusJoin(timelineId: id, statusId: status.id) TimelineStatusJoin(timelineId: id, statusId: status.id)
} }
} }
@ -323,7 +323,7 @@ private extension Timeline {
} }
} }
extension Filter: TableRecord, FetchableRecord, PersistableRecord { extension Filter: FetchableRecord, PersistableRecord {
public static func databaseJSONDecoder(for column: String) -> JSONDecoder { public static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder() APIDecoder()
} }
@ -333,7 +333,7 @@ extension Filter: TableRecord, FetchableRecord, PersistableRecord {
} }
} }
private struct TransientStatusCollectionElement: Codable, TableRecord, FetchableRecord, PersistableRecord { private struct TransientStatusCollectionElement: Codable, FetchableRecord, PersistableRecord {
let transientStatusCollectionId: String let transientStatusCollectionId: String
let statusId: String let statusId: String
@ -341,7 +341,7 @@ private struct TransientStatusCollectionElement: Codable, TableRecord, Fetchable
} }
extension TransientStatusCollection: StatusCollection { extension TransientStatusCollection: StatusCollection {
var fetch: (Database) throws -> [StatusResult] { public var fetch: (Database) throws -> [StatusResult] {
{ {
try StatusResult.fetchAll( try StatusResult.fetchAll(
$0, $0,
@ -356,7 +356,7 @@ extension TransientStatusCollection: StatusCollection {
} }
} }
func joinRecord(status: Status) -> PersistableRecord { public func joinRecord(status: Status) -> PersistableRecord {
TransientStatusCollectionElement(transientStatusCollectionId: id, statusId: status.id) TransientStatusCollectionElement(transientStatusCollectionId: id, statusId: status.id)
} }
} }
@ -451,7 +451,7 @@ private extension StoredStatus {
} }
} }
extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord { extension StoredStatus: FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder { static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder() APIDecoder()
} }
@ -461,7 +461,7 @@ extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord {
} }
} }
struct StatusResult: Codable, Hashable, FetchableRecord { public struct StatusResult: Codable, Hashable, FetchableRecord {
let account: Account let account: Account
fileprivate let status: StoredStatus fileprivate let status: StoredStatus
let reblogAccount: Account? let reblogAccount: Account?

View File

@ -2,6 +2,6 @@
import Foundation import Foundation
enum DatabaseError: Error { public enum DatabaseError: Error {
case documentsDirectoryNotFound case documentsDirectoryNotFound
} }

View File

@ -237,7 +237,7 @@ private extension IdentityDatabase {
} }
} }
private struct StoredIdentity: Codable, Hashable, TableRecord, FetchableRecord, PersistableRecord { private struct StoredIdentity: Codable, Hashable, FetchableRecord, PersistableRecord {
let id: UUID let id: UUID
let url: URL let url: URL
let lastUsedAt: Date let lastUsedAt: Date
@ -281,6 +281,6 @@ private extension Identity {
} }
} }
extension Identity.Instance: TableRecord, FetchableRecord, PersistableRecord {} extension Identity.Instance: FetchableRecord, PersistableRecord {}
extension Identity.Account: TableRecord, FetchableRecord, PersistableRecord {} extension Identity.Account: FetchableRecord, PersistableRecord {}

View File

@ -4,15 +4,27 @@ import Foundation
import HTTP import HTTP
import Mastodon import Mastodon
struct AppEnvironment { public struct AppEnvironment {
let session: Session let session: Session
let webAuthSessionType: WebAuthSession.Type let webAuthSessionType: WebAuthSession.Type
let keychainServiceType: KeychainService.Type let keychainServiceType: KeychainService.Type
let userDefaults: UserDefaults let userDefaults: UserDefaults
let inMemoryContent: Bool let inMemoryContent: Bool
public init(session: Session,
webAuthSessionType: WebAuthSession.Type,
keychainServiceType: KeychainService.Type,
userDefaults: UserDefaults,
inMemoryContent: Bool) {
self.session = session
self.webAuthSessionType = webAuthSessionType
self.keychainServiceType = keychainServiceType
self.userDefaults = userDefaults
self.inMemoryContent = inMemoryContent
}
} }
extension AppEnvironment { public extension AppEnvironment {
static let live: Self = Self( static let live: Self = Self(
session: Session(configuration: .default), session: Session(configuration: .default),
webAuthSessionType: LiveWebAuthSession.self, webAuthSessionType: LiveWebAuthSession.self,

View File

@ -0,0 +1,71 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import Mastodon
public struct Identity: Codable, Hashable, Identifiable {
public let id: UUID
public let url: URL
public let lastUsedAt: Date
public let preferences: Identity.Preferences
public let instance: Identity.Instance?
public let account: Identity.Account?
public let lastRegisteredDeviceToken: String?
public let pushSubscriptionAlerts: PushSubscription.Alerts
}
public extension Identity {
struct Instance: Codable, Hashable {
public let uri: String
public let streamingAPI: URL
public let title: String
public let thumbnail: URL?
}
struct Account: Codable, Hashable {
public let id: String
public let identityID: UUID
public let username: String
public let displayName: String
public let url: URL
public let avatar: URL
public let avatarStatic: URL
public let header: URL
public let headerStatic: URL
public let emojis: [Emoji]
}
struct Preferences: Codable, Hashable {
@DecodableDefault.True public var useServerPostingReadingPreferences
@DecodableDefault.StatusVisibilityPublic public var postingDefaultVisibility: Status.Visibility
@DecodableDefault.False public var postingDefaultSensitive
public var postingDefaultLanguage: String?
@DecodableDefault.ExpandMediaDefault public var readingExpandMedia: Mastodon.Preferences.ExpandMedia
@DecodableDefault.False public var readingExpandSpoilers
}
var handle: String {
if let account = account, let host = account.url.host {
return account.url.lastPathComponent + "@" + host
}
return instance?.title ?? url.host ?? url.absoluteString
}
var image: URL? { account?.avatar ?? instance?.thumbnail }
}
public extension Identity.Preferences {
func updated(from serverPreferences: Preferences) -> Self {
var mutable = self
if useServerPostingReadingPreferences {
mutable.postingDefaultVisibility = serverPreferences.postingDefaultVisibility
mutable.postingDefaultSensitive = serverPreferences.postingDefaultSensitive
mutable.readingExpandMedia = serverPreferences.readingExpandMedia
mutable.readingExpandSpoilers = serverPreferences.readingExpandSpoilers
}
return mutable
}
}

View File

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

View File

@ -4,7 +4,7 @@ import Foundation
import AuthenticationServices import AuthenticationServices
import Combine import Combine
protocol WebAuthSession: AnyObject { public protocol WebAuthSession: AnyObject {
init(url URL: URL, init(url URL: URL,
callbackURLScheme: String?, callbackURLScheme: String?,
completionHandler: @escaping WebAuthSessionCompletionHandler) completionHandler: @escaping WebAuthSessionCompletionHandler)
@ -41,9 +41,9 @@ class WebAuthSessionContextProvider: NSObject, ASWebAuthenticationPresentationCo
} }
} }
typealias WebAuthSessionCompletionHandler = ASWebAuthenticationSession.CompletionHandler public typealias WebAuthSessionCompletionHandler = ASWebAuthenticationSession.CompletionHandler
typealias WebAuthSessionError = ASWebAuthenticationSessionError public typealias WebAuthSessionError = ASWebAuthenticationSessionError
typealias WebAuthPresentationContextProviding = ASWebAuthenticationPresentationContextProviding public typealias WebAuthPresentationContextProviding = ASWebAuthenticationPresentationContextProviding
typealias LiveWebAuthSession = ASWebAuthenticationSession public typealias LiveWebAuthSession = ASWebAuthenticationSession
extension LiveWebAuthSession: WebAuthSession {} extension LiveWebAuthSession: WebAuthSession {}

View File

@ -4,9 +4,9 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
class IdentityService { public class IdentityService {
@Published private(set) var identity: Identity @Published public private(set) var identity: Identity
let observationErrors: AnyPublisher<Error, Never> public let observationErrors: AnyPublisher<Error, Never>
private let identityDatabase: IdentityDatabase private let identityDatabase: IdentityDatabase
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
@ -50,7 +50,7 @@ class IdentityService {
} }
} }
extension IdentityService { public extension IdentityService {
var isAuthorized: Bool { networkClient.accessToken != nil } var isAuthorized: Bool { networkClient.accessToken != nil }
func updateLastUse() -> AnyPublisher<Never, Error> { func updateLastUse() -> AnyPublisher<Never, Error> {

View File

@ -2,7 +2,7 @@
import Foundation import Foundation
protocol KeychainService { public protocol KeychainService {
static func setGenericPassword(data: Data, forAccount key: String, service: String) throws static func setGenericPassword(data: Data, forAccount key: String, service: String) throws
static func deleteGenericPassword(account: String, service: String) throws static func deleteGenericPassword(account: String, service: String) throws
static func getGenericPassword(account: String, service: String) throws -> Data? static func getGenericPassword(account: String, service: String) throws -> Data?
@ -11,10 +11,10 @@ protocol KeychainService {
static func deleteKey(applicationTag: String) throws static func deleteKey(applicationTag: String) throws
} }
struct LiveKeychainService {} public struct LiveKeychainService {}
extension LiveKeychainService: KeychainService { extension LiveKeychainService: KeychainService {
static func setGenericPassword(data: Data, forAccount account: String, service: String) throws { public static func setGenericPassword(data: Data, forAccount account: String, service: String) throws {
var query = genericPasswordQueryDictionary(account: account, service: service) var query = genericPasswordQueryDictionary(account: account, service: service)
query[kSecValueData as String] = data query[kSecValueData as String] = data
@ -26,7 +26,7 @@ extension LiveKeychainService: KeychainService {
} }
} }
static func deleteGenericPassword(account: String, service: String) throws { public static func deleteGenericPassword(account: String, service: String) throws {
let status = SecItemDelete(genericPasswordQueryDictionary(account: account, service: service) as CFDictionary) let status = SecItemDelete(genericPasswordQueryDictionary(account: account, service: service) as CFDictionary)
if status != errSecSuccess { if status != errSecSuccess {
@ -34,7 +34,7 @@ extension LiveKeychainService: KeychainService {
} }
} }
static func getGenericPassword(account: String, service: String) throws -> Data? { public static func getGenericPassword(account: String, service: String) throws -> Data? {
var result: AnyObject? var result: AnyObject?
var query = genericPasswordQueryDictionary(account: account, service: service) var query = genericPasswordQueryDictionary(account: account, service: service)
@ -53,7 +53,7 @@ extension LiveKeychainService: KeychainService {
} }
} }
static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data { public static func generateKeyAndReturnPublicKey(applicationTag: String, attributes: [String: Any]) throws -> Data {
var attributes = attributes var attributes = attributes
var error: Unmanaged<CFError>? var error: Unmanaged<CFError>?
@ -78,7 +78,7 @@ extension LiveKeychainService: KeychainService {
return publicKeyData return publicKeyData
} }
static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? { public static func getPrivateKey(applicationTag: String, attributes: [String: Any]) throws -> Data? {
var result: AnyObject? var result: AnyObject?
var error: Unmanaged<CFError>? var error: Unmanaged<CFError>?
var query = keyQueryDictionary(applicationTag: applicationTag) var query = keyQueryDictionary(applicationTag: applicationTag)
@ -106,7 +106,7 @@ extension LiveKeychainService: KeychainService {
} }
} }
static func deleteKey(applicationTag: String) throws { public static func deleteKey(applicationTag: String) throws {
let status = SecItemDelete(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary) let status = SecItemDelete(keyQueryDictionary(applicationTag: applicationTag) as CFDictionary)
if status != errSecSuccess { if status != errSecSuccess {

View File

@ -2,7 +2,7 @@
import Foundation import Foundation
protocol SecretsStorable { public protocol SecretsStorable {
var dataStoredInSecrets: Data { get } var dataStoredInSecrets: Data { get }
static func fromDataStoredInSecrets(_ data: Data) throws -> Self static func fromDataStoredInSecrets(_ data: Data) throws -> Self
} }
@ -11,17 +11,17 @@ enum SecretsStorableError: Error {
case conversionFromDataStoredInSecrets(Data) case conversionFromDataStoredInSecrets(Data)
} }
struct SecretsService { public struct SecretsService {
let identityID: UUID public let identityID: UUID
private let keychainService: KeychainService.Type private let keychainService: KeychainService.Type
init(identityID: UUID, keychainService: KeychainService.Type) { public init(identityID: UUID, keychainService: KeychainService.Type) {
self.identityID = identityID self.identityID = identityID
self.keychainService = keychainService self.keychainService = keychainService
} }
} }
extension SecretsService { public extension SecretsService {
enum Item: String, CaseIterable { enum Item: String, CaseIterable {
case clientID case clientID
case clientSecret case clientSecret
@ -49,7 +49,7 @@ extension SecretsService.Item {
} }
} }
extension SecretsService { public extension SecretsService {
func set(_ data: SecretsStorable, forItem item: Item) throws { func set(_ data: SecretsStorable, forItem item: Item) throws {
try keychainService.setGenericPassword( try keychainService.setGenericPassword(
data: data.dataStoredInSecrets, data: data.dataStoredInSecrets,
@ -118,17 +118,17 @@ private extension SecretsService {
} }
extension Data: SecretsStorable { extension Data: SecretsStorable {
var dataStoredInSecrets: Data { self } public var dataStoredInSecrets: Data { self }
static func fromDataStoredInSecrets(_ data: Data) throws -> Data { public static func fromDataStoredInSecrets(_ data: Data) throws -> Data {
data data
} }
} }
extension String: SecretsStorable { extension String: SecretsStorable {
var dataStoredInSecrets: Data { Data(utf8) } public var dataStoredInSecrets: Data { Data(utf8) }
static func fromDataStoredInSecrets(_ data: Data) throws -> String { public static func fromDataStoredInSecrets(_ data: Data) throws -> String {
guard let string = String(data: data, encoding: .utf8) else { guard let string = String(data: data, encoding: .utf8) else {
throw SecretsStorableError.conversionFromDataStoredInSecrets(data) throw SecretsStorableError.conversionFromDataStoredInSecrets(data)
} }
@ -137,7 +137,7 @@ extension String: SecretsStorable {
} }
} }
struct PushKey { private struct PushKey {
static let authLength = 16 static let authLength = 16
static let sizeInBits = 256 static let sizeInBits = 256
static let attributes: [String: Any] = [ static let attributes: [String: Any] = [

View File

@ -4,9 +4,9 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
struct ContextService { public struct ContextService {
let statusSections: AnyPublisher<[[Status]], Error> public let statusSections: AnyPublisher<[[Status]], Error>
let paginates = false public let paginates = false
private let status: Status private let status: Status
private let context = CurrentValueSubject<Context, Never>(Context(ancestors: [], descendants: [])) private let context = CurrentValueSubject<Context, Never>(Context(ancestors: [], descendants: []))
@ -34,13 +34,13 @@ struct ContextService {
} }
extension ContextService: StatusListService { extension ContextService: StatusListService {
var filters: AnyPublisher<[Filter], Error> { public var filters: AnyPublisher<[Filter], Error> {
contentDatabase.activeFiltersObservation(date: Date(), context: .thread) contentDatabase.activeFiltersObservation(date: Date(), context: .thread)
} }
var contextParentID: String? { status.id } public var contextParentID: String? { status.id }
func isReplyInContext(status: Status) -> Bool { public func isReplyInContext(status: Status) -> Bool {
let flatContext = flattenedContext() let flatContext = flattenedContext()
guard guard
@ -53,7 +53,7 @@ extension ContextService: StatusListService {
return previousStatus.id != contextParentID && status.inReplyToId == previousStatus.id return previousStatus.id != contextParentID && status.inReplyToId == previousStatus.id
} }
func hasReplyFollowing(status: Status) -> Bool { public func hasReplyFollowing(status: Status) -> Bool {
let flatContext = flattenedContext() let flatContext = flattenedContext()
guard guard
@ -66,7 +66,7 @@ extension ContextService: StatusListService {
return status.id != contextParentID && nextStatus.inReplyToId == status.id return status.id != contextParentID && nextStatus.inReplyToId == status.id
} }
func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> { public func request(maxID: String?, minID: String?) -> AnyPublisher<Never, Error> {
Publishers.Merge( Publishers.Merge(
networkClient.request(StatusEndpoint.status(id: status.id)) networkClient.request(StatusEndpoint.status(id: status.id))
.map { ([$0], collection) } .map { ([$0], collection) }
@ -78,11 +78,11 @@ extension ContextService: StatusListService {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func statusService(status: Status) -> StatusService { public func statusService(status: Status) -> StatusService {
StatusService(status: status, networkClient: networkClient, contentDatabase: contentDatabase) StatusService(status: status, networkClient: networkClient, contentDatabase: contentDatabase)
} }
func contextService(status: Status) -> ContextService { public func contextService(status: Status) -> ContextService {
ContextService(status: status.displayStatus, networkClient: networkClient, contentDatabase: contentDatabase) ContextService(status: status.displayStatus, networkClient: networkClient, contentDatabase: contentDatabase)
} }
} }

View File

@ -4,7 +4,7 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
protocol StatusListService { public protocol StatusListService {
var statusSections: AnyPublisher<[[Status]], Error> { get } var statusSections: AnyPublisher<[[Status]], Error> { get }
var filters: AnyPublisher<[Filter], Error> { get } var filters: AnyPublisher<[Filter], Error> { get }
var paginates: Bool { get } var paginates: Bool { get }
@ -17,7 +17,7 @@ protocol StatusListService {
func contextService(status: Status) -> ContextService func contextService(status: Status) -> ContextService
} }
extension StatusListService { public extension StatusListService {
var paginates: Bool { true } var paginates: Bool { true }
var contextParentID: String? { nil } var contextParentID: String? { nil }

View File

@ -4,8 +4,8 @@ import Foundation
import Combine import Combine
import Mastodon import Mastodon
struct StatusService { public struct StatusService {
let status: Status public let status: Status
private let networkClient: APIClient private let networkClient: APIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
@ -16,7 +16,7 @@ struct StatusService {
} }
} }
extension StatusService { public extension StatusService {
func toggleFavorited() -> AnyPublisher<Never, Error> { func toggleFavorited() -> AnyPublisher<Never, Error> {
networkClient.request(status.favourited networkClient.request(status.favourited
? StatusEndpoint.unfavourite(id: status.id) ? StatusEndpoint.unfavourite(id: status.id)

View File

@ -4,10 +4,10 @@ import Foundation
import Combine import Combine
import UserNotifications import UserNotifications
class UserNotificationService: NSObject { public class UserNotificationService: NSObject {
private let userNotificationCenter: UNUserNotificationCenter private let userNotificationCenter: UNUserNotificationCenter
init(userNotificationCenter: UNUserNotificationCenter = .current()) { public init(userNotificationCenter: UNUserNotificationCenter = .current()) {
self.userNotificationCenter = userNotificationCenter self.userNotificationCenter = userNotificationCenter
super.init() super.init()
@ -16,7 +16,7 @@ class UserNotificationService: NSObject {
} }
} }
extension UserNotificationService { public extension UserNotificationService {
func isAuthorized() -> AnyPublisher<Bool, Error> { func isAuthorized() -> AnyPublisher<Bool, Error> {
getNotificationSettings() getNotificationSettings()
.map(\.authorizationStatus) .map(\.authorizationStatus)
@ -59,7 +59,7 @@ private extension UserNotificationService {
} }
extension UserNotificationService: UNUserNotificationCenterDelegate { extension UserNotificationService: UNUserNotificationCenterDelegate {
func userNotificationCenter( public func userNotificationCenter(
_ center: UNUserNotificationCenter, _ center: UNUserNotificationCenter,
willPresent notification: UNNotification, willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

View File

@ -3,11 +3,12 @@
import XCTest import XCTest
import Combine import Combine
import CombineExpectations import CombineExpectations
@testable import Metatext @testable import Services
@testable import ServiceMocks
class AuthenticationServiceTests: XCTestCase { class AuthenticationServiceTests: XCTestCase {
func testAuthentication() throws { func testAuthentication() throws {
let sut = AuthenticationService(environment: .development) let sut = AuthenticationService(environment: .mock)
let instanceURL = URL(string: "https://mastodon.social")! let instanceURL = URL(string: "https://mastodon.social")!
let appAuthorizationRecorder = sut.authorizeApp(instanceURL: instanceURL).record() let appAuthorizationRecorder = sut.authorizeApp(instanceURL: instanceURL).record()
let appAuthorization = try wait(for: appAuthorizationRecorder.next(), timeout: 1) let appAuthorization = try wait(for: appAuthorizationRecorder.next(), timeout: 1)

View File

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import SwiftUI import SwiftUI
import Services
@main @main
struct MetatextApp: App { struct MetatextApp: App {
@ -8,23 +9,13 @@ struct MetatextApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
// swiftlint:enable weak_delegate // swiftlint:enable weak_delegate
private let allIdentitiesService: AllIdentitiesService = {
let identityDatabase: IdentityDatabase
do {
try identityDatabase = IdentityDatabase()
} catch {
fatalError("Failed to initialize identity database")
}
return AllIdentitiesService(identityDatabase: identityDatabase, environment: .live)
}()
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
RootView( RootView(
viewModel: RootViewModel(appDelegate: appDelegate, viewModel: RootViewModel(appDelegate: appDelegate,
allIdentitiesService: allIdentitiesService, // swiftlint:disable force_try
allIdentitiesService: try! AllIdentitiesService(environment: .live),
// swiftlint:enable force_try
userNotificationService: UserNotificationService())) userNotificationService: UserNotificationService()))
} }
} }

View File

@ -7,10 +7,11 @@ import HTTP
import Mastodon import Mastodon
@testable import Metatext @testable import Metatext
import Services
class AddIdentityViewModelTests: XCTestCase { class AddIdentityViewModelTests: XCTestCase {
func testAddIdentity() throws { func testAddIdentity() throws {
let identityDatabase = IdentityDatabase.fresh() let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
let sut = AddIdentityViewModel(allIdentitiesService: .fresh(identityDatabase: identityDatabase))
let addedIDRecorder = sut.addedIdentityID.record() let addedIDRecorder = sut.addedIdentityID.record()
sut.urlFieldText = "https://mastodon.social" sut.urlFieldText = "https://mastodon.social"
@ -20,8 +21,7 @@ class AddIdentityViewModelTests: XCTestCase {
} }
func testAddIdentityWithoutScheme() throws { func testAddIdentityWithoutScheme() throws {
let identityDatabase = IdentityDatabase.fresh() let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
let sut = AddIdentityViewModel(allIdentitiesService: .fresh(identityDatabase: identityDatabase))
let addedIDRecorder = sut.addedIdentityID.record() let addedIDRecorder = sut.addedIdentityID.record()
sut.urlFieldText = "mastodon.social" sut.urlFieldText = "mastodon.social"
@ -31,7 +31,7 @@ class AddIdentityViewModelTests: XCTestCase {
} }
func testInvalidURL() throws { func testInvalidURL() throws {
let sut = AddIdentityViewModel(allIdentitiesService: .fresh()) let sut = AddIdentityViewModel(allIdentitiesService: .fresh)
let recorder = sut.$alertItem.record() let recorder = sut.$alertItem.record()
XCTAssertNil(try wait(for: recorder.next(), timeout: 1)) XCTAssertNil(try wait(for: recorder.next(), timeout: 1))
@ -51,9 +51,7 @@ class AddIdentityViewModelTests: XCTestCase {
keychainServiceType: MockKeychainService.self, keychainServiceType: MockKeychainService.self,
userDefaults: MockUserDefaults(), userDefaults: MockUserDefaults(),
inMemoryContent: true) inMemoryContent: true)
let allIdentitiesService = AllIdentitiesService( let allIdentitiesService = try AllIdentitiesService(environment: environment)
identityDatabase: .fresh(),
environment: environment)
let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService) let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService)
let recorder = sut.$alertItem.record() let recorder = sut.$alertItem.record()
@ -64,4 +62,8 @@ class AddIdentityViewModelTests: XCTestCase {
try wait(for: recorder.next().inverted, timeout: 1) try wait(for: recorder.next().inverted, timeout: 1)
} }
func testFuck() {
}
} }

View File

@ -3,6 +3,7 @@
import XCTest import XCTest
import Combine import Combine
import CombineExpectations import CombineExpectations
import Services
@testable import Metatext @testable import Metatext
class RootViewModelTests: XCTestCase { class RootViewModelTests: XCTestCase {
@ -10,9 +11,7 @@ class RootViewModelTests: XCTestCase {
func testAddIdentity() throws { func testAddIdentity() throws {
let sut = RootViewModel(appDelegate: AppDelegate(), let sut = RootViewModel(appDelegate: AppDelegate(),
allIdentitiesService: AllIdentitiesService( allIdentitiesService: .fresh,
identityDatabase: .fresh(),
environment: .development),
userNotificationService: UserNotificationService()) userNotificationService: UserNotificationService())
let recorder = sut.$tabNavigationViewModel.record() let recorder = sut.$tabNavigationViewModel.record()

View File

@ -2,6 +2,7 @@
import Foundation import Foundation
import Combine import Combine
import Services
class AddIdentityViewModel: ObservableObject { class AddIdentityViewModel: ObservableObject {
@Published var urlFieldText = "" @Published var urlFieldText = ""

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class EditFilterViewModel: ObservableObject { class EditFilterViewModel: ObservableObject {
@Published var filter: Filter @Published var filter: Filter

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class FiltersViewModel: ObservableObject { class FiltersViewModel: ObservableObject {
@Published var activeFilters = [Filter]() @Published var activeFilters = [Filter]()

View File

@ -2,6 +2,7 @@
import Combine import Combine
import Foundation import Foundation
import Services
class IdentitiesViewModel: ObservableObject { class IdentitiesViewModel: ObservableObject {
@Published private(set) var identity: Identity @Published private(set) var identity: Identity

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class ListsViewModel: ObservableObject { class ListsViewModel: ObservableObject {
@Published private(set) var lists = [MastodonList]() @Published private(set) var lists = [MastodonList]()

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class NotificationTypesPreferencesViewModel: ObservableObject { class NotificationTypesPreferencesViewModel: ObservableObject {
@Published var pushSubscriptionAlerts: PushSubscription.Alerts @Published var pushSubscriptionAlerts: PushSubscription.Alerts

View File

@ -2,6 +2,7 @@
import Foundation import Foundation
import Combine import Combine
import Services
class PostingReadingPreferencesViewModel: ObservableObject { class PostingReadingPreferencesViewModel: ObservableObject {
@Published var preferences: Identity.Preferences @Published var preferences: Identity.Preferences

View File

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import Foundation import Foundation
import Services
class PreferencesViewModel: ObservableObject { class PreferencesViewModel: ObservableObject {
let handle: String let handle: String

View File

@ -2,6 +2,7 @@
import Foundation import Foundation
import Combine import Combine
import Services
class RootViewModel: ObservableObject { class RootViewModel: ObservableObject {
@Published private(set) var tabNavigationViewModel: TabNavigationViewModel? @Published private(set) var tabNavigationViewModel: TabNavigationViewModel?

View File

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import Foundation import Foundation
import Services
class SecondaryNavigationViewModel: ObservableObject { class SecondaryNavigationViewModel: ObservableObject {
@Published private(set) var identity: Identity @Published private(set) var identity: Identity

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class StatusListViewModel: ObservableObject { class StatusListViewModel: ObservableObject {
@Published private(set) var statusIDs = [[String]]() @Published private(set) var statusIDs = [[String]]()

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
struct StatusViewModel { struct StatusViewModel {
let content: NSAttributedString let content: NSAttributedString

View File

@ -3,6 +3,7 @@
import Foundation import Foundation
import Combine import Combine
import Mastodon import Mastodon
import Services
class TabNavigationViewModel: ObservableObject { class TabNavigationViewModel: ObservableObject {
@Published private(set) var identity: Identity @Published private(set) var identity: Identity

View File

@ -2,6 +2,7 @@
import SwiftUI import SwiftUI
import KingfisherSwiftUI import KingfisherSwiftUI
import struct Services.Identity
struct IdentitiesView: View { struct IdentitiesView: View {
@StateObject var viewModel: IdentitiesViewModel @StateObject var viewModel: IdentitiesViewModel