mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-30 03:08:38 +09:00
322 lines
15 KiB
Swift
322 lines
15 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import UIKit
|
|
import Shared
|
|
|
|
class TrayToBrowserAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
if let bvc = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? BrowserViewController,
|
|
let tabTray = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? TabTrayController {
|
|
transitionFromTray(tabTray, toBrowser: bvc, usingContext: transitionContext)
|
|
}
|
|
}
|
|
|
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
return 0.4
|
|
}
|
|
}
|
|
|
|
private extension TrayToBrowserAnimator {
|
|
func transitionFromTray(_ tabTray: TabTrayController, toBrowser bvc: BrowserViewController, usingContext transitionContext: UIViewControllerContextTransitioning) {
|
|
let container = transitionContext.containerView
|
|
guard let selectedTab = bvc.tabManager.selectedTab else { return }
|
|
|
|
let tabManager = bvc.tabManager
|
|
let displayedTabs = selectedTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
|
|
guard let expandFromIndex = displayedTabs.index(of: selectedTab) else { return }
|
|
|
|
bvc.view.frame = transitionContext.finalFrame(for: bvc)
|
|
|
|
// Hide browser components
|
|
bvc.toggleSnackBarVisibility(show: false)
|
|
toggleWebViewVisibility(false, usingTabManager: bvc.tabManager)
|
|
bvc.homePanelController?.view.isHidden = true
|
|
bvc.webViewContainerBackdrop.isHidden = true
|
|
bvc.statusBarOverlay.isHidden = false
|
|
if let url = selectedTab.url, !url.isReaderModeURL {
|
|
bvc.hideReaderModeBar(animated: false)
|
|
}
|
|
|
|
// Take a snapshot of the collection view that we can scale/fade out. We don't need to wait for screen updates since it's already rendered on the screen
|
|
let tabCollectionViewSnapshot = tabTray.collectionView.snapshotView(afterScreenUpdates: false)!
|
|
tabTray.collectionView.alpha = 0
|
|
tabCollectionViewSnapshot.frame = tabTray.collectionView.frame
|
|
container.insertSubview(tabCollectionViewSnapshot, at: 0)
|
|
|
|
// Create a fake cell to use for the upscaling animation
|
|
let startingFrame = calculateCollapsedCellFrameUsingCollectionView(tabTray.collectionView, atIndex: expandFromIndex)
|
|
let cell = createTransitionCellFromTab(bvc.tabManager.selectedTab, withFrame: startingFrame)
|
|
cell.backgroundHolder.layer.cornerRadius = 0
|
|
|
|
container.insertSubview(bvc.view, aboveSubview: tabCollectionViewSnapshot)
|
|
container.insertSubview(cell, aboveSubview: bvc.view)
|
|
|
|
// Flush any pending layout/animation code in preperation of the animation call
|
|
container.layoutIfNeeded()
|
|
|
|
let finalFrame = calculateExpandedCellFrameFromBVC(bvc)
|
|
bvc.footer.alpha = shouldDisplayFooterForBVC(bvc) ? 1 : 0
|
|
bvc.urlBar.isTransitioning = true
|
|
|
|
// Re-calculate the starting transforms for header/footer views in case we switch orientation
|
|
resetTransformsForViews([bvc.header, bvc.readerModeBar, bvc.footer])
|
|
transformHeaderFooterForBVC(bvc, toFrame: startingFrame, container: container)
|
|
|
|
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
|
|
delay: 0, usingSpringWithDamping: 1,
|
|
initialSpringVelocity: 0,
|
|
options: UIViewAnimationOptions(),
|
|
animations: {
|
|
// Scale up the cell and reset the transforms for the header/footers
|
|
cell.frame = finalFrame
|
|
container.layoutIfNeeded()
|
|
cell.title.transform = CGAffineTransform(translationX: 0, y: -cell.title.frame.height)
|
|
|
|
bvc.tabTrayDidDismiss(tabTray)
|
|
UIApplication.shared.windows.first?.backgroundColor = UIConstants.AppBackgroundColor
|
|
tabTray.navigationController?.setNeedsStatusBarAppearanceUpdate()
|
|
tabTray.toolbar.transform = CGAffineTransform(translationX: 0, y: UIConstants.BottomToolbarHeight)
|
|
tabCollectionViewSnapshot.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
|
tabCollectionViewSnapshot.alpha = 0
|
|
}, completion: { finished in
|
|
// Remove any of the views we used for the animation
|
|
cell.removeFromSuperview()
|
|
tabCollectionViewSnapshot.removeFromSuperview()
|
|
bvc.footer.alpha = 1
|
|
bvc.toggleSnackBarVisibility(show: true)
|
|
toggleWebViewVisibility(true, usingTabManager: bvc.tabManager)
|
|
bvc.webViewContainerBackdrop.isHidden = false
|
|
bvc.homePanelController?.view.isHidden = false
|
|
bvc.urlBar.isTransitioning = false
|
|
transitionContext.completeTransition(true)
|
|
})
|
|
}
|
|
}
|
|
|
|
class BrowserToTrayAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
if let bvc = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as? BrowserViewController,
|
|
let tabTray = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as? TabTrayController {
|
|
transitionFromBrowser(bvc, toTabTray: tabTray, usingContext: transitionContext)
|
|
}
|
|
}
|
|
|
|
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
return 0.4
|
|
}
|
|
}
|
|
|
|
private extension BrowserToTrayAnimator {
|
|
func transitionFromBrowser(_ bvc: BrowserViewController, toTabTray tabTray: TabTrayController, usingContext transitionContext: UIViewControllerContextTransitioning) {
|
|
|
|
let container = transitionContext.containerView
|
|
guard let selectedTab = bvc.tabManager.selectedTab else { return }
|
|
|
|
let tabManager = bvc.tabManager
|
|
let displayedTabs = selectedTab.isPrivate ? tabManager.privateTabs : tabManager.normalTabs
|
|
guard let scrollToIndex = displayedTabs.index(of: selectedTab) else { return }
|
|
|
|
tabTray.view.frame = transitionContext.finalFrame(for: tabTray)
|
|
|
|
// Insert tab tray below the browser and force a layout so the collection view can get it's frame right
|
|
container.insertSubview(tabTray.view, belowSubview: bvc.view)
|
|
|
|
// Force subview layout on the collection view so we can calculate the correct end frame for the animation
|
|
tabTray.view.layoutSubviews()
|
|
|
|
tabTray.collectionView.scrollToItem(at: IndexPath(item: scrollToIndex, section: 0), at: .centeredVertically, animated: false)
|
|
|
|
// Build a tab cell that we will use to animate the scaling of the browser to the tab
|
|
let expandedFrame = calculateExpandedCellFrameFromBVC(bvc)
|
|
let cell = createTransitionCellFromTab(bvc.tabManager.selectedTab, withFrame: expandedFrame)
|
|
cell.backgroundHolder.layer.cornerRadius = TabTrayControllerUX.CornerRadius
|
|
|
|
// Take a snapshot of the collection view to perform the scaling/alpha effect
|
|
let tabCollectionViewSnapshot = tabTray.collectionView.snapshotView(afterScreenUpdates: true)!
|
|
tabCollectionViewSnapshot.frame = tabTray.collectionView.frame
|
|
tabCollectionViewSnapshot.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
|
|
tabCollectionViewSnapshot.alpha = 0
|
|
tabTray.view.insertSubview(tabCollectionViewSnapshot, belowSubview: tabTray.toolbar)
|
|
|
|
if let toast = bvc.clipboardBarDisplayHandler?.clipboardToast {
|
|
toast.removeFromSuperview()
|
|
}
|
|
|
|
container.addSubview(cell)
|
|
cell.layoutIfNeeded()
|
|
cell.title.transform = CGAffineTransform(translationX: 0, y: -cell.title.frame.size.height)
|
|
|
|
// Hide views we don't want to show during the animation in the BVC
|
|
bvc.homePanelController?.view.isHidden = true
|
|
bvc.statusBarOverlay.isHidden = true
|
|
bvc.toggleSnackBarVisibility(show: false)
|
|
toggleWebViewVisibility(false, usingTabManager: bvc.tabManager)
|
|
bvc.urlBar.isTransitioning = true
|
|
|
|
// Since we are hiding the collection view and the snapshot API takes the snapshot after the next screen update,
|
|
// the screenshot ends up being blank unless we set the collection view hidden after the screen update happens.
|
|
// To work around this, we dispatch the setting of collection view to hidden after the screen update is completed.
|
|
|
|
DispatchQueue.main.async {
|
|
tabTray.collectionView.isHidden = true
|
|
let finalFrame = calculateCollapsedCellFrameUsingCollectionView(tabTray.collectionView,
|
|
atIndex: scrollToIndex)
|
|
tabTray.toolbar.transform = CGAffineTransform(translationX: 0, y: UIConstants.BottomToolbarHeight)
|
|
|
|
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
|
|
delay: 0, usingSpringWithDamping: 1,
|
|
initialSpringVelocity: 0,
|
|
options: UIViewAnimationOptions(),
|
|
animations: {
|
|
cell.frame = finalFrame
|
|
cell.title.transform = CGAffineTransform.identity
|
|
cell.layoutIfNeeded()
|
|
|
|
UIApplication.shared.windows.first?.backgroundColor = TabTrayControllerUX.BackgroundColor
|
|
tabTray.navigationController?.setNeedsStatusBarAppearanceUpdate()
|
|
|
|
transformHeaderFooterForBVC(bvc, toFrame: finalFrame, container: container)
|
|
|
|
bvc.urlBar.updateAlphaForSubviews(0)
|
|
bvc.footer.alpha = 0
|
|
tabCollectionViewSnapshot.alpha = 1
|
|
|
|
tabTray.toolbar.transform = CGAffineTransform.identity
|
|
resetTransformsForViews([tabCollectionViewSnapshot])
|
|
}, completion: { finished in
|
|
// Remove any of the views we used for the animation
|
|
cell.removeFromSuperview()
|
|
tabCollectionViewSnapshot.removeFromSuperview()
|
|
tabTray.collectionView.isHidden = false
|
|
|
|
bvc.toggleSnackBarVisibility(show: true)
|
|
toggleWebViewVisibility(true, usingTabManager: bvc.tabManager)
|
|
bvc.homePanelController?.view.isHidden = false
|
|
|
|
bvc.urlBar.isTransitioning = false
|
|
transitionContext.completeTransition(true)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
private func transformHeaderFooterForBVC(_ bvc: BrowserViewController, toFrame finalFrame: CGRect, container: UIView) {
|
|
let footerForTransform = footerTransform(bvc.footer.frame, toFrame: finalFrame, container: container)
|
|
let headerForTransform = headerTransform(bvc.header.frame, toFrame: finalFrame, container: container)
|
|
|
|
bvc.footer.transform = footerForTransform
|
|
bvc.header.transform = headerForTransform
|
|
bvc.readerModeBar?.transform = headerForTransform
|
|
}
|
|
|
|
private func footerTransform( _ frame: CGRect, toFrame finalFrame: CGRect, container: UIView) -> CGAffineTransform {
|
|
let frame = container.convert(frame, to: container)
|
|
let endY = finalFrame.maxY - (frame.size.height / 2)
|
|
let endX = finalFrame.midX
|
|
let translation = CGPoint(x: endX - frame.midX, y: endY - frame.midY)
|
|
|
|
let scaleX = finalFrame.width / frame.width
|
|
|
|
var transform = CGAffineTransform.identity
|
|
transform = transform.translatedBy(x: translation.x, y: translation.y)
|
|
transform = transform.scaledBy(x: scaleX, y: scaleX)
|
|
return transform
|
|
}
|
|
|
|
private func headerTransform(_ frame: CGRect, toFrame finalFrame: CGRect, container: UIView) -> CGAffineTransform {
|
|
let frame = container.convert(frame, to: container)
|
|
let endY = finalFrame.minY + (frame.size.height / 2)
|
|
let endX = finalFrame.midX
|
|
let translation = CGPoint(x: endX - frame.midX, y: endY - frame.midY)
|
|
|
|
let scaleX = finalFrame.width / frame.width
|
|
|
|
var transform = CGAffineTransform.identity
|
|
transform = transform.translatedBy(x: translation.x, y: translation.y)
|
|
transform = transform.scaledBy(x: scaleX, y: scaleX)
|
|
return transform
|
|
}
|
|
|
|
//MARK: Private Helper Methods
|
|
private func calculateCollapsedCellFrameUsingCollectionView(_ collectionView: UICollectionView, atIndex index: Int) -> CGRect {
|
|
if let attr = collectionView.collectionViewLayout.layoutAttributesForItem(at: IndexPath(item: index, section: 0)) {
|
|
return collectionView.convert(attr.frame, to: collectionView.superview)
|
|
} else {
|
|
return CGRect.zero
|
|
}
|
|
}
|
|
|
|
private func calculateExpandedCellFrameFromBVC(_ bvc: BrowserViewController) -> CGRect {
|
|
var frame = bvc.webViewContainer.frame
|
|
|
|
// If we're navigating to a home panel and we were expecting to show the toolbar, add more height to end frame since
|
|
// there is no toolbar for home panels
|
|
if !bvc.shouldShowFooterForTraitCollection(bvc.traitCollection) {
|
|
return frame
|
|
} else if let url = bvc.tabManager.selectedTab?.url, url.isAboutURL && bvc.toolbar == nil {
|
|
frame.size.height += UIConstants.BottomToolbarHeight
|
|
}
|
|
|
|
return frame
|
|
}
|
|
|
|
private func shouldDisplayFooterForBVC(_ bvc: BrowserViewController) -> Bool {
|
|
if bvc.shouldShowFooterForTraitCollection(bvc.traitCollection) {
|
|
if let url = bvc.tabManager.selectedTab?.url {
|
|
return !url.isAboutURL
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func toggleWebViewVisibility(_ show: Bool, usingTabManager tabManager: TabManager) {
|
|
for i in 0..<tabManager.count {
|
|
if let tab = tabManager[i] {
|
|
tab.webView?.isHidden = !show
|
|
}
|
|
}
|
|
}
|
|
|
|
private func resetTransformsForViews(_ views: [UIView?]) {
|
|
for view in views {
|
|
// Reset back to origin
|
|
view?.transform = CGAffineTransform.identity
|
|
}
|
|
}
|
|
|
|
private func transformToolbarsToFrame(_ toolbars: [UIView?], toRect endRect: CGRect) {
|
|
for toolbar in toolbars {
|
|
// Reset back to origin
|
|
toolbar?.transform = CGAffineTransform.identity
|
|
|
|
// Transform from origin to where we want them to end up
|
|
if let toolbarFrame = toolbar?.frame {
|
|
toolbar?.transform = CGAffineTransformMakeRectToRect(toolbarFrame, toFrame: endRect)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func createTransitionCellFromTab(_ tab: Tab?, withFrame frame: CGRect) -> TabCell {
|
|
let cell = TabCell(frame: frame)
|
|
cell.screenshotView.image = tab?.screenshot
|
|
cell.titleText.text = tab?.displayTitle
|
|
|
|
if let tab = tab, tab.isPrivate {
|
|
cell.style = .dark
|
|
}
|
|
|
|
if let favIcon = tab?.displayFavicon {
|
|
cell.favicon.sd_setImage(with: URL(string: favIcon.url)!)
|
|
} else {
|
|
let defaultFavicon = UIImage(named: "defaultFavicon")
|
|
if tab?.isPrivate ?? false {
|
|
cell.favicon.image = defaultFavicon
|
|
cell.favicon.tintColor = (tab?.isPrivate ?? false) ? UIColor.white : UIColor.darkGray
|
|
} else {
|
|
cell.favicon.image = defaultFavicon
|
|
}
|
|
}
|
|
return cell
|
|
}
|