diff --git a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist index cb88c3960..879d91053 100644 --- a/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Mastodon.xcodeproj/xcuserdata/mainasuk.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ AppShared.xcscheme_^#shared#^_ orderHint - 42 + 35 CoreDataStack.xcscheme_^#shared#^_ orderHint - 43 + 36 Mastodon - ASDK.xcscheme_^#shared#^_ @@ -97,7 +97,7 @@ MastodonIntent.xcscheme_^#shared#^_ orderHint - 44 + 37 MastodonIntents.xcscheme_^#shared#^_ @@ -117,7 +117,7 @@ ShareActionExtension.xcscheme_^#shared#^_ orderHint - 41 + 38 SuppressBuildableAutocreation diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 23c2a6f44..696b211bc 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -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 diff --git a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift index 743ad1dc2..722896641 100644 --- a/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift +++ b/Mastodon/Scene/Account/Cell/AddAccountTableViewCell.swift @@ -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), ]) diff --git a/Mastodon/Scene/Root/ContentSplitViewController.swift b/Mastodon/Scene/Root/ContentSplitViewController.swift index 6f5e294cd..6e595f2ee 100644 --- a/Mastodon/Scene/Root/ContentSplitViewController.swift +++ b/Mastodon/Scene/Root/ContentSplitViewController.swift @@ -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) + } + } diff --git a/Mastodon/Scene/Root/RootSplitViewController.swift b/Mastodon/Scene/Root/RootSplitViewController.swift index fc1e7a4f7..22354751d 100644 --- a/Mastodon/Scene/Root/RootSplitViewController.swift +++ b/Mastodon/Scene/Root/RootSplitViewController.swift @@ -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 { diff --git a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift index 060091922..7958c5080 100644 --- a/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift +++ b/Mastodon/Scene/Root/Sidebar/SidebarViewController.swift @@ -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)) -// } } } diff --git a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift index 8c013efda..d85d3a8be 100644 --- a/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift +++ b/Mastodon/Scene/Root/Sidebar/View/SidebarListContentView.swift @@ -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) {