From 92fcd2e665b66e96a3265a9f29213d214e330117 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 13 Nov 2023 14:43:14 +0100 Subject: [PATCH 1/6] Make account conform to hashable (IOS-190) --- .../Entity/Mastodon+Entity+Account.swift | 60 ++++++++++++++++++- .../Entity/Mastodon+Entity+Emoji.swift | 2 +- .../Entity/Mastodon+Entity+Field.swift | 2 +- .../Entity/Mastodon+Entity+Source.swift | 4 +- .../Entity/Mastodon+Entity+Suggestion.swift | 2 +- 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift index eb2910bb9..0838ab793 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift @@ -18,7 +18,7 @@ extension Mastodon.Entity { /// # Reference /// [Document](https://docs.joinmastodon.org/entities/account/) public final class Account: Codable, Sendable { - + public typealias ID = String // Base @@ -84,6 +84,64 @@ extension Mastodon.Entity { } } +extension Mastodon.Entity.Account: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(username) + hasher.combine(acct) + hasher.combine(url) + hasher.combine(displayName) + hasher.combine(note) + hasher.combine(avatar) + hasher.combine(avatarStatic) + hasher.combine(header) + hasher.combine(headerStatic) + hasher.combine(locked) + hasher.combine(emojis) + hasher.combine(discoverable) + hasher.combine(createdAt) + hasher.combine(lastStatusAt) + hasher.combine(statusesCount) + hasher.combine(followersCount) + hasher.combine(followingCount) + hasher.combine(moved) + hasher.combine(fields) + hasher.combine(bot) + hasher.combine(source) + hasher.combine(suspended) + hasher.combine(muteExpiresAt) + } +} + +extension Mastodon.Entity.Account: Equatable { + public static func == (lhs: Mastodon.Entity.Account, rhs: Mastodon.Entity.Account) -> Bool { + return lhs.id == rhs.id && + lhs.username == rhs.username && + lhs.acct == rhs.acct && + lhs.url == rhs.url && + lhs.displayName == rhs.displayName && + lhs.note == rhs.note && + lhs.avatar == rhs.avatar && + lhs.avatarStatic == rhs.avatarStatic && + lhs.header == rhs.header && + lhs.headerStatic == rhs.headerStatic && + lhs.locked == rhs.locked && + lhs.emojis == rhs.emojis && + lhs.discoverable == rhs.discoverable && + lhs.createdAt == rhs.createdAt && + lhs.lastStatusAt == rhs.lastStatusAt && + lhs.statusesCount == rhs.statusesCount && + lhs.followersCount == rhs.followersCount && + lhs.followingCount == rhs.followingCount && + lhs.moved == rhs.moved && + lhs.fields == rhs.fields && + lhs.bot == rhs.bot && + lhs.source == rhs.source && + lhs.suspended == rhs.suspended && + lhs.muteExpiresAt == rhs.muteExpiresAt + } +} + extension Mastodon.Entity.Account { public func acctWithDomainIfMissing(_ localDomain: String) -> String { guard acct.contains("@") else { diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Emoji.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Emoji.swift index 284e505da..122938024 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Emoji.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Emoji.swift @@ -16,7 +16,7 @@ extension Mastodon.Entity { /// 2021/1/28 /// # Reference /// [Document](https://docs.joinmastodon.org/entities/emoji/) - public struct Emoji: Codable, Sendable { + public struct Emoji: Codable, Sendable, Hashable { public let shortcode: String public let url: String public let staticURL: String diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Field.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Field.swift index 9f9cf3e74..5886fb86b 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Field.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Field.swift @@ -16,7 +16,7 @@ extension Mastodon.Entity { /// 2021/1/28 /// # Reference /// [Document](https://docs.joinmastodon.org/entities/field/) - public struct Field: Codable, Sendable { + public struct Field: Codable, Sendable, Hashable { public let name: String public let value: String diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Source.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Source.swift index 8e8f94452..bc9c60f7a 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Source.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Source.swift @@ -16,7 +16,7 @@ extension Mastodon.Entity { /// 2021/2/3 /// # Reference /// [Document](https://docs.joinmastodon.org/entities/source/) - public struct Source: Codable, Sendable { + public struct Source: Codable, Sendable, Hashable { // Base public let note: String @@ -40,7 +40,7 @@ extension Mastodon.Entity { } extension Mastodon.Entity.Source { - public enum Privacy: RawRepresentable, Codable, Sendable { + public enum Privacy: RawRepresentable, Codable, Sendable, Hashable { case `public` case unlisted case `private` diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Suggestion.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Suggestion.swift index 7bec16990..b46cbaf15 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Suggestion.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Suggestion.swift @@ -9,7 +9,7 @@ import Foundation extension Mastodon.Entity.V2 { - public struct SuggestionAccount: Codable, Sendable { + public struct SuggestionAccount: Codable, Sendable, Hashable { public let source: String public let account: Mastodon.Entity.Account From 49f6cd6d298560ef6a8397d8e3fb41d2d6317a8b Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 13 Nov 2023 14:44:26 +0100 Subject: [PATCH 2/6] Use entities on suggestion-screen (IOS-190) --- .../RecommendAccountItem.swift | 4 +- .../RecommendAccountSection.swift | 16 +--- .../SuggestionAccountViewController.swift | 21 +++-- .../SuggestionAccountViewModel.swift | 82 ++++++++----------- .../SuggestionAccountTableViewCell.swift | 21 ++--- 5 files changed, 58 insertions(+), 86 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift index 998f2f3e9..76ce3f434 100644 --- a/Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift +++ b/Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift @@ -6,8 +6,8 @@ // import Foundation -import CoreDataStack +import MastodonSDK enum RecommendAccountItem: Hashable { - case account(ManagedObjectRecord) + case account(Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) } diff --git a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift index c3f477a96..7b69dd200 100644 --- a/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift +++ b/Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift @@ -34,21 +34,11 @@ extension RecommendAccountSection { UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SuggestionAccountTableViewCell.self)) as! SuggestionAccountTableViewCell switch item { - case .account(let record): - cell.delegate = configuration.suggestionAccountTableViewCellDelegate - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - cell.configure(viewModel: - SuggestionAccountTableViewCell.ViewModel( - user: user, - followedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds, - blockedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds, - followRequestedUsers: configuration.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs) - ) - } + case .account(let account, let relationship): + cell.delegate = configuration.suggestionAccountTableViewCellDelegate + cell.configure(account: account, relationship: relationship) } return cell } } - } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 35e1f929d..45d925170 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -85,18 +85,21 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { // MARK: - UITableViewDelegate extension SuggestionAccountViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let tableViewDiffableDataSource = viewModel.tableViewDiffableDataSource else { return } guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return } switch item { - case .account(let record): - guard let account = record.object(in: context.managedObjectContext) else { return } - let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account) - _ = coordinator.present( - scene: .profile(viewModel: cachedProfileViewModel), - from: self, - transition: .show - ) + case .account(let account, _): + print("Show \(account.acct)") +// let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account) +// _ = coordinator.present( +// scene: .profile(viewModel: cachedProfileViewModel), +// from: self, +// transition: .show +// ) } + + tableView.deselectRow(at: indexPath, animated: true) } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { @@ -104,7 +107,7 @@ extension SuggestionAccountViewController: UITableViewDelegate { return nil } - footerView.followAllButton.isEnabled = viewModel.userFetchedResultsController.records.isNotEmpty + footerView.followAllButton.isEnabled = viewModel.accounts.isNotEmpty footerView.delegate = self return footerView diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index 81238105b..cb83ae511 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -6,9 +6,6 @@ // import Combine -import CoreData -import CoreDataStack -import GameplayKit import MastodonSDK import MastodonCore import UIKit @@ -25,7 +22,8 @@ final class SuggestionAccountViewModel: NSObject { // input let context: AppContext let authContext: AuthContext - let userFetchedResultsController: UserFetchedResultsController + @Published var accounts: [Mastodon.Entity.V2.SuggestionAccount] + var relationships: [Mastodon.Entity.Relationship] var viewWillAppear = PassthroughSubject() @@ -38,51 +36,38 @@ final class SuggestionAccountViewModel: NSObject { ) { self.context = context self.authContext = authContext - self.userFetchedResultsController = UserFetchedResultsController( - managedObjectContext: context.managedObjectContext, - domain: nil, - additionalPredicate: nil - ) + + accounts = [] + relationships = [] + super.init() - - userFetchedResultsController.domain = authContext.mastodonAuthenticationBox.domain // fetch recommended users Task { - var userIDs: [MastodonUser.ID] = [] + var suggestedAccounts: [Mastodon.Entity.V2.SuggestionAccount] = [] do { let response = try await context.apiService.suggestionAccountV2( query: .init(limit: 5), authenticationBox: authContext.mastodonAuthenticationBox ) - userIDs = response.value.map { $0.account.id } - } catch let error as Mastodon.API.Error where error.httpResponseStatus == .notFound { - let response = try await context.apiService.suggestionAccount( - query: nil, + suggestedAccounts = response.value + + guard suggestedAccounts.isNotEmpty else { return } + + let accounts = suggestedAccounts.compactMap { $0.account } + + let relationships = try await context.apiService.relationship( + forAccounts: accounts, authenticationBox: authContext.mastodonAuthenticationBox - ) - userIDs = response.value.map { $0.id } + ).value + + self.relationships = relationships + self.accounts = suggestedAccounts } catch { - + self.relationships = [] + self.accounts = [] } - - guard userIDs.isNotEmpty else { return } - userFetchedResultsController.userIDs = userIDs } - - // fetch relationship - userFetchedResultsController.$records - .removeDuplicates() - .sink { [weak self] records in - guard let _ = self else { return } - Task { - _ = try await context.apiService.relationship( - records: records, - authenticationBox: authContext.mastodonAuthenticationBox - ) - } - } - .store(in: &disposeBag) } func setupDiffableDataSource( @@ -98,15 +83,22 @@ final class SuggestionAccountViewModel: NSObject { ) ) - userFetchedResultsController.$records + $accounts .receive(on: DispatchQueue.main) - .sink { [weak self] records in - guard let self = self else { return } - guard let tableViewDiffableDataSource = self.tableViewDiffableDataSource else { return } + .sink { [weak self] suggestedAccounts in + guard let self, let tableViewDiffableDataSource = self.tableViewDiffableDataSource else { return } + + let accounts = suggestedAccounts.compactMap { $0.account } + + let accountsWithRelationship: [(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)] = accounts.compactMap { account in + guard let relationship = self.relationships.first(where: {$0.id == account.id }) else { return (account: account, relationship: nil)} + + return (account: account, relationship: relationship) + } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) - let items: [RecommendAccountItem] = records.map { RecommendAccountItem.account($0) } + let items: [RecommendAccountItem] = accountsWithRelationship.map { RecommendAccountItem.account($0.account, relationship: $0.relationship) } snapshot.appendItems(items, toSection: .main) tableViewDiffableDataSource.applySnapshotUsingReloadData(snapshot) @@ -116,17 +108,15 @@ final class SuggestionAccountViewModel: NSObject { func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, completion: (() -> Void)? = nil) { - let userRecords = userFetchedResultsController.records.compactMap { - $0.object(in: dependency.context.managedObjectContext)?.asRecord - } + let tmpAccounts = accounts.compactMap { $0.account } Task { await withTaskGroup(of: Void.self, body: { taskGroup in - for user in userRecords { + for account in tmpAccounts { taskGroup.addTask { try? await DataSourceFacade.responseToUserViewButtonAction( dependency: dependency, - user: user, + user: account, buttonState: .follow ) } diff --git a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift index 1448be17f..17a8e5b55 100644 --- a/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift +++ b/Mastodon/Scene/SuggestionAccount/TableView-Components/SuggestionAccountTableViewCell.swift @@ -85,28 +85,17 @@ final class SuggestionAccountTableViewCell: UITableViewCell { disposeBag.removeAll() } - func configure(viewModel: SuggestionAccountTableViewCell.ViewModel) { - userView.configure(user: viewModel.user, delegate: delegate) - - if viewModel.blockedUsers.contains(viewModel.user.id) { - self.userView.setButtonState(.blocked) - } else if viewModel.followedUsers.contains(viewModel.user.id) { - self.userView.setButtonState(.unfollow) - } else if viewModel.followRequestedUsers.contains(viewModel.user.id) { - self.userView.setButtonState(.pending) - } else if viewModel.user.locked { - self.userView.setButtonState(.request) - } else { - self.userView.setButtonState(.follow) - } + func configure(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) { + userView.configure(with: account, relationship: relationship, delegate: delegate) + userView.updateButtonState(with: relationship, isMe: false) let metaContent: MetaContent = { do { - let mastodonContent = MastodonContent(content: viewModel.user.note ?? "", emojis: viewModel.user.emojis.asDictionary) + let mastodonContent = MastodonContent(content: account.note, emojis: account.emojis?.asDictionary ?? [:]) return try MastodonMetaContent.convert(document: mastodonContent) } catch { assertionFailure() - return PlaintextMetaContent(string: viewModel.user.note ?? "") + return PlaintextMetaContent(string: account.note) } }() From 6eadd41c43b3c64e7c2cad99024102f9a152924f Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 13 Nov 2023 14:55:42 +0100 Subject: [PATCH 3/6] Show account-details (IOS-190) --- .../SuggestionAccountViewController.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 45d925170..0fddc36d8 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -90,13 +90,7 @@ extension SuggestionAccountViewController: UITableViewDelegate { guard let item = tableViewDiffableDataSource.itemIdentifier(for: indexPath) else { return } switch item { case .account(let account, _): - print("Show \(account.acct)") -// let cachedProfileViewModel = CachedProfileViewModel(context: context, authContext: viewModel.authContext, mastodonUser: account) -// _ = coordinator.present( -// scene: .profile(viewModel: cachedProfileViewModel), -// from: self, -// transition: .show -// ) + Task { await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) } } tableView.deselectRow(at: indexPath, animated: true) From b6f3aa52de62c609e1b99a319e86a907955c1222 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 13 Nov 2023 15:48:45 +0100 Subject: [PATCH 4/6] Update suggestions (IOS-190) --- Mastodon/Coordinator/SceneCoordinator.swift | 19 +++++++++++++++---- .../SuggestionAccountViewController.swift | 6 ++++-- .../SuggestionAccountViewModel.swift | 9 +++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index d74a4ccd8..ef15ce6e4 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -562,18 +562,29 @@ private extension SceneCoordinator { //MARK: - Loading public extension SceneCoordinator { + @MainActor func showLoading() { - guard let rootViewController else { return } + showLoading(on: rootViewController) + } - MBProgressHUD.showAdded(to: rootViewController.view, animated: true) + @MainActor + func showLoading(on viewController: UIViewController?) { + guard let viewController else { return } + + MBProgressHUD.showAdded(to: viewController.view, animated: true) } @MainActor func hideLoading() { - guard let rootViewController else { return } + hideLoading(on: rootViewController) + } - MBProgressHUD.hide(for: rootViewController.view, animated: true) + @MainActor + func hideLoading(on viewController: UIViewController?) { + guard let viewController else { return } + + MBProgressHUD.hide(for: viewController.view, animated: true) } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift index 0fddc36d8..84d4f9e23 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewController.swift @@ -38,7 +38,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { setupNavigationBarAppearance() defer { setupNavigationBarBackgroundView() } - title = L10n.Scene.SuggestionAccount.title navigationItem.rightBarButtonItem = UIBarButtonItem( barButtonSystemItem: UIBarButtonItem.SystemItem.done, @@ -72,6 +71,8 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency { navigationItem.largeTitleDisplayMode = .automatic tableView.deselectRow(with: transitionCoordinator, animated: animated) + + viewModel.updateSuggestions() } //MARK: - Actions @@ -122,8 +123,9 @@ extension SuggestionAccountViewController: SuggestionAccountTableViewCellDelegat extension SuggestionAccountViewController: SuggestionAccountTableViewFooterDelegate { func followAll(_ footerView: SuggestionAccountTableViewFooter) { - viewModel.followAllSuggestedAccounts(self) { + viewModel.followAllSuggestedAccounts(self, presentedOn: self.navigationController) { DispatchQueue.main.async { + self.coordinator.hideLoading(on: self.navigationController) self.dismiss(animated: true) } } diff --git a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift index cb83ae511..84cc4c46d 100644 --- a/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift +++ b/Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift @@ -42,7 +42,11 @@ final class SuggestionAccountViewModel: NSObject { super.init() - // fetch recommended users + updateSuggestions() + } + + + func updateSuggestions() { Task { var suggestedAccounts: [Mastodon.Entity.V2.SuggestionAccount] = [] do { @@ -106,11 +110,12 @@ final class SuggestionAccountViewModel: NSObject { .store(in: &disposeBag) } - func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, completion: (() -> Void)? = nil) { + func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, presentedOn: UIViewController?, completion: (() -> Void)? = nil) { let tmpAccounts = accounts.compactMap { $0.account } Task { + await dependency.coordinator.showLoading(on: presentedOn) await withTaskGroup(of: Void.self, body: { taskGroup in for account in tmpAccounts { taskGroup.addTask { From 9947335b20a3c00fb689d573e4511b09046d2914 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Nov 2023 10:03:33 +0100 Subject: [PATCH 5/6] Use url for hashable/equatable (IOS-190) and do some refactoring --- MastodonSDK/Package.swift | 1 + .../MastodonSDK/Mastodon+Entity+Account.swift | 27 -------- .../Entity/Mastodon+Entity+Account.swift | 69 ++++++------------- 3 files changed, 23 insertions(+), 74 deletions(-) diff --git a/MastodonSDK/Package.swift b/MastodonSDK/Package.swift index a6ac785e7..00773dfe9 100644 --- a/MastodonSDK/Package.swift +++ b/MastodonSDK/Package.swift @@ -107,6 +107,7 @@ let package = Package( name: "MastodonSDK", dependencies: [ .product(name: "NIOHTTP1", package: "swift-nio"), + "MastodonCommon" ] ), .target( diff --git a/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Account.swift b/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Account.swift index 059f09420..56c00e31d 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Account.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Account.swift @@ -9,33 +9,6 @@ import Foundation import MastodonSDK import MastodonMeta -extension Mastodon.Entity.Account: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(id) - } - - public static func == (lhs: Mastodon.Entity.Account, rhs: Mastodon.Entity.Account) -> Bool { - return lhs.id == rhs.id - } -} - -extension Mastodon.Entity.Account { - public func avatarImageURL() -> URL? { - let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar - return URL(string: string) - } - - public func avatarImageURLWithFallback(domain: String) -> URL { - return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! - } -} - -extension Mastodon.Entity.Account { - public var displayNameWithFallback: String { - return !displayName.isEmpty ? displayName : username - } -} - extension Mastodon.Entity.Account { public var emojiMeta: MastodonContent.Emojis { let isAnimated = !UserDefaults.shared.preferredStaticEmoji diff --git a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift index 0838ab793..9e37770f9 100644 --- a/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift +++ b/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon+Entity+Account.swift @@ -6,6 +6,7 @@ // import Foundation +import MastodonCommon extension Mastodon.Entity { @@ -84,64 +85,24 @@ extension Mastodon.Entity { } } +//MARK: - Hashable extension Mastodon.Entity.Account: Hashable { public func hash(into hasher: inout Hasher) { - hasher.combine(id) - hasher.combine(username) - hasher.combine(acct) + // The URL seems to be the only thing that doesn't change across instances. hasher.combine(url) - hasher.combine(displayName) - hasher.combine(note) - hasher.combine(avatar) - hasher.combine(avatarStatic) - hasher.combine(header) - hasher.combine(headerStatic) - hasher.combine(locked) - hasher.combine(emojis) - hasher.combine(discoverable) - hasher.combine(createdAt) - hasher.combine(lastStatusAt) - hasher.combine(statusesCount) - hasher.combine(followersCount) - hasher.combine(followingCount) - hasher.combine(moved) - hasher.combine(fields) - hasher.combine(bot) - hasher.combine(source) - hasher.combine(suspended) - hasher.combine(muteExpiresAt) } + } +//MARK: - Equatable extension Mastodon.Entity.Account: Equatable { public static func == (lhs: Mastodon.Entity.Account, rhs: Mastodon.Entity.Account) -> Bool { - return lhs.id == rhs.id && - lhs.username == rhs.username && - lhs.acct == rhs.acct && - lhs.url == rhs.url && - lhs.displayName == rhs.displayName && - lhs.note == rhs.note && - lhs.avatar == rhs.avatar && - lhs.avatarStatic == rhs.avatarStatic && - lhs.header == rhs.header && - lhs.headerStatic == rhs.headerStatic && - lhs.locked == rhs.locked && - lhs.emojis == rhs.emojis && - lhs.discoverable == rhs.discoverable && - lhs.createdAt == rhs.createdAt && - lhs.lastStatusAt == rhs.lastStatusAt && - lhs.statusesCount == rhs.statusesCount && - lhs.followersCount == rhs.followersCount && - lhs.followingCount == rhs.followingCount && - lhs.moved == rhs.moved && - lhs.fields == rhs.fields && - lhs.bot == rhs.bot && - lhs.source == rhs.source && - lhs.suspended == rhs.suspended && - lhs.muteExpiresAt == rhs.muteExpiresAt + // The URL seems to be the only thing that doesn't change across instances. + return lhs.url == rhs.url } } +//MARK: - Convenience extension Mastodon.Entity.Account { public func acctWithDomainIfMissing(_ localDomain: String) -> String { guard acct.contains("@") else { @@ -160,4 +121,18 @@ extension Mastodon.Entity.Account { return components.host } + + public func avatarImageURL() -> URL? { + let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar + return URL(string: string) + } + + public func avatarImageURLWithFallback(domain: String) -> URL { + return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")! + } + + public var displayNameWithFallback: String { + return !displayName.isEmpty ? displayName : username + + } } From a2aa9b70d260ffc19bb89c26215ca8ff3b272908 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 16 Nov 2023 10:08:34 +0100 Subject: [PATCH 6/6] Fix some warnings --- .../MastodonPickServerViewModel.swift | 2 +- .../ThreadViewModel+LoadThreadState.swift | 4 ++-- .../Sources/CoreDataStack/CoreDataStack.swift | 12 ------------ .../MastodonSDK/Mastodon+Entity+Field.swift | 19 ------------------- .../API/APIService+Status+History.swift | 2 +- .../Notification/NotificationService.swift | 2 +- ...eContentViewModel+UITextViewDelegate.swift | 2 +- .../ComposeContentViewModel.swift | 4 ++-- .../ComposeContentToolbarView+ViewModel.swift | 2 +- .../Toolbar/LanguagePicker.swift | 2 +- 10 files changed, 10 insertions(+), 41 deletions(-) delete mode 100644 MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Field.swift diff --git a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift index 00a287b22..a9f760a52 100644 --- a/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift +++ b/Mastodon/Scene/Onboarding/PickServer/MastodonPickServerViewModel.swift @@ -203,7 +203,7 @@ extension MastodonPickServerViewModel { func chooseRandomServer() -> Mastodon.Entity.Server? { - let language = Locale.autoupdatingCurrent.languageCode?.lowercased() ?? "en" + let language = Locale.autoupdatingCurrent.language.languageCode?.identifier.lowercased() ?? "en" let servers = indexedServers.value guard servers.isNotEmpty else { return nil } diff --git a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift index 09411e88a..4679e0976 100644 --- a/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift +++ b/Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift @@ -65,7 +65,7 @@ extension ThreadViewModel.LoadThreadState { authenticationBox: viewModel.authContext.mastodonAuthenticationBox ) - await enter(state: NoMore.self) + enter(state: NoMore.self) // assert(!Thread.isMainThread) // await Task.sleep(1_000_000_000) // 1s delay to prevent UI render issue @@ -88,7 +88,7 @@ extension ThreadViewModel.LoadThreadState { } ) } catch { - await enter(state: Fail.self) + enter(state: Fail.self) } } // end Task } diff --git a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift index 3a30e9443..50b135c4f 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift +++ b/MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift @@ -94,18 +94,6 @@ public final class CoreDataStack { container.viewContext.automaticallyMergesChangesFromParent = true callback() - - #if DEBUG - do { - let storeURL = URL.storeURL(for: AppName.groupID, databaseName: "shared") - let data = try Data(contentsOf: storeURL) - let formatter = ByteCountFormatter() - formatter.allowedUnits = [.useMB] - formatter.countStyle = .file - let size = formatter.string(fromByteCount: Int64(data.count)) - } catch { - } - #endif }) } diff --git a/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Field.swift b/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Field.swift deleted file mode 100644 index c7fe63465..000000000 --- a/MastodonSDK/Sources/MastodonCore/Extension/MastodonSDK/Mastodon+Entity+Field.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Mastodon+Entity+Field.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-5-25. -// - -import Foundation -import MastodonSDK - -extension Mastodon.Entity.Field: Equatable { - public static func == (lhs: Mastodon.Entity.Field, rhs: Mastodon.Entity.Field) -> Bool { - return lhs.name == rhs.name && - lhs.value == rhs.value && - lhs.verifiedAt == rhs.verifiedAt - } - - -} diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift index 523f95617..46ec1b9d4 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+History.swift @@ -50,7 +50,7 @@ extension APIService { domain: domain, authorization: authorization).singleOutput() - let responseHistory = try await Mastodon.API.Statuses.editHistory( + _ = try await Mastodon.API.Statuses.editHistory( forStatusID: statusID, session: session, domain: domain, diff --git a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift index f7c6c08cf..0c60ad8e7 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/Notification/NotificationService.swift @@ -238,7 +238,7 @@ extension NotificationService { } private func authenticationBox(for pushNotification: MastodonPushNotification) async throws -> MastodonAuthenticationBox? { - guard let authenticationService = self.authenticationService else { return nil } + guard self.authenticationService != nil else { return nil } let results = AuthenticationServiceProvider.shared.authentications.filter { $0.userAccessToken == pushNotification.accessToken } guard let authentication = results.first else { return nil } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+UITextViewDelegate.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+UITextViewDelegate.swift index 53184cdfc..f7315700b 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+UITextViewDelegate.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel+UITextViewDelegate.swift @@ -31,7 +31,7 @@ extension ComposeContentViewModel: UITextViewDelegate { switch textView { case contentMetaText?.textView: // update model - guard let metaText = self.contentMetaText else { + guard self.contentMetaText != nil else { assertionFailure() return } diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift index 1e669d654..cd4b67780 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/ComposeContentViewModel.swift @@ -211,7 +211,7 @@ public final class ComposeContentViewModel: NSObject, ObservableObject { let recentLanguages = context.settingService.currentSetting.value?.recentLanguages ?? [] self.recentLanguages = recentLanguages - self.language = recentLanguages.first ?? Locale.current.languageCode ?? "en" + self.language = recentLanguages.first ?? Locale.current.language.languageCode?.identifier ?? "en" super.init() // end init @@ -490,7 +490,7 @@ extension ComposeContentViewModel { .flatMap { settings in if let settings { return settings.publisher(for: \.recentLanguages, options: .initial).eraseToAnyPublisher() - } else if let code = Locale.current.languageCode { + } else if let code = Locale.current.language.languageCode?.identifier { return Just([code]).eraseToAnyPublisher() } return Just([]).eraseToAnyPublisher() diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView+ViewModel.swift index 2b25755b2..e830d04d3 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/ComposeContentToolbarView+ViewModel.swift @@ -32,7 +32,7 @@ extension ComposeContentToolbarView { @Published var isAttachmentButtonEnabled = false @Published var isPollButtonEnabled = false - @Published var language = Locale.current.languageCode ?? "en" + @Published var language = Locale.current.language.languageCode?.identifier ?? "en" @Published var recentLanguages: [String] = [] @Published public var maxTextInputLimit = 500 diff --git a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/LanguagePicker.swift b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/LanguagePicker.swift index 10911c875..2f63a03d5 100644 --- a/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/LanguagePicker.swift +++ b/MastodonSDK/Sources/MastodonUI/Scene/ComposeContent/Toolbar/LanguagePicker.swift @@ -14,7 +14,7 @@ struct LanguagePicker: View { let locales = Locale.availableIdentifiers.map(Locale.init(identifier:)) var languages: [String: Language] = [:] for locale in locales { - if let code = locale.languageCode, + if let code = locale.language.languageCode?.identifier, let endonym = locale.localizedString(forLanguageCode: code), let exonym = Locale.current.localizedString(forLanguageCode: code) { // don’t overwrite the “base” language