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 HTTP
import Mastodon
import Services
import ServiceMocks
// swiftlint:disable force_try
private let decoder = APIDecoder()
@ -20,31 +22,6 @@ extension Instance {
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 {
static let development = AppEnvironment(
session: Session(configuration: .stubbing),
@ -55,18 +32,30 @@ extension AppEnvironment {
}
extension AllIdentitiesService {
static func fresh(
identityDatabase: IdentityDatabase = .fresh(),
keychainService: KeychainService = MockKeychainService(),
environment: AppEnvironment = .development) -> AllIdentitiesService {
AllIdentitiesService(
identityDatabase: identityDatabase,
environment: environment)
}
static let fresh = try! AllIdentitiesService(environment: .development)
static let development = AllIdentitiesService(
identityDatabase: .development,
environment: .development)
static var development: Self = {
let allIdentitiesService = try! AllIdentitiesService(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 {

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

View file

@ -2,16 +2,16 @@
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 data(url: URL) -> Data?
func dataString(url: URL) -> String?
func statusCode(url: URL) -> Int?
}
extension Stubbing {
public extension Stubbing {
func stub(url: URL) -> HTTPStub? {
if let data = data(url: url),
let statusCode = statusCode(url: url),

View file

@ -3,25 +3,25 @@
import Foundation
import HTTP
class StubbingURLProtocol: URLProtocol {
public class StubbingURLProtocol: URLProtocol {
private static var targetsForURLs = [URL: Target]()
override class func canInit(with task: URLSessionTask) -> Bool {
override public class func canInit(with task: URLSessionTask) -> Bool {
true
}
override class func canInit(with request: URLRequest) -> Bool {
override public class func canInit(with request: URLRequest) -> Bool {
true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
override public class func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
override public func startLoading() {
guard
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")
}
@ -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 {
static func process(target: Target) {
public static func process(target: Target) {
if let url = try? target.asURLRequest().url {
targetsForURLs[url] = target
}

View file

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

View file

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

View file

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

View file

@ -2,9 +2,10 @@
import Foundation
import Mastodon
import Stubbing
extension ContextEndpoint: Stubbing {
func dataString(url: URL) -> String? {
public func dataString(url: URL) -> String? {
switch self {
case .context:
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 Mastodon
import Stubbing
extension APITarget: Stubbing {
func stub(url: URL) -> HTTPStub? {
public func stub(url: URL) -> HTTPStub? {
(endpoint as? Stubbing)?.stub(url: url)
}
func data(url: URL) -> Data? {
public func data(url: URL) -> Data? {
(endpoint as? Stubbing)?.data(url: url)
}
func dataString(url: URL) -> String? {
public func dataString(url: URL) -> String? {
(endpoint as? Stubbing)?.dataString(url: url)
}
func statusCode(url: URL) -> Int? {
public func statusCode(url: URL) -> Int? {
(endpoint as? Stubbing)?.statusCode(url: url)
}
}

View file

@ -2,9 +2,10 @@
import Foundation
import Mastodon
import Stubbing
extension PreferencesEndpoint: Stubbing {
func dataString(url: URL) -> String? {
public func dataString(url: URL) -> String? {
switch self {
case .preferences:
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",
"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",
"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 */; };
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41DE24F8868800D55A2D /* AttachmentViewModel.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 */; };
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 */; };
D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = D0666A4824C6C1A300F3F04B /* GRDB */; };
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577624D29006004758DB /* MockWebAuthSession.swift */; };
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; };
D0A652AD24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */; };
D075C28524FCD41D00D35112 /* Services in Frameworks */ = {isa = PBXBuildFile; productRef = D075C28424FCD41D00D35112 /* Services */; };
D075C28724FCD92400D35112 /* Services in Frameworks */ = {isa = PBXBuildFile; productRef = D075C28624FCD92400D35112 /* Services */; };
D0ADCBF124FD05510062ACCE /* ServiceMocks in Frameworks */ = {isa = PBXBuildFile; productRef = D0ADCBF024FD05510062ACCE /* ServiceMocks */; };
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.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 */; };
D0C7D4A324F7616A001EBDBB /* TabNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42E24F76169001EBDBB /* TabNavigationView.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 */; };
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D0C7D45224F76169001EBDBB /* Assets.xcassets */; };
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 */; };
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46224F76169001EBDBB /* StatusViewModel.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 */; };
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 */; };
@ -76,32 +62,8 @@
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D46F24F76169001EBDBB /* View+Extensions.swift */; };
D0C7D4DB24F7616A001EBDBB /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47024F76169001EBDBB /* Date+Extensions.swift */; };
D0C7D4DC24F7616A001EBDBB /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D47124F76169001EBDBB /* Data+Extensions.swift */; };
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 */; };
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 */; };
/* End PBXBuildFile section */
@ -142,20 +104,12 @@
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>"; };
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; };
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>"; };
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; };
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>"; };
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>"; };
D075C28324FCD27300D35112 /* Services */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Services; 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>"; };
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>"; };
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>"; };
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>"; };
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>"; };
@ -197,10 +148,6 @@
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>"; };
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>"; };
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>"; };
@ -209,30 +156,11 @@
D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
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>"; };
D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Notification Service Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
D0E5361B24E3EB4D00FB1CE1 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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>"; };
/* End PBXFileReference section */
@ -241,9 +169,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D075C28524FCD41D00D35112 /* Services in Frameworks */,
D0ADCBF124FD05510062ACCE /* ServiceMocks in Frameworks */,
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
D0E0F1E624FC4B76002C04BF /* Mastodon in Frameworks */,
D0666A4924C6C1A300F3F04B /* GRDB in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -259,7 +187,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D0E0F1E824FC5A61002C04BF /* Mastodon in Frameworks */,
D075C28724FCD92400D35112 /* Services in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -289,7 +217,6 @@
isa = PBXGroup;
children = (
D0C7D45224F76169001EBDBB /* Assets.xcassets */,
D0C7D46424F76169001EBDBB /* Databases */,
D0ED1BB224CE3A1600B4899C /* Development Assets */,
D0C7D46824F76169001EBDBB /* Extensions */,
D0666A7924C7745A00F3F04B /* Frameworks */,
@ -297,10 +224,9 @@
D0C7D45624F76169001EBDBB /* Localizations */,
D0E0F1E424FC49FC002C04BF /* Mastodon */,
D0C7D43824F76169001EBDBB /* Model */,
D0C7D47324F76169001EBDBB /* Networking */,
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
D047FA8D24C3E21200AF17C5 /* Products */,
D0C7D48924F76169001EBDBB /* Services */,
D075C28324FCD27300D35112 /* Services */,
D0C7D41D24F76169001EBDBB /* Supporting Files */,
D0C7D45324F76169001EBDBB /* System */,
D0666A2224C677B400F3F04B /* Tests */,
@ -324,7 +250,6 @@
isa = PBXGroup;
children = (
D0666A2524C677B400F3F04B /* Info.plist */,
D0EC8DD024DFE34F00A08489 /* Services */,
D0ED1B6C24CE0EED00B4899C /* View Models */,
);
path = Tests;
@ -381,9 +306,6 @@
isa = PBXGroup;
children = (
D0C7D45024F76169001EBDBB /* AlertItem.swift */,
D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */,
D0C7D43B24F76169001EBDBB /* Identity.swift */,
D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */,
);
path = Model;
sourceTree = "<group>";
@ -427,23 +349,12 @@
path = "View Models";
sourceTree = "<group>";
};
D0C7D46424F76169001EBDBB /* Databases */ = {
isa = PBXGroup;
children = (
D0C7D46624F76169001EBDBB /* ContentDatabase.swift */,
D0C7D46724F76169001EBDBB /* DatabaseError.swift */,
D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */,
);
path = Databases;
sourceTree = "<group>";
};
D0C7D46824F76169001EBDBB /* Extensions */ = {
isa = PBXGroup;
children = (
D0C7D47124F76169001EBDBB /* Data+Extensions.swift */,
D0C7D47024F76169001EBDBB /* Date+Extensions.swift */,
D0C7D46E24F76169001EBDBB /* KingfisherOptionsInfo+Extensions.swift */,
D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */,
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
D0C7D46D24F76169001EBDBB /* Publisher+Extensions.swift */,
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
@ -453,54 +364,6 @@
path = Extensions;
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 */ = {
isa = PBXGroup;
children = (
@ -511,14 +374,6 @@
path = "Notification Service Extension";
sourceTree = "<group>";
};
D0EC8DD024DFE34F00A08489 /* Services */ = {
isa = PBXGroup;
children = (
D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */,
);
path = Services;
sourceTree = "<group>";
};
D0ED1B6C24CE0EED00B4899C /* View Models */ = {
isa = PBXGroup;
children = (
@ -531,16 +386,7 @@
D0ED1BB224CE3A1600B4899C /* Development Assets */ = {
isa = PBXGroup;
children = (
D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */,
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";
sourceTree = "<group>";
@ -565,9 +411,9 @@
);
name = Metatext;
packageProductDependencies = (
D0666A4824C6C1A300F3F04B /* GRDB */,
D06B492224D4611300642749 /* KingfisherSwiftUI */,
D0E0F1E524FC4B76002C04BF /* Mastodon */,
D075C28424FCD41D00D35112 /* Services */,
D0ADCBF024FD05510062ACCE /* ServiceMocks */,
);
productName = "Metatext (iOS)";
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
@ -608,7 +454,7 @@
);
name = "Notification Service Extension";
packageProductDependencies = (
D0E0F1E724FC5A61002C04BF /* Mastodon */,
D075C28624FCD92400D35112 /* Services */,
);
productName = "Notification Service Extension";
productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */;
@ -647,7 +493,6 @@
);
mainGroup = D047FA7F24C3E21000AF17C5;
packageReferences = (
D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */,
D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */,
D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */,
);
@ -669,7 +514,6 @@
files = (
D0C7D4C524F7616A001EBDBB /* Localizable.strings in Resources */,
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */,
D054950124EA4FFE008B00A5 /* DevelopmentAssets.xcassets in Resources */,
D0C7D4C224F7616A001EBDBB /* Assets.xcassets in Resources */,
D0C7D4C624F7616A001EBDBB /* Localizable.stringsdict in Resources */,
);
@ -717,81 +561,48 @@
buildActionMask = 2147483647;
files = (
D0C7D4CA24F7616A001EBDBB /* NotificationTypesPreferencesViewModel.swift in Sources */,
D0C7D4F424F7616A001EBDBB /* StatusListService.swift in Sources */,
D0C7D4B724F7616A001EBDBB /* TransientStatusCollection.swift in Sources */,
D01F41DF24F8868800D55A2D /* AttachmentViewModel.swift in Sources */,
D0C7D4A324F7616A001EBDBB /* TabNavigationView.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 */,
D0C7D4FA24F7616A001EBDBB /* AllIdentitiesService.swift in Sources */,
D0C7D4CD24F7616A001EBDBB /* AddIdentityViewModel.swift in Sources */,
D03658D124EDD80900AC17EC /* ContextEndpoint+Stubbing.swift in Sources */,
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */,
D0DC174A24CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift in Sources */,
D0C7D49A24F7616A001EBDBB /* StatusListView.swift in Sources */,
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
D0C7D4A524F7616A001EBDBB /* StatusListViewController.swift in Sources */,
D0C7D4CC24F7616A001EBDBB /* IdentitiesViewModel.swift in Sources */,
D0C7D4E024F7616A001EBDBB /* WebAuthSession.swift in Sources */,
D0C7D4CB24F7616A001EBDBB /* RootViewModel.swift in Sources */,
D0C7D4CE24F7616A001EBDBB /* PreferencesViewModel.swift in Sources */,
D0C7D4D124F7616A001EBDBB /* IdentityDatabase.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 */,
D0C7D4D024F7616A001EBDBB /* StatusListViewModel.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 */,
D0C7D4DA24F7616A001EBDBB /* View+Extensions.swift in Sources */,
D0C7D4C824F7616A001EBDBB /* SecondaryNavigationViewModel.swift in Sources */,
D0C7D4D524F7616A001EBDBB /* String+Extensions.swift in Sources */,
D0C7D4AB24F7616A001EBDBB /* Identity.swift in Sources */,
D0C7D4C024F7616A001EBDBB /* AlertItem.swift in Sources */,
D0C7D4A224F7616A001EBDBB /* NotificationTypesPreferencesView.swift in Sources */,
D0C7D4CF24F7616A001EBDBB /* StatusViewModel.swift in Sources */,
D0C7D4C724F7616A001EBDBB /* PostingReadingPreferencesViewModel.swift in Sources */,
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */,
D0DC175224D008E300A75C65 /* MastodonTarget+Stubbing.swift in Sources */,
D0C7D4F124F7616A001EBDBB /* IdentityService.swift in Sources */,
D04FD74224D4AA34007D572D /* DevelopmentModels.swift in Sources */,
D0C7D4F524F7616A001EBDBB /* AuthenticationService.swift in Sources */,
D0DC175824D0130800A75C65 /* HTTPStubs.swift in Sources */,
D0C7D4D924F7616A001EBDBB /* KingfisherOptionsInfo+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 */,
D0C7D4F824F7616A001EBDBB /* SecretsService.swift in Sources */,
D0C7D4F624F7616A001EBDBB /* KeychainService.swift in Sources */,
D0DC174D24CFF1F100A75C65 /* Stubbing.swift in Sources */,
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */,
D074577724D29006004758DB /* MockWebAuthSession.swift in Sources */,
D0C7D4F924F7616A001EBDBB /* UserNotificationService.swift in Sources */,
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */,
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */,
D0BEB20724FA1121001B0F04 /* FiltersViewModel.swift in Sources */,
D0C7D4C924F7616A001EBDBB /* TabNavigationViewModel.swift in Sources */,
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */,
D0C7D4C424F7616A001EBDBB /* AppDelegate.swift in Sources */,
D074577A24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */,
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */,
D0BEB1FD24F9E4E5001B0F04 /* ListsViewModel.swift in Sources */,
D0C7D4C324F7616A001EBDBB /* MetatextApp.swift in Sources */,
D0C7D4F324F7616A001EBDBB /* ContextService.swift in Sources */,
D0C7D4D824F7616A001EBDBB /* Publisher+Extensions.swift in Sources */,
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */,
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */,
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
D0C7D49B24F7616A001EBDBB /* PreferencesView.swift in Sources */,
D0C7D4D724F7616A001EBDBB /* UIColor+Extensions.swift in Sources */,
);
@ -801,7 +612,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0EC8DD424DFE38900A08489 /* AuthenticationServiceTests.swift in Sources */,
D0ED1B6E24CE100C00B4899C /* AddIdentityViewModelTests.swift in Sources */,
D052BBC724D749C800A80A7A /* RootViewModelTests.swift in Sources */,
);
@ -811,10 +621,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D0C7D4FE24F761C9001EBDBB /* SecretsService.swift in Sources */,
D0E5361C24E3EB4D00FB1CE1 /* NotificationService.swift in Sources */,
D0C7D50024F761E0001EBDBB /* NSError+Extensions.swift in Sources */,
D0C7D4FF24F761D0001EBDBB /* KeychainService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -953,7 +760,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs";
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets";
DEVELOPMENT_TEAM = 82HL67AXQ2;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "Supporting Files/Info.plist";
@ -979,7 +786,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Supporting Files/Metatext.entitlements";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets Development\\ Assets/Mastodon\\ API\\ Stubs";
DEVELOPMENT_ASSET_PATHS = "Development\\ Assets";
DEVELOPMENT_TEAM = 82HL67AXQ2;
ENABLE_PREVIEWS = YES;
INFOPLIST_FILE = "Supporting Files/Info.plist";
@ -1143,14 +950,6 @@
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" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
@ -1167,23 +966,22 @@
package = D065F53724D37E5100741304 /* XCRemoteSwiftPackageReference "CombineExpectations" */;
productName = CombineExpectations;
};
D0666A4824C6C1A300F3F04B /* GRDB */ = {
isa = XCSwiftPackageProductDependency;
package = D0666A4724C6C1A300F3F04B /* XCRemoteSwiftPackageReference "GRDB" */;
productName = GRDB;
};
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
isa = XCSwiftPackageProductDependency;
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = KingfisherSwiftUI;
};
D0E0F1E524FC4B76002C04BF /* Mastodon */ = {
D075C28424FCD41D00D35112 /* Services */ = {
isa = XCSwiftPackageProductDependency;
productName = Mastodon;
productName = Services;
};
D0E0F1E724FC5A61002C04BF /* Mastodon */ = {
D075C28624FCD92400D35112 /* Services */ = {
isa = XCSwiftPackageProductDependency;
productName = Mastodon;
productName = Services;
};
D0ADCBF024FD05510062ACCE /* ServiceMocks */ = {
isa = XCSwiftPackageProductDependency;
productName = ServiceMocks;
};
/* End XCSwiftPackageProductDependency section */
};

View file

@ -21,7 +21,7 @@
},
{
"package": "GRDB",
"repositoryURL": "https://github.com/groue/GRDB.swift",
"repositoryURL": "https://github.com/groue/GRDB.swift.git",
"state": {
"branch": null,
"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 CryptoKit
import Mastodon
import Services
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.
import Foundation
import Services
struct MockKeychainService {}

View file

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

View file

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

View file

@ -4,18 +4,18 @@ import Foundation
import Combine
import Mastodon
struct AuthenticationService {
public struct AuthenticationService {
private let networkClient: APIClient
private let webAuthSessionType: WebAuthSession.Type
private let webAuthSessionContextProvider = WebAuthSessionContextProvider()
init(environment: AppEnvironment) {
public init(environment: AppEnvironment) {
networkClient = APIClient(session: environment.session)
webAuthSessionType = environment.webAuthSessionType
}
}
extension AuthenticationService {
public extension AuthenticationService {
func authorizeApp(instanceURL: URL) -> AnyPublisher<AppAuthorization, Error> {
let endpoint = AppAuthorizationEndpoint.apps(
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 {
APIDecoder()
}
@ -251,14 +251,14 @@ extension Account: TableRecord, FetchableRecord, PersistableRecord {
}
}
protocol StatusCollection: FetchableRecord, PersistableRecord {
public protocol StatusCollection: FetchableRecord, PersistableRecord {
var id: String { get }
var fetch: (Database) throws -> [StatusResult] { get }
func joinRecord(status: Status) -> PersistableRecord
}
private struct TimelineStatusJoin: Codable, TableRecord, FetchableRecord, PersistableRecord {
private struct TimelineStatusJoin: Codable, FetchableRecord, PersistableRecord {
let timelineId: String
let statusId: String
@ -293,7 +293,7 @@ extension Timeline: StatusCollection {
}
}
var fetch: (Database) throws -> [StatusResult] {
public var fetch: (Database) throws -> [StatusResult] {
statuses
.including(required: StoredStatus.account)
.including(optional: StoredStatus.reblogAccount)
@ -302,7 +302,7 @@ extension Timeline: StatusCollection {
.fetchAll
}
func joinRecord(status: Status) -> PersistableRecord {
public func joinRecord(status: Status) -> PersistableRecord {
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 {
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 statusId: String
@ -341,7 +341,7 @@ private struct TransientStatusCollectionElement: Codable, TableRecord, Fetchable
}
extension TransientStatusCollection: StatusCollection {
var fetch: (Database) throws -> [StatusResult] {
public var fetch: (Database) throws -> [StatusResult] {
{
try StatusResult.fetchAll(
$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)
}
}
@ -451,7 +451,7 @@ private extension StoredStatus {
}
}
extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord {
extension StoredStatus: FetchableRecord, PersistableRecord {
static func databaseJSONDecoder(for column: String) -> JSONDecoder {
APIDecoder()
}
@ -461,7 +461,7 @@ extension StoredStatus: TableRecord, FetchableRecord, PersistableRecord {
}
}
struct StatusResult: Codable, Hashable, FetchableRecord {
public struct StatusResult: Codable, Hashable, FetchableRecord {
let account: Account
fileprivate let status: StoredStatus
let reblogAccount: Account?

View file

@ -2,6 +2,6 @@
import Foundation
enum DatabaseError: Error {
public enum DatabaseError: Error {
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 url: URL
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 Mastodon
struct AppEnvironment {
public struct AppEnvironment {
let session: Session
let webAuthSessionType: WebAuthSession.Type
let keychainServiceType: KeychainService.Type
let userDefaults: UserDefaults
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(
session: Session(configuration: .default),
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 Combine
protocol WebAuthSession: AnyObject {
public protocol WebAuthSession: AnyObject {
init(url URL: URL,
callbackURLScheme: String?,
completionHandler: @escaping WebAuthSessionCompletionHandler)
@ -41,9 +41,9 @@ class WebAuthSessionContextProvider: NSObject, ASWebAuthenticationPresentationCo
}
}
typealias WebAuthSessionCompletionHandler = ASWebAuthenticationSession.CompletionHandler
typealias WebAuthSessionError = ASWebAuthenticationSessionError
typealias WebAuthPresentationContextProviding = ASWebAuthenticationPresentationContextProviding
typealias LiveWebAuthSession = ASWebAuthenticationSession
public typealias WebAuthSessionCompletionHandler = ASWebAuthenticationSession.CompletionHandler
public typealias WebAuthSessionError = ASWebAuthenticationSessionError
public typealias WebAuthPresentationContextProviding = ASWebAuthenticationPresentationContextProviding
public typealias LiveWebAuthSession = ASWebAuthenticationSession
extension LiveWebAuthSession: WebAuthSession {}

View file

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

View file

@ -2,7 +2,7 @@
import Foundation
protocol KeychainService {
public protocol KeychainService {
static func setGenericPassword(data: Data, forAccount key: String, service: String) throws
static func deleteGenericPassword(account: String, service: String) throws
static func getGenericPassword(account: String, service: String) throws -> Data?
@ -11,10 +11,10 @@ protocol KeychainService {
static func deleteKey(applicationTag: String) throws
}
struct LiveKeychainService {}
public struct LiveKeychainService {}
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)
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)
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 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 error: Unmanaged<CFError>?
@ -78,7 +78,7 @@ extension LiveKeychainService: KeychainService {
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 error: Unmanaged<CFError>?
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)
if status != errSecSuccess {

View file

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

View file

@ -4,9 +4,9 @@ import Foundation
import Combine
import Mastodon
struct ContextService {
let statusSections: AnyPublisher<[[Status]], Error>
let paginates = false
public struct ContextService {
public let statusSections: AnyPublisher<[[Status]], Error>
public let paginates = false
private let status: Status
private let context = CurrentValueSubject<Context, Never>(Context(ancestors: [], descendants: []))
@ -34,13 +34,13 @@ struct ContextService {
}
extension ContextService: StatusListService {
var filters: AnyPublisher<[Filter], Error> {
public var filters: AnyPublisher<[Filter], Error> {
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()
guard
@ -53,7 +53,7 @@ extension ContextService: StatusListService {
return previousStatus.id != contextParentID && status.inReplyToId == previousStatus.id
}
func hasReplyFollowing(status: Status) -> Bool {
public func hasReplyFollowing(status: Status) -> Bool {
let flatContext = flattenedContext()
guard
@ -66,7 +66,7 @@ extension ContextService: StatusListService {
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(
networkClient.request(StatusEndpoint.status(id: status.id))
.map { ([$0], collection) }
@ -78,11 +78,11 @@ extension ContextService: StatusListService {
.eraseToAnyPublisher()
}
func statusService(status: Status) -> StatusService {
public func statusService(status: Status) -> StatusService {
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)
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
// Copyright © 2020 Metabolist. All rights reserved.
import SwiftUI
import Services
@main
struct MetatextApp: App {
@ -8,23 +9,13 @@ struct MetatextApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
// 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 {
WindowGroup {
RootView(
viewModel: RootViewModel(appDelegate: appDelegate,
allIdentitiesService: allIdentitiesService,
// swiftlint:disable force_try
allIdentitiesService: try! AllIdentitiesService(environment: .live),
// swiftlint:enable force_try
userNotificationService: UserNotificationService()))
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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