feat: add account switcher for iPad sidebar

This commit is contained in:
CMK 2021-10-29 17:26:26 +08:00
parent 0ec20c6c88
commit bcddcf226b
7 changed files with 106 additions and 33 deletions

View File

@ -7,12 +7,12 @@
<key>AppShared.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>42</integer>
<integer>35</integer>
</dict>
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>43</integer>
<integer>36</integer>
</dict>
<key>Mastodon - ASDK.xcscheme_^#shared#^_</key>
<dict>
@ -97,7 +97,7 @@
<key>MastodonIntent.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>44</integer>
<integer>37</integer>
</dict>
<key>MastodonIntents.xcscheme_^#shared#^_</key>
<dict>
@ -117,7 +117,7 @@
<key>ShareActionExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>41</integer>
<integer>38</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -139,6 +139,7 @@ extension SceneCoordinator {
case show // push
case showDetail // replace
case modal(animated: Bool, completion: (() -> Void)? = nil)
case popover(sourceView: UIView)
case panModal
case custom(transitioningDelegate: UIViewControllerTransitioningDelegate)
case customPush
@ -326,7 +327,10 @@ extension SceneCoordinator {
panModalPresentable.transitioningDelegate = PanModalPresentationDelegate.default
presentingViewController.present(panModalPresentable, animated: true, completion: nil)
//presentingViewController.presentPanModal(panModalPresentable)
case .popover(let sourceView):
viewController.modalPresentationStyle = .popover
viewController.popoverPresentationController?.sourceView = sourceView
(splitViewController ?? presentingViewController)?.present(viewController, animated: true, completion: nil)
case .custom(let transitioningDelegate):
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningDelegate

View File

@ -9,7 +9,7 @@ import UIKit
import MetaTextKit
final class AddAccountTableViewCell: UITableViewCell {
let iconImageView: UIImageView = {
let image = UIImage(systemName: "plus.circle.fill")!
let imageView = UIImageView(image: image)
@ -51,6 +51,28 @@ extension AddAccountTableViewCell {
])
iconImageView.setContentHuggingPriority(.defaultLow, for: .horizontal)
iconImageView.setContentHuggingPriority(.defaultLow, for: .vertical)
// layout the same placeholder UI from `AccountListTableViewCell`
let placeholderLabelContainerStackView = UIStackView()
placeholderLabelContainerStackView.axis = .vertical
placeholderLabelContainerStackView.distribution = .equalCentering
placeholderLabelContainerStackView.spacing = 2
placeholderLabelContainerStackView.distribution = .fillProportionally
placeholderLabelContainerStackView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(placeholderLabelContainerStackView)
NSLayoutConstraint.activate([
placeholderLabelContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
placeholderLabelContainerStackView.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: placeholderLabelContainerStackView.bottomAnchor, constant: 10),
iconImageView.heightAnchor.constraint(equalTo: placeholderLabelContainerStackView.heightAnchor, multiplier: 0.8).priority(.required - 10),
])
let _nameLabel = MetaLabel(style: .accountListName)
_nameLabel.configure(content: PlaintextMetaContent(string: " "))
let _usernameLabel = MetaLabel(style: .accountListUsername)
_usernameLabel.configure(content: PlaintextMetaContent(string: " "))
placeholderLabelContainerStackView.addArrangedSubview(_nameLabel)
placeholderLabelContainerStackView.addArrangedSubview(_usernameLabel)
placeholderLabelContainerStackView.isHidden = true
titleLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(titleLabel)
@ -58,7 +80,7 @@ extension AddAccountTableViewCell {
titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15),
titleLabel.leadingAnchor.constraint(equalTo: iconImageView.trailingAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 15),
iconImageView.heightAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 1.0).priority(.required - 10),
// iconImageView.heightAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 1.0).priority(.required - 10),
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])

View File

@ -63,7 +63,7 @@ extension ContentSplitViewController {
sidebarViewController.didMove(toParent: self)
NSLayoutConstraint.activate([
mainTabBarController.view.topAnchor.constraint(equalTo: view.topAnchor),
mainTabBarController.view.leadingAnchor.constraint(equalTo: sidebarViewController.view.trailingAnchor),
mainTabBarController.view.leadingAnchor.constraint(equalTo: sidebarViewController.view.trailingAnchor, constant: UIView.separatorLineHeight(of: view)),
mainTabBarController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mainTabBarController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
@ -87,6 +87,22 @@ extension ContentSplitViewController: SidebarViewControllerDelegate {
assertionFailure()
return
}
let previousTab = currentSupplementaryTab
currentSupplementaryTab = tab
if previousTab == tab,
let navigationController = mainTabBarController.selectedViewController as? UINavigationController
{
navigationController.popToRootViewController(animated: true)
}
}
func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView) {
guard case let .tab(tab) = item, tab == .me else { return }
let accountListViewController = coordinator.present(scene: .accountList, from: nil, transition: .popover(sourceView: sourceView)) as! AccountListViewController
accountListViewController.dragIndicatorView.barView.isHidden = true
accountListViewController.preferredContentSize = CGSize(width: 300, height: 320)
}
}

View File

@ -26,8 +26,17 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
return contentSplitViewController
}()
private(set) lazy var searchViewController: SearchViewController = {
let searchViewController = SearchViewController()
searchViewController.context = context
searchViewController.coordinator = coordinator
return searchViewController
}()
lazy var compactMainTabBarViewController = MainTabBarController(context: context, coordinator: coordinator)
let separatorLine = UIView.separatorLine
init(context: AppContext, coordinator: SceneCoordinator) {
self.context = context
self.coordinator = coordinator
@ -48,13 +57,9 @@ final class RootSplitViewController: UISplitViewController, NeedsDependency {
// Fallback on earlier versions
}
setViewController(UIViewController(), for: .primary)
setViewController(searchViewController, for: .primary)
setViewController(contentSplitViewController, for: .secondary)
setViewController(compactMainTabBarViewController, for: .compact)
contentSplitViewController.sidebarViewController.view.layer.zPosition = 100
contentSplitViewController.mainTabBarController.view.layer.zPosition = 90
view.layer.zPosition = 80
}
required init?(coder: NSCoder) {
@ -80,6 +85,15 @@ extension RootSplitViewController {
self.updateBehavior(size: self.view.frame.size)
}
.store(in: &disposeBag)
setupBackground(theme: ThemeService.shared.currentTheme.value)
ThemeService.shared.currentTheme
.receive(on: DispatchQueue.main)
.sink { [weak self] theme in
guard let self = self else { return }
self.setupBackground(theme: theme)
}
.store(in: &disposeBag)
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
@ -108,6 +122,15 @@ extension RootSplitViewController {
}
extension RootSplitViewController {
private func setupBackground(theme: Theme) {
// this set column separator line color
view.backgroundColor = theme.separator
}
}
// MARK: - UISplitViewControllerDelegate
extension RootSplitViewController: UISplitViewControllerDelegate {

View File

@ -12,10 +12,13 @@ import CoreDataStack
protocol SidebarViewControllerDelegate: AnyObject {
func sidebarViewController(_ sidebarViewController: SidebarViewController, didSelectTab tab: MainTabBarController.Tab)
func sidebarViewController(_ sidebarViewController: SidebarViewController, didLongPressItem item: SidebarViewModel.Item, sourceView: UIView)
}
final class SidebarViewController: UIViewController, NeedsDependency {
let logger = Logger(subsystem: "SidebarViewController", category: "ViewController")
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@ -127,6 +130,10 @@ extension SidebarViewController {
self.collectionView.contentInset.bottom = height
}
.store(in: &observations)
let sidebarLongPressGestureRecognizer = UILongPressGestureRecognizer()
sidebarLongPressGestureRecognizer.addTarget(self, action: #selector(SidebarViewController.sidebarLongPressGestureRecognizerHandler(_:)))
collectionView.addGestureRecognizer(sidebarLongPressGestureRecognizer)
}
private func setupBackground(theme: Theme) {
@ -148,6 +155,23 @@ extension SidebarViewController {
}
extension SidebarViewController {
@objc private func sidebarLongPressGestureRecognizerHandler(_ sender: UILongPressGestureRecognizer) {
guard sender.state == .began else { return }
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
assert(sender.view === collectionView)
let position = sender.location(in: collectionView)
guard let indexPath = collectionView.indexPathForItem(at: position) else { return }
guard let diffableDataSource = viewModel.diffableDataSource else { return }
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return }
guard let cell = collectionView.cellForItem(at: indexPath) else { return }
delegate?.sidebarViewController(self, didLongPressItem: item, sourceView: cell)
}
}
// MARK: - UICollectionViewDelegate
extension SidebarViewController: UICollectionViewDelegate {
@ -179,25 +203,5 @@ extension SidebarViewController: UICollectionViewDelegate {
default:
assertionFailure()
}
// switch item {
// case .tab(let tab):
// delegate?.sidebarViewController(self, didSelectTab: tab)
// case .searchHistory(let viewModel):
// delegate?.sidebarViewController(self, didSelectSearchHistory: viewModel)
// case .header:
// break
// case .account(let viewModel):
// assert(Thread.isMainThread)
// let authentication = context.managedObjectContext.object(with: viewModel.authenticationObjectID) 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)
// case .addAccount:
// coordinator.present(scene: .welcome, from: self, transition: .modal(animated: true, completion: nil))
// }
}
}

View File

@ -18,6 +18,7 @@ final class SidebarListContentView: UIView, UIContentView {
let avatarButton: CircleAvatarButton = {
let button = CircleAvatarButton()
button.borderWidth = 2
button.borderColor = UIColor.label.cgColor
return button
}()
@ -71,6 +72,9 @@ extension SidebarListContentView {
imageView.contentMode = .scaleAspectFit
avatarButton.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = false
avatarButton.isUserInteractionEnabled = false
}
private func apply(configuration: ContentConfiguration) {