feat: [WIP] add account list for multiple account switch

This commit is contained in:
CMK 2021-09-13 19:14:26 +08:00
parent 7a089831cd
commit 66af30da2a
10 changed files with 292 additions and 35 deletions

View File

@ -415,6 +415,9 @@
DB9D6C3825E508BE0051B173 /* Attachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D6C3725E508BE0051B173 /* Attachment.swift */; };
DB9D7C21269824B80054B3DF /* APIService+Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9D7C20269824B80054B3DF /* APIService+Filter.swift */; };
DB9E0D6F25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */; };
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */; };
DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */; };
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */; };
DBA088DF26958164003EB4B2 /* UserFetchedResultsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA088DE26958164003EB4B2 /* UserFetchedResultsController.swift */; };
DBA0A11325FB3FC10079C110 /* ComposeToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */; };
DBA1DB80268F84F80052DB59 /* NotificationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA1DB7F268F84F80052DB59 /* NotificationType.swift */; };
@ -1184,6 +1187,9 @@
DB9D6C3725E508BE0051B173 /* Attachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Attachment.swift; sourceTree = "<group>"; };
DB9D7C20269824B80054B3DF /* APIService+Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Filter.swift"; sourceTree = "<group>"; };
DB9E0D6E25EE008500CFDD76 /* UIInterpolatingMotionEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIInterpolatingMotionEffect.swift; sourceTree = "<group>"; };
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountViewController.swift; sourceTree = "<group>"; };
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListViewModel.swift; sourceTree = "<group>"; };
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountListTableViewCell.swift; sourceTree = "<group>"; };
DBA088DE26958164003EB4B2 /* UserFetchedResultsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFetchedResultsController.swift; sourceTree = "<group>"; };
DBA0A11225FB3FC10079C110 /* ComposeToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeToolbarView.swift; sourceTree = "<group>"; };
DBA1DB7F268F84F80052DB59 /* NotificationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationType.swift; sourceTree = "<group>"; };
@ -2601,6 +2607,7 @@
DB6180E426391A500018D199 /* Transition */,
DB8AF54E25C13703002E6C99 /* MainTab */,
DB01409B25C40BB600F9F3CF /* Onboarding */,
DB9F58ED26EF435800E7BBE9 /* Account */,
2D38F1D325CD463600561493 /* HomeTimeline */,
2D76316325C14BAC00929FB9 /* PublicTimeline */,
5B24BBD6262DB14800A9381B /* Report */,
@ -2775,6 +2782,16 @@
path = ViewModel;
sourceTree = "<group>";
};
DB9F58ED26EF435800E7BBE9 /* Account */ = {
isa = PBXGroup;
children = (
DB9F58EB26EF435000E7BBE9 /* AccountViewController.swift */,
DB9F58EE26EF491E00E7BBE9 /* AccountListViewModel.swift */,
DB9F58F026EF512300E7BBE9 /* AccountListTableViewCell.swift */,
);
path = Account;
sourceTree = "<group>";
};
DBA5E7A6263BD298004598BB /* ContextMenu */ = {
isa = PBXGroup;
children = (
@ -3960,6 +3977,7 @@
2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */,
DB4F096C269EFA2000D62E92 /* SearchResultViewController+StatusProvider.swift in Sources */,
DB87D4452609BE0500D12C0D /* ComposeStatusPollOptionCollectionViewCell.swift in Sources */,
DB9F58EF26EF491E00E7BBE9 /* AccountListViewModel.swift in Sources */,
DB6D9F7D26358ED4008423CD /* SettingsSection.swift in Sources */,
DB0E91EA26A9675100BD2ACC /* MetaLabel.swift in Sources */,
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */,
@ -4017,6 +4035,7 @@
DB938F1526241FDF00E5B6C1 /* APIService+Thread.swift in Sources */,
DB482A45261335BA008AE74C /* UserTimelineViewController+Provider.swift in Sources */,
2D206B8625F5FB0900143C56 /* Double.swift in Sources */,
DB9F58F126EF512300E7BBE9 /* AccountListTableViewCell.swift in Sources */,
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
DB35FC252612FD7A006193C9 /* ProfileFieldView.swift in Sources */,
DB938F0326240EA300E5B6C1 /* CachedThreadViewModel.swift in Sources */,
@ -4105,6 +4124,7 @@
2D198649261C0B8500F0B013 /* SearchResultSection.swift in Sources */,
DB4F097B26A039FF00D62E92 /* SearchHistorySection.swift in Sources */,
DBB525302611EBF3002F1F29 /* ProfilePagingViewModel.swift in Sources */,
DB9F58EC26EF435000E7BBE9 /* AccountViewController.swift in Sources */,
DBCBCC0B2680B03F000F5B51 /* AsyncHomeTimelineViewModel+LoadOldestState.swift in Sources */,
2D5A3D6225CFD9CB002347D6 /* HomeTimelineViewController+DebugAction.swift in Sources */,
DB49A62525FF334C00B98345 /* EmojiService+CustomEmojiViewModel+LoadState.swift in Sources */,

View File

@ -7,17 +7,17 @@
<key>AppShared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>60</integer>
<integer>24</integer>
</dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>62</integer>
<integer>23</integer>
</dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>11</integer>
<integer>2</integer>
</dict>
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>
@ -27,7 +27,7 @@
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>10</integer>
<integer>1</integer>
</dict>
<key>Mastodon - ar.xcscheme_^#shared#^_</key>
<dict>
@ -97,7 +97,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>56</integer>
<integer>25</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
@ -112,12 +112,12 @@
<key>NotificationService.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>13</integer>
<integer>3</integer>
</dict>
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>58</integer>
<integer>22</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -141,8 +141,8 @@
"repositoryURL": "https://github.com/apple/swift-collections.git",
"state": {
"branch": null,
"revision": "0959ba76a1d4a98fd11163aa83fd49c25b93bfae",
"version": "0.0.5"
"revision": "9d8719c8bebdc79740b6969c912ac706eb721d7a",
"version": "0.0.7"
}
},
{

View File

@ -87,6 +87,7 @@ extension SceneCoordinator {
case activityViewController(activityViewController: UIActivityViewController, sourceView: UIView?, barButtonItem: UIBarButtonItem?)
#if DEBUG
case accountList
case publicTimeline
#endif
@ -321,6 +322,9 @@ private extension SceneCoordinator {
_viewController.viewModel = viewModel
viewController = _viewController
#if DEBUG
case .accountList:
let _viewController = AccountListViewController()
viewController = _viewController
case .publicTimeline:
let _viewController = PublicTimelineViewController()
_viewController.viewModel = PublicTimelineViewModel(context: appContext)

View File

@ -0,0 +1,34 @@
//
// AccountListTableViewCell.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-13.
//
#if DEBUG
import UIKit
final class AccountListTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
_init()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
_init()
}
}
extension AccountListTableViewCell {
private func _init() {
}
}
#endif

View File

@ -0,0 +1,80 @@
//
// AccountListViewModel.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-13.
//
#if DEBUG
import UIKit
import Combine
import CoreData
import CoreDataStack
final class AccountListViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
// output
let authentications = CurrentValueSubject<[Item], Never>([])
var diffableDataSource: UITableViewDiffableDataSource<Section, Item>!
init(context: AppContext) {
self.context = context
context.authenticationService.mastodonAuthentications
.map { authentications in
return authentications.map {
Item.authentication(objectID: $0.objectID)
}
}
.assign(to: \.value, on: authentications)
.store(in: &disposeBag)
authentications
.receive(on: DispatchQueue.main)
.sink { [weak self] authentications in
guard let self = self else { return }
guard let diffableDataSource = self.diffableDataSource else { return }
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(authentications, toSection: .main)
diffableDataSource.apply(snapshot)
}
.store(in: &disposeBag)
}
}
extension AccountListViewModel {
enum Section: Hashable {
case main
}
enum Item: Hashable {
case authentication(objectID: NSManagedObjectID)
}
func setupDiffableDataSource(
tableView: UITableView,
managedObjectContext: NSManagedObjectContext
) {
diffableDataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item in
switch item {
case .authentication(let objectID):
let authentication = managedObjectContext.object(with: objectID) as! MastodonAuthentication
let user = authentication.user
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: AccountListTableViewCell.self), for: indexPath) as! AccountListTableViewCell
cell.textLabel?.text = user.acctWithDomain
return cell
}
}
}
}
#endif

View File

@ -0,0 +1,98 @@
//
// AccountViewController.swift
// Mastodon
//
// Created by Cirno MainasuK on 2021-9-13.
//
#if DEBUG
import os.log
import UIKit
import Combine
import CoreDataStack
final class AccountListViewController: UIViewController, NeedsDependency {
let logger = Logger(subsystem: "AccountListViewController", category: "UI")
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var disposeBag = Set<AnyCancellable>()
private(set) lazy var viewModel = AccountListViewModel(context: context)
private(set) lazy var addBarButtonItem: UIBarButtonItem = {
let barButtonItem = UIBarButtonItem(
image: UIImage(systemName: "plus"),
style: .plain,
target: self,
action: #selector(AccountListViewController.addBarButtonItem(_:))
)
return barButtonItem
}()
private(set) lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.register(AccountListTableViewCell.self, forCellReuseIdentifier: String(describing: AccountListTableViewCell.self))
return tableView
}()
}
extension AccountListViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
navigationItem.rightBarButtonItem = addBarButtonItem
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(
tableView: tableView,
managedObjectContext: context.managedObjectContext
)
}
}
extension AccountListViewController {
@objc private func addBarButtonItem(_ sender: UIBarButtonItem) {
logger.debug("\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
}
}
// MARK: - UITableViewDelegate
extension AccountListViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard case let .authentication(objectID) = diffableDataSource.itemIdentifier(for: indexPath) else { return }
assert(Thread.isMainThread)
let authentication = context.managedObjectContext.object(with: objectID) as! MastodonAuthentication
context.authenticationService.activeMastodonUser(domain: authentication.domain, userID: authentication.userID)
.receive(on: DispatchQueue.main)
.sink { [weak self] result in
guard let self = self else { return }
self.coordinator.setup()
}
.store(in: &disposeBag)
}
}
#endif

View File

@ -23,20 +23,9 @@ extension HomeTimelineViewController {
identifier: nil,
options: .displayInline,
children: [
UIAction(title: "Show FLEX", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.showFLEXAction(action)
}),
showMenu,
moveMenu,
dropMenu,
UIAction(title: "Show Welcome", image: UIImage(systemName: "figure.walk"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showWelcomeAction(action)
},
UIAction(title: "Show Confirm Email", image: UIImage(systemName: "envelope"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showConfirmEmail(action)
},
UIAction(title: "Toggle EmptyView", image: UIImage(systemName: "clear"), attributes: []) { [weak self] action in
guard let self = self else { return }
if self.emptyView.superview != nil {
@ -45,18 +34,6 @@ extension HomeTimelineViewController {
self.showEmptyView()
}
},
UIAction(title: "Show Public Timeline", image: UIImage(systemName: "list.dash"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showPublicTimelineAction(action)
},
UIAction(title: "Show Profile", image: UIImage(systemName: "person.crop.circle"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showProfileAction(action)
},
UIAction(title: "Show Thread", image: UIImage(systemName: "bubble.left.and.bubble.right"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showThreadAction(action)
},
UIAction(title: "Settings", image: UIImage(systemName: "gear"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showSettings(action)
@ -69,6 +46,45 @@ extension HomeTimelineViewController {
)
return menu
}
var showMenu: UIMenu {
return UIMenu(
title: "Show…",
image: UIImage(systemName: "plus.rectangle.on.rectangle"),
identifier: nil,
options: [],
children: [
UIAction(title: "FLEX", image: nil, attributes: [], handler: { [weak self] action in
guard let self = self else { return }
self.showFLEXAction(action)
}),
UIAction(title: "Welcome", image: UIImage(systemName: "figure.walk"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showWelcomeAction(action)
},
UIAction(title: "Confirm Email", image: UIImage(systemName: "envelope"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showConfirmEmail(action)
},
UIAction(title: "Account List", image: UIImage(systemName: "person"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showAccountList(action)
},
UIAction(title: "Public Timeline", image: UIImage(systemName: "list.dash"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showPublicTimelineAction(action)
},
UIAction(title: "Profile", image: UIImage(systemName: "person.crop.circle"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showProfileAction(action)
},
UIAction(title: "Thread", image: UIImage(systemName: "bubble.left.and.bubble.right"), attributes: []) { [weak self] action in
guard let self = self else { return }
self.showThreadAction(action)
},
]
)
}
var moveMenu: UIMenu {
return UIMenu(
@ -321,6 +337,10 @@ extension HomeTimelineViewController {
let mastodonConfirmEmailViewModel = MastodonConfirmEmailViewModel()
coordinator.present(scene: .mastodonConfirmEmail(viewModel: mastodonConfirmEmailViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
@objc private func showAccountList(_ sender: UIAction) {
coordinator.present(scene: .accountList, from: self, transition: .modal(animated: true, completion: nil))
}
@objc private func showPublicTimelineAction(_ sender: UIAction) {
coordinator.present(scene: .publicTimeline, from: self, transition: .show)

View File

@ -222,7 +222,8 @@ extension MastodonPickServerViewController {
assertionFailure(error.localizedDescription)
case .success(let isActived):
assert(isActived)
self.dismiss(animated: true, completion: nil)
// self.dismiss(animated: true, completion: nil)
self.coordinator.setup()
}
}
.store(in: &disposeBag)

View File

@ -80,4 +80,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 4db0bdf969729c5758bd923e33d9e097cb892086
COCOAPODS: 1.10.2
COCOAPODS: 1.11.0