Refactoring

This commit is contained in:
Justin Mazzocchi 2020-09-12 17:50:22 -07:00
parent a6185c9cf4
commit 62ddaac083
No known key found for this signature in database
GPG Key ID: E223E6937AAFB01C
9 changed files with 37 additions and 35 deletions

View File

@ -13,6 +13,7 @@ public struct AppEnvironment {
let keychain: Keychain.Type let keychain: Keychain.Type
let userDefaults: UserDefaults let userDefaults: UserDefaults
let userNotificationClient: UserNotificationClient let userNotificationClient: UserNotificationClient
let uuid: () -> UUID
let inMemoryContent: Bool let inMemoryContent: Bool
let fixtureDatabase: IdentityDatabase? let fixtureDatabase: IdentityDatabase?
@ -21,6 +22,7 @@ public struct AppEnvironment {
keychain: Keychain.Type, keychain: Keychain.Type,
userDefaults: UserDefaults, userDefaults: UserDefaults,
userNotificationClient: UserNotificationClient, userNotificationClient: UserNotificationClient,
uuid: @escaping () -> UUID,
inMemoryContent: Bool, inMemoryContent: Bool,
fixtureDatabase: IdentityDatabase?) { fixtureDatabase: IdentityDatabase?) {
self.session = session self.session = session
@ -28,6 +30,7 @@ public struct AppEnvironment {
self.keychain = keychain self.keychain = keychain
self.userDefaults = userDefaults self.userDefaults = userDefaults
self.userNotificationClient = userNotificationClient self.userNotificationClient = userNotificationClient
self.uuid = uuid
self.inMemoryContent = inMemoryContent self.inMemoryContent = inMemoryContent
self.fixtureDatabase = fixtureDatabase self.fixtureDatabase = fixtureDatabase
} }
@ -41,6 +44,7 @@ public extension AppEnvironment {
keychain: LiveKeychain.self, keychain: LiveKeychain.self,
userDefaults: .standard, userDefaults: .standard,
userNotificationClient: .live(userNotificationCenter), userNotificationClient: .live(userNotificationCenter),
uuid: UUID.init,
inMemoryContent: false, inMemoryContent: false,
fixtureDatabase: nil) fixtureDatabase: nil)
} }

View File

@ -8,14 +8,18 @@ import MastodonAPI
import Secrets import Secrets
public struct AllIdentitiesService { public struct AllIdentitiesService {
public let identitiesCreated: AnyPublisher<UUID, Never>
private let environment: AppEnvironment private let environment: AppEnvironment
private let database: IdentityDatabase private let database: IdentityDatabase
private let identitiesCreatedSubject = PassthroughSubject<UUID, Never>()
public init(environment: AppEnvironment) throws { public init(environment: AppEnvironment) throws {
self.environment = environment self.environment = environment
self.database = try environment.fixtureDatabase ?? IdentityDatabase( self.database = try environment.fixtureDatabase ?? IdentityDatabase(
inMemory: environment.inMemoryContent, inMemory: environment.inMemoryContent,
keychain: environment.keychain) keychain: environment.keychain)
identitiesCreated = identitiesCreatedSubject.eraseToAnyPublisher()
} }
} }
@ -28,18 +32,16 @@ public extension AllIdentitiesService {
database.immediateMostRecentlyUsedIdentityIDObservation() database.immediateMostRecentlyUsedIdentityIDObservation()
} }
func createIdentity(id: UUID, url: URL, authenticated: Bool) -> AnyPublisher<Never, Error> { func createIdentity(url: URL, authenticated: Bool) -> AnyPublisher<Never, Error> {
createIdentity( createIdentity(
id: id,
url: url, url: url,
authenticationPublisher: authenticated authenticationPublisher: authenticated
? AuthenticationService(url: url, environment: environment).authenticate() ? AuthenticationService(url: url, environment: environment).authenticate()
: nil) : nil)
} }
func createIdentity(id: UUID, url: URL, registration: Registration) -> AnyPublisher<Never, Error> { func createIdentity(url: URL, registration: Registration) -> AnyPublisher<Never, Error> {
createIdentity( createIdentity(
id: id,
url: url, url: url,
authenticationPublisher: AuthenticationService(url: url, environment: environment) authenticationPublisher: AuthenticationService(url: url, environment: environment)
.register(registration)) .register(registration))
@ -91,9 +93,9 @@ public extension AllIdentitiesService {
private extension AllIdentitiesService { private extension AllIdentitiesService {
func createIdentity( func createIdentity(
id: UUID,
url: URL, url: URL,
authenticationPublisher: AnyPublisher<(AppAuthorization, AccessToken), Error>?) -> AnyPublisher<Never, Error> { authenticationPublisher: AnyPublisher<(AppAuthorization, AccessToken), Error>?) -> AnyPublisher<Never, Error> {
let id = environment.uuid()
let secrets = Secrets(identityID: id, keychain: environment.keychain) let secrets = Secrets(identityID: id, keychain: environment.keychain)
do { do {
@ -107,6 +109,11 @@ private extension AllIdentitiesService {
url: url, url: url,
authenticated: authenticationPublisher != nil) authenticated: authenticationPublisher != nil)
.ignoreOutput() .ignoreOutput()
.handleEvents(receiveCompletion: {
if case .finished = $0 {
identitiesCreatedSubject.send(id)
}
})
.eraseToAnyPublisher() .eraseToAnyPublisher()
if let authenticationPublisher = authenticationPublisher { if let authenticationPublisher = authenticationPublisher {

View File

@ -14,6 +14,7 @@ public extension AppEnvironment {
keychain: Keychain.Type = MockKeychain.self, keychain: Keychain.Type = MockKeychain.self,
userDefaults: UserDefaults = MockUserDefaults(), userDefaults: UserDefaults = MockUserDefaults(),
userNotificationClient: UserNotificationClient = .mock, userNotificationClient: UserNotificationClient = .mock,
uuid: @escaping () -> UUID = UUID.init,
inMemoryContent: Bool = true, inMemoryContent: Bool = true,
fixtureDatabase: IdentityDatabase? = nil) -> Self { fixtureDatabase: IdentityDatabase? = nil) -> Self {
AppEnvironment( AppEnvironment(
@ -22,6 +23,7 @@ public extension AppEnvironment {
keychain: keychain, keychain: keychain,
userDefaults: userDefaults, userDefaults: userDefaults,
userNotificationClient: userNotificationClient, userNotificationClient: userNotificationClient,
uuid: uuid,
inMemoryContent: inMemoryContent, inMemoryContent: inMemoryContent,
fixtureDatabase: fixtureDatabase) fixtureDatabase: fixtureDatabase)
} }

View File

@ -16,17 +16,14 @@ public final class AddIdentityViewModel: ObservableObject {
@Published public private(set) var url: URL? @Published public private(set) var url: URL?
@Published public private(set) var instance: Instance? @Published public private(set) var instance: Instance?
@Published public private(set) var isPublicTimelineAvailable = false @Published public private(set) var isPublicTimelineAvailable = false
public let addedIdentityID: AnyPublisher<UUID, Never>
private let allIdentitiesService: AllIdentitiesService private let allIdentitiesService: AllIdentitiesService
private let instanceURLService: InstanceURLService private let instanceURLService: InstanceURLService
private let addedIdentityIDSubject = PassthroughSubject<UUID, Never>()
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
init(allIdentitiesService: AllIdentitiesService, instanceURLService: InstanceURLService) { init(allIdentitiesService: AllIdentitiesService, instanceURLService: InstanceURLService) {
self.allIdentitiesService = allIdentitiesService self.allIdentitiesService = allIdentitiesService
self.instanceURLService = instanceURLService self.instanceURLService = instanceURLService
addedIdentityID = addedIdentityIDSubject.eraseToAnyPublisher()
setupURLObservation() setupURLObservation()
} }
} }
@ -88,18 +85,13 @@ private extension AddIdentityViewModel {
} }
func addIdentity(authenticated: Bool) { func addIdentity(authenticated: Bool) {
let identityID = UUID()
guard let url = instanceURLService.url(text: urlFieldText) else { guard let url = instanceURLService.url(text: urlFieldText) else {
alertItem = AlertItem(error: AddIdentityError.unableToConnectToInstance) alertItem = AlertItem(error: AddIdentityError.unableToConnectToInstance)
return return
} }
allIdentitiesService.createIdentity( allIdentitiesService.createIdentity(url: url, authenticated: authenticated)
id: identityID,
url: url,
authenticated: authenticated)
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true }) .handleEvents(receiveSubscription: { [weak self] _ in self?.loading = true })
.sink { [weak self] in .sink { [weak self] in
@ -107,10 +99,7 @@ private extension AddIdentityViewModel {
self.loading = false self.loading = false
switch $0 { if case let .failure(error) = $0 {
case .finished:
self.addedIdentityIDSubject.send(identityID)
case let .failure(error):
if case AuthenticationError.canceled = error { if case AuthenticationError.canceled = error {
return return
} }

View File

@ -50,7 +50,7 @@ public extension RegistrationViewModel {
return return
} }
allIdentitiesService.createIdentity(id: UUID(), url: url, registration: registration) allIdentitiesService.createIdentity(url: url, registration: registration)
.handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true }) .handleEvents(receiveSubscription: { [weak self] _ in self?.registering = true })
.mapError { error -> Error in .mapError { error -> Error in
if error is URLError { if error is URLError {

View File

@ -27,6 +27,10 @@ public final class RootViewModel: ObservableObject {
identitySelected(id: mostRecentlyUsedIdentityID, immediate: true) identitySelected(id: mostRecentlyUsedIdentityID, immediate: true)
allIdentitiesService.identitiesCreated
.sink { [weak self] in self?.identitySelected(id: $0) }
.store(in: &cancellables)
userNotificationService.isAuthorized() userNotificationService.isAuthorized()
.filter { $0 } .filter { $0 }
.zip(registerForRemoteNotifications()) .zip(registerForRemoteNotifications())

View File

@ -12,11 +12,13 @@ import XCTest
class AddIdentityViewModelTests: XCTestCase { class AddIdentityViewModelTests: XCTestCase {
func testAddIdentity() throws { 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( let sut = AddIdentityViewModel(
allIdentitiesService: try AllIdentitiesService(environment: environment), allIdentitiesService: allIdentitiesService,
instanceURLService: InstanceURLService(environment: environment)) instanceURLService: InstanceURLService(environment: environment))
let addedIDRecorder = sut.addedIdentityID.record() let addedIDRecorder = allIdentitiesService.identitiesCreated.record()
sut.urlFieldText = "https://mastodon.social" sut.urlFieldText = "https://mastodon.social"
sut.logInTapped() sut.logInTapped()
@ -25,11 +27,13 @@ class AddIdentityViewModelTests: XCTestCase {
} }
func testAddIdentityWithoutScheme() throws { 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( let sut = AddIdentityViewModel(
allIdentitiesService: try AllIdentitiesService(environment: environment), allIdentitiesService: allIdentitiesService,
instanceURLService: InstanceURLService(environment: environment)) instanceURLService: InstanceURLService(environment: environment))
let addedIDRecorder = sut.addedIdentityID.record() let addedIDRecorder = allIdentitiesService.identitiesCreated.record()
sut.urlFieldText = "mastodon.social" sut.urlFieldText = "mastodon.social"
sut.logInTapped() sut.logInTapped()

View File

@ -11,8 +11,9 @@ class RootViewModelTests: XCTestCase {
var cancellables = Set<AnyCancellable>() var cancellables = Set<AnyCancellable>()
func testAddIdentity() throws { func testAddIdentity() throws {
let uuid = UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!
let sut = try RootViewModel( let sut = try RootViewModel(
environment: .mock(), environment: .mock(uuid: { uuid }),
registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() }) registerForRemoteNotifications: { Empty().setFailureType(to: Error.self).eraseToAnyPublisher() })
let recorder = sut.$navigationViewModel.record() let recorder = sut.$navigationViewModel.record()
@ -20,10 +21,6 @@ class RootViewModelTests: XCTestCase {
let addIdentityViewModel = sut.addIdentityViewModel() let addIdentityViewModel = sut.addIdentityViewModel()
addIdentityViewModel.addedIdentityID
.sink(receiveValue: sut.identitySelected(id:))
.store(in: &cancellables)
addIdentityViewModel.urlFieldText = "https://mastodon.social" addIdentityViewModel.urlFieldText = "https://mastodon.social"
addIdentityViewModel.logInTapped() addIdentityViewModel.logInTapped()

View File

@ -74,11 +74,6 @@ struct AddIdentityView: View {
} }
.animation(.default, if: !accessibilityReduceMotion) .animation(.default, if: !accessibilityReduceMotion)
.alertItem($viewModel.alertItem) .alertItem($viewModel.alertItem)
.onReceive(viewModel.addedIdentityID) { id in
withAnimation {
rootViewModel.identitySelected(id: id)
}
}
.onAppear(perform: viewModel.refreshFilter) .onAppear(perform: viewModel.refreshFilter)
} }
} }