-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathBrowserTrayAnimators.swift
318 lines (262 loc) · 14.6 KB
/
BrowserTrayAnimators.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
/* 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
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.headerBackdrop, bvc.readerModeBar, bvc.footer, bvc.footerBackdrop])
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)
tabTray.toolbar.transform = CGAffineTransform(translationX: 0, y: UIConstants.ToolbarHeight)
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
cell.innerStroke.isHidden = true
// 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.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.ToolbarHeight)
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()
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.footerBackdrop.transform = footerForTransform
bvc.header.transform = headerForTransform
bvc.readerModeBar?.transform = headerForTransform
bvc.headerBackdrop.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.ToolbarHeight
}
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.background.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 {
var defaultFavicon = UIImage(named: "defaultFavicon")
if tab?.isPrivate ?? false {
defaultFavicon = defaultFavicon?.withRenderingMode(.alwaysTemplate)
cell.favicon.image = defaultFavicon
cell.favicon.tintColor = (tab?.isPrivate ?? false) ? UIColor.white : UIColor.darkGray
} else {
cell.favicon.image = defaultFavicon
}
}
return cell
}