User UserIdentification for search and accounts (IOS-192)

Thanks to @kimar!
This commit is contained in:
Nathan Mattes 2023-12-28 22:39:24 +01:00
parent 2a14e293e9
commit 460ede4852
9 changed files with 59 additions and 67 deletions

View File

@ -27,7 +27,7 @@ extension DataSourceFacade {
hashtag: nil hashtag: nil
) )
try? FileManager.default.addSearchItem(searchEntry) try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
case .hashtag(let tag): case .hashtag(let tag):
let now = Date() let now = Date()
@ -39,7 +39,7 @@ extension DataSourceFacade {
hashtag: tag hashtag: tag
) )
try? FileManager.default.addSearchItem(searchEntry) try? FileManager.default.addSearchItem(searchEntry, for: provider.authContext.mastodonAuthenticationBox)
case .status: case .status:
break break
case .user(_): case .user(_):

View File

@ -139,30 +139,23 @@ class MastodonLoginViewController: UIViewController, NeedsDependency {
@objc func login() { @objc func login() {
guard let server = viewModel.selectedServer else { return } guard let server = viewModel.selectedServer else { return }
authenticationViewModel authenticationViewModel
.authenticated .authenticated.sink { (domain, account) in
.asyncMap { domain, user -> Result<Mastodon.Entity.Account, Error> in Task {
do { do {
let result = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id) _ = try await self.context.authenticationService.activeMastodonUser(domain: domain, userID: account.id)
return .success(user) FileManager.default.store(account: account, forUserID: MastodonUserIdentifier(domain: domain, userID: account.id))
} catch { Task { @MainActor in
return .failure(error) self.coordinator.setup()
} }
} } catch {
.receive(on: DispatchQueue.main) assertionFailure(error.localizedDescription)
.sink { [weak self] result in }
guard let self = self else { return }
switch result {
case .failure(let error):
assertionFailure(error.localizedDescription)
case .success(let account):
FileManager.default.store(account: account, forUserID: account.id)
self.coordinator.setup()
} }
} }
.store(in: &disposeBag) .store(in: &disposeBag)
authenticationViewModel.isAuthenticating.send(true) authenticationViewModel.isAuthenticating.send(true)
context.apiService.createApplication(domain: server.domain) context.apiService.createApplication(domain: server.domain)
.tryMap { response -> AuthenticationViewModel.AuthenticateInfo in .tryMap { response -> AuthenticationViewModel.AuthenticateInfo in

View File

@ -50,8 +50,7 @@ extension SearchHistoryViewController {
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
let userID = authContext.mastodonAuthenticationBox.userID viewModel.items = (try? FileManager.default.searchItems(for: authContext.mastodonAuthenticationBox)) ?? []
viewModel.items = (try? FileManager.default.searchItems(forUser: userID)) ?? []
} }
} }
@ -103,9 +102,7 @@ extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusa
_ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView, _ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView,
clearButtonDidPressed button: UIButton clearButtonDidPressed button: UIButton
) { ) {
let userID = authContext.mastodonAuthenticationBox.userID FileManager.default.removeSearchHistory(for: authContext.mastodonAuthenticationBox)
FileManager.default.removeSearchHistory(forUser: userID)
viewModel.items = [] viewModel.items = []
} }
} }
@ -113,7 +110,6 @@ extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusa
//MARK: - SearchResultOverviewCoordinatorDelegate //MARK: - SearchResultOverviewCoordinatorDelegate
extension SearchHistoryViewController: SearchResultOverviewCoordinatorDelegate { extension SearchHistoryViewController: SearchResultOverviewCoordinatorDelegate {
func newSearchHistoryItemAdded(_ coordinator: SearchResultOverviewCoordinator) { func newSearchHistoryItemAdded(_ coordinator: SearchResultOverviewCoordinator) {
let userID = authContext.mastodonAuthenticationBox.userID viewModel.items = (try? FileManager.default.searchItems(for: authContext.mastodonAuthenticationBox)) ?? []
viewModel.items = (try? FileManager.default.searchItems(forUser: userID)) ?? []
} }
} }

View File

@ -24,7 +24,7 @@ final class SearchHistoryViewModel {
init(context: AppContext, authContext: AuthContext) { init(context: AppContext, authContext: AuthContext) {
self.context = context self.context = context
self.authContext = authContext self.authContext = authContext
self.items = (try? FileManager.default.searchItems(forUser: authContext.mastodonAuthenticationBox.userID)) ?? [] self.items = (try? FileManager.default.searchItems(for: authContext.mastodonAuthenticationBox)) ?? []
} }
} }

View File

@ -116,7 +116,7 @@ public extension AuthenticationServiceProvider {
userID: authentication.userID, userID: authentication.userID,
authorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)).value else { continue } authorization: Mastodon.API.OAuth.Authorization(accessToken: authentication.userAccessToken)).value else { continue }
FileManager.default.store(account: account, forUserID: authentication.userID) FileManager.default.store(account: account, forUserID: authentication.userIdentifier())
} }
NotificationCenter.default.post(name: .userFetched, object: nil) NotificationCenter.default.post(name: .userFetched, object: nil)

View File

@ -100,11 +100,19 @@ public struct MastodonAuthentication: Codable, Hashable {
} }
public func account() -> Mastodon.Entity.Account? { public func account() -> Mastodon.Entity.Account? {
let account = FileManager.default.accounts(forUserID: userID).first(where: { $0.id == userID })
let account = FileManager
.default
.accounts(for: self.userIdentifier())
.first(where: { $0.id == userID })
return account return account
} }
public func userIdentifier() -> MastodonUserIdentifier {
MastodonUserIdentifier(domain: domain, userID: userID)
}
func updating(instance: Instance) -> Self { func updating(instance: Instance) -> Self {
copy(instanceObjectIdURI: instance.objectID.uriRepresentation()) copy(instanceObjectIdURI: instance.objectID.uriRepresentation())
} }

View File

@ -3,10 +3,9 @@
import Foundation import Foundation
import MastodonSDK import MastodonSDK
extension FileManager { public extension FileManager {
public func store(account: Mastodon.Entity.Account, forUserID userID: String) { func store(account: Mastodon.Entity.Account, forUserID userID: UserIdentifier) {
// store accounts for each loged in user var accounts = accounts(for: userID)
var accounts = accounts(forUserID: userID)
if let index = accounts.firstIndex(of: account) { if let index = accounts.firstIndex(of: account) {
accounts.remove(at: index) accounts.remove(at: index)
@ -17,10 +16,10 @@ extension FileManager {
storeJSON(accounts, userID: userID) storeJSON(accounts, userID: userID)
} }
public func accounts(forUserID userID: String) -> [Mastodon.Entity.Account] { func accounts(for userId: UserIdentifier) -> [Mastodon.Entity.Account] {
guard let documentsDirectory else { return [] } guard let documentsDirectory else { return [] }
let accountPath = Persistence.accounts(userID: userID).filepath(baseURL: documentsDirectory) let accountPath = Persistence.accounts(userId).filepath(baseURL: documentsDirectory)
guard let data = try? Data(contentsOf: accountPath) else { return [] } guard let data = try? Data(contentsOf: accountPath) else { return [] }
@ -35,8 +34,10 @@ extension FileManager {
} }
} }
}
private func storeJSON(_ encodable: Encodable, userID: String) { private extension FileManager {
private func storeJSON(_ encodable: Encodable, userID: UserIdentifier) {
guard let documentsDirectory else { return } guard let documentsDirectory else { return }
let jsonEncoder = JSONEncoder() let jsonEncoder = JSONEncoder()
@ -44,7 +45,7 @@ extension FileManager {
do { do {
let data = try jsonEncoder.encode(encodable) let data = try jsonEncoder.encode(encodable)
let accountsPath = Persistence.accounts(userID: userID).filepath(baseURL: documentsDirectory) let accountsPath = Persistence.accounts( userID).filepath(baseURL: documentsDirectory)
try data.write(to: accountsPath) try data.write(to: accountsPath)
} catch { } catch {
debugPrint(error.localizedDescription) debugPrint(error.localizedDescription)

View File

@ -1,17 +1,12 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved. // Copyright © 2023 Mastodon gGmbH. All rights reserved.
import Foundation import Foundation
import MastodonCore
extension FileManager { public extension FileManager {
public func searchItems(forUser userID: String) throws -> [Persistence.SearchHistory.Item] { func searchItems(for userId: UserIdentifier) throws -> [Persistence.SearchHistory.Item] {
return try searchItems().filter { $0.userID == userID }
}
public func searchItems() throws -> [Persistence.SearchHistory.Item] {
guard let documentsDirectory else { return [] } guard let documentsDirectory else { return [] }
let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) let searchHistoryPath = Persistence.searchHistory(userId).filepath(baseURL: documentsDirectory)
guard let data = try? Data(contentsOf: searchHistoryPath) else { return [] } guard let data = try? Data(contentsOf: searchHistoryPath) else { return [] }
@ -28,16 +23,23 @@ extension FileManager {
} }
} }
public func addSearchItem(_ newSearchItem: Persistence.SearchHistory.Item) throws { func addSearchItem(_ newSearchItem: Persistence.SearchHistory.Item, for userId: UserIdentifier) throws {
var searchItems = (try? searchItems()) ?? [] var searchItems = (try? searchItems(for: userId)) ?? []
if let index = searchItems.firstIndex(of: newSearchItem) { if let index = searchItems.firstIndex(of: newSearchItem) {
searchItems.remove(at: index) searchItems.remove(at: index)
} }
searchItems.append(newSearchItem) searchItems.append(newSearchItem)
storeJSON(searchItems, .searchHistory) storeJSON(searchItems, .searchHistory(userId))
}
func removeSearchHistory(for userId: UserIdentifier) {
let searchItems = (try? searchItems(for: userId)) ?? []
let newSearchItems = searchItems.filter { $0.userID != userId.userID }
storeJSON(newSearchItems, .searchHistory(userId))
} }
private func storeJSON(_ encodable: Encodable, _ persistence: Persistence) { private func storeJSON(_ encodable: Encodable, _ persistence: Persistence) {
@ -53,13 +55,5 @@ extension FileManager {
} catch { } catch {
debugPrint(error.localizedDescription) debugPrint(error.localizedDescription)
} }
}
public func removeSearchHistory(forUser userID: String) {
let searchItems = (try? searchItems()) ?? []
let newSearchItems = searchItems.filter { $0.userID != userID }
storeJSON(newSearchItems, .searchHistory)
} }
} }

View File

@ -9,11 +9,11 @@
import Foundation import Foundation
public enum Persistence { public enum Persistence {
case searchHistory case searchHistory(UserIdentifier)
case homeTimeline(UserIdentifier) case homeTimeline(UserIdentifier)
case notificationsMentions(UserIdentifier) case notificationsMentions(UserIdentifier)
case notificationsAll(UserIdentifier) case notificationsAll(UserIdentifier)
case accounts(userID: String) case accounts(UserIdentifier)
private func uniqueUserDomainIdentifier(for userIdentifier: UserIdentifier) -> String { private func uniqueUserDomainIdentifier(for userIdentifier: UserIdentifier) -> String {
"\(userIdentifier.userID)@\(userIdentifier.domain)" "\(userIdentifier.userID)@\(userIdentifier.domain)"
@ -21,16 +21,16 @@ public enum Persistence {
private var filename: String { private var filename: String {
switch self { switch self {
case .searchHistory: case .searchHistory(let userIdentifier):
return "search_history" // todo: @zeitschlag should this be user-scoped as well? return "search_history_\(uniqueUserDomainIdentifier(for: userIdentifier))" // todo: @zeitschlag should this be user-scoped as well?
case let .homeTimeline(userIdentifier): case let .homeTimeline(userIdentifier):
return "home_timeline_\(userIdentifier.uniqueUserDomainIdentifier)" return "home_timeline_\(uniqueUserDomainIdentifier(for: userIdentifier))"
case let .notificationsMentions(userIdentifier): case let .notificationsMentions(userIdentifier):
return "notifications_mentions_\(userIdentifier.uniqueUserDomainIdentifier)" return "notifications_mentions_\(userIdentifier.uniqueUserDomainIdentifier)"
case let .notificationsAll(userIdentifier): case let .notificationsAll(userIdentifier):
return "notifications_all_\(uniqueUserDomainIdentifier(for: userIdentifier))" return "notifications_all_\(uniqueUserDomainIdentifier(for: userIdentifier))"
case .accounts(let userID): case .accounts(let userIdentifier):
return "account_\(userID)" return "account_\(uniqueUserDomainIdentifier(for: userIdentifier))"
} }
} }