diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift index 544cc82..a0fcdda 100644 --- a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift @@ -13,6 +13,7 @@ public struct AppEnvironment { let keychain: Keychain.Type let userDefaults: UserDefaults let userNotificationClient: UserNotificationClient + let uuid: () -> UUID let inMemoryContent: Bool let fixtureDatabase: IdentityDatabase? @@ -21,6 +22,7 @@ public struct AppEnvironment { keychain: Keychain.Type, userDefaults: UserDefaults, userNotificationClient: UserNotificationClient, + uuid: @escaping () -> UUID, inMemoryContent: Bool, fixtureDatabase: IdentityDatabase?) { self.session = session @@ -28,6 +30,7 @@ public struct AppEnvironment { self.keychain = keychain self.userDefaults = userDefaults self.userNotificationClient = userNotificationClient + self.uuid = uuid self.inMemoryContent = inMemoryContent self.fixtureDatabase = fixtureDatabase } @@ -41,6 +44,7 @@ public extension AppEnvironment { keychain: LiveKeychain.self, userDefaults: .standard, userNotificationClient: .live(userNotificationCenter), + uuid: UUID.init, inMemoryContent: false, fixtureDatabase: nil) } diff --git a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift index fbeb381..f5439b7 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/AllIdentitiesService.swift @@ -8,14 +8,18 @@ import MastodonAPI import Secrets public struct AllIdentitiesService { + public let identitiesCreated: AnyPublisher + private let environment: AppEnvironment private let database: IdentityDatabase + private let identitiesCreatedSubject = PassthroughSubject() public init(environment: AppEnvironment) throws { self.environment = environment self.database = try environment.fixtureDatabase ?? IdentityDatabase( inMemory: environment.inMemoryContent, keychain: environment.keychain) + identitiesCreated = identitiesCreatedSubject.eraseToAnyPublisher() } } @@ -28,18 +32,16 @@ public extension AllIdentitiesService { database.immediateMostRecentlyUsedIdentityIDObservation() } - func createIdentity(id: UUID, url: URL, authenticated: Bool) -> AnyPublisher { + func createIdentity(url: URL, authenticated: Bool) -> AnyPublisher { createIdentity( - id: id, url: url, authenticationPublisher: authenticated ? AuthenticationService(url: url, environment: environment).authenticate() : nil) } - func createIdentity(id: UUID, url: URL, registration: Registration) -> AnyPublisher { + func createIdentity(url: URL, registration: Registration) -> AnyPublisher { createIdentity( - id: id, url: url, authenticationPublisher: AuthenticationService(url: url, environment: environment) .register(registration)) @@ -91,9 +93,9 @@ public extension AllIdentitiesService { private extension AllIdentitiesService { func createIdentity( - id: UUID, url: URL, authenticationPublisher: AnyPublisher<(AppAuthorization, AccessToken), Error>?) -> AnyPublisher { + let id = environment.uuid() let secrets = Secrets(identityID: id, keychain: environment.keychain) do { @@ -107,6 +109,11 @@ private extension AllIdentitiesService { url: url, authenticated: authenticationPublisher != nil) .ignoreOutput() + .handleEvents(receiveCompletion: { + if case .finished = $0 { + identitiesCreatedSubject.send(id) + } + }) .eraseToAnyPublisher() if let authenticationPublisher = authenticationPublisher { diff --git a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift index a49e3cd..ac7c12b 100644 --- a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift @@ -14,6 +14,7 @@ public extension AppEnvironment { keychain: Keychain.Type = MockKeychain.self, userDefaults: UserDefaults = MockUserDefaults(), userNotificationClient: UserNotificationClient = .mock, + uuid: @escaping () -> UUID = UUID.init, inMemoryContent: Bool = true, fixtureDatabase: IdentityDatabase? = nil) -> Self { AppEnvironment( @@ -22,6 +23,7 @@ public extension AppEnvironment { keychain: keychain, userDefaults: userDefaults, userNotificationClient: userNotificationClient, + uuid: uuid, inMemoryContent: inMemoryContent, fixtureDatabase: fixtureDatabase) } diff --git a/ViewModels/Sources/ViewModels/AddIdentityViewModel.swift b/ViewModels/Sources/ViewModels/AddIdentityViewModel.swift index b62b74b..36075eb 100644 --- a/ViewModels/Sources/ViewModels/AddIdentityViewModel.swift +++ b/ViewModels/Sources/ViewModels/AddIdentityViewModel.swift @@ -16,17 +16,14 @@ public final class AddIdentityViewModel: ObservableObject { @Published public private(set) var url: URL? @Published public private(set) var instance: Instance? @Published public private(set) var isPublicTimelineAvailable = false - public let addedIdentityID: AnyPublisher private let allIdentitiesService: AllIdentitiesService private let instanceURLService: InstanceURLService - private let addedIdentityIDSubject = PassthroughSubject() private var cancellables = Set() init(allIdentitiesService: AllIdentitiesService, instanceURLService: InstanceURLService) { self.allIdentitiesService = allIdentitiesService self.instanceURLService = instanceURLService - addedIdentityID = addedIdentityIDSubject.eraseToAnyPublisher() setupURLObservation() } } @@ -88,18 +85,13 @@ private extension AddIdentityViewModel { } func addIdentity(authenticated: Bool) { - let identityID = UUID() - guard let url = instanceURLService.url(text: urlFieldText) else { alertItem = AlertItem(error: AddIdentityError.unableToConnectToInstance) return } - allIdentitiesService.createIdentity( - id: identityID, - url: url, - authenticated: authenticated) + allIdentitiesService.createIdentity(url: url, authenticated: authenticated) .receive(on: DispatchQueue.main) .handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true }) .sink { [weak self] in @@ -107,10 +99,7 @@ private extension AddIdentityViewModel { self.loading = false - switch $0 { - case .finished: - self.addedIdentityIDSubject.send(identityID) - case let .failure(error): + if case let .failure(error) = $0 { if case AuthenticationError.canceled = error { return } diff --git a/ViewModels/Sources/ViewModels/RegistrationViewModel.swift b/ViewModels/Sources/ViewModels/RegistrationViewModel.swift index e17eecf..a4377fb 100644 --- a/ViewModels/Sources/ViewModels/RegistrationViewModel.swift +++ b/ViewModels/Sources/ViewModels/RegistrationViewModel.swift @@ -50,7 +50,7 @@ public extension RegistrationViewModel { return } - allIdentitiesService.createIdentity(id: UUID(), url: url, registration: registration) + allIdentitiesService.createIdentity(url: url, registration: registration) .handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true }) .mapError { error -> Error in if error is URLError { diff --git a/ViewModels/Sources/ViewModels/RootViewModel.swift b/ViewModels/Sources/ViewModels/RootViewModel.swift index 00dfc2f..21411c9 100644 --- a/ViewModels/Sources/ViewModels/RootViewModel.swift +++ b/ViewModels/Sources/ViewModels/RootViewModel.swift @@ -27,6 +27,10 @@ public final class RootViewModel: ObservableObject { identitySelected(id: mostRecentlyUsedIdentityID, immediate: true) + allIdentitiesService.identitiesCreated + .sink { [weak self] in self?.identitySelected(id: $0) } + .store(in: &cancellables) + userNotificationService.isAuthorized() .filter { $0 } .zip(registerForRemoteNotifications()) diff --git a/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift b/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift index 9678eac..120f14a 100644 --- a/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift +++ b/ViewModels/Tests/ViewModelsTests/AddIdentityViewModelTests.swift @@ -12,11 +12,13 @@ import XCTest class AddIdentityViewModelTests: XCTestCase { func testAddIdentity() throws { - let environment = AppEnvironment.mock() + let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! + let environment = AppEnvironment.mock(uuid: { uuid }) + let allIdentitiesService = try AllIdentitiesService(environment: environment) let sut = AddIdentityViewModel( - allIdentitiesService: try AllIdentitiesService(environment: environment), + allIdentitiesService: allIdentitiesService, instanceURLService: InstanceURLService(environment: environment)) - let addedIDRecorder = sut.addedIdentityID.record() + let addedIDRecorder = allIdentitiesService.identitiesCreated.record() sut.urlFieldText = "https://mastodon.social" sut.logInTapped() @@ -25,11 +27,13 @@ class AddIdentityViewModelTests: XCTestCase { } func testAddIdentityWithoutScheme() throws { - let environment = AppEnvironment.mock() + let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! + let environment = AppEnvironment.mock(uuid: { uuid }) + let allIdentitiesService = try AllIdentitiesService(environment: environment) let sut = AddIdentityViewModel( - allIdentitiesService: try AllIdentitiesService(environment: environment), + allIdentitiesService: allIdentitiesService, instanceURLService: InstanceURLService(environment: environment)) - let addedIDRecorder = sut.addedIdentityID.record() + let addedIDRecorder = allIdentitiesService.identitiesCreated.record() sut.urlFieldText = "mastodon.social" sut.logInTapped() diff --git a/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift b/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift index 1e4197c..e2604e6 100644 --- a/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift +++ b/ViewModels/Tests/ViewModelsTests/RootViewModelTests.swift @@ -11,8 +11,9 @@ class RootViewModelTests: XCTestCase { var cancellables = Set() func testAddIdentity() throws { + let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")! let sut = try RootViewModel( - environment: .mock(), + environment: .mock(uuid: { uuid }), registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() }) let recorder = sut.$navigationViewModel.record() @@ -20,10 +21,6 @@ class RootViewModelTests: XCTestCase { let addIdentityViewModel = sut.addIdentityViewModel() - addIdentityViewModel.addedIdentityID - .sink(receiveValue: sut.identitySelected(id:)) - .store(in: &cancellables) - addIdentityViewModel.urlFieldText = "https://mastodon.social" addIdentityViewModel.logInTapped() diff --git a/Views/AddIdentityView.swift b/Views/AddIdentityView.swift index 4cd5d41..00a1042 100644 --- a/Views/AddIdentityView.swift +++ b/Views/AddIdentityView.swift @@ -74,11 +74,6 @@ struct AddIdentityView: View { } .animation(.default, if: !accessibilityReduceMotion) .alertItem($viewModel.alertItem) - .onReceive(viewModel.addedIdentityID) { id in - withAnimation { - rootViewModel.identitySelected(id: id) - } - } .onAppear(perform: viewModel.refreshFilter) } }