forked from shadowfacts/Tusker
Fast account switching on iPad
This commit is contained in:
parent
a22059a1a1
commit
514e569bd5
|
@ -9,6 +9,8 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
protocol FastAccountSwitcherViewControllerDelegate: AnyObject {
|
protocol FastAccountSwitcherViewControllerDelegate: AnyObject {
|
||||||
|
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController)
|
||||||
|
/// - Parameter point: In the coordinate space of the view to which the pan gesture recognizer is attached.
|
||||||
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool
|
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,11 +22,13 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
@IBOutlet weak var blurContentView: UIView!
|
@IBOutlet weak var blurContentView: UIView!
|
||||||
@IBOutlet weak var accountsStack: UIStackView!
|
@IBOutlet weak var accountsStack: UIStackView!
|
||||||
|
|
||||||
private var accountViews: [FastSwitchingAccountView] = []
|
private(set) var accountViews: [FastSwitchingAccountView] = []
|
||||||
private var lastSelectedAccountViewIndex: Int?
|
private var lastSelectedAccountViewIndex: Int?
|
||||||
private var selectionChangedFeedbackGenerator: UIImpactFeedbackGenerator?
|
private var selectionChangedFeedbackGenerator: UIImpactFeedbackGenerator?
|
||||||
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
private var touchBeganFeedbackWorkItem: DispatchWorkItem?
|
||||||
|
|
||||||
|
var itemOrientation: ItemOrientation = .iconsTrailing
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(nibName: "FastAccountSwitcherViewController", bundle: .main)
|
super.init(nibName: "FastAccountSwitcherViewController", bundle: .main)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +55,15 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
func show() {
|
func show() {
|
||||||
createAccountViews()
|
createAccountViews()
|
||||||
|
// add after creating account views so that the presenter can align based on them
|
||||||
|
delegate?.fastAccountSwitcherAddToViewHierarchy(self)
|
||||||
|
|
||||||
|
switch itemOrientation {
|
||||||
|
case .iconsLeading:
|
||||||
|
accountsStack.alignment = .leading
|
||||||
|
case .iconsTrailing:
|
||||||
|
accountsStack.alignment = .trailing
|
||||||
|
}
|
||||||
|
|
||||||
view.isHidden = false
|
view.isHidden = false
|
||||||
|
|
||||||
|
@ -87,22 +100,27 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hide(completion: (() -> Void)? = nil) {
|
func hide(completion: (() -> Void)? = nil) {
|
||||||
|
guard view.superview != nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
lastSelectedAccountViewIndex = nil
|
lastSelectedAccountViewIndex = nil
|
||||||
selectionChangedFeedbackGenerator = nil
|
selectionChangedFeedbackGenerator = nil
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) {
|
UIView.animate(withDuration: 0.15, delay: 0, options: .curveEaseInOut) {
|
||||||
self.view.alpha = 0
|
self.view.alpha = 0
|
||||||
} completion: { (_) in
|
} completion: { (_) in
|
||||||
|
// todo: probably remove these two lines
|
||||||
self.view.alpha = 1
|
self.view.alpha = 1
|
||||||
self.view.isHidden = true
|
self.view.isHidden = true
|
||||||
completion?()
|
completion?()
|
||||||
|
self.view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createAccountViews() {
|
private func createAccountViews() {
|
||||||
accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
accountsStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
|
|
||||||
let addAccountPlaceholder = FastSwitchingAccountView()
|
let addAccountPlaceholder = FastSwitchingAccountView(orientation: itemOrientation)
|
||||||
accountsStack.addArrangedSubview(addAccountPlaceholder)
|
accountsStack.addArrangedSubview(addAccountPlaceholder)
|
||||||
|
|
||||||
accountViews = [
|
accountViews = [
|
||||||
|
@ -110,7 +128,7 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
]
|
]
|
||||||
|
|
||||||
for account in LocalData.shared.accounts {
|
for account in LocalData.shared.accounts {
|
||||||
let accountView = FastSwitchingAccountView(account: account)
|
let accountView = FastSwitchingAccountView(account: account, orientation: itemOrientation)
|
||||||
accountView.isCurrent = account.id == LocalData.shared.mostRecentAccountID
|
accountView.isCurrent = account.id == LocalData.shared.mostRecentAccountID
|
||||||
accountsStack.addArrangedSubview(accountView)
|
accountsStack.addArrangedSubview(accountView)
|
||||||
accountViews.append(accountView)
|
accountViews.append(accountView)
|
||||||
|
@ -172,10 +190,9 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
handleGestureMoved(to: location)
|
handleGestureMoved(to: location)
|
||||||
|
|
||||||
case .ended:
|
case .ended:
|
||||||
let location = recognizer.location(in: view)
|
|
||||||
if let index = lastSelectedAccountViewIndex {
|
if let index = lastSelectedAccountViewIndex {
|
||||||
switchAccount(newIndex: index)
|
switchAccount(newIndex: index)
|
||||||
} else if !(delegate?.fastAccountSwitcher(self, triggerZoneContains: location) ?? false) {
|
} else if !(delegate?.fastAccountSwitcher(self, triggerZoneContains: recognizer.location(in: recognizer.view)) ?? false) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,9 +274,16 @@ class FastAccountSwitcherViewController: UIViewController {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension FastAccountSwitcherViewController {
|
||||||
|
enum ItemOrientation {
|
||||||
|
case iconsLeading
|
||||||
|
case iconsTrailing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension FastAccountSwitcherViewController: UIGestureRecognizerDelegate {
|
extension FastAccountSwitcherViewController: UIGestureRecognizerDelegate {
|
||||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
let point = gestureRecognizer.location(in: view)
|
let point = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||||
return delegate?.fastAccountSwitcher(self, triggerZoneContains: point) ?? false
|
return delegate?.fastAccountSwitcher(self, triggerZoneContains: point) ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17504.1"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -12,7 +12,6 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="accountsStack" destination="lYU-Bb-3Wi" id="Dxs-ta-ORu"/>
|
<outlet property="accountsStack" destination="lYU-Bb-3Wi" id="Dxs-ta-ORu"/>
|
||||||
<outlet property="blurContentView" destination="1Gd-Da-Vab" id="JqT-uq-1o2"/>
|
<outlet property="blurContentView" destination="1Gd-Da-Vab" id="JqT-uq-1o2"/>
|
||||||
<outlet property="dimmingView" destination="Lul-oI-bZ7" id="JhP-ZX-8fb"/>
|
|
||||||
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
|
||||||
</connections>
|
</connections>
|
||||||
</placeholder>
|
</placeholder>
|
||||||
|
@ -21,10 +20,6 @@
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view alpha="0.25" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Lul-oI-bZ7">
|
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
|
||||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
</view>
|
|
||||||
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5fd-Ni-Owc">
|
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5fd-Ni-Owc">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="1Gd-Da-Vab">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="1Gd-Da-Vab">
|
||||||
|
@ -38,7 +33,7 @@
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="top" secondItem="1Gd-Da-Vab" secondAttribute="topMargin" placeholder="YES" id="KQs-d5-U3f"/>
|
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="top" secondItem="1Gd-Da-Vab" secondAttribute="topMargin" placeholder="YES" id="KQs-d5-U3f"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="lYU-Bb-3Wi" secondAttribute="trailingMargin" constant="8" id="UZh-xR-XVt"/>
|
<constraint firstAttribute="trailing" secondItem="lYU-Bb-3Wi" secondAttribute="trailingMargin" constant="8" id="UZh-xR-XVt"/>
|
||||||
<constraint firstAttribute="bottomMargin" secondItem="lYU-Bb-3Wi" secondAttribute="bottom" id="j6f-r5-NNI"/>
|
<constraint firstAttribute="bottomMargin" secondItem="lYU-Bb-3Wi" secondAttribute="bottom" placeholder="YES" id="j6f-r5-NNI"/>
|
||||||
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="leading" secondItem="1Gd-Da-Vab" secondAttribute="leading" constant="8" id="sae-ga-MGE"/>
|
<constraint firstItem="lYU-Bb-3Wi" firstAttribute="leading" secondItem="1Gd-Da-Vab" secondAttribute="leading" constant="8" id="sae-ga-MGE"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
|
@ -48,20 +43,11 @@
|
||||||
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
|
||||||
<gestureRecognizers/>
|
<gestureRecognizers/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="Lul-oI-bZ7" secondAttribute="trailing" id="9Fp-IG-O9W"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="5fd-Ni-Owc" secondAttribute="trailing" id="c27-P9-lLK"/>
|
<constraint firstAttribute="trailing" secondItem="5fd-Ni-Owc" secondAttribute="trailing" id="c27-P9-lLK"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="Lul-oI-bZ7" secondAttribute="bottom" id="o6y-tG-MwH"/>
|
|
||||||
<constraint firstItem="5fd-Ni-Owc" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="phf-PC-bdH"/>
|
<constraint firstItem="5fd-Ni-Owc" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="phf-PC-bdH"/>
|
||||||
<constraint firstItem="5fd-Ni-Owc" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="rz7-cQ-PIC"/>
|
<constraint firstItem="5fd-Ni-Owc" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="rz7-cQ-PIC"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="5fd-Ni-Owc" secondAttribute="bottom" id="sHl-iD-kGi"/>
|
<constraint firstAttribute="bottom" secondItem="5fd-Ni-Owc" secondAttribute="bottom" id="sHl-iD-kGi"/>
|
||||||
<constraint firstItem="Lul-oI-bZ7" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="tfE-Xr-YBo"/>
|
|
||||||
<constraint firstItem="Lul-oI-bZ7" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="ua7-DO-kdp"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
<variation key="default">
|
|
||||||
<mask key="subviews">
|
|
||||||
<exclude reference="Lul-oI-bZ7"/>
|
|
||||||
</mask>
|
|
||||||
</variation>
|
|
||||||
<point key="canvasLocation" x="140.57971014492756" y="144.64285714285714"/>
|
<point key="canvasLocation" x="140.57971014492756" y="144.64285714285714"/>
|
||||||
</view>
|
</view>
|
||||||
</objects>
|
</objects>
|
||||||
|
|
|
@ -35,19 +35,23 @@ class FastSwitchingAccountView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let orientation: FastAccountSwitcherViewController.ItemOrientation
|
||||||
|
|
||||||
private let usernameLabel = UILabel()
|
private let usernameLabel = UILabel()
|
||||||
private let instanceLabel = UILabel()
|
private let instanceLabel = UILabel()
|
||||||
private let avatarImageView = UIImageView()
|
private let avatarImageView = UIImageView()
|
||||||
|
|
||||||
private var avatarRequest: ImageCache.Request?
|
private var avatarRequest: ImageCache.Request?
|
||||||
|
|
||||||
init(account: LocalData.UserAccountInfo) {
|
init(account: LocalData.UserAccountInfo, orientation: FastAccountSwitcherViewController.ItemOrientation) {
|
||||||
|
self.orientation = orientation
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
commonInit()
|
commonInit()
|
||||||
setupAccount(account: account)
|
setupAccount(account: account)
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init(orientation: FastAccountSwitcherViewController.ItemOrientation) {
|
||||||
|
self.orientation = orientation
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
commonInit()
|
commonInit()
|
||||||
setupPlaceholder()
|
setupPlaceholder()
|
||||||
|
@ -70,7 +74,6 @@ class FastSwitchingAccountView: UIView {
|
||||||
])
|
])
|
||||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
stackView.axis = .vertical
|
stackView.axis = .vertical
|
||||||
stackView.alignment = .trailing
|
|
||||||
addSubview(stackView)
|
addSubview(stackView)
|
||||||
|
|
||||||
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
avatarImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -83,15 +86,30 @@ class FastSwitchingAccountView: UIView {
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: 8),
|
||||||
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
|
avatarImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8),
|
||||||
avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
||||||
avatarImageView.widthAnchor.constraint(equalToConstant: 40),
|
avatarImageView.widthAnchor.constraint(equalToConstant: 40),
|
||||||
avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor),
|
avatarImageView.heightAnchor.constraint(equalTo: avatarImageView.widthAnchor),
|
||||||
|
|
||||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
|
||||||
stackView.trailingAnchor.constraint(equalTo: avatarImageView.leadingAnchor, constant: -8),
|
|
||||||
stackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor),
|
stackView.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
switch orientation {
|
||||||
|
case .iconsLeading:
|
||||||
|
stackView.alignment = .leading
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarImageView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||||
|
stackView.leadingAnchor.constraint(equalTo: avatarImageView.trailingAnchor, constant: 8),
|
||||||
|
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
])
|
||||||
|
|
||||||
|
case .iconsTrailing:
|
||||||
|
stackView.alignment = .trailing
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
avatarImageView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||||
|
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
|
||||||
|
stackView.trailingAnchor.constraint(equalTo: avatarImageView.leadingAnchor, constant: -8),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
updateLabelColors()
|
updateLabelColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,14 @@ class MainSidebarMyProfileCollectionViewCell: UICollectionViewListCell {
|
||||||
config.text = item.title
|
config.text = item.title
|
||||||
config.image = UIImage(systemName: item.imageName!)
|
config.image = UIImage(systemName: item.imageName!)
|
||||||
self.contentConfiguration = config
|
self.contentConfiguration = config
|
||||||
|
if UIDevice.current.userInterfaceIdiom != .mac {
|
||||||
|
let indicator = FastAccountSwitcherIndicatorView()
|
||||||
|
// need to explicitly set the frame to get it vertically centered
|
||||||
|
indicator.frame = CGRect(origin: .zero, size: indicator.intrinsicContentSize)
|
||||||
|
accessories = [
|
||||||
|
.customView(configuration: .init(customView: indicator, placement: .trailing()))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
let mastodonController = MastodonController.getForAccount(account)
|
let mastodonController = MastodonController.getForAccount(account)
|
||||||
guard let account = try? await mastodonController.getOwnAccount(),
|
guard let account = try? await mastodonController.getOwnAccount(),
|
||||||
|
|
|
@ -330,6 +330,14 @@ class MainSidebarViewController: UIViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func myProfileCell() -> UICollectionViewCell? {
|
||||||
|
guard let indexPath = dataSource.indexPath(for: .tab(.myProfile)),
|
||||||
|
let item = collectionView.cellForItem(at: indexPath) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainSidebarViewController {
|
extension MainSidebarViewController {
|
||||||
|
@ -514,6 +522,11 @@ extension MainSidebarViewController: UICollectionViewDelegate {
|
||||||
let activity = userActivityForItem(item) else {
|
let activity = userActivityForItem(item) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if case .tab(.myProfile) = item,
|
||||||
|
// only disable context menu on long-press, to allow fast account switching
|
||||||
|
collectionView.contextMenuInteraction?.menuAppearance == .rich {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) in
|
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { (_) in
|
||||||
return UIMenu(children: [
|
return UIMenu(children: [
|
||||||
UIWindowScene.ActivationAction({ action in
|
UIWindowScene.ActivationAction({ action in
|
||||||
|
@ -530,6 +543,9 @@ extension MainSidebarViewController: UICollectionViewDragDelegate {
|
||||||
let activity = userActivityForItem(item) else {
|
let activity = userActivityForItem(item) else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
if case .tab(.myProfile) = item {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
let provider = NSItemProvider(object: activity)
|
let provider = NSItemProvider(object: activity)
|
||||||
return [UIDragItem(itemProvider: provider)]
|
return [UIDragItem(itemProvider: provider)]
|
||||||
|
|
|
@ -13,6 +13,7 @@ class MainSplitViewController: UISplitViewController {
|
||||||
weak var mastodonController: MastodonController!
|
weak var mastodonController: MastodonController!
|
||||||
|
|
||||||
private var sidebar: MainSidebarViewController!
|
private var sidebar: MainSidebarViewController!
|
||||||
|
private var fastAccountSwitcher: FastAccountSwitcherViewController?
|
||||||
|
|
||||||
// Keep track of navigation stacks per-item so that we can only ever use a single navigation controller
|
// Keep track of navigation stacks per-item so that we can only ever use a single navigation controller
|
||||||
private var navigationStacks: [MainSidebarViewController.Item: [UIViewController]] = [:]
|
private var navigationStacks: [MainSidebarViewController.Item: [UIViewController]] = [:]
|
||||||
|
@ -23,14 +24,6 @@ class MainSplitViewController: UISplitViewController {
|
||||||
viewController(for: .secondary) as? UINavigationController
|
viewController(for: .secondary) as? UINavigationController
|
||||||
}
|
}
|
||||||
|
|
||||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
|
||||||
if UIDevice.current.userInterfaceIdiom == .phone {
|
|
||||||
return .portrait
|
|
||||||
} else {
|
|
||||||
return .all
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init(mastodonController: MastodonController) {
|
init(mastodonController: MastodonController) {
|
||||||
self.mastodonController = mastodonController
|
self.mastodonController = mastodonController
|
||||||
|
|
||||||
|
@ -60,6 +53,18 @@ class MainSplitViewController: UISplitViewController {
|
||||||
select(item: .tab(.timelines))
|
select(item: .tab(.timelines))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if UIDevice.current.userInterfaceIdiom != .mac {
|
||||||
|
let switcher = FastAccountSwitcherViewController()
|
||||||
|
fastAccountSwitcher = switcher
|
||||||
|
switcher.itemOrientation = .iconsLeading
|
||||||
|
switcher.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
switcher.delegate = self
|
||||||
|
sidebar.view.addGestureRecognizer(switcher.createSwitcherGesture())
|
||||||
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(sidebarTapped))
|
||||||
|
tapRecognizer.cancelsTouchesInView = false
|
||||||
|
sidebar.view.addGestureRecognizer(tapRecognizer)
|
||||||
|
}
|
||||||
|
|
||||||
tabBarViewController = MainTabBarViewController(mastodonController: mastodonController)
|
tabBarViewController = MainTabBarViewController(mastodonController: mastodonController)
|
||||||
setViewController(tabBarViewController, for: .compact)
|
setViewController(tabBarViewController, for: .compact)
|
||||||
|
|
||||||
|
@ -100,6 +105,10 @@ class MainSplitViewController: UISplitViewController {
|
||||||
sidebar.select(item: item, animated: false)
|
sidebar.select(item: item, animated: false)
|
||||||
select(item: item)
|
select(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func sidebarTapped() {
|
||||||
|
fastAccountSwitcher?.hide()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,3 +468,27 @@ extension MainSplitViewController: BackgroundableViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MainSplitViewController: FastAccountSwitcherViewControllerDelegate {
|
||||||
|
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController) {
|
||||||
|
view.addSubview(fastAccountSwitcher.view)
|
||||||
|
let currentAccount = fastAccountSwitcher.accountViews.first(where: \.isCurrent)!
|
||||||
|
let myProfileCell = sidebar.myProfileCell()!
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
currentAccount.centerYAnchor.constraint(equalTo: myProfileCell.centerYAnchor),
|
||||||
|
|
||||||
|
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: sidebar.view.trailingAnchor),
|
||||||
|
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
||||||
|
guard !isCollapsed,
|
||||||
|
let cell = sidebar.myProfileCell() else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let cellRect = cell.convert(cell.bounds, to: sidebar.view)
|
||||||
|
return cellRect.contains(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,13 +60,6 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
fastAccountSwitcher = FastAccountSwitcherViewController()
|
fastAccountSwitcher = FastAccountSwitcherViewController()
|
||||||
fastAccountSwitcher.delegate = self
|
fastAccountSwitcher.delegate = self
|
||||||
fastAccountSwitcher.view.translatesAutoresizingMaskIntoConstraints = false
|
fastAccountSwitcher.view.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(fastAccountSwitcher.view)
|
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
||||||
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
||||||
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
|
|
||||||
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
|
|
||||||
])
|
|
||||||
|
|
||||||
tabBar.addGestureRecognizer(fastAccountSwitcher.createSwitcherGesture())
|
tabBar.addGestureRecognizer(fastAccountSwitcher.createSwitcherGesture())
|
||||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tabBarTapped))
|
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tabBarTapped))
|
||||||
|
@ -77,18 +70,17 @@ class MainTabBarViewController: UITabBarController, UITabBarControllerDelegate {
|
||||||
fastSwitcherIndicator = FastAccountSwitcherIndicatorView()
|
fastSwitcherIndicator = FastAccountSwitcherIndicatorView()
|
||||||
fastSwitcherIndicator.translatesAutoresizingMaskIntoConstraints = false
|
fastSwitcherIndicator.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(fastSwitcherIndicator)
|
view.addSubview(fastSwitcherIndicator)
|
||||||
NSLayoutConstraint.activate([
|
|
||||||
fastSwitcherIndicator.widthAnchor.constraint(equalToConstant: 10),
|
|
||||||
fastSwitcherIndicator.heightAnchor.constraint(equalToConstant: 12),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tabBar.isSpringLoaded = true
|
tabBar.isSpringLoaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewDidLayoutSubviews() {
|
||||||
super.viewWillAppear(animated)
|
super.viewDidLayoutSubviews()
|
||||||
|
|
||||||
|
// i hate that we have to do this so often :S
|
||||||
|
// but doing it only in viewWillAppear makes it not appear initially
|
||||||
|
// doing it in viewWillAppear inside a DispatchQueue.main.async works initially but then it disappears when long-pressed
|
||||||
repositionFastSwitcherIndicator()
|
repositionFastSwitcherIndicator()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,11 +193,23 @@ extension MainTabBarViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
extension MainTabBarViewController: FastAccountSwitcherViewControllerDelegate {
|
||||||
|
func fastAccountSwitcherAddToViewHierarchy(_ fastAccountSwitcher: FastAccountSwitcherViewController) {
|
||||||
|
view.addSubview(fastAccountSwitcher.view)
|
||||||
|
NSLayoutConstraint.activate([
|
||||||
|
fastAccountSwitcher.accountsStack.bottomAnchor.constraint(equalTo: fastAccountSwitcher.view.bottomAnchor),
|
||||||
|
|
||||||
|
fastAccountSwitcher.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||||
|
fastAccountSwitcher.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||||
|
fastAccountSwitcher.view.topAnchor.constraint(equalTo: view.topAnchor),
|
||||||
|
fastAccountSwitcher.view.bottomAnchor.constraint(equalTo: tabBar.topAnchor),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
func fastAccountSwitcher(_ fastAccountSwitcher: FastAccountSwitcherViewController, triggerZoneContains point: CGPoint) -> Bool {
|
||||||
guard let myProfileButton = findMyProfileTabBarButton() else {
|
guard let myProfileButton = findMyProfileTabBarButton() else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let locationInButton = myProfileButton.convert(point, from: fastAccountSwitcher.view)
|
let locationInButton = myProfileButton.convert(point, from: tabBar)
|
||||||
return myProfileButton.bounds.contains(locationInButton)
|
return myProfileButton.bounds.contains(locationInButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ import UIKit
|
||||||
|
|
||||||
class FastAccountSwitcherIndicatorView: UIView {
|
class FastAccountSwitcherIndicatorView: UIView {
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
CGSize(width: 10, height: 12)
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue