diff --git a/DB/Sources/DB/Identity/IdentityDatabase.swift b/DB/Sources/DB/Identity/IdentityDatabase.swift index 2180d90..589af87 100644 --- a/DB/Sources/DB/Identity/IdentityDatabase.swift +++ b/DB/Sources/DB/Identity/IdentityDatabase.swift @@ -14,7 +14,7 @@ public enum IdentityDatabaseError: Error { public struct IdentityDatabase { private let databaseQueue: DatabaseQueue - public init(inMemory: Bool, fixture: IdentityFixture?, keychain: Keychain.Type) throws { + public init(inMemory: Bool, keychain: Keychain.Type) throws { if inMemory { databaseQueue = DatabaseQueue() } else { @@ -29,10 +29,6 @@ public struct IdentityDatabase { } try Self.migrate(databaseQueue) - - if let fixture = fixture { - try populate(fixture: fixture) - } } } @@ -262,22 +258,4 @@ private extension IdentityDatabase { try migrator.migrate(writer) } - - func populate(fixture: IdentityFixture) throws { - _ = createIdentity(id: fixture.id, url: fixture.instanceURL) - .receive(on: ImmediateScheduler.shared) - .sink { _ in } receiveValue: { _ in } - - if let instance = fixture.instance { - _ = updateInstance(instance, forIdentityID: fixture.id) - .receive(on: ImmediateScheduler.shared) - .sink { _ in } receiveValue: { _ in } - } - - if let account = fixture.account { - _ = updateAccount(account, forIdentityID: fixture.id) - .receive(on: ImmediateScheduler.shared) - .sink { _ in } receiveValue: { _ in } - } - } } diff --git a/MastodonAPI/Sources/MastodonAPIStubs/AccountEndpoint+Stubbing.swift b/MastodonAPI/Sources/MastodonAPIStubs/AccountEndpoint+Stubbing.swift index 160bbb1..fa684e2 100644 --- a/MastodonAPI/Sources/MastodonAPIStubs/AccountEndpoint+Stubbing.swift +++ b/MastodonAPI/Sources/MastodonAPIStubs/AccountEndpoint+Stubbing.swift @@ -6,9 +6,6 @@ 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")!) - } + StubData.account } } diff --git a/MastodonAPI/Sources/MastodonAPIStubs/InstanceEndpoint+Stubbing.swift b/MastodonAPI/Sources/MastodonAPIStubs/InstanceEndpoint+Stubbing.swift index 06ea80c..e2405c6 100644 --- a/MastodonAPI/Sources/MastodonAPIStubs/InstanceEndpoint+Stubbing.swift +++ b/MastodonAPI/Sources/MastodonAPIStubs/InstanceEndpoint+Stubbing.swift @@ -6,8 +6,6 @@ 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")!) - } + StubData.instance } } diff --git a/MastodonAPI/Sources/MastodonAPIStubs/PreferencesEndpoint+Stubbing.swift b/MastodonAPI/Sources/MastodonAPIStubs/PreferencesEndpoint+Stubbing.swift index 80cc514..c25b2aa 100644 --- a/MastodonAPI/Sources/MastodonAPIStubs/PreferencesEndpoint+Stubbing.swift +++ b/MastodonAPI/Sources/MastodonAPIStubs/PreferencesEndpoint+Stubbing.swift @@ -5,18 +5,7 @@ import MastodonAPI import Stubbing extension PreferencesEndpoint: Stubbing { - public func dataString(url: URL) -> String? { - switch self { - case .preferences: - return """ - { - "posting:default:visibility": "public", - "posting:default:sensitive": false, - "posting:default:language": null, - "reading:expand:media": "default", - "reading:expand:spoilers": false - } - """ - } + public func data(url: URL) -> Data? { + StubData.preferences } } diff --git a/MastodonAPI/Sources/MastodonAPIStubs/Resources/preferences.json b/MastodonAPI/Sources/MastodonAPIStubs/Resources/preferences.json new file mode 100644 index 0000000..49b8146 --- /dev/null +++ b/MastodonAPI/Sources/MastodonAPIStubs/Resources/preferences.json @@ -0,0 +1,7 @@ +{ + "posting:default:visibility": "public", + "posting:default:sensitive": false, + "posting:default:language": null, + "reading:expand:media": "default", + "reading:expand:spoilers": false +} diff --git a/MastodonAPI/Sources/MastodonAPIStubs/StubData.swift b/MastodonAPI/Sources/MastodonAPIStubs/StubData.swift new file mode 100644 index 0000000..a8d389e --- /dev/null +++ b/MastodonAPI/Sources/MastodonAPIStubs/StubData.swift @@ -0,0 +1,18 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Foundation + +public enum StubData {} + +public extension StubData { + // swiftlint:disable force_try + static let account = try! Data(contentsOf: Bundle.module.url(forResource: "account", + withExtension: "json")!) + static let instance = try! Data(contentsOf: Bundle.module.url(forResource: "instance", + withExtension: "json")!) + static let preferences = try! Data(contentsOf: Bundle.module.url(forResource: "preferences", + withExtension: "json")!) + static let timeline = try! Data(contentsOf: Bundle.module.url(forResource: "timeline", + withExtension: "json")!) + // swiftlint:enable force_try +} diff --git a/MastodonAPI/Sources/MastodonAPIStubs/TimelinesEndpoint+Stubbing.swift b/MastodonAPI/Sources/MastodonAPIStubs/TimelinesEndpoint+Stubbing.swift index 2d9efe4..b82ac30 100644 --- a/MastodonAPI/Sources/MastodonAPIStubs/TimelinesEndpoint+Stubbing.swift +++ b/MastodonAPI/Sources/MastodonAPIStubs/TimelinesEndpoint+Stubbing.swift @@ -6,6 +6,6 @@ import Stubbing extension TimelinesEndpoint: Stubbing { public func data(url: URL) -> Data? { - try? Data(contentsOf: Bundle.module.url(forResource: "timeline", withExtension: "json")!) + StubData.timeline } } diff --git a/Secrets/Sources/Secrets/Secrets.swift b/Secrets/Sources/Secrets/Secrets.swift index d7d1133..33a1a31 100644 --- a/Secrets/Sources/Secrets/Secrets.swift +++ b/Secrets/Sources/Secrets/Secrets.swift @@ -25,6 +25,7 @@ public struct Secrets { public extension Secrets { enum Item: String, CaseIterable { + case instanceURL case clientID case clientSecret case accessToken @@ -101,6 +102,14 @@ public extension Secrets { } } + func getInstanceURL() throws -> URL { + try item(.instanceURL) + } + + func setInstanceURL(_ instanceURL: URL) throws { + try set(instanceURL, forItem: .instanceURL) + } + func getClientID() throws -> String { try item(.clientID) } @@ -219,6 +228,18 @@ extension String: SecretsStorable { } } +extension URL: SecretsStorable { + public var dataStoredInSecrets: Data { absoluteString.dataStoredInSecrets } + + public static func fromDataStoredInSecrets(_ data: Data) throws -> URL { + guard let url = URL(string: try String.fromDataStoredInSecrets(data)) else { + throw SecretsStorableError.conversionFromDataStoredInSecrets(data) + } + + return url + } +} + private struct PushKey { static let authLength = 16 static let sizeInBits = 256 diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift index 9e72ea1..544cc82 100644 --- a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift @@ -14,7 +14,7 @@ public struct AppEnvironment { let userDefaults: UserDefaults let userNotificationClient: UserNotificationClient let inMemoryContent: Bool - let identityFixture: IdentityFixture? + let fixtureDatabase: IdentityDatabase? public init(session: Session, webAuthSessionType: WebAuthSession.Type, @@ -22,14 +22,14 @@ public struct AppEnvironment { userDefaults: UserDefaults, userNotificationClient: UserNotificationClient, inMemoryContent: Bool, - identityFixture: IdentityFixture?) { + fixtureDatabase: IdentityDatabase?) { self.session = session self.webAuthSessionType = webAuthSessionType self.keychain = keychain self.userDefaults = userDefaults self.userNotificationClient = userNotificationClient self.inMemoryContent = inMemoryContent - self.identityFixture = identityFixture + self.fixtureDatabase = fixtureDatabase } } @@ -42,6 +42,6 @@ public extension AppEnvironment { userDefaults: .standard, userNotificationClient: .live(userNotificationCenter), inMemoryContent: false, - identityFixture: nil) + fixtureDatabase: nil) } } diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/IdentifiedEnvironment.swift b/ServiceLayer/Sources/ServiceLayer/Entities/IdentifiedEnvironment.swift deleted file mode 100644 index 89acfa2..0000000 --- a/ServiceLayer/Sources/ServiceLayer/Entities/IdentifiedEnvironment.swift +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Combine -import DB -import Foundation - -public class IdentifiedEnvironment { - @Published public private(set) var identity: Identity - public let appEnvironment: AppEnvironment - public let identityService: IdentityService - public let observationErrors: AnyPublisher - - init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws { - appEnvironment = environment - - // The scheduling on the observation is immediate so an initial value can be extracted - let sharedObservation = database.identityObservation(id: id).share() - var initialIdentity: Identity? - - _ = sharedObservation.first().sink( - receiveCompletion: { _ in }, - receiveValue: { initialIdentity = $0 }) - - guard let identity = initialIdentity else { throw IdentityDatabaseError.identityNotFound } - - self.identity = identity - identityService = try IdentityService(id: identity.id, - instanceURL: identity.url, - database: database, - environment: environment) - - let observationErrorsSubject = PassthroughSubject() - - self.observationErrors = observationErrorsSubject.eraseToAnyPublisher() - - sharedObservation.catch { error -> Empty in - observationErrorsSubject.send(error) - - return Empty() - } - .assign(to: &$identity) - } -} diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift index 02ff0cc..3ce5020 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift @@ -15,9 +15,9 @@ public struct AllIdentitiesService { private let environment: AppEnvironment public init(environment: AppEnvironment) throws { - self.database = try IdentityDatabase(inMemory: environment.inMemoryContent, - fixture: environment.identityFixture, - keychain: environment.keychain) + self.database = try environment.fixtureDatabase ?? IdentityDatabase( + inMemory: environment.inMemoryContent, + keychain: environment.keychain) self.environment = environment mostRecentlyUsedIdentityID = database.mostRecentlyUsedIdentityIDObservation() @@ -28,8 +28,8 @@ public struct AllIdentitiesService { } public extension AllIdentitiesService { - func identifiedEnvironment(id: UUID) throws -> IdentifiedEnvironment { - try IdentifiedEnvironment(id: id, database: database, environment: environment) + func identityService(id: UUID) throws -> IdentityService { + try IdentityService(id: id, database: database, environment: environment) } func createIdentity(id: UUID, instanceURL: URL) -> AnyPublisher { @@ -42,6 +42,7 @@ public extension AllIdentitiesService { return authenticationService.authorizeApp(instanceURL: instanceURL) .tryMap { appAuthorization -> (URL, AppAuthorization) in + try secrets.setInstanceURL(instanceURL) try secrets.setClientID(appAuthorization.clientId) try secrets.setClientSecret(appAuthorization.clientSecret) @@ -81,7 +82,7 @@ public extension AllIdentitiesService { database.identitiesWithOutdatedDeviceTokens(deviceToken: deviceToken) .tryMap { identities -> [AnyPublisher] in try identities.map { - try IdentityService(id: $0.id, instanceURL: $0.url, database: database, environment: environment) + try IdentityService(id: $0.id, database: database, environment: environment) .createPushSubscription(deviceToken: deviceToken, alerts: $0.pushSubscriptionAlerts) .catch { _ in Empty() } // don't want to disrupt pipeline .eraseToAnyPublisher() diff --git a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift index af9bd07..f0f35cf 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/IdentityService.swift @@ -17,7 +17,7 @@ public struct IdentityService { private let secrets: Secrets private let observationErrorsInput = PassthroughSubject() - init(id: UUID, instanceURL: URL, database: IdentityDatabase, environment: AppEnvironment) throws { + init(id: UUID, database: IdentityDatabase, environment: AppEnvironment) throws { identityID = id identityDatabase = database self.environment = environment @@ -25,7 +25,7 @@ public struct IdentityService { identityID: id, keychain: environment.keychain) mastodonAPIClient = MastodonAPIClient(session: environment.session) - mastodonAPIClient.instanceURL = instanceURL + mastodonAPIClient.instanceURL = try secrets.getInstanceURL() mastodonAPIClient.accessToken = try? secrets.getAccessToken() contentDatabase = try ContentDatabase(identityID: id, @@ -86,6 +86,10 @@ public extension IdentityService { .eraseToAnyPublisher() } + func observation() -> AnyPublisher { + identityDatabase.identityObservation(id: identityID) + } + func listsObservation() -> AnyPublisher<[Timeline], Error> { contentDatabase.listsObservation() } diff --git a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift index 89f1054..a1995f2 100644 --- a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift @@ -3,12 +3,19 @@ import DB import Foundation import HTTP +import Keychain import MockKeychain import ServiceLayer import Stubbing public extension AppEnvironment { - static func mock(identityFixture: IdentityFixture? = nil) -> Self { + static func mock(session: Session = Session(configuration: .stubbing), + webAuthSessionType: WebAuthSession.Type = SuccessfulMockWebAuthSession.self, + keychain: Keychain.Type = MockKeychain.self, + userDefaults: UserDefaults = MockUserDefaults(), + userNotificationClient: UserNotificationClient = .mock, + inMemoryContent: Bool = true, + fixtureDatabase: IdentityDatabase? = nil) -> Self { AppEnvironment( session: Session(configuration: .stubbing), webAuthSessionType: SuccessfulMockWebAuthSession.self, @@ -16,6 +23,6 @@ public extension AppEnvironment { userDefaults: MockUserDefaults(), userNotificationClient: .mock, inMemoryContent: true, - identityFixture: identityFixture) + fixtureDatabase: fixtureDatabase) } } diff --git a/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift new file mode 100644 index 0000000..cd603dc --- /dev/null +++ b/ViewModels/Sources/PreviewViewModels/PreviewViewModels.swift @@ -0,0 +1,53 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import DB +import Foundation +import Mastodon +import MastodonAPIStubs +import MockKeychain +import Secrets +import ServiceLayer +import ServiceLayerMocks +import ViewModels + +// swiftlint:disable force_try + +let db: IdentityDatabase = { + let id = UUID() + let url = URL(string: "https://mastodon.social")! + let db = try! IdentityDatabase(inMemory: true, keychain: MockKeychain.self) + let decoder = MastodonDecoder() + let instance = try! decoder.decode(Instance.self, from: StubData.instance) + let account = try! decoder.decode(Account.self, from: StubData.account) + let secrets = Secrets(identityID: id, keychain: MockKeychain.self) + + try! secrets.setInstanceURL(url) + try! secrets.setAccessToken(UUID().uuidString) + + _ = db.createIdentity(id: id, url: url) + .receive(on: ImmediateScheduler.shared) + .sink { _ in } receiveValue: { _ in } + + _ = db.updateInstance(instance, forIdentityID: id) + .receive(on: ImmediateScheduler.shared) + .sink { _ in } receiveValue: { _ in } + + _ = db.updateAccount(account, forIdentityID: id) + .receive(on: ImmediateScheduler.shared) + .sink { _ in } receiveValue: { _ in } + + return db +}() + +let environment = AppEnvironment.mock(fixtureDatabase: db) + +public extension RootViewModel { + static let preview = try! RootViewModel(environment: environment) { Empty().eraseToAnyPublisher() } +} + +public extension Identification { + static let preview = RootViewModel.preview.identification! +} + +// swiftlint:enable force_try diff --git a/ViewModels/Sources/PreviewViewModels/ViewModelMocks.swift b/ViewModels/Sources/PreviewViewModels/ViewModelMocks.swift deleted file mode 100644 index 7cfa236..0000000 --- a/ViewModels/Sources/PreviewViewModels/ViewModelMocks.swift +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Combine -import Foundation -import HTTP -import Mastodon -import MastodonAPI -import MastodonAPIStubs -import ServiceLayer -import ServiceLayerMocks -import ViewModels - -private let decoder = MastodonDecoder() -private let devInstanceURL = URL(string: "https://mastodon.social")! - -// swiftlint:disable force_try -extension AppEnvironment { - public static let mockAuthenticated: Self = .mock( - identityFixture: .init( - id: UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!, - instanceURL: devInstanceURL, - instance: try! decoder.decode(Instance.self, - from: InstanceEndpoint.instance.data(url: devInstanceURL)!), - account: try! decoder.decode(Account.self, - from: AccountEndpoint.verifyCredentials.data(url: devInstanceURL)!))) -} - -extension RootViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> Self { - try! Self(environment: environment, - registerForRemoteNotifications: { Empty().eraseToAnyPublisher() }) - } -} -// swiftlint:enable force_try - -extension AddIdentityViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> AddIdentityViewModel { - RootViewModel.mock(environment: environment).addIdentityViewModel() - } -} - -extension TabNavigationViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> TabNavigationViewModel { - RootViewModel.mock(environment: environment).tabNavigationViewModel! - } -} - -extension SecondaryNavigationViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> SecondaryNavigationViewModel { - TabNavigationViewModel.mock(environment: environment) - .secondaryNavigationViewModel() - } -} - -extension IdentitiesViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> IdentitiesViewModel { - SecondaryNavigationViewModel.mock(environment: environment).identitiesViewModel() - } -} - -extension ListsViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> ListsViewModel { - SecondaryNavigationViewModel.mock(environment: environment).listsViewModel() - } -} - -extension PreferencesViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> PreferencesViewModel { - SecondaryNavigationViewModel.mock(environment: environment).preferencesViewModel() - } -} - -extension PostingReadingPreferencesViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> PostingReadingPreferencesViewModel { - PreferencesViewModel.mock(environment: environment) - .postingReadingPreferencesViewModel() - } -} - -extension NotificationTypesPreferencesViewModel { - public static func mock( - environment: AppEnvironment = .mockAuthenticated) -> NotificationTypesPreferencesViewModel { - PreferencesViewModel.mock(environment: environment) - .notificationTypesPreferencesViewModel() - } -} - -extension FiltersViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> FiltersViewModel { - PreferencesViewModel.mock(environment: environment).filtersViewModel() - } -} - -extension EditFilterViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> EditFilterViewModel { - FiltersViewModel.mock(environment: environment).editFilterViewModel(filter: .new) - } -} - -extension StatusListViewModel { - public static func mock(environment: AppEnvironment = .mockAuthenticated) -> StatusListViewModel { - TabNavigationViewModel.mock(environment: environment).viewModel(timeline: .home) - } -} diff --git a/ViewModels/Sources/ViewModels/EditFilterViewModel.swift b/ViewModels/Sources/ViewModels/EditFilterViewModel.swift index c590242..bfa003d 100644 --- a/ViewModels/Sources/ViewModels/EditFilterViewModel.swift +++ b/ViewModels/Sources/ViewModels/EditFilterViewModel.swift @@ -15,13 +15,13 @@ public class EditFilterViewModel: ObservableObject { didSet { filter.expiresAt = date } } - private let environment: IdentifiedEnvironment + private let identification: Identification private let saveCompletedInput = PassthroughSubject() private var cancellables = Set() - init(filter: Filter, environment: IdentifiedEnvironment) { + public init(filter: Filter, identification: Identification) { self.filter = filter - self.environment = environment + self.identification = identification date = filter.expiresAt ?? Date() saveCompleted = saveCompletedInput.eraseToAnyPublisher() } @@ -41,7 +41,7 @@ public extension EditFilterViewModel { } func save() { - (isNew ? environment.identityService.createFilter(filter) : environment.identityService.updateFilter(filter)) + (isNew ? identification.service.createFilter(filter) : identification.service.updateFilter(filter)) .assignErrorsToAlertItem(to: \.alertItem, on: self) .handleEvents( receiveSubscription: { [weak self] _ in self?.saving = true }, diff --git a/ViewModels/Sources/ViewModels/Entities/Identification.swift b/ViewModels/Sources/ViewModels/Entities/Identification.swift new file mode 100644 index 0000000..454b4c6 --- /dev/null +++ b/ViewModels/Sources/ViewModels/Entities/Identification.swift @@ -0,0 +1,41 @@ +// Copyright © 2020 Metabolist. All rights reserved. + +import Combine +import Foundation +import ServiceLayer + +enum IdentificationError: Error { + case initialIdentityValueAbsent +} + +public final class Identification: ObservableObject { + @Published private(set) var identity: Identity + let service: IdentityService + let observationErrors: AnyPublisher + + init(service: IdentityService) throws { + self.service = service + // The scheduling on the observation is immediate so an initial value can be extracted + let sharedObservation = service.observation().share() + var initialIdentity: Identity? + + _ = sharedObservation.first().sink( + receiveCompletion: { _ in }, + receiveValue: { initialIdentity = $0 }) + + guard let identity = initialIdentity else { throw IdentificationError.initialIdentityValueAbsent } + + self.identity = identity + + let observationErrorsSubject = PassthroughSubject() + + observationErrors = observationErrorsSubject.eraseToAnyPublisher() + + sharedObservation.catch { error -> Empty in + observationErrorsSubject.send(error) + + return Empty() + } + .assign(to: &$identity) + } +} diff --git a/ViewModels/Sources/ViewModels/FiltersViewModel.swift b/ViewModels/Sources/ViewModels/FiltersViewModel.swift index 451b76d..8d38dbb 100644 --- a/ViewModels/Sources/ViewModels/FiltersViewModel.swift +++ b/ViewModels/Sources/ViewModels/FiltersViewModel.swift @@ -10,19 +10,19 @@ public class FiltersViewModel: ObservableObject { @Published public var expiredFilters = [Filter]() @Published public var alertItem: AlertItem? - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment + public init(identification: Identification) { + self.identification = identification let now = Date() - environment.identityService.activeFiltersObservation(date: now) + identification.service.activeFiltersObservation(date: now) .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$activeFilters) - environment.identityService.expiredFiltersObservation(date: now) + identification.service.expiredFiltersObservation(date: now) .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$expiredFilters) } @@ -30,20 +30,16 @@ public class FiltersViewModel: ObservableObject { public extension FiltersViewModel { func refreshFilters() { - environment.identityService.refreshFilters() + identification.service.refreshFilters() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } func delete(filter: Filter) { - environment.identityService.deleteFilter(id: filter.id) + identification.service.deleteFilter(id: filter.id) .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } - - func editFilterViewModel(filter: Filter) -> EditFilterViewModel { - EditFilterViewModel(filter: filter, environment: environment) - } } diff --git a/ViewModels/Sources/ViewModels/IdentitiesViewModel.swift b/ViewModels/Sources/ViewModels/IdentitiesViewModel.swift index bcd4cb4..9135cfe 100644 --- a/ViewModels/Sources/ViewModels/IdentitiesViewModel.swift +++ b/ViewModels/Sources/ViewModels/IdentitiesViewModel.swift @@ -9,14 +9,14 @@ public class IdentitiesViewModel: ObservableObject { @Published public var identities = [Identity]() @Published public var alertItem: AlertItem? - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment - currentIdentityID = environment.identity.id + public init(identification: Identification) { + self.identification = identification + currentIdentityID = identification.identity.id - environment.identityService.identitiesObservation() + identification.service.identitiesObservation() .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$identities) } diff --git a/ViewModels/Sources/ViewModels/ListsViewModel.swift b/ViewModels/Sources/ViewModels/ListsViewModel.swift index 0fe9b2b..4f8fd83 100644 --- a/ViewModels/Sources/ViewModels/ListsViewModel.swift +++ b/ViewModels/Sources/ViewModels/ListsViewModel.swift @@ -10,13 +10,13 @@ public class ListsViewModel: ObservableObject { @Published public private(set) var creatingList = false @Published public var alertItem: AlertItem? - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment + public init(identification: Identification) { + self.identification = identification - environment.identityService.listsObservation() + identification.service.listsObservation() .map { $0.compactMap { guard case let .list(list) = $0 else { return nil } @@ -31,14 +31,14 @@ public class ListsViewModel: ObservableObject { public extension ListsViewModel { func refreshLists() { - environment.identityService.refreshLists() + identification.service.refreshLists() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } func createList(title: String) { - environment.identityService.createList(title: title) + identification.service.createList(title: title) .assignErrorsToAlertItem(to: \.alertItem, on: self) .handleEvents( receiveSubscription: { [weak self] _ in self?.creatingList = true }, @@ -48,7 +48,7 @@ public extension ListsViewModel { } func delete(list: MastodonList) { - environment.identityService.deleteList(id: list.id) + identification.service.deleteList(id: list.id) .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) diff --git a/ViewModels/Sources/ViewModels/NotificationTypesPreferencesViewModel.swift b/ViewModels/Sources/ViewModels/NotificationTypesPreferencesViewModel.swift index b592796..e99a084 100644 --- a/ViewModels/Sources/ViewModels/NotificationTypesPreferencesViewModel.swift +++ b/ViewModels/Sources/ViewModels/NotificationTypesPreferencesViewModel.swift @@ -9,14 +9,14 @@ public class NotificationTypesPreferencesViewModel: ObservableObject { @Published public var pushSubscriptionAlerts: PushSubscription.Alerts @Published public var alertItem: AlertItem? - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment - pushSubscriptionAlerts = environment.identity.pushSubscriptionAlerts + public init(identification: Identification) { + self.identification = identification + pushSubscriptionAlerts = identification.identity.pushSubscriptionAlerts - environment.$identity + identification.$identity .map(\.pushSubscriptionAlerts) .dropFirst() .removeDuplicates() @@ -32,14 +32,14 @@ public class NotificationTypesPreferencesViewModel: ObservableObject { private extension NotificationTypesPreferencesViewModel { func update(alerts: PushSubscription.Alerts) { - guard alerts != environment.identity.pushSubscriptionAlerts else { return } + guard alerts != identification.identity.pushSubscriptionAlerts else { return } - environment.identityService.updatePushSubscription(alerts: alerts) + identification.service.updatePushSubscription(alerts: alerts) .sink { [weak self] in guard let self = self, case let .failure(error) = $0 else { return } self.alertItem = AlertItem(error: error) - self.pushSubscriptionAlerts = self.environment.identity.pushSubscriptionAlerts + self.pushSubscriptionAlerts = self.identification.identity.pushSubscriptionAlerts } receiveValue: { _ in } .store(in: &cancellables) } diff --git a/ViewModels/Sources/ViewModels/PostingReadingPreferencesViewModel.swift b/ViewModels/Sources/ViewModels/PostingReadingPreferencesViewModel.swift index 5b03c01..63efe2b 100644 --- a/ViewModels/Sources/ViewModels/PostingReadingPreferencesViewModel.swift +++ b/ViewModels/Sources/ViewModels/PostingReadingPreferencesViewModel.swift @@ -8,14 +8,14 @@ public class PostingReadingPreferencesViewModel: ObservableObject { @Published public var preferences: Identity.Preferences @Published public var alertItem: AlertItem? - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment - preferences = environment.identity.preferences + public init(identification: Identification) { + self.identification = identification + preferences = identification.identity.preferences - environment.$identity + identification.$identity .map(\.preferences) .dropFirst() .removeDuplicates() @@ -23,7 +23,7 @@ public class PostingReadingPreferencesViewModel: ObservableObject { $preferences .dropFirst() - .flatMap(environment.identityService.updatePreferences) + .flatMap(identification.service.updatePreferences) .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) diff --git a/ViewModels/Sources/ViewModels/PreferencesViewModel.swift b/ViewModels/Sources/ViewModels/PreferencesViewModel.swift index 7ca4e6c..3b702d7 100644 --- a/ViewModels/Sources/ViewModels/PreferencesViewModel.swift +++ b/ViewModels/Sources/ViewModels/PreferencesViewModel.swift @@ -7,26 +7,12 @@ public class PreferencesViewModel: ObservableObject { public let handle: String public let shouldShowNotificationTypePreferences: Bool - private let environment: IdentifiedEnvironment + private let identification: Identification - init(environment: IdentifiedEnvironment) { - self.environment = environment - handle = environment.identity.handle + public init(identification: Identification) { + self.identification = identification + handle = identification.identity.handle - shouldShowNotificationTypePreferences = environment.identity.lastRegisteredDeviceToken != nil - } -} - -public extension PreferencesViewModel { - func postingReadingPreferencesViewModel() -> PostingReadingPreferencesViewModel { - PostingReadingPreferencesViewModel(environment: environment) - } - - func notificationTypesPreferencesViewModel() -> NotificationTypesPreferencesViewModel { - NotificationTypesPreferencesViewModel(environment: environment) - } - - func filtersViewModel() -> FiltersViewModel { - FiltersViewModel(environment: environment) + shouldShowNotificationTypePreferences = identification.identity.lastRegisteredDeviceToken != nil } } diff --git a/ViewModels/Sources/ViewModels/RootViewModel.swift b/ViewModels/Sources/ViewModels/RootViewModel.swift index 44d0c5c..97969db 100644 --- a/ViewModels/Sources/ViewModels/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/RootViewModel.swift @@ -5,10 +5,9 @@ import Foundation import ServiceLayer public final class RootViewModel: ObservableObject { - @Published public private(set) var tabNavigationViewModel: TabNavigationViewModel? + @Published public private(set) var identification: Identification? @Published private var mostRecentlyUsedIdentityID: UUID? - private let environment: AppEnvironment private let allIdentitiesService: AllIdentitiesService private let userNotificationService: UserNotificationService private let registerForRemoteNotifications: () -> AnyPublisher @@ -16,7 +15,6 @@ public final class RootViewModel: ObservableObject { public init(environment: AppEnvironment, registerForRemoteNotifications: @escaping () -> AnyPublisher) throws { - self.environment = environment allIdentitiesService = try AllIdentitiesService(environment: environment) userNotificationService = UserNotificationService(environment: environment) self.registerForRemoteNotifications = registerForRemoteNotifications @@ -38,39 +36,38 @@ public final class RootViewModel: ObservableObject { public extension RootViewModel { func newIdentitySelected(id: UUID?) { guard let id = id else { - tabNavigationViewModel = nil + identification = nil return } - let identifiedEnvironment: IdentifiedEnvironment + let identification: Identification do { - identifiedEnvironment = try allIdentitiesService.identifiedEnvironment(id: id) + identification = try Identification(service: allIdentitiesService.identityService(id: id)) + self.identification = identification } catch { return } - identifiedEnvironment.observationErrors + identification.observationErrors .receive(on: RunLoop.main) .map { [weak self] _ in self?.mostRecentlyUsedIdentityID } .sink { [weak self] in self?.newIdentitySelected(id: $0) } .store(in: &cancellables) - identifiedEnvironment.identityService.updateLastUse() + identification.service.updateLastUse() .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) userNotificationService.isAuthorized() .filter { $0 } .zip(registerForRemoteNotifications()) - .filter { identifiedEnvironment.identity.lastRegisteredDeviceToken != $1 } - .map { ($1, identifiedEnvironment.identity.pushSubscriptionAlerts) } - .flatMap(identifiedEnvironment.identityService.createPushSubscription(deviceToken:alerts:)) + .filter { identification.identity.lastRegisteredDeviceToken != $1 } + .map { ($1, identification.identity.pushSubscriptionAlerts) } + .flatMap(identification.service.createPushSubscription(deviceToken:alerts:)) .sink { _ in } receiveValue: { _ in } .store(in: &cancellables) - - tabNavigationViewModel = TabNavigationViewModel(environment: identifiedEnvironment) } func deleteIdentity(_ identity: Identity) { diff --git a/ViewModels/Sources/ViewModels/SecondaryNavigationViewModel.swift b/ViewModels/Sources/ViewModels/SecondaryNavigationViewModel.swift deleted file mode 100644 index 76e6759..0000000 --- a/ViewModels/Sources/ViewModels/SecondaryNavigationViewModel.swift +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2020 Metabolist. All rights reserved. - -import Foundation -import ServiceLayer - -public class SecondaryNavigationViewModel: ObservableObject { - @Published public private(set) var identity: Identity - - private let environment: IdentifiedEnvironment - - init(environment: IdentifiedEnvironment) { - self.environment = environment - identity = environment.identity - environment.$identity.dropFirst().assign(to: &$identity) - } -} - -public extension SecondaryNavigationViewModel { - func identitiesViewModel() -> IdentitiesViewModel { - IdentitiesViewModel(environment: environment) - } - - func listsViewModel() -> ListsViewModel { - ListsViewModel(environment: environment) - } - - func preferencesViewModel() -> PreferencesViewModel { - PreferencesViewModel(environment: environment) - } -} diff --git a/ViewModels/Sources/ViewModels/TabNavigationViewModel.swift b/ViewModels/Sources/ViewModels/TabNavigationViewModel.swift index 7b37dce..8abb08c 100644 --- a/ViewModels/Sources/ViewModels/TabNavigationViewModel.swift +++ b/ViewModels/Sources/ViewModels/TabNavigationViewModel.swift @@ -14,19 +14,19 @@ public class TabNavigationViewModel: ObservableObject { @Published public var alertItem: AlertItem? public var selectedTab: Tab? = .timelines - private let environment: IdentifiedEnvironment + private let identification: Identification private var cancellables = Set() - init(environment: IdentifiedEnvironment) { - self.environment = environment - identity = environment.identity - environment.$identity.dropFirst().assign(to: &$identity) + public init(identification: Identification) { + self.identification = identification + identity = identification.identity + identification.$identity.dropFirst().assign(to: &$identity) - environment.identityService.recentIdentitiesObservation() + identification.service.recentIdentitiesObservation() .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$recentIdentities) - environment.identityService.listsObservation() + identification.service.listsObservation() .map { Timeline.nonLists + $0 } .assignErrorsToAlertItem(to: \.alertItem, on: self) .assign(to: &$timelinesAndLists) @@ -54,42 +54,38 @@ public extension TabNavigationViewModel { } func refreshIdentity() { - if environment.identityService.isAuthorized { - environment.identityService.verifyCredentials() + if identification.service.isAuthorized { + identification.service.verifyCredentials() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) - environment.identityService.refreshLists() + identification.service.refreshLists() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) - environment.identityService.refreshFilters() + identification.service.refreshFilters() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) if identity.preferences.useServerPostingReadingPreferences { - environment.identityService.refreshServerPreferences() + identification.service.refreshServerPreferences() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } } - environment.identityService.refreshInstance() + identification.service.refreshInstance() .assignErrorsToAlertItem(to: \.alertItem, on: self) .sink { _ in } .store(in: &cancellables) } - func secondaryNavigationViewModel() -> SecondaryNavigationViewModel { - SecondaryNavigationViewModel(environment: environment) - } - func viewModel(timeline: Timeline) -> StatusListViewModel { - StatusListViewModel(statusListService: environment.identityService.service(timeline: timeline)) + StatusListViewModel(statusListService: identification.service.service(timeline: timeline)) } } diff --git a/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift b/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift index 70b5195..2f3db59 100644 --- a/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift +++ b/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift @@ -46,15 +46,8 @@ class AddIdentityViewModelTests: XCTestCase { } func testDoesNotAlertCanceledLogin() throws { - let environment = AppEnvironment( - session: Session(configuration: .stubbing), - webAuthSessionType: CanceledLoginMockWebAuthSession.self, - keychain: MockKeychain.self, - userDefaults: MockUserDefaults(), - userNotificationClient: .mock, - inMemoryContent: true, - identityFixture: nil) - let allIdentitiesService = try AllIdentitiesService(environment: environment) + let allIdentitiesService = try AllIdentitiesService( + environment: .mock(webAuthSessionType: CanceledLoginMockWebAuthSession.self)) let sut = AddIdentityViewModel(allIdentitiesService: allIdentitiesService) let recorder = sut.$alertItem.record() diff --git a/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift b/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift index caa94fa..cd1abac 100644 --- a/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift +++ b/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift @@ -14,7 +14,7 @@ class RootViewModelTests: XCTestCase { let sut = try RootViewModel( environment: .mock(), registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() }) - let recorder = sut.$tabNavigationViewModel.record() + let recorder = sut.$identification.record() XCTAssertNil(try wait(for: recorder.next(), timeout: 1)) diff --git a/Views/AddIdentityView.swift b/Views/AddIdentityView.swift index 34080f1..6ff6099 100644 --- a/Views/AddIdentityView.swift +++ b/Views/AddIdentityView.swift @@ -46,7 +46,7 @@ import PreviewViewModels struct AddAccountView_Previews: PreviewProvider { static var previews: some View { - AddIdentityView(viewModel: .mock()) + AddIdentityView(viewModel: RootViewModel.preview.addIdentityViewModel()) } } #endif diff --git a/Views/EditFilterView.swift b/Views/EditFilterView.swift index 116d8b4..04767da 100644 --- a/Views/EditFilterView.swift +++ b/Views/EditFilterView.swift @@ -101,7 +101,7 @@ import PreviewViewModels struct EditFilterView_Previews: PreviewProvider { static var previews: some View { - EditFilterView(viewModel: .mock()) + EditFilterView(viewModel: .init(filter: .new, identification: .preview)) } } #endif diff --git a/Views/FiltersView.swift b/Views/FiltersView.swift index 96a24b6..9fa3423 100644 --- a/Views/FiltersView.swift +++ b/Views/FiltersView.swift @@ -6,12 +6,13 @@ import ViewModels struct FiltersView: View { @StateObject var viewModel: FiltersViewModel + @EnvironmentObject var identification: Identification var body: some View { Form { Section { NavigationLink(destination: EditFilterView( - viewModel: viewModel.editFilterViewModel(filter: .new))) { + viewModel: .init(filter: .new, identification: identification))) { Label("add", systemImage: "plus.circle") } } @@ -36,7 +37,7 @@ private extension FiltersView { Section(header: Text(title)) { ForEach(filters) { filter in NavigationLink(destination: EditFilterView( - viewModel: viewModel.editFilterViewModel(filter: filter))) { + viewModel: .init(filter: filter, identification: identification))) { HStack { Text(filter.phrase) Spacer() @@ -60,7 +61,7 @@ import PreviewViewModels struct FiltersView_Previews: PreviewProvider { static var previews: some View { - FiltersView(viewModel: .mock()) + FiltersView(viewModel: .init(identification: .preview)) } } #endif diff --git a/Views/IdentitiesView.swift b/Views/IdentitiesView.swift index 5f17281..690ca67 100644 --- a/Views/IdentitiesView.swift +++ b/Views/IdentitiesView.swift @@ -72,8 +72,8 @@ import PreviewViewModels struct IdentitiesView_Previews: PreviewProvider { static var previews: some View { - IdentitiesView(viewModel: .mock()) - .environmentObject(RootViewModel.mock()) + IdentitiesView(viewModel: .init(identification: .preview)) + .environmentObject(RootViewModel.preview) } } #endif diff --git a/Views/ListsView.swift b/Views/ListsView.swift index 8d4c5e1..6745464 100644 --- a/Views/ListsView.swift +++ b/Views/ListsView.swift @@ -60,8 +60,8 @@ import PreviewViewModels struct ListsView_Previews: PreviewProvider { static var previews: some View { - ListsView(viewModel: .mock()) - .environmentObject(TabNavigationViewModel.mock()) + ListsView(viewModel: .init(identification: .preview)) + .environmentObject(TabNavigationViewModel(identification: .preview)) } } #endif diff --git a/Views/NotificationTypesPreferencesView.swift b/Views/NotificationTypesPreferencesView.swift index f2a792b..53d2f8b 100644 --- a/Views/NotificationTypesPreferencesView.swift +++ b/Views/NotificationTypesPreferencesView.swift @@ -29,7 +29,7 @@ import PreviewViewModels struct NotificationTypesPreferencesView_Previews: PreviewProvider { static var previews: some View { - NotificationTypesPreferencesView(viewModel: .mock()) + NotificationTypesPreferencesView(viewModel: .init(identification: .preview)) } } #endif diff --git a/Views/PostingReadingPreferencesView.swift b/Views/PostingReadingPreferencesView.swift index 4784312..64f4912 100644 --- a/Views/PostingReadingPreferencesView.swift +++ b/Views/PostingReadingPreferencesView.swift @@ -55,7 +55,7 @@ import PreviewViewModels struct PostingReadingPreferencesViewView_Previews: PreviewProvider { static var previews: some View { - PostingReadingPreferencesView(viewModel: .mock()) + PostingReadingPreferencesView(viewModel: .init(identification: .preview)) } } #endif diff --git a/Views/PreferencesView.swift b/Views/PreferencesView.swift index 47d8bc6..7edcf42 100644 --- a/Views/PreferencesView.swift +++ b/Views/PreferencesView.swift @@ -5,20 +5,21 @@ import ViewModels struct PreferencesView: View { @StateObject var viewModel: PreferencesViewModel + @EnvironmentObject var identification: Identification var body: some View { Form { Section(header: Text(viewModel.handle)) { NavigationLink("preferences.posting-reading", destination: PostingReadingPreferencesView( - viewModel: viewModel.postingReadingPreferencesViewModel())) + viewModel: .init(identification: identification))) NavigationLink("preferences.filters", destination: FiltersView( - viewModel: viewModel.filtersViewModel())) + viewModel: .init(identification: identification))) if viewModel.shouldShowNotificationTypePreferences { NavigationLink("preferences.notification-types", destination: NotificationTypesPreferencesView( - viewModel: viewModel.notificationTypesPreferencesViewModel())) + viewModel: .init(identification: identification))) } } } @@ -31,7 +32,7 @@ import PreviewViewModels struct PreferencesView_Previews: PreviewProvider { static var previews: some View { - PreferencesView(viewModel: .mock()) + PreferencesView(viewModel: .init(identification: .preview)) } } #endif diff --git a/Views/RootView.swift b/Views/RootView.swift index 19518f2..d80a109 100644 --- a/Views/RootView.swift +++ b/Views/RootView.swift @@ -7,9 +7,11 @@ struct RootView: View { @StateObject var viewModel: RootViewModel var body: some View { - if let tabNavigationViewModel = viewModel.tabNavigationViewModel { - TabNavigationView(viewModel: tabNavigationViewModel) + if let identification = viewModel.identification { + TabNavigationView() .id(UUID()) + .environmentObject(identification) + .environmentObject(TabNavigationViewModel(identification: identification)) .environmentObject(viewModel) .transition(.opacity) } else { @@ -21,11 +23,12 @@ struct RootView: View { } #if DEBUG +import Combine import PreviewViewModels struct ContentView_Previews: PreviewProvider { static var previews: some View { - RootView(viewModel: .mock()) + RootView(viewModel: .preview) } } #endif diff --git a/Views/SecondaryNavigationView.swift b/Views/SecondaryNavigationView.swift index 9935594..f63a7a3 100644 --- a/Views/SecondaryNavigationView.swift +++ b/Views/SecondaryNavigationView.swift @@ -5,7 +5,8 @@ import SwiftUI import ViewModels struct SecondaryNavigationView: View { - @StateObject var viewModel: SecondaryNavigationViewModel + @EnvironmentObject var identification: Identification + @EnvironmentObject var tabNavigationViewModel: TabNavigationViewModel @Environment(\.presentationMode) var presentationMode @Environment(\.displayScale) var displayScale: CGFloat @@ -14,19 +15,19 @@ struct SecondaryNavigationView: View { Form { Section { NavigationLink( - destination: IdentitiesView(viewModel: viewModel.identitiesViewModel()), + destination: IdentitiesView(viewModel: .init(identification: identification)), label: { HStack { - KFImage(viewModel.identity.image, + KFImage(tabNavigationViewModel.identity.image, options: .downsampled(dimension: 50, scaleFactor: displayScale)) VStack(alignment: .leading) { - if let account = viewModel.identity.account { + if let account = tabNavigationViewModel.identity.account { CustomEmojiText( text: account.displayName, emoji: account.emojis, textStyle: .headline) } - Text(viewModel.identity.handle) + Text(tabNavigationViewModel.identity.handle) .font(.subheadline) .foregroundColor(.secondary) .lineLimit(1) @@ -40,7 +41,7 @@ struct SecondaryNavigationView: View { }) } Section { - NavigationLink(destination: ListsView(viewModel: viewModel.listsViewModel())) { + NavigationLink(destination: ListsView(viewModel: .init(identification: identification))) { Label("secondary-navigation.lists", systemImage: "scroll") } } @@ -48,7 +49,7 @@ struct SecondaryNavigationView: View { NavigationLink( "secondary-navigation.preferences", destination: PreferencesView( - viewModel: viewModel.preferencesViewModel())) + viewModel: .init(identification: identification))) } } .navigationBarTitleDisplayMode(.inline) @@ -71,9 +72,9 @@ import PreviewViewModels struct SecondaryNavigationView_Previews: PreviewProvider { static var previews: some View { - SecondaryNavigationView(viewModel: .mock()) - .environmentObject(RootViewModel.mock()) - .environmentObject(TabNavigationViewModel.mock()) + SecondaryNavigationView() + .environmentObject(Identification.preview) + .environmentObject(TabNavigationViewModel(identification: .preview)) } } #endif diff --git a/Views/StatusListView.swift b/Views/StatusListView.swift index 43f5346..c63583e 100644 --- a/Views/StatusListView.swift +++ b/Views/StatusListView.swift @@ -20,7 +20,7 @@ import PreviewViewModels struct StatusListView_Previews: PreviewProvider { static var previews: some View { - StatusListView(viewModel: .mock()) + StatusListView(viewModel: TabNavigationViewModel(identification: .preview).viewModel(timeline: .home)) } } #endif diff --git a/Views/TabNavigationView.swift b/Views/TabNavigationView.swift index bddce3b..437f1b3 100644 --- a/Views/TabNavigationView.swift +++ b/Views/TabNavigationView.swift @@ -6,7 +6,7 @@ import SwiftUI import ViewModels struct TabNavigationView: View { - @ObservedObject var viewModel: TabNavigationViewModel + @EnvironmentObject var viewModel: TabNavigationViewModel @EnvironmentObject var rootViewModel: RootViewModel @Environment(\.displayScale) var displayScale: CGFloat @@ -25,7 +25,7 @@ struct TabNavigationView: View { } } .sheet(isPresented: $viewModel.presentingSecondaryNavigation) { - SecondaryNavigationView(viewModel: viewModel.secondaryNavigationViewModel()) + SecondaryNavigationView() .environmentObject(viewModel) } .alertItem($viewModel.alertItem) @@ -145,8 +145,10 @@ import PreviewViewModels struct TabNavigation_Previews: PreviewProvider { static var previews: some View { - TabNavigationView(viewModel: .mock()) - .environmentObject(RootViewModel.mock()) + TabNavigationView() + .environmentObject(Identification.preview) + .environmentObject(TabNavigationViewModel(identification: .preview)) + .environmentObject(RootViewModel.preview) } } #endif