Merge pull request #1149 from mastodon/remove_coredata/ios-186-followed-tags-screen

Make "Followed Hashtags"-screen work with entities (IOS-186)
This commit is contained in:
Nathan Mattes 2023-11-13 14:50:40 +01:00 committed by GitHub
commit d8a795ab26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 129 deletions

View File

@ -448,9 +448,9 @@ private extension SceneCoordinator {
_viewController.viewModel = viewModel
viewController = _viewController
case .followedTags(let viewModel):
let _viewController = FollowedTagsViewController()
_viewController.viewModel = viewModel
viewController = _viewController
guard let authContext else { return nil }
viewController = FollowedTagsViewController(appContext: appContext, sceneCoordinator: self, authContext: authContext, viewModel: viewModel)
case .favorite(let viewModel):
let _viewController = FavoriteViewController()
_viewController.viewModel = viewModel

View File

@ -6,23 +6,24 @@
//
import UIKit
import CoreDataStack
import MastodonSDK
final class FollowedTagsTableViewCell: UITableViewCell {
static let reuseIdentifier = "FollowedTagsTableViewCell"
private var hashtagView: HashtagTimelineHeaderView!
private let separatorLine = UIView.separatorLine
private weak var viewModel: FollowedTagsViewModel?
private weak var hashtag: Tag?
private var viewModel: FollowedTagsViewModel?
private var hashtag: Mastodon.Entity.Tag?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("Not implemented")
}
required init?(coder: NSCoder) { fatalError("Not implemented") }
override func prepareForReuse() {
hashtagView.removeFromSuperview()
viewModel = nil
@ -67,7 +68,7 @@ private extension FollowedTagsTableViewCell {
}
extension FollowedTagsTableViewCell {
func populate(with tag: Tag) {
func populate(with tag: Mastodon.Entity.Tag) {
self.hashtag = tag
hashtagView.update(HashtagTimelineHeaderView.Data.from(tag))
}

View File

@ -5,61 +5,87 @@
// Created by Marcus Kida on 22.11.22.
//
import os
import UIKit
import Combine
import MastodonAsset
import MastodonCore
import MastodonUI
import MastodonLocalization
final class FollowedTagsViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
var viewModel: FollowedTagsViewModel!
var context: AppContext!
var coordinator: SceneCoordinator!
let authContext: AuthContext
var viewModel: FollowedTagsViewModel
let titleView = DoubleTitleLabelNavigationBarTitleView()
let tableView: UITableView
let refreshControl: UIRefreshControl
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.register(FollowedTagsTableViewCell.self, forCellReuseIdentifier: String(describing: FollowedTagsTableViewCell.self))
init(appContext: AppContext, sceneCoordinator: SceneCoordinator, authContext: AuthContext, viewModel: FollowedTagsViewModel) {
self.context = appContext
self.coordinator = sceneCoordinator
self.authContext = authContext
self.viewModel = viewModel
refreshControl = UIRefreshControl()
tableView = UITableView()
tableView.register(FollowedTagsTableViewCell.self, forCellReuseIdentifier: FollowedTagsTableViewCell.reuseIdentifier)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.rowHeight = UITableView.automaticDimension
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
return tableView
}()
}
tableView.refreshControl = refreshControl
extension FollowedTagsViewController {
override func viewDidLoad() {
super.viewDidLoad()
let _title = L10n.Scene.FollowedTags.title
title = _title
titleView.update(title: _title, subtitle: nil)
super.init(nibName: nil, bundle: nil)
title = L10n.Scene.FollowedTags.title
navigationItem.titleView = titleView
view.backgroundColor = .secondarySystemBackground
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.pinToParent()
tableView.delegate = self
refreshControl.addTarget(self, action: #selector(FollowedTagsViewController.refresh(_:)), for: .valueChanged)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
viewModel.setupTableView(tableView)
viewModel.presentHashtagTimeline
.receive(on: DispatchQueue.main)
.sink { [weak self] hashtagTimelineViewModel in
guard let self = self else { return }
_ = self.coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show
)
}
//MARK: - Actions
@objc
func refresh(_ sender: UIRefreshControl) {
viewModel.fetchFollowedTags(completion: {
DispatchQueue.main.async {
self.refreshControl.endRefreshing()
}
.store(in: &disposeBag)
})
}
}
extension FollowedTagsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let object = viewModel.followedTags[indexPath.row]
let hashtagTimelineViewModel = HashtagTimelineViewModel(
context: self.context,
authContext: self.authContext,
hashtag: object.name
)
_ = self.coordinator.present(
scene: .hashtagTimeline(viewModel: hashtagTimelineViewModel),
from: self,
transition: .show
)
}
}

View File

@ -6,9 +6,6 @@
//
import UIKit
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import MastodonCore
@ -18,7 +15,7 @@ extension FollowedTagsViewModel {
}
enum Item: Hashable {
case hashtag(Tag)
case hashtag(Mastodon.Entity.Tag)
}
func tableViewDiffableDataSource(
@ -27,7 +24,7 @@ extension FollowedTagsViewModel {
UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
switch item {
case let .hashtag(tag):
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: FollowedTagsTableViewCell.self), for: indexPath) as? FollowedTagsTableViewCell else {
guard let cell = tableView.dequeueReusableCell(withIdentifier: FollowedTagsTableViewCell.reuseIdentifier, for: indexPath) as? FollowedTagsTableViewCell else {
assertionFailure()
return UITableViewCell()
}
@ -39,9 +36,7 @@ extension FollowedTagsViewModel {
}
}
func setupDiffableDataSource(
tableView: UITableView
) {
func setupDiffableDataSource(tableView: UITableView) {
diffableDataSource = tableViewDiffableDataSource(for: tableView)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()

View File

@ -5,17 +5,12 @@
// Created by Marcus Kida on 23.11.22.
//
import os
import UIKit
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import MastodonCore
final class FollowedTagsViewModel: NSObject {
var disposeBag = Set<AnyCancellable>()
let fetchedResultsController: FollowedTagsFetchedResultController
private(set) var followedTags: [Mastodon.Entity.Tag]
private weak var tableView: UITableView?
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>?
@ -23,85 +18,60 @@ final class FollowedTagsViewModel: NSObject {
// input
let context: AppContext
let authContext: AuthContext
// output
let presentHashtagTimeline = PassthroughSubject<HashtagTimelineViewModel, Never>()
init(context: AppContext, authContext: AuthContext) {
self.context = context
self.authContext = authContext
self.fetchedResultsController = FollowedTagsFetchedResultController(
managedObjectContext: context.managedObjectContext,
domain: authContext.mastodonAuthenticationBox.domain,
user: authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext)! // fixme:
)
self.followedTags = []
super.init()
self.fetchedResultsController
.$records
.receive(on: DispatchQueue.main)
.sink { [weak self] records in
guard let self = self else { return }
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(records.map {.hashtag($0) })
self.diffableDataSource?.apply(snapshot, animatingDifferences: true)
}
.store(in: &disposeBag)
}
}
extension FollowedTagsViewModel {
func setupTableView(_ tableView: UITableView) {
self.tableView = tableView
setupDiffableDataSource(tableView: tableView)
tableView.delegate = self
fetchFollowedTags()
}
func fetchFollowedTags() {
func fetchFollowedTags(completion: (() -> Void)? = nil ) {
Task { @MainActor in
try await context.apiService.getFollowedTags(
domain: authContext.mastodonAuthenticationBox.domain,
query: Mastodon.API.Account.FollowedTagsQuery(limit: nil),
authenticationBox: authContext.mastodonAuthenticationBox
)
do {
followedTags = try await context.apiService.getFollowedTags(
domain: authContext.mastodonAuthenticationBox.domain,
query: Mastodon.API.Account.FollowedTagsQuery(limit: nil),
authenticationBox: authContext.mastodonAuthenticationBox
).value
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
let items = followedTags.compactMap { Item.hashtag($0) }
snapshot.appendItems(items, toSection: .main)
await diffableDataSource?.apply(snapshot)
} catch {}
completion?()
}
}
func followOrUnfollow(_ tag: Tag) {
func followOrUnfollow(_ tag: Mastodon.Entity.Tag) {
Task { @MainActor in
switch tag.following {
case true:
if tag.following ?? false {
_ = try? await context.apiService.unfollowTag(
for: tag.name,
authenticationBox: authContext.mastodonAuthenticationBox
)
case false:
} else {
_ = try? await context.apiService.followTag(
for: tag.name,
authenticationBox: authContext.mastodonAuthenticationBox
)
}
fetchFollowedTags()
}
}
}
extension FollowedTagsViewModel: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let object = fetchedResultsController.records[indexPath.row]
let hashtagTimelineViewModel = HashtagTimelineViewModel(
context: self.context,
authContext: self.authContext,
hashtag: object.name
)
presentHashtagTimeline.send(hashtagTimelineViewModel)
}
}

View File

@ -158,32 +158,15 @@ extension APIService {
let domain = authenticationBox.domain
let authorization = authenticationBox.userAuthorization
let response = try await Mastodon.API.Account.followedTags(
let followedTags = try await Mastodon.API.Account.followedTags(
session: session,
domain: domain,
query: query,
authorization: authorization
).singleOutput()
let managedObjectContext = self.backgroundManagedObjectContext
try await managedObjectContext.performChanges {
let me = authenticationBox.authentication.user(in: managedObjectContext)
for entity in response.value {
_ = Persistence.Tag.createOrMerge(
in: managedObjectContext,
context: Persistence.Tag.PersistContext(
domain: domain,
entity: entity,
me: me,
networkDate: response.networkDate
)
)
}
}
return response
} // end func
return followedTags
}
}
extension APIService {

View File

@ -66,6 +66,7 @@ extension APIService {
}
fileprivate extension APIService {
@available(*, deprecated, message: "We don't persist tags anymore")
func persistTag(
from response: Mastodon.Response.Content<Mastodon.Entity.Tag>,
domain: String,