Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make "Suggestions" use Entities (IOS-190) #1155

Merged
merged 6 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions Mastodon/Coordinator/SceneCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -562,18 +562,29 @@ private extension SceneCoordinator {
//MARK: - Loading

public extension SceneCoordinator {

@MainActor
func showLoading() {
guard let rootViewController else { return }
showLoading(on: rootViewController)
}

@MainActor
func showLoading(on viewController: UIViewController?) {
guard let viewController else { return }

MBProgressHUD.showAdded(to: rootViewController.view, animated: true)
MBProgressHUD.showAdded(to: viewController.view, animated: true)
}

@MainActor
func hideLoading() {
guard let rootViewController else { return }
hideLoading(on: rootViewController)
}

@MainActor
func hideLoading(on viewController: UIViewController?) {
guard let viewController else { return }

MBProgressHUD.hide(for: rootViewController.view, animated: true)
MBProgressHUD.hide(for: viewController.view, animated: true)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
4 changes: 2 additions & 2 deletions Mastodon/Scene/SuggestionAccount/RecommendAccountItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
//

import Foundation
import CoreDataStack
import MastodonSDK

enum RecommendAccountItem: Hashable {
case account(ManagedObjectRecord<MastodonUser>)
case account(Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)
}
16 changes: 3 additions & 13 deletions Mastodon/Scene/SuggestionAccount/RecommendAccountSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency {
setupNavigationBarAppearance()
defer { setupNavigationBarBackgroundView() }


title = L10n.Scene.SuggestionAccount.title
navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.done,
Expand Down Expand Up @@ -72,6 +71,8 @@ class SuggestionAccountViewController: UIViewController, NeedsDependency {
navigationItem.largeTitleDisplayMode = .automatic

tableView.deselectRow(with: transitionCoordinator, animated: animated)

viewModel.updateSuggestions()
}

//MARK: - Actions
Expand All @@ -85,26 +86,23 @@ 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, _):
Task { await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) }
}

tableView.deselectRow(at: indexPath, animated: true)
}

func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
guard let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SuggestionAccountTableViewFooter.reuseIdentifier) as? SuggestionAccountTableViewFooter else {
return nil
}

footerView.followAllButton.isEnabled = viewModel.userFetchedResultsController.records.isNotEmpty
footerView.followAllButton.isEnabled = viewModel.accounts.isNotEmpty

footerView.delegate = self
return footerView
Expand All @@ -125,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)
}
}
Expand Down
91 changes: 43 additions & 48 deletions Mastodon/Scene/SuggestionAccount/SuggestionAccountViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
//

import Combine
import CoreData
import CoreDataStack
import GameplayKit
import MastodonSDK
import MastodonCore
import UIKit
Expand All @@ -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<Void, Never>()

Expand All @@ -38,51 +36,42 @@ 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
updateSuggestions()
}


func updateSuggestions() {
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(
Expand All @@ -98,35 +87,41 @@ 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<RecommendAccountSection, RecommendAccountItem>()
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)
}
.store(in: &disposeBag)
}

func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, completion: (() -> Void)? = nil) {
func followAllSuggestedAccounts(_ dependency: NeedsDependency & AuthContextProvider, presentedOn: UIViewController?, completion: (() -> Void)? = nil) {

let userRecords = userFetchedResultsController.records.compactMap {
$0.object(in: dependency.context.managedObjectContext)?.asRecord
}
let tmpAccounts = accounts.compactMap { $0.account }

Task {
await dependency.coordinator.showLoading(on: presentedOn)
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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}()

Expand Down
4 changes: 2 additions & 2 deletions Mastodon/Scene/Thread/ThreadViewModel+LoadThreadState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -88,7 +88,7 @@ extension ThreadViewModel.LoadThreadState {
}
)
} catch {
await enter(state: Fail.self)
enter(state: Fail.self)
}
} // end Task
}
Expand Down
1 change: 1 addition & 0 deletions MastodonSDK/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ let package = Package(
name: "MastodonSDK",
dependencies: [
.product(name: "NIOHTTP1", package: "swift-nio"),
"MastodonCommon"
]
),
.target(
Expand Down
12 changes: 0 additions & 12 deletions MastodonSDK/Sources/CoreDataStack/CoreDataStack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}

Expand Down
Loading
Loading