From 5f96f59ac31dd3d855d5933f4b87912ed704e8f1 Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Mon, 31 Aug 2020 03:21:01 -0700 Subject: [PATCH] wip --- .../DevelopmentAssets.xcassets/Contents.json | 6 - .../timelineJSON.dataset/Contents.json | 13 - Development Assets/DevelopmentModels.swift | 61 ++--- Development Assets/FakeKeychain.swift | 21 -- Development Assets/HTTPStubs.swift | 17 -- .../TimelinesEndpoint+Stubbing.swift | 11 - HTTP/Package.swift | 8 +- .../Sources/Stubbing}/Stubbing.swift | 6 +- .../Stubbing}/StubbingURLProtocol.swift | 29 ++- .../URLSessionConfiguration+Extensions.swift | 2 +- Mastodon/Package.swift | 11 +- .../AccessTokenEndpoint+Stubbing.swift | 3 +- .../AccountEndpoint+Stubbing.swift | 13 + .../AppAuthorizationEndpoint+Stubbing.swift | 3 +- .../ContextEndpoint+Stubbing.swift | 3 +- .../InstanceEndpoint+Stubbing.swift | 13 + .../MastodonTarget+Stubbing.swift | 9 +- .../PreferencesEndpoint+Stubbing.swift | 3 +- .../MastodonStubs/Resources/account.json | 17 -- .../MastodonStubs/Resources/instance.json | 18 -- .../MastodonStubs/Resources}/timeline.json | 0 .../TimelinesEndpoint+Stubbing.swift | 11 + Metatext.xcodeproj/project.pbxproj | 244 ++---------------- .../xcshareddata/swiftpm/Package.resolved | 2 +- Model/Identity.swift | 73 ------ Model/TransientStatusCollection.swift | 7 - .../NotificationService.swift | 1 + Services/.gitignore | 5 + Services/Package.swift | 35 +++ .../ServiceMocks/MockAppEnvironment.swift | 13 + .../ServiceMocks}/MockKeychainService.swift | 1 + .../ServiceMocks}/MockUserDefaults.swift | 0 .../ServiceMocks}/MockWebAuthSession.swift | 1 + .../Services}/AllIdentitiesService.swift | 10 +- .../Services}/AuthenticationService.swift | 6 +- .../Services/Database}/ContentDatabase.swift | 22 +- .../Services/Database}/DatabaseError.swift | 2 +- .../Services/Database}/IdentityDatabase.swift | 6 +- .../Services/Entities}/AppEnvironment.swift | 16 +- .../Sources/Services/Entities/Identity.swift | 71 +++++ .../Entities/TransientStatusCollection.swift | 11 + .../Services/Entities}/WebAuthSession.swift | 10 +- .../Extensions}/NSError+Extensions.swift | 0 .../Services}/IdentityService.swift | 8 +- .../Services}/KeychainService.swift | 16 +- .../Services}/SecretsService.swift | 22 +- .../Status List Services/ContextService.swift | 20 +- .../StatusListService.swift | 4 +- .../TimelineService.swift | 0 .../Services}/StatusService.swift | 6 +- .../Services}/UserNotificationService.swift | 8 +- .../AuthenticationServiceTests.swift | 5 +- System/MetatextApp.swift | 17 +- .../AddIdentityViewModelTests.swift | 18 +- Tests/View Models/RootViewModelTests.swift | 5 +- View Models/AddIdentityViewModel.swift | 1 + View Models/EditFilterViewModel.swift | 1 + View Models/FiltersViewModel.swift | 1 + View Models/IdentitiesViewModel.swift | 1 + View Models/ListsViewModel.swift | 1 + ...otificationTypesPreferencesViewModel.swift | 1 + .../PostingReadingPreferencesViewModel.swift | 1 + View Models/PreferencesViewModel.swift | 1 + View Models/RootViewModel.swift | 1 + .../SecondaryNavigationViewModel.swift | 1 + View Models/StatusListViewModel.swift | 1 + View Models/StatusViewModel.swift | 1 + View Models/TabNavigationViewModel.swift | 1 + Views/IdentitiesView.swift | 1 + 69 files changed, 393 insertions(+), 564 deletions(-) delete mode 100644 Development Assets/DevelopmentAssets.xcassets/Contents.json delete mode 100644 Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/Contents.json delete mode 100644 Development Assets/FakeKeychain.swift delete mode 100644 Development Assets/HTTPStubs.swift delete mode 100644 Development Assets/Mastodon API Stubs/TimelinesEndpoint+Stubbing.swift rename {Development Assets => HTTP/Sources/Stubbing}/Stubbing.swift (87%) rename {Development Assets => HTTP/Sources/Stubbing}/StubbingURLProtocol.swift (51%) rename {Development Assets => HTTP/Sources/Stubbing}/URLSessionConfiguration+Extensions.swift (86%) rename {Development Assets/Mastodon API Stubs => Mastodon/Sources/MastodonStubs}/AccessTokenEndpoint+Stubbing.swift (88%) create mode 100644 Mastodon/Sources/MastodonStubs/AccountEndpoint+Stubbing.swift rename {Development Assets/Mastodon API Stubs => Mastodon/Sources/MastodonStubs}/AppAuthorizationEndpoint+Stubbing.swift (91%) rename {Development Assets/Mastodon API Stubs => Mastodon/Sources/MastodonStubs}/ContextEndpoint+Stubbing.swift (82%) create mode 100644 Mastodon/Sources/MastodonStubs/InstanceEndpoint+Stubbing.swift rename {Development Assets/Mastodon API Stubs => Mastodon/Sources/MastodonStubs}/MastodonTarget+Stubbing.swift (63%) rename {Development Assets/Mastodon API Stubs => Mastodon/Sources/MastodonStubs}/PreferencesEndpoint+Stubbing.swift (88%) rename Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift => Mastodon/Sources/MastodonStubs/Resources/account.json (81%) rename Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift => Mastodon/Sources/MastodonStubs/Resources/instance.json (89%) rename {Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset => Mastodon/Sources/MastodonStubs/Resources}/timeline.json (100%) create mode 100644 Mastodon/Sources/MastodonStubs/TimelinesEndpoint+Stubbing.swift delete mode 100644 Model/Identity.swift delete mode 100644 Model/TransientStatusCollection.swift create mode 100644 Services/.gitignore create mode 100644 Services/Package.swift create mode 100644 Services/Sources/ServiceMocks/MockAppEnvironment.swift rename {Development Assets => Services/Sources/ServiceMocks}/MockKeychainService.swift (98%) rename {Development Assets => Services/Sources/ServiceMocks}/MockUserDefaults.swift (100%) rename {Development Assets => Services/Sources/ServiceMocks}/MockWebAuthSession.swift (99%) rename Services/{ => Sources/Services}/AllIdentitiesService.swift (91%) rename Services/{ => Sources/Services}/AuthenticationService.swift (97%) rename {Databases => Services/Sources/Services/Database}/ContentDatabase.swift (96%) rename {Databases => Services/Sources/Services/Database}/DatabaseError.swift (76%) rename {Databases => Services/Sources/Services/Database}/IdentityDatabase.swift (97%) rename {Model => Services/Sources/Services/Entities}/AppEnvironment.swift (50%) create mode 100644 Services/Sources/Services/Entities/Identity.swift create mode 100644 Services/Sources/Services/Entities/TransientStatusCollection.swift rename {Networking => Services/Sources/Services/Entities}/WebAuthSession.swift (78%) rename {Extensions => Services/Sources/Services/Extensions}/NSError+Extensions.swift (100%) rename Services/{ => Sources/Services}/IdentityService.swift (97%) rename Services/{ => Sources/Services}/KeychainService.swift (86%) rename Services/{ => Sources/Services}/SecretsService.swift (86%) rename Services/{ => Sources/Services}/Status List Services/ContextService.swift (83%) rename Services/{ => Sources/Services}/Status List Services/StatusListService.swift (92%) rename Services/{ => Sources/Services}/Status List Services/TimelineService.swift (100%) rename Services/{ => Sources/Services}/StatusService.swift (89%) rename Services/{ => Sources/Services}/UserNotificationService.swift (91%) rename {Tests/Services => Services/Tests/ServicesTests}/AuthenticationServiceTests.swift (89%) diff --git a/Development Assets/DevelopmentAssets.xcassets/Contents.json b/Development Assets/DevelopmentAssets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Development Assets/DevelopmentAssets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/Contents.json b/Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/Contents.json deleted file mode 100644 index 7e6a022..0000000 --- a/Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/Contents.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "data" : [ - { - "filename" : "timeline.json", - "idiom" : "universal", - "universal-type-identifier" : "public.json" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Development Assets/DevelopmentModels.swift b/Development Assets/DevelopmentModels.swift index 3433b67..f2a9459 100644 --- a/Development Assets/DevelopmentModels.swift +++ b/Development Assets/DevelopmentModels.swift @@ -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 { diff --git a/Development Assets/FakeKeychain.swift b/Development Assets/FakeKeychain.swift deleted file mode 100644 index cdaa093..0000000 --- a/Development Assets/FakeKeychain.swift +++ /dev/null @@ -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] - } -} diff --git a/Development Assets/HTTPStubs.swift b/Development Assets/HTTPStubs.swift deleted file mode 100644 index 00a31ff..0000000 --- a/Development Assets/HTTPStubs.swift +++ /dev/null @@ -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) - } -} diff --git a/Development Assets/Mastodon API Stubs/TimelinesEndpoint+Stubbing.swift b/Development Assets/Mastodon API Stubs/TimelinesEndpoint+Stubbing.swift deleted file mode 100644 index 64fcc30..0000000 --- a/Development Assets/Mastodon API Stubs/TimelinesEndpoint+Stubbing.swift +++ /dev/null @@ -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 - } -} diff --git a/HTTP/Package.swift b/HTTP/Package.swift index b63650d..1e0cdcb 100644 --- a/HTTP/Package.swift +++ b/HTTP/Package.swift @@ -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"]) diff --git a/Development Assets/Stubbing.swift b/HTTP/Sources/Stubbing/Stubbing.swift similarity index 87% rename from Development Assets/Stubbing.swift rename to HTTP/Sources/Stubbing/Stubbing.swift index c86569e..94347ec 100644 --- a/Development Assets/Stubbing.swift +++ b/HTTP/Sources/Stubbing/Stubbing.swift @@ -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), diff --git a/Development Assets/StubbingURLProtocol.swift b/HTTP/Sources/Stubbing/StubbingURLProtocol.swift similarity index 51% rename from Development Assets/StubbingURLProtocol.swift rename to HTTP/Sources/Stubbing/StubbingURLProtocol.swift index e49d12e..337ff48 100644 --- a/Development Assets/StubbingURLProtocol.swift +++ b/HTTP/Sources/Stubbing/StubbingURLProtocol.swift @@ -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 } diff --git a/Development Assets/URLSessionConfiguration+Extensions.swift b/HTTP/Sources/Stubbing/URLSessionConfiguration+Extensions.swift similarity index 86% rename from Development Assets/URLSessionConfiguration+Extensions.swift rename to HTTP/Sources/Stubbing/URLSessionConfiguration+Extensions.swift index f7266cc..dac9bde 100644 --- a/Development Assets/URLSessionConfiguration+Extensions.swift +++ b/HTTP/Sources/Stubbing/URLSessionConfiguration+Extensions.swift @@ -2,7 +2,7 @@ import Foundation -extension URLSessionConfiguration { +public extension URLSessionConfiguration { static var stubbing: URLSessionConfiguration { let configuration = Self.default diff --git a/Mastodon/Package.swift b/Mastodon/Package.swift index abd26b6..063a654 100644 --- a/Mastodon/Package.swift +++ b/Mastodon/Package.swift @@ -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"]) ] ) diff --git a/Development Assets/Mastodon API Stubs/AccessTokenEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/AccessTokenEndpoint+Stubbing.swift similarity index 88% rename from Development Assets/Mastodon API Stubs/AccessTokenEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/AccessTokenEndpoint+Stubbing.swift index 1989ee8..aa8a440 100644 --- a/Development Assets/Mastodon API Stubs/AccessTokenEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/AccessTokenEndpoint+Stubbing.swift @@ -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 """ diff --git a/Mastodon/Sources/MastodonStubs/AccountEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/AccountEndpoint+Stubbing.swift new file mode 100644 index 0000000..08b07be --- /dev/null +++ b/Mastodon/Sources/MastodonStubs/AccountEndpoint+Stubbing.swift @@ -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")!) + } + } +} diff --git a/Development Assets/Mastodon API Stubs/AppAuthorizationEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/AppAuthorizationEndpoint+Stubbing.swift similarity index 91% rename from Development Assets/Mastodon API Stubs/AppAuthorizationEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/AppAuthorizationEndpoint+Stubbing.swift index 9ef836e..cb95ee5 100644 --- a/Development Assets/Mastodon API Stubs/AppAuthorizationEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/AppAuthorizationEndpoint+Stubbing.swift @@ -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 """ diff --git a/Development Assets/Mastodon API Stubs/ContextEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/ContextEndpoint+Stubbing.swift similarity index 82% rename from Development Assets/Mastodon API Stubs/ContextEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/ContextEndpoint+Stubbing.swift index d7afbca..68e0935 100644 --- a/Development Assets/Mastodon API Stubs/ContextEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/ContextEndpoint+Stubbing.swift @@ -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 """ diff --git a/Mastodon/Sources/MastodonStubs/InstanceEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/InstanceEndpoint+Stubbing.swift new file mode 100644 index 0000000..a2ac529 --- /dev/null +++ b/Mastodon/Sources/MastodonStubs/InstanceEndpoint+Stubbing.swift @@ -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")!) + } + } +} diff --git a/Development Assets/Mastodon API Stubs/MastodonTarget+Stubbing.swift b/Mastodon/Sources/MastodonStubs/MastodonTarget+Stubbing.swift similarity index 63% rename from Development Assets/Mastodon API Stubs/MastodonTarget+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/MastodonTarget+Stubbing.swift index 0c857b9..ac09eb6 100644 --- a/Development Assets/Mastodon API Stubs/MastodonTarget+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/MastodonTarget+Stubbing.swift @@ -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) } } diff --git a/Development Assets/Mastodon API Stubs/PreferencesEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/PreferencesEndpoint+Stubbing.swift similarity index 88% rename from Development Assets/Mastodon API Stubs/PreferencesEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/PreferencesEndpoint+Stubbing.swift index ca53be3..1af6143 100644 --- a/Development Assets/Mastodon API Stubs/PreferencesEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/PreferencesEndpoint+Stubbing.swift @@ -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 """ diff --git a/Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/Resources/account.json similarity index 81% rename from Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/Resources/account.json index c5c64d2..18ae0aa 100644 --- a/Development Assets/Mastodon API Stubs/AccountEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/Resources/account.json @@ -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 diff --git a/Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/Resources/instance.json similarity index 89% rename from Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift rename to Mastodon/Sources/MastodonStubs/Resources/instance.json index d60cdd1..021ace5 100644 --- a/Development Assets/Mastodon API Stubs/InstanceEndpoint+Stubbing.swift +++ b/Mastodon/Sources/MastodonStubs/Resources/instance.json @@ -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 diff --git a/Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/timeline.json b/Mastodon/Sources/MastodonStubs/Resources/timeline.json similarity index 100% rename from Development Assets/DevelopmentAssets.xcassets/timelineJSON.dataset/timeline.json rename to Mastodon/Sources/MastodonStubs/Resources/timeline.json diff --git a/Mastodon/Sources/MastodonStubs/TimelinesEndpoint+Stubbing.swift b/Mastodon/Sources/MastodonStubs/TimelinesEndpoint+Stubbing.swift new file mode 100644 index 0000000..73d7cc9 --- /dev/null +++ b/Mastodon/Sources/MastodonStubs/TimelinesEndpoint+Stubbing.swift @@ -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")!) + } +} diff --git a/Metatext.xcodeproj/project.pbxproj b/Metatext.xcodeproj/project.pbxproj index eb65e2b..8e56d57 100644 --- a/Metatext.xcodeproj/project.pbxproj +++ b/Metatext.xcodeproj/project.pbxproj @@ -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 = ""; }; D01F41DE24F8868800D55A2D /* AttachmentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewModel.swift; sourceTree = ""; }; D01F41E224F8889700D55A2D /* AttachmentsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentsView.swift; sourceTree = ""; }; - D03658D024EDD80900AC17EC /* ContextEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContextEndpoint+Stubbing.swift"; sourceTree = ""; }; 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 = ""; }; - D04FD73B24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InstanceEndpoint+Stubbing.swift"; sourceTree = ""; }; D04FD74124D4AA34007D572D /* DevelopmentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentModels.swift; sourceTree = ""; }; D052BBC624D749C800A80A7A /* RootViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModelTests.swift; sourceTree = ""; }; - D052BBC824D74B6400A80A7A /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = ""; }; - D05494F924EA4E5E008B00A5 /* TimelinesEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelinesEndpoint+Stubbing.swift"; sourceTree = ""; }; - D054950024EA4FFE008B00A5 /* DevelopmentAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DevelopmentAssets.xcassets; sourceTree = ""; }; 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 = ""; }; - D074577624D29006004758DB /* MockWebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockWebAuthSession.swift; sourceTree = ""; }; - D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+Extensions.swift"; sourceTree = ""; }; - D0A652AC24DE3EB6002EA33F /* PreferencesEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PreferencesEndpoint+Stubbing.swift"; sourceTree = ""; }; + D075C28324FCD27300D35112 /* Services */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Services; sourceTree = ""; }; D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentView.swift; sourceTree = ""; }; D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingTableFooterView.swift; sourceTree = ""; }; D0BEB1FC24F9E4E5001B0F04 /* ListsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsViewModel.swift; sourceTree = ""; }; @@ -178,9 +132,6 @@ D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationTypesPreferencesView.swift; sourceTree = ""; }; D0C7D42E24F76169001EBDBB /* TabNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabNavigationView.swift; sourceTree = ""; }; D0C7D43124F76169001EBDBB /* StatusListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewController.swift; sourceTree = ""; }; - D0C7D43B24F76169001EBDBB /* Identity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; - D0C7D44724F76169001EBDBB /* TransientStatusCollection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransientStatusCollection.swift; sourceTree = ""; }; - D0C7D44F24F76169001EBDBB /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; }; D0C7D45024F76169001EBDBB /* AlertItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = ""; }; D0C7D45224F76169001EBDBB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D0C7D45424F76169001EBDBB /* MetatextApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetatextApp.swift; sourceTree = ""; }; @@ -197,10 +148,6 @@ D0C7D46124F76169001EBDBB /* PreferencesViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesViewModel.swift; sourceTree = ""; }; D0C7D46224F76169001EBDBB /* StatusViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusViewModel.swift; sourceTree = ""; }; D0C7D46324F76169001EBDBB /* StatusListViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListViewModel.swift; sourceTree = ""; }; - D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityDatabase.swift; sourceTree = ""; }; - D0C7D46624F76169001EBDBB /* ContentDatabase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentDatabase.swift; sourceTree = ""; }; - D0C7D46724F76169001EBDBB /* DatabaseError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseError.swift; sourceTree = ""; }; - D0C7D46924F76169001EBDBB /* NSError+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = ""; }; D0C7D46A24F76169001EBDBB /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extensions.swift"; sourceTree = ""; }; D0C7D46C24F76169001EBDBB /* UIColor+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; @@ -209,30 +156,11 @@ D0C7D46F24F76169001EBDBB /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; D0C7D47024F76169001EBDBB /* Date+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; D0C7D47124F76169001EBDBB /* Data+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; - D0C7D47624F76169001EBDBB /* WebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebAuthSession.swift; sourceTree = ""; }; - D0C7D48A24F76169001EBDBB /* IdentityService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityService.swift; sourceTree = ""; }; - D0C7D48C24F76169001EBDBB /* TimelineService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineService.swift; sourceTree = ""; }; - D0C7D48D24F76169001EBDBB /* ContextService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextService.swift; sourceTree = ""; }; - D0C7D48E24F7616A001EBDBB /* StatusListService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusListService.swift; sourceTree = ""; }; - D0C7D48F24F7616A001EBDBB /* AuthenticationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = ""; }; - D0C7D49024F7616A001EBDBB /* KeychainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = ""; }; - D0C7D49124F7616A001EBDBB /* StatusService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusService.swift; sourceTree = ""; }; - D0C7D49224F7616A001EBDBB /* SecretsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsService.swift; sourceTree = ""; }; - D0C7D49324F7616A001EBDBB /* UserNotificationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserNotificationService.swift; sourceTree = ""; }; - D0C7D49424F7616A001EBDBB /* AllIdentitiesService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllIdentitiesService.swift; sourceTree = ""; }; - D0DC174524CFEC2000A75C65 /* StubbingURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StubbingURLProtocol.swift; sourceTree = ""; }; - D0DC174924CFF15F00A75C65 /* AppAuthorizationEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppAuthorizationEndpoint+Stubbing.swift"; sourceTree = ""; }; - D0DC174C24CFF1F100A75C65 /* Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubbing.swift; sourceTree = ""; }; - D0DC175124D008E300A75C65 /* MastodonTarget+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonTarget+Stubbing.swift"; sourceTree = ""; }; - D0DC175424D00F0A00A75C65 /* AccessTokenEndpoint+Stubbing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessTokenEndpoint+Stubbing.swift"; sourceTree = ""; }; - D0DC175724D0130800A75C65 /* HTTPStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStubs.swift; sourceTree = ""; }; - D0DC177624D0CF2600A75C65 /* MockKeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKeychainService.swift; sourceTree = ""; }; D0E0F1E424FC49FC002C04BF /* Mastodon */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Mastodon; sourceTree = ""; }; 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 = ""; }; D0E5361D24E3EB4D00FB1CE1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D0E5362824E4A06B00FB1CE1 /* Notification Service Extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Notification Service Extension.entitlements"; sourceTree = ""; }; - D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceTests.swift; sourceTree = ""; }; D0ED1B6D24CE100C00B4899C /* AddIdentityViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddIdentityViewModelTests.swift; sourceTree = ""; }; /* 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 = ""; @@ -427,23 +349,12 @@ path = "View Models"; sourceTree = ""; }; - D0C7D46424F76169001EBDBB /* Databases */ = { - isa = PBXGroup; - children = ( - D0C7D46624F76169001EBDBB /* ContentDatabase.swift */, - D0C7D46724F76169001EBDBB /* DatabaseError.swift */, - D0C7D46524F76169001EBDBB /* IdentityDatabase.swift */, - ); - path = Databases; - sourceTree = ""; - }; 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 = ""; }; - D0C7D47324F76169001EBDBB /* Networking */ = { - isa = PBXGroup; - children = ( - D0C7D47624F76169001EBDBB /* WebAuthSession.swift */, - ); - path = Networking; - sourceTree = ""; - }; - 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 = ""; - }; - D0C7D48B24F76169001EBDBB /* Status List Services */ = { - isa = PBXGroup; - children = ( - D0C7D48D24F76169001EBDBB /* ContextService.swift */, - D0C7D48E24F7616A001EBDBB /* StatusListService.swift */, - D0C7D48C24F76169001EBDBB /* TimelineService.swift */, - ); - path = "Status List Services"; - sourceTree = ""; - }; - 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 = ""; - }; D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */ = { isa = PBXGroup; children = ( @@ -511,14 +374,6 @@ path = "Notification Service Extension"; sourceTree = ""; }; - D0EC8DD024DFE34F00A08489 /* Services */ = { - isa = PBXGroup; - children = ( - D0EC8DD324DFE38900A08489 /* AuthenticationServiceTests.swift */, - ); - path = Services; - sourceTree = ""; - }; 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 = ""; @@ -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 */ }; diff --git a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d5c0c93..21a99f1 100644 --- a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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", diff --git a/Model/Identity.swift b/Model/Identity.swift deleted file mode 100644 index 0f01a3e..0000000 --- a/Model/Identity.swift +++ /dev/null @@ -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 - } -} diff --git a/Model/TransientStatusCollection.swift b/Model/TransientStatusCollection.swift deleted file mode 100644 index a0c3deb..0000000 --- a/Model/TransientStatusCollection.swift +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Foundation - -struct TransientStatusCollection: Codable { - let id: String -} diff --git a/Notification Service Extension/NotificationService.swift b/Notification Service Extension/NotificationService.swift index 6a77763..e6868d9 100644 --- a/Notification Service Extension/NotificationService.swift +++ b/Notification Service Extension/NotificationService.swift @@ -3,6 +3,7 @@ import UserNotifications import CryptoKit import Mastodon +import Services class NotificationService: UNNotificationServiceExtension { diff --git a/Services/.gitignore b/Services/.gitignore new file mode 100644 index 0000000..95c4320 --- /dev/null +++ b/Services/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ diff --git a/Services/Package.swift b/Services/Package.swift new file mode 100644 index 0000000..3b6b09f --- /dev/null +++ b/Services/Package.swift @@ -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"]) + ] +) diff --git a/Services/Sources/ServiceMocks/MockAppEnvironment.swift b/Services/Sources/ServiceMocks/MockAppEnvironment.swift new file mode 100644 index 0000000..58f50d1 --- /dev/null +++ b/Services/Sources/ServiceMocks/MockAppEnvironment.swift @@ -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) +} diff --git a/Development Assets/MockKeychainService.swift b/Services/Sources/ServiceMocks/MockKeychainService.swift similarity index 98% rename from Development Assets/MockKeychainService.swift rename to Services/Sources/ServiceMocks/MockKeychainService.swift index 743aef5..2a05b6b 100644 --- a/Development Assets/MockKeychainService.swift +++ b/Services/Sources/ServiceMocks/MockKeychainService.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. import Foundation +import Services struct MockKeychainService {} diff --git a/Development Assets/MockUserDefaults.swift b/Services/Sources/ServiceMocks/MockUserDefaults.swift similarity index 100% rename from Development Assets/MockUserDefaults.swift rename to Services/Sources/ServiceMocks/MockUserDefaults.swift diff --git a/Development Assets/MockWebAuthSession.swift b/Services/Sources/ServiceMocks/MockWebAuthSession.swift similarity index 99% rename from Development Assets/MockWebAuthSession.swift rename to Services/Sources/ServiceMocks/MockWebAuthSession.swift index 4733d0b..cbfa3d6 100644 --- a/Development Assets/MockWebAuthSession.swift +++ b/Services/Sources/ServiceMocks/MockWebAuthSession.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. import Foundation +import Services class MockWebAuthSession: WebAuthSession { let completionHandler: WebAuthSessionCompletionHandler diff --git a/Services/AllIdentitiesService.swift b/Services/Sources/Services/AllIdentitiesService.swift similarity index 91% rename from Services/AllIdentitiesService.swift rename to Services/Sources/Services/AllIdentitiesService.swift index 5bb6219..7c3c360 100644 --- a/Services/AllIdentitiesService.swift +++ b/Services/Sources/Services/AllIdentitiesService.swift @@ -4,14 +4,14 @@ import Foundation import Combine import Mastodon -struct AllIdentitiesService { - let mostRecentlyUsedIdentityID: AnyPublisher +public struct AllIdentitiesService { + public let mostRecentlyUsedIdentityID: AnyPublisher 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, diff --git a/Services/AuthenticationService.swift b/Services/Sources/Services/AuthenticationService.swift similarity index 97% rename from Services/AuthenticationService.swift rename to Services/Sources/Services/AuthenticationService.swift index 33f1f45..e0c109f 100644 --- a/Services/AuthenticationService.swift +++ b/Services/Sources/Services/AuthenticationService.swift @@ -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 { let endpoint = AppAuthorizationEndpoint.apps( clientName: OAuth.clientName, diff --git a/Databases/ContentDatabase.swift b/Services/Sources/Services/Database/ContentDatabase.swift similarity index 96% rename from Databases/ContentDatabase.swift rename to Services/Sources/Services/Database/ContentDatabase.swift index 2bdd6f6..089c654 100644 --- a/Databases/ContentDatabase.swift +++ b/Services/Sources/Services/Database/ContentDatabase.swift @@ -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? diff --git a/Databases/DatabaseError.swift b/Services/Sources/Services/Database/DatabaseError.swift similarity index 76% rename from Databases/DatabaseError.swift rename to Services/Sources/Services/Database/DatabaseError.swift index ae20fce..889bca1 100644 --- a/Databases/DatabaseError.swift +++ b/Services/Sources/Services/Database/DatabaseError.swift @@ -2,6 +2,6 @@ import Foundation -enum DatabaseError: Error { +public enum DatabaseError: Error { case documentsDirectoryNotFound } diff --git a/Databases/IdentityDatabase.swift b/Services/Sources/Services/Database/IdentityDatabase.swift similarity index 97% rename from Databases/IdentityDatabase.swift rename to Services/Sources/Services/Database/IdentityDatabase.swift index e63591a..55a7f89 100644 --- a/Databases/IdentityDatabase.swift +++ b/Services/Sources/Services/Database/IdentityDatabase.swift @@ -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 {} diff --git a/Model/AppEnvironment.swift b/Services/Sources/Services/Entities/AppEnvironment.swift similarity index 50% rename from Model/AppEnvironment.swift rename to Services/Sources/Services/Entities/AppEnvironment.swift index 8fed092..120b4d1 100644 --- a/Model/AppEnvironment.swift +++ b/Services/Sources/Services/Entities/AppEnvironment.swift @@ -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, diff --git a/Services/Sources/Services/Entities/Identity.swift b/Services/Sources/Services/Entities/Identity.swift new file mode 100644 index 0000000..06f7d9e --- /dev/null +++ b/Services/Sources/Services/Entities/Identity.swift @@ -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 + } +} diff --git a/Services/Sources/Services/Entities/TransientStatusCollection.swift b/Services/Sources/Services/Entities/TransientStatusCollection.swift new file mode 100644 index 0000000..693f7c5 --- /dev/null +++ b/Services/Sources/Services/Entities/TransientStatusCollection.swift @@ -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 + } +} diff --git a/Networking/WebAuthSession.swift b/Services/Sources/Services/Entities/WebAuthSession.swift similarity index 78% rename from Networking/WebAuthSession.swift rename to Services/Sources/Services/Entities/WebAuthSession.swift index c87e823..d2bee29 100644 --- a/Networking/WebAuthSession.swift +++ b/Services/Sources/Services/Entities/WebAuthSession.swift @@ -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 {} diff --git a/Extensions/NSError+Extensions.swift b/Services/Sources/Services/Extensions/NSError+Extensions.swift similarity index 100% rename from Extensions/NSError+Extensions.swift rename to Services/Sources/Services/Extensions/NSError+Extensions.swift diff --git a/Services/IdentityService.swift b/Services/Sources/Services/IdentityService.swift similarity index 97% rename from Services/IdentityService.swift rename to Services/Sources/Services/IdentityService.swift index 45e5084..551ac41 100644 --- a/Services/IdentityService.swift +++ b/Services/Sources/Services/IdentityService.swift @@ -4,9 +4,9 @@ import Foundation import Combine import Mastodon -class IdentityService { - @Published private(set) var identity: Identity - let observationErrors: AnyPublisher +public class IdentityService { + @Published public private(set) var identity: Identity + public let observationErrors: AnyPublisher 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 { diff --git a/Services/KeychainService.swift b/Services/Sources/Services/KeychainService.swift similarity index 86% rename from Services/KeychainService.swift rename to Services/Sources/Services/KeychainService.swift index e1e5431..1a7c2ca 100644 --- a/Services/KeychainService.swift +++ b/Services/Sources/Services/KeychainService.swift @@ -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? @@ -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? 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 { diff --git a/Services/SecretsService.swift b/Services/Sources/Services/SecretsService.swift similarity index 86% rename from Services/SecretsService.swift rename to Services/Sources/Services/SecretsService.swift index b4487c0..5fd7bce 100644 --- a/Services/SecretsService.swift +++ b/Services/Sources/Services/SecretsService.swift @@ -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] = [ diff --git a/Services/Status List Services/ContextService.swift b/Services/Sources/Services/Status List Services/ContextService.swift similarity index 83% rename from Services/Status List Services/ContextService.swift rename to Services/Sources/Services/Status List Services/ContextService.swift index d0bb30c..1a68f63 100644 --- a/Services/Status List Services/ContextService.swift +++ b/Services/Sources/Services/Status List Services/ContextService.swift @@ -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(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 { + public func request(maxID: String?, minID: String?) -> AnyPublisher { 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) } } diff --git a/Services/Status List Services/StatusListService.swift b/Services/Sources/Services/Status List Services/StatusListService.swift similarity index 92% rename from Services/Status List Services/StatusListService.swift rename to Services/Sources/Services/Status List Services/StatusListService.swift index 1310575..2d63ac3 100644 --- a/Services/Status List Services/StatusListService.swift +++ b/Services/Sources/Services/Status List Services/StatusListService.swift @@ -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 } diff --git a/Services/Status List Services/TimelineService.swift b/Services/Sources/Services/Status List Services/TimelineService.swift similarity index 100% rename from Services/Status List Services/TimelineService.swift rename to Services/Sources/Services/Status List Services/TimelineService.swift diff --git a/Services/StatusService.swift b/Services/Sources/Services/StatusService.swift similarity index 89% rename from Services/StatusService.swift rename to Services/Sources/Services/StatusService.swift index 784b54d..1d0247e 100644 --- a/Services/StatusService.swift +++ b/Services/Sources/Services/StatusService.swift @@ -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 { networkClient.request(status.favourited ? StatusEndpoint.unfavourite(id: status.id) diff --git a/Services/UserNotificationService.swift b/Services/Sources/Services/UserNotificationService.swift similarity index 91% rename from Services/UserNotificationService.swift rename to Services/Sources/Services/UserNotificationService.swift index ef65e2f..265491e 100644 --- a/Services/UserNotificationService.swift +++ b/Services/Sources/Services/UserNotificationService.swift @@ -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 { 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) { diff --git a/Tests/Services/AuthenticationServiceTests.swift b/Services/Tests/ServicesTests/AuthenticationServiceTests.swift similarity index 89% rename from Tests/Services/AuthenticationServiceTests.swift rename to Services/Tests/ServicesTests/AuthenticationServiceTests.swift index a8e5143..816fccd 100644 --- a/Tests/Services/AuthenticationServiceTests.swift +++ b/Services/Tests/ServicesTests/AuthenticationServiceTests.swift @@ -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) diff --git a/System/MetatextApp.swift b/System/MetatextApp.swift index d94c4e0..8b131cc 100644 --- a/System/MetatextApp.swift +++ b/System/MetatextApp.swift @@ -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())) } } diff --git a/Tests/View Models/AddIdentityViewModelTests.swift b/Tests/View Models/AddIdentityViewModelTests.swift index f70ced4..5377d65 100644 --- a/Tests/View Models/AddIdentityViewModelTests.swift +++ b/Tests/View Models/AddIdentityViewModelTests.swift @@ -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() { + + } } diff --git a/Tests/View Models/RootViewModelTests.swift b/Tests/View Models/RootViewModelTests.swift index c6ad723..e8df76c 100644 --- a/Tests/View Models/RootViewModelTests.swift +++ b/Tests/View Models/RootViewModelTests.swift @@ -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() diff --git a/View Models/AddIdentityViewModel.swift b/View Models/AddIdentityViewModel.swift index db23c73..32d54a0 100644 --- a/View Models/AddIdentityViewModel.swift +++ b/View Models/AddIdentityViewModel.swift @@ -2,6 +2,7 @@ import Foundation import Combine +import Services class AddIdentityViewModel: ObservableObject { @Published var urlFieldText = "" diff --git a/View Models/EditFilterViewModel.swift b/View Models/EditFilterViewModel.swift index cbffc26..9416645 100644 --- a/View Models/EditFilterViewModel.swift +++ b/View Models/EditFilterViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class EditFilterViewModel: ObservableObject { @Published var filter: Filter diff --git a/View Models/FiltersViewModel.swift b/View Models/FiltersViewModel.swift index 2e39c8d..12fb300 100644 --- a/View Models/FiltersViewModel.swift +++ b/View Models/FiltersViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class FiltersViewModel: ObservableObject { @Published var activeFilters = [Filter]() diff --git a/View Models/IdentitiesViewModel.swift b/View Models/IdentitiesViewModel.swift index ae5488a..8879979 100644 --- a/View Models/IdentitiesViewModel.swift +++ b/View Models/IdentitiesViewModel.swift @@ -2,6 +2,7 @@ import Combine import Foundation +import Services class IdentitiesViewModel: ObservableObject { @Published private(set) var identity: Identity diff --git a/View Models/ListsViewModel.swift b/View Models/ListsViewModel.swift index 33f8b22..1ca38d2 100644 --- a/View Models/ListsViewModel.swift +++ b/View Models/ListsViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class ListsViewModel: ObservableObject { @Published private(set) var lists = [MastodonList]() diff --git a/View Models/NotificationTypesPreferencesViewModel.swift b/View Models/NotificationTypesPreferencesViewModel.swift index ce8f8b9..77cd9ae 100644 --- a/View Models/NotificationTypesPreferencesViewModel.swift +++ b/View Models/NotificationTypesPreferencesViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class NotificationTypesPreferencesViewModel: ObservableObject { @Published var pushSubscriptionAlerts: PushSubscription.Alerts diff --git a/View Models/PostingReadingPreferencesViewModel.swift b/View Models/PostingReadingPreferencesViewModel.swift index 6585bd4..f00b74e 100644 --- a/View Models/PostingReadingPreferencesViewModel.swift +++ b/View Models/PostingReadingPreferencesViewModel.swift @@ -2,6 +2,7 @@ import Foundation import Combine +import Services class PostingReadingPreferencesViewModel: ObservableObject { @Published var preferences: Identity.Preferences diff --git a/View Models/PreferencesViewModel.swift b/View Models/PreferencesViewModel.swift index 2b6a11b..c981a8d 100644 --- a/View Models/PreferencesViewModel.swift +++ b/View Models/PreferencesViewModel.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. import Foundation +import Services class PreferencesViewModel: ObservableObject { let handle: String diff --git a/View Models/RootViewModel.swift b/View Models/RootViewModel.swift index 22780e3..bf65d59 100644 --- a/View Models/RootViewModel.swift +++ b/View Models/RootViewModel.swift @@ -2,6 +2,7 @@ import Foundation import Combine +import Services class RootViewModel: ObservableObject { @Published private(set) var tabNavigationViewModel: TabNavigationViewModel? diff --git a/View Models/SecondaryNavigationViewModel.swift b/View Models/SecondaryNavigationViewModel.swift index 7f787c3..83a8c33 100644 --- a/View Models/SecondaryNavigationViewModel.swift +++ b/View Models/SecondaryNavigationViewModel.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Metabolist. All rights reserved. import Foundation +import Services class SecondaryNavigationViewModel: ObservableObject { @Published private(set) var identity: Identity diff --git a/View Models/StatusListViewModel.swift b/View Models/StatusListViewModel.swift index 39e1561..63a13ed 100644 --- a/View Models/StatusListViewModel.swift +++ b/View Models/StatusListViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class StatusListViewModel: ObservableObject { @Published private(set) var statusIDs = [[String]]() diff --git a/View Models/StatusViewModel.swift b/View Models/StatusViewModel.swift index 0865133..483ccb9 100644 --- a/View Models/StatusViewModel.swift +++ b/View Models/StatusViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services struct StatusViewModel { let content: NSAttributedString diff --git a/View Models/TabNavigationViewModel.swift b/View Models/TabNavigationViewModel.swift index 9f493b8..5e851e5 100644 --- a/View Models/TabNavigationViewModel.swift +++ b/View Models/TabNavigationViewModel.swift @@ -3,6 +3,7 @@ import Foundation import Combine import Mastodon +import Services class TabNavigationViewModel: ObservableObject { @Published private(set) var identity: Identity diff --git a/Views/IdentitiesView.swift b/Views/IdentitiesView.swift index 2ef228f..2004e85 100644 --- a/Views/IdentitiesView.swift +++ b/Views/IdentitiesView.swift @@ -2,6 +2,7 @@ import SwiftUI import KingfisherSwiftUI +import struct Services.Identity struct IdentitiesView: View { @StateObject var viewModel: IdentitiesViewModel