mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Refactoring
This commit is contained in:
parent
a6185c9cf4
commit
62ddaac083
9 changed files with 37 additions and 35 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue