feat: add following list
This commit is contained in:
parent
0d39d061a1
commit
8ebb2e5347
|
@ -296,6 +296,11 @@
|
||||||
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */; };
|
DB59F10425EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */; };
|
||||||
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; };
|
DB59F10E25EF724F001F1DAB /* APIService+Poll.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */; };
|
||||||
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F11725EFA35B001F1DAB /* StripProgressView.swift */; };
|
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB59F11725EFA35B001F1DAB /* StripProgressView.swift */; };
|
||||||
|
DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B7294273112B100081888 /* FollowingListViewController.swift */; };
|
||||||
|
DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B7297273112C800081888 /* FollowingListViewModel.swift */; };
|
||||||
|
DB5B729A2731137900081888 /* FollowingListViewController+Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */; };
|
||||||
|
DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */; };
|
||||||
|
DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */; };
|
||||||
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */; };
|
DB6180DD263918E30018D199 /* MediaPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */; };
|
||||||
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */; };
|
DB6180E02639194B0018D199 /* MediaPreviewPagingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */; };
|
||||||
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */; };
|
DB6180E326391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */; };
|
||||||
|
@ -1110,6 +1115,11 @@
|
||||||
DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellHeightCacheableContainer.swift; sourceTree = "<group>"; };
|
DB59F10325EF5EBC001F1DAB /* TableViewCellHeightCacheableContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCellHeightCacheableContainer.swift; sourceTree = "<group>"; };
|
||||||
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = "<group>"; };
|
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Poll.swift"; sourceTree = "<group>"; };
|
||||||
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripProgressView.swift; sourceTree = "<group>"; };
|
DB59F11725EFA35B001F1DAB /* StripProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripProgressView.swift; sourceTree = "<group>"; };
|
||||||
|
DB5B7294273112B100081888 /* FollowingListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingListViewController.swift; sourceTree = "<group>"; };
|
||||||
|
DB5B7297273112C800081888 /* FollowingListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingListViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewController+Provider.swift"; sourceTree = "<group>"; };
|
||||||
|
DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+Diffable.swift"; sourceTree = "<group>"; };
|
||||||
|
DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FollowingListViewModel+State.swift"; sourceTree = "<group>"; };
|
||||||
DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewController.swift; sourceTree = "<group>"; };
|
DB6180DC263918E30018D199 /* MediaPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewViewController.swift; sourceTree = "<group>"; };
|
||||||
DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewPagingViewController.swift; sourceTree = "<group>"; };
|
DB6180DF2639194B0018D199 /* MediaPreviewPagingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPreviewPagingViewController.swift; sourceTree = "<group>"; };
|
||||||
DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerAnimatedTransitioning.swift; sourceTree = "<group>"; };
|
DB6180E226391A4C0018D199 /* ViewControllerAnimatedTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerAnimatedTransitioning.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2453,6 +2463,18 @@
|
||||||
path = View;
|
path = View;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
DB5B7296273112B400081888 /* Following */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DB5B7294273112B100081888 /* FollowingListViewController.swift */,
|
||||||
|
DB5B72992731137900081888 /* FollowingListViewController+Provider.swift */,
|
||||||
|
DB5B7297273112C800081888 /* FollowingListViewModel.swift */,
|
||||||
|
DB5B729B273113C200081888 /* FollowingListViewModel+Diffable.swift */,
|
||||||
|
DB5B729D273113F300081888 /* FollowingListViewModel+State.swift */,
|
||||||
|
);
|
||||||
|
path = Following;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
DB6180DE263919350018D199 /* MediaPreview */ = {
|
DB6180DE263919350018D199 /* MediaPreview */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -2902,6 +2924,7 @@
|
||||||
DBB5253B2611ECF5002F1F29 /* Timeline */,
|
DBB5253B2611ECF5002F1F29 /* Timeline */,
|
||||||
DBE3CDF1261C6B3100430CC6 /* Favorite */,
|
DBE3CDF1261C6B3100430CC6 /* Favorite */,
|
||||||
DB6B74F0272FB55400C70B6E /* Follower */,
|
DB6B74F0272FB55400C70B6E /* Follower */,
|
||||||
|
DB5B7296273112B400081888 /* Following */,
|
||||||
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */,
|
DB9D6BFE25E4F5940051B173 /* ProfileViewController.swift */,
|
||||||
DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */,
|
DBAE3F812615DDA3004B8251 /* ProfileViewController+UserProvider.swift */,
|
||||||
DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */,
|
DBB5255D2611F07A002F1F29 /* ProfileViewModel.swift */,
|
||||||
|
@ -3918,6 +3941,7 @@
|
||||||
DBAE3F8E2616E0B1004B8251 /* APIService+Block.swift in Sources */,
|
DBAE3F8E2616E0B1004B8251 /* APIService+Block.swift in Sources */,
|
||||||
5DF1057F25F88A4100D6C0D4 /* TouchBlockingView.swift in Sources */,
|
5DF1057F25F88A4100D6C0D4 /* TouchBlockingView.swift in Sources */,
|
||||||
DB1D843426579931000346B3 /* TableViewControllerNavigateable.swift in Sources */,
|
DB1D843426579931000346B3 /* TableViewControllerNavigateable.swift in Sources */,
|
||||||
|
DB5B729A2731137900081888 /* FollowingListViewController+Provider.swift in Sources */,
|
||||||
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */,
|
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */,
|
||||||
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */,
|
2D206B8C25F6015000143C56 /* AudioPlaybackService.swift in Sources */,
|
||||||
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
|
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
|
||||||
|
@ -3929,6 +3953,7 @@
|
||||||
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */,
|
DB03A793272A7E5700EE37C5 /* SidebarListHeaderView.swift in Sources */,
|
||||||
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */,
|
DB4FFC2B269EC39600D62E92 /* SearchToSearchDetailViewControllerAnimatedTransitioning.swift in Sources */,
|
||||||
DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */,
|
DBCC3B9526157E6E0045B23D /* APIService+Relationship.swift in Sources */,
|
||||||
|
DB5B7298273112C800081888 /* FollowingListViewModel.swift in Sources */,
|
||||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
||||||
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
|
5DF1054125F886D400D6C0D4 /* VideoPlaybackService.swift in Sources */,
|
||||||
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
|
DB6B35182601FA3400DC1E11 /* MastodonAttachmentService.swift in Sources */,
|
||||||
|
@ -4065,6 +4090,7 @@
|
||||||
DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */,
|
DBE3CE01261D623D00430CC6 /* FavoriteViewModel+State.swift in Sources */,
|
||||||
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */,
|
2D82BA0525E7897700E36F0F /* MastodonResendEmailViewModelNavigationDelegateShim.swift in Sources */,
|
||||||
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */,
|
2D38F1EB25CD477000561493 /* HomeTimelineViewModel+LoadLatestState.swift in Sources */,
|
||||||
|
DB5B7295273112B100081888 /* FollowingListViewController.swift in Sources */,
|
||||||
0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */,
|
0F202201261326E6000C64BF /* HashtagTimelineViewModel.swift in Sources */,
|
||||||
DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */,
|
DB6D9F9726367249008423CD /* SettingsViewController.swift in Sources */,
|
||||||
DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */,
|
DB4F097F26A03DA600D62E92 /* SearchHistoryFetchedResultController.swift in Sources */,
|
||||||
|
@ -4075,8 +4101,10 @@
|
||||||
DB73BF47271199CA00781945 /* Instance.swift in Sources */,
|
DB73BF47271199CA00781945 /* Instance.swift in Sources */,
|
||||||
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */,
|
DB0F8150264D1E2500F2A12B /* PickServerLoaderTableViewCell.swift in Sources */,
|
||||||
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */,
|
||||||
|
DB5B729C273113C200081888 /* FollowingListViewModel+Diffable.swift in Sources */,
|
||||||
DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */,
|
DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */,
|
||||||
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */,
|
DBB9759C262462E1004620BD /* ThreadMetaView.swift in Sources */,
|
||||||
|
DB5B729E273113F300081888 /* FollowingListViewModel+State.swift in Sources */,
|
||||||
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
2DF123A725C3B0210020F248 /* ActiveLabel.swift in Sources */,
|
||||||
5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */,
|
5DDDF1A92617489F00311060 /* Mastodon+Entity+History.swift in Sources */,
|
||||||
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */,
|
DB59F11825EFA35B001F1DAB /* StripProgressView.swift in Sources */,
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
<key>AppShared.xcscheme_^#shared#^_</key>
|
<key>AppShared.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>42</integer>
|
<integer>36</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>43</integer>
|
<integer>35</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
<key>MastodonIntent.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>44</integer>
|
<integer>38</integer>
|
||||||
</dict>
|
</dict>
|
||||||
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
<key>MastodonIntents.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>41</integer>
|
<integer>37</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
<key>SuppressBuildableAutocreation</key>
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
|
|
@ -179,6 +179,7 @@ extension SceneCoordinator {
|
||||||
case profile(viewModel: ProfileViewModel)
|
case profile(viewModel: ProfileViewModel)
|
||||||
case favorite(viewModel: FavoriteViewModel)
|
case favorite(viewModel: FavoriteViewModel)
|
||||||
case follower(viewModel: FollowerListViewModel)
|
case follower(viewModel: FollowerListViewModel)
|
||||||
|
case following(viewModel: FollowingListViewModel)
|
||||||
|
|
||||||
// setting
|
// setting
|
||||||
case settings(viewModel: SettingsViewModel)
|
case settings(viewModel: SettingsViewModel)
|
||||||
|
@ -429,6 +430,10 @@ private extension SceneCoordinator {
|
||||||
let _viewController = FollowerListViewController()
|
let _viewController = FollowerListViewController()
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
viewController = _viewController
|
viewController = _viewController
|
||||||
|
case .following(let viewModel):
|
||||||
|
let _viewController = FollowingListViewController()
|
||||||
|
_viewController.viewModel = viewModel
|
||||||
|
viewController = _viewController
|
||||||
case .suggestionAccount(let viewModel):
|
case .suggestionAccount(let viewModel):
|
||||||
let _viewController = SuggestionAccountViewController()
|
let _viewController = SuggestionAccountViewController()
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
|
|
|
@ -10,6 +10,7 @@ import CoreData
|
||||||
|
|
||||||
enum UserItem: Hashable {
|
enum UserItem: Hashable {
|
||||||
case follower(objectID: NSManagedObjectID)
|
case follower(objectID: NSManagedObjectID)
|
||||||
|
case following(objectID: NSManagedObjectID)
|
||||||
case bottomLoader
|
case bottomLoader
|
||||||
case bottomHeader(text: String)
|
case bottomHeader(text: String)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ extension UserSection {
|
||||||
] tableView, indexPath, item -> UITableViewCell? in
|
] tableView, indexPath, item -> UITableViewCell? in
|
||||||
guard let dependency = dependency else { return UITableViewCell() }
|
guard let dependency = dependency else { return UITableViewCell() }
|
||||||
switch item {
|
switch item {
|
||||||
case .follower(let objectID):
|
case .follower(let objectID),
|
||||||
|
.following(let objectID):
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: UserTableViewCell.self), for: indexPath) as! UserTableViewCell
|
||||||
managedObjectContext.performAndWait {
|
managedObjectContext.performAndWait {
|
||||||
let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
let user = managedObjectContext.object(with: objectID) as! MastodonUser
|
||||||
|
|
|
@ -37,7 +37,8 @@ extension FollowerListViewController: UserProvider {
|
||||||
let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext
|
let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext
|
||||||
|
|
||||||
switch item {
|
switch item {
|
||||||
case .follower(let objectID):
|
case .follower(let objectID),
|
||||||
|
.following(let objectID):
|
||||||
managedObjectContext.perform {
|
managedObjectContext.perform {
|
||||||
let user = managedObjectContext.object(with: objectID) as? MastodonUser
|
let user = managedObjectContext.object(with: objectID) as? MastodonUser
|
||||||
promise(.success(user))
|
promise(.success(user))
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
|
|
||||||
import os.log
|
import os.log
|
||||||
import UIKit
|
import UIKit
|
||||||
import AVKit
|
|
||||||
import GameplayKit
|
import GameplayKit
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
final class FollowerListViewController: UIViewController, NeedsDependency, MediaPreviewableViewController {
|
final class FollowerListViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -19,9 +18,7 @@ final class FollowerListViewController: UIViewController, NeedsDependency, Media
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
var viewModel: FollowerListViewModel!
|
var viewModel: FollowerListViewModel!
|
||||||
|
|
||||||
let mediaPreviewTransitionController = MediaPreviewTransitionController()
|
|
||||||
|
|
||||||
lazy var tableView: UITableView = {
|
lazy var tableView: UITableView = {
|
||||||
let tableView = UITableView()
|
let tableView = UITableView()
|
||||||
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self))
|
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self))
|
||||||
|
|
|
@ -18,16 +18,19 @@ extension FollowerListViewModel {
|
||||||
managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext
|
managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// workaround to append loader wrong animation issue
|
||||||
// set empty section to make update animation top-to-bottom style
|
// set empty section to make update animation top-to-bottom style
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<UserSection, UserItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<UserSection, UserItem>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
diffableDataSource?.apply(snapshot)
|
|
||||||
|
|
||||||
// workaround to append loader wrong animation issue
|
|
||||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
diffableDataSource?.apply(snapshot)
|
if #available(iOS 15.0, *) {
|
||||||
|
diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil)
|
||||||
|
} else {
|
||||||
|
// Fallback on earlier versions
|
||||||
|
diffableDataSource?.apply(snapshot, animatingDifferences: false)
|
||||||
|
}
|
||||||
|
|
||||||
userFetchedResultsController.objectIDs.removeDuplicates()
|
userFetchedResultsController.objectIDs
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] objectIDs in
|
.sink { [weak self] objectIDs in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -45,7 +48,8 @@ extension FollowerListViewModel {
|
||||||
case is State.Idle, is State.Loading, is State.Fail:
|
case is State.Idle, is State.Loading, is State.Fail:
|
||||||
snapshot.appendItems([.bottomLoader], toSection: .main)
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
case is State.NoMore:
|
case is State.NoMore:
|
||||||
break
|
let text = "Followers from other servers are not displayed."
|
||||||
|
snapshot.appendItems([.bottomHeader(text: text)], toSection: .main)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,18 +179,6 @@ extension FollowerListViewModel.State {
|
||||||
|
|
||||||
override func didEnter(from previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
guard let viewModel = viewModel, let _ = stateMachine else { return }
|
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else {
|
|
||||||
assertionFailure()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
var snapshot = diffableDataSource.snapshot()
|
|
||||||
snapshot.deleteItems([.bottomLoader])
|
|
||||||
let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed")
|
|
||||||
snapshot.appendItems([header], toSection: .main)
|
|
||||||
diffableDataSource.apply(snapshot, animatingDifferences: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// FollowingListViewController+Provider.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
|
||||||
|
extension FollowingListViewController: UserProvider {
|
||||||
|
|
||||||
|
func mastodonUser() -> Future<MastodonUser?, Never> {
|
||||||
|
Future { promise in
|
||||||
|
promise(.success(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mastodonUser(for cell: UITableViewCell?) -> Future<MastodonUser?, Never> {
|
||||||
|
Future { [weak self] promise in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||||
|
assertionFailure()
|
||||||
|
promise(.success(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let cell = cell,
|
||||||
|
let indexPath = self.tableView.indexPath(for: cell),
|
||||||
|
let item = diffableDataSource.itemIdentifier(for: indexPath) else {
|
||||||
|
promise(.success(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let managedObjectContext = self.viewModel.userFetchedResultsController.fetchedResultsController.managedObjectContext
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .follower(let objectID),
|
||||||
|
.following(let objectID):
|
||||||
|
managedObjectContext.perform {
|
||||||
|
let user = managedObjectContext.object(with: objectID) as? MastodonUser
|
||||||
|
promise(.success(user))
|
||||||
|
}
|
||||||
|
case .bottomLoader, .bottomHeader:
|
||||||
|
promise(.success(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// FollowingListViewController.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import UIKit
|
||||||
|
import GameplayKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
final class FollowingListViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
|
||||||
|
var viewModel: FollowingListViewModel!
|
||||||
|
|
||||||
|
lazy var tableView: UITableView = {
|
||||||
|
let tableView = UITableView()
|
||||||
|
tableView.register(UserTableViewCell.self, forCellReuseIdentifier: String(describing: UserTableViewCell.self))
|
||||||
|
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
||||||
|
tableView.register(TimelineFooterTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineFooterTableViewCell.self))
|
||||||
|
tableView.rowHeight = UITableView.automaticDimension
|
||||||
|
tableView.separatorStyle = .none
|
||||||
|
tableView.backgroundColor = .clear
|
||||||
|
return tableView
|
||||||
|
}()
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FollowingListViewController {
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
view.backgroundColor = ThemeService.shared.currentTheme.value.secondarySystemBackgroundColor
|
||||||
|
ThemeService.shared.currentTheme
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] theme in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.view.backgroundColor = theme.secondarySystemBackgroundColor
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
|
||||||
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
view.addSubview(tableView)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
tableView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
tableView.delegate = self
|
||||||
|
viewModel.setupDiffableDataSource(
|
||||||
|
for: tableView,
|
||||||
|
dependency: self
|
||||||
|
)
|
||||||
|
// TODO: add UserTableViewCellDelegate
|
||||||
|
|
||||||
|
// trigger user timeline loading
|
||||||
|
Publishers.CombineLatest(
|
||||||
|
viewModel.domain.removeDuplicates().eraseToAnyPublisher(),
|
||||||
|
viewModel.userID.removeDuplicates().eraseToAnyPublisher()
|
||||||
|
)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.viewModel.stateMachine.enter(FollowingListViewModel.State.Reloading.self)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - LoadMoreConfigurableTableViewContainer
|
||||||
|
extension FollowingListViewController: LoadMoreConfigurableTableViewContainer {
|
||||||
|
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
|
||||||
|
typealias LoadingState = FollowingListViewModel.State.Loading
|
||||||
|
var loadMoreConfigurableTableView: UITableView { tableView }
|
||||||
|
var loadMoreConfigurableStateMachine: GKStateMachine { viewModel.stateMachine }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UIScrollViewDelegate
|
||||||
|
extension FollowingListViewController {
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
handleScrollViewDidScroll(scrollView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - UITableViewDelegate
|
||||||
|
extension FollowingListViewController: UITableViewDelegate {
|
||||||
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
|
handleTableView(tableView, didSelectRowAt: indexPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - UserTableViewCellDelegate
|
||||||
|
extension FollowingListViewController: UserTableViewCellDelegate { }
|
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// FollowingListViewModel+Diffable.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension FollowingListViewModel {
|
||||||
|
func setupDiffableDataSource(
|
||||||
|
for tableView: UITableView,
|
||||||
|
dependency: NeedsDependency
|
||||||
|
) {
|
||||||
|
diffableDataSource = UserSection.tableViewDiffableDataSource(
|
||||||
|
for: tableView,
|
||||||
|
dependency: dependency,
|
||||||
|
managedObjectContext: userFetchedResultsController.fetchedResultsController.managedObjectContext
|
||||||
|
)
|
||||||
|
|
||||||
|
// workaround to append loader wrong animation issue
|
||||||
|
// set empty section to make update animation top-to-bottom style
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<UserSection, UserItem>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
diffableDataSource?.applySnapshotUsingReloadData(snapshot, completion: nil)
|
||||||
|
} else {
|
||||||
|
// Fallback on earlier versions
|
||||||
|
diffableDataSource?.apply(snapshot, animatingDifferences: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
userFetchedResultsController.objectIDs
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] objectIDs in
|
||||||
|
guard let self = self else { return }
|
||||||
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||||
|
|
||||||
|
var snapshot = NSDiffableDataSourceSnapshot<UserSection, UserItem>()
|
||||||
|
snapshot.appendSections([.main])
|
||||||
|
let items: [UserItem] = objectIDs.map {
|
||||||
|
UserItem.following(objectID: $0)
|
||||||
|
}
|
||||||
|
snapshot.appendItems(items, toSection: .main)
|
||||||
|
|
||||||
|
if let currentState = self.stateMachine.currentState {
|
||||||
|
switch currentState {
|
||||||
|
case is State.Idle, is State.Loading, is State.Fail:
|
||||||
|
snapshot.appendItems([.bottomLoader], toSection: .main)
|
||||||
|
case is State.NoMore:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diffableDataSource.apply(snapshot)
|
||||||
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
//
|
||||||
|
// FollowingListViewModel+State.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import Foundation
|
||||||
|
import GameplayKit
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
extension FollowingListViewModel {
|
||||||
|
class State: GKState {
|
||||||
|
weak var viewModel: FollowingListViewModel?
|
||||||
|
|
||||||
|
init(viewModel: FollowingListViewModel) {
|
||||||
|
self.viewModel = viewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
os_log("%{public}s[%{public}ld], %{public}s: enter %s, previous: %s", ((#file as NSString).lastPathComponent), #line, #function, self.debugDescription, previousState.debugDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FollowingListViewModel.State {
|
||||||
|
class Initial: FollowingListViewModel.State {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
guard let viewModel = viewModel else { return false }
|
||||||
|
switch stateClass {
|
||||||
|
case is Reloading.Type:
|
||||||
|
return viewModel.userID.value != nil
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Reloading: FollowingListViewModel.State {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
switch stateClass {
|
||||||
|
case is Loading.Type:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
|
// reset
|
||||||
|
viewModel.userFetchedResultsController.userIDs.value = []
|
||||||
|
|
||||||
|
stateMachine.enter(Loading.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Fail: FollowingListViewModel.State {
|
||||||
|
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
switch stateClass {
|
||||||
|
case is Loading.Type:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
guard let _ = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading 3s later…", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: retry loading", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
stateMachine.enter(Loading.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Idle: FollowingListViewModel.State {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
switch stateClass {
|
||||||
|
case is Reloading.Type, is Loading.Type:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Loading: FollowingListViewModel.State {
|
||||||
|
|
||||||
|
var maxID: String?
|
||||||
|
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
switch stateClass {
|
||||||
|
case is Fail.Type:
|
||||||
|
return true
|
||||||
|
case is Idle.Type:
|
||||||
|
return true
|
||||||
|
case is NoMore.Type:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
|
if previousState is Reloading {
|
||||||
|
maxID = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let viewModel = viewModel, let stateMachine = stateMachine else { return }
|
||||||
|
|
||||||
|
guard let userID = viewModel.userID.value, !userID.isEmpty else {
|
||||||
|
stateMachine.enter(Fail.self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let activeMastodonAuthenticationBox = viewModel.context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||||
|
stateMachine.enter(Fail.self)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.context.apiService.followers(
|
||||||
|
userID: userID,
|
||||||
|
maxID: maxID,
|
||||||
|
authorizationBox: activeMastodonAuthenticationBox
|
||||||
|
)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { completion in
|
||||||
|
switch completion {
|
||||||
|
case .failure(let error):
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: fetch user timeline fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
|
||||||
|
stateMachine.enter(Fail.self)
|
||||||
|
case .finished:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} receiveValue: { response in
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||||
|
|
||||||
|
var hasNewAppend = false
|
||||||
|
var userIDs = viewModel.userFetchedResultsController.userIDs.value
|
||||||
|
for user in response.value {
|
||||||
|
guard !userIDs.contains(user.id) else { continue }
|
||||||
|
userIDs.append(user.id)
|
||||||
|
hasNewAppend = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let maxID = response.link?.maxID
|
||||||
|
|
||||||
|
if hasNewAppend, maxID != nil {
|
||||||
|
stateMachine.enter(Idle.self)
|
||||||
|
} else {
|
||||||
|
stateMachine.enter(NoMore.self)
|
||||||
|
}
|
||||||
|
self.maxID = maxID
|
||||||
|
viewModel.userFetchedResultsController.userIDs.value = userIDs
|
||||||
|
}
|
||||||
|
.store(in: &viewModel.disposeBag)
|
||||||
|
} // end func didEnter
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoMore: FollowingListViewModel.State {
|
||||||
|
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
|
||||||
|
switch stateClass {
|
||||||
|
case is Reloading.Type:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didEnter(from previousState: GKState?) {
|
||||||
|
super.didEnter(from: previousState)
|
||||||
|
guard let viewModel = viewModel, let _ = stateMachine else { return }
|
||||||
|
guard let diffableDataSource = viewModel.diffableDataSource else {
|
||||||
|
assertionFailure()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
var snapshot = diffableDataSource.snapshot()
|
||||||
|
snapshot.deleteItems([.bottomLoader])
|
||||||
|
let header = UserItem.bottomHeader(text: "Followers from other servers are not displayed")
|
||||||
|
snapshot.appendItems([header], toSection: .main)
|
||||||
|
diffableDataSource.apply(snapshot, animatingDifferences: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// FollowingListViewModel.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by Cirno MainasuK on 2021-11-2.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
import GameplayKit
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
final class FollowingListViewModel {
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
// input
|
||||||
|
let context: AppContext
|
||||||
|
let domain: CurrentValueSubject<String?, Never>
|
||||||
|
let userID: CurrentValueSubject<String?, Never>
|
||||||
|
let userFetchedResultsController: UserFetchedResultsController
|
||||||
|
|
||||||
|
// output
|
||||||
|
var diffableDataSource: UITableViewDiffableDataSource<UserSection, UserItem>?
|
||||||
|
private(set) lazy var stateMachine: GKStateMachine = {
|
||||||
|
let stateMachine = GKStateMachine(states: [
|
||||||
|
State.Initial(viewModel: self),
|
||||||
|
State.Reloading(viewModel: self),
|
||||||
|
State.Fail(viewModel: self),
|
||||||
|
State.Idle(viewModel: self),
|
||||||
|
State.Loading(viewModel: self),
|
||||||
|
State.NoMore(viewModel: self),
|
||||||
|
])
|
||||||
|
stateMachine.enter(State.Initial.self)
|
||||||
|
return stateMachine
|
||||||
|
}()
|
||||||
|
|
||||||
|
init(context: AppContext, domain: String?, userID: String?) {
|
||||||
|
self.context = context
|
||||||
|
self.userFetchedResultsController = UserFetchedResultsController(
|
||||||
|
managedObjectContext: context.managedObjectContext,
|
||||||
|
domain: domain,
|
||||||
|
additionalTweetPredicate: nil
|
||||||
|
)
|
||||||
|
self.domain = CurrentValueSubject(domain)
|
||||||
|
self.userID = CurrentValueSubject(userID)
|
||||||
|
// super.init()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1001,8 +1001,19 @@ extension ProfileViewController: ProfileHeaderViewDelegate {
|
||||||
transition: .show
|
transition: .show
|
||||||
)
|
)
|
||||||
case .following:
|
case .following:
|
||||||
// TODO:
|
guard let domain = viewModel.domain.value,
|
||||||
break
|
let userID = viewModel.userID.value
|
||||||
|
else { return }
|
||||||
|
let followingListViewModel = FollowingListViewModel(
|
||||||
|
context: context,
|
||||||
|
domain: domain,
|
||||||
|
userID: userID
|
||||||
|
)
|
||||||
|
coordinator.present(
|
||||||
|
scene: .following(viewModel: followingListViewModel),
|
||||||
|
from: self,
|
||||||
|
transition: .show
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue