mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Attachment menu improvements
This commit is contained in:
parent
f0e55a0e0a
commit
a354c45ccf
@ -74,17 +74,17 @@ private func generateShadowImage() -> UIImage? {
|
||||
}
|
||||
|
||||
private func generateMaskImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 390.0, height: 620.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 609.0), cornerRadius: 10.0).cgPath
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
try? drawSvgPath(context, path: "M183.219,608.889 H206.781 C205.648,608.889 204.567,609.37 203.808,610.213 L197.23,617.522 C196.038,618.847 193.962,618.847 192.77,617.522 L186.192,610.213 C185.433,609.37 184.352,608.889 183.219,608.889 Z ")
|
||||
})
|
||||
try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ")
|
||||
})?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110)
|
||||
}
|
||||
|
||||
public class AttachmentController: ViewController {
|
||||
@ -368,6 +368,8 @@ public class AttachmentController: ViewController {
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.animating = false
|
||||
}
|
||||
|
||||
snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
@ -457,7 +459,9 @@ public class AttachmentController: ViewController {
|
||||
var containerLayout = layout
|
||||
let containerRect: CGRect
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
let size = CGSize(width: 390.0, height: 620.0)
|
||||
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0
|
||||
|
||||
let size = CGSize(width: 390.0, height: min(620.0, availableHeight))
|
||||
|
||||
let insets = layout.insets(options: [.input])
|
||||
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
|
||||
@ -471,9 +475,12 @@ public class AttachmentController: ViewController {
|
||||
if self.wrapperNode.view.mask == nil {
|
||||
let maskView = UIImageView()
|
||||
maskView.image = generateMaskImage()
|
||||
maskView.frame = CGRect(origin: CGPoint(), size: maskView.image?.size ?? CGSize())
|
||||
maskView.contentMode = .scaleToFill
|
||||
self.wrapperNode.view.mask = maskView
|
||||
}
|
||||
if let maskView = self.wrapperNode.view.mask {
|
||||
transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
|
||||
self.shadowNode.alpha = 1.0
|
||||
if self.shadowNode.image == nil {
|
||||
|
@ -114,13 +114,26 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
var _cachedTag: Int32?
|
||||
var tag: Int32? {
|
||||
if let tag = self._cachedTag {
|
||||
return tag
|
||||
} else if let asset = self.asset, let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
self._cachedTag = tag
|
||||
return tag
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool = false) {
|
||||
if self.checkNode == nil, let _ = self.interaction?.selectionState, let theme = self.theme {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
if let strongSelf = self, let asset = strongSelf.asset, let interaction = strongSelf.interaction {
|
||||
if let legacyAsset = TGMediaAsset(phAsset: asset) {
|
||||
interaction.toggleSelection(legacyAsset, value)
|
||||
interaction.toggleSelection(legacyAsset, value, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,6 +177,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index {
|
||||
let editingContext = interaction.editingState
|
||||
let asset = fetchResult.object(at: index)
|
||||
|
@ -17,13 +17,15 @@ import LegacyMediaPickerUI
|
||||
import AttachmentUI
|
||||
import ContextUI
|
||||
import WebSearchUI
|
||||
import SparseItemGrid
|
||||
import UndoUI
|
||||
|
||||
let overflowInset: CGFloat = 70.0
|
||||
let overflowInset: CGFloat = 0.0
|
||||
|
||||
final class MediaPickerInteraction {
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Void
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void
|
||||
let schedule: () -> Void
|
||||
let dismissInput: () -> Void
|
||||
@ -31,7 +33,7 @@ final class MediaPickerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
var hiddenMediaId: String?
|
||||
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
self.toggleSelection = toggleSelection
|
||||
@ -73,6 +75,33 @@ private struct MediaPickerGridTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
struct Month: Equatable {
|
||||
var packedValue: Int32
|
||||
|
||||
init(packedValue: Int32) {
|
||||
self.packedValue = packedValue
|
||||
}
|
||||
|
||||
init(localTimestamp: Int32) {
|
||||
var time: time_t = time_t(localTimestamp)
|
||||
var timeinfo: tm = tm()
|
||||
gmtime_r(&time, &timeinfo)
|
||||
|
||||
let year = UInt32(timeinfo.tm_year)
|
||||
let month = UInt32(timeinfo.tm_mon)
|
||||
|
||||
self.packedValue = Int32(bitPattern: year | (month << 16))
|
||||
}
|
||||
|
||||
var year: Int32 {
|
||||
return Int32(bitPattern: (UInt32(bitPattern: self.packedValue) >> 0) & 0xffff)
|
||||
}
|
||||
|
||||
var month: Int32 {
|
||||
return Int32(bitPattern: (UInt32(bitPattern: self.packedValue) >> 16) & 0xffff)
|
||||
}
|
||||
}
|
||||
|
||||
public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
@ -131,6 +160,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
fileprivate var cameraView: TGAttachmentCameraView?
|
||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||
private var manageNode: MediaPickerManageNode?
|
||||
private var scrollingArea: SparseItemGridScrollingArea
|
||||
|
||||
private var selectionNode: MediaPickerSelectedListNode?
|
||||
|
||||
@ -167,6 +197,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: self.presentationData.theme.rootController.tabBar.backgroundColor)
|
||||
self.backgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
self.gridNode = GridNode()
|
||||
self.scrollingArea = SparseItemGridScrollingArea()
|
||||
|
||||
super.init()
|
||||
|
||||
@ -177,7 +208,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.gridNode)
|
||||
|
||||
self.containerNode.addSubnode(self.scrollingArea)
|
||||
|
||||
let collection = controller.collection
|
||||
let preloadPromise = self.preloadPromise
|
||||
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|
||||
@ -215,6 +247,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
self.gridNode.visibleContentOffsetChanged = { [weak self] _ in
|
||||
self?.updateNavigation(transition: .immediate)
|
||||
self?.updateScrollingArea()
|
||||
}
|
||||
|
||||
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
||||
@ -295,6 +328,21 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollingArea.beginScrolling = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return nil
|
||||
}
|
||||
return strongSelf.gridNode.scrollView
|
||||
}
|
||||
self.scrollingArea.setContentOffset = { [weak self] offset in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
// strongSelf.isFastScrolling = true
|
||||
strongSelf.gridNode.scrollView.setContentOffset(offset, animated: false)
|
||||
// strongSelf.isFastScrolling = false
|
||||
}
|
||||
|
||||
if self.controller?.collection == nil {
|
||||
let cameraView = TGAttachmentCameraView(forSelfPortrait: false)!
|
||||
cameraView.clipsToBounds = true
|
||||
@ -347,6 +395,44 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.view.window?.endEditing(true)
|
||||
}
|
||||
|
||||
private func scrollerTextForTag(tag: Int32) -> String {
|
||||
let month = Month(packedValue: tag)
|
||||
return stringForMonth(strings: self.presentationData.strings, month: month.month, ofYear: month.year)
|
||||
}
|
||||
|
||||
private var currentScrollingTag: Int32?
|
||||
private func updateScrollingArea() {
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
var tag: Int32?
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode {
|
||||
tag = itemNode.tag
|
||||
}
|
||||
}
|
||||
|
||||
let dateString = tag.flatMap { self.scrollerTextForTag(tag: $0) }
|
||||
if self.currentScrollingTag != tag {
|
||||
self.currentScrollingTag = tag
|
||||
if self.scrollingArea.isDragging {
|
||||
self.scrollingArea.feedbackTap()
|
||||
}
|
||||
}
|
||||
|
||||
self.scrollingArea.update(
|
||||
containerSize: layout.size,
|
||||
containerInsets: self.gridNode.gridLayout.insets,
|
||||
contentHeight: self.gridNode.scrollView.contentSize.height,
|
||||
contentOffset: self.gridNode.scrollView.bounds.minY,
|
||||
isScrolling: self.gridNode.scrollView.isDragging || self.gridNode.scrollView.isDecelerating,
|
||||
date: (dateString ?? "", tag ?? 0),
|
||||
theme: self.presentationData.theme,
|
||||
transition: .immediate
|
||||
)
|
||||
}
|
||||
|
||||
private func updateState(_ state: State) {
|
||||
guard let controller = self.controller, let interaction = controller.interaction else {
|
||||
return
|
||||
@ -442,7 +528,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
private var currentDisplayMode: DisplayMode = .all
|
||||
func updateMode(_ displayMode: DisplayMode) {
|
||||
func updateDisplayMode(_ displayMode: DisplayMode) {
|
||||
let updated = self.currentDisplayMode != displayMode
|
||||
self.currentDisplayMode = displayMode
|
||||
|
||||
@ -452,6 +538,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
selectionNode.alpha = 0.0
|
||||
selectionNode.isUserInteractionEnabled = false
|
||||
selectionNode.interaction = self.controller?.interaction
|
||||
selectionNode.getTransitionView = { [weak self] identifier in
|
||||
if let strongSelf = self {
|
||||
var view: UIView?
|
||||
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.asset?.localIdentifier == identifier {
|
||||
view = itemNode.view
|
||||
}
|
||||
}
|
||||
return view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.containerNode.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
|
||||
self.selectionNode = selectionNode
|
||||
|
||||
@ -460,25 +559,26 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
self.gridNode.isUserInteractionEnabled = displayMode == .all
|
||||
self.selectionNode?.isUserInteractionEnabled = displayMode == .selected
|
||||
|
||||
var completion: () -> Void = {}
|
||||
if updated && displayMode == .all {
|
||||
completion = {
|
||||
self.updateNavigation(transition: .animated(duration: 0.1, curve: .easeInOut))
|
||||
self.selectionNode?.alpha = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
transition.updateAlpha(node: selectionNode, alpha: displayMode == .selected ? 1.0 : 0.0, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
selectionNode.isUserInteractionEnabled = displayMode == .selected
|
||||
}
|
||||
|
||||
if updated && displayMode == .selected {
|
||||
self.updateNavigation(transition: .immediate)
|
||||
if updated {
|
||||
switch displayMode {
|
||||
case .selected:
|
||||
self.selectionNode?.alpha = 1.0
|
||||
self.selectionNode?.animateIn(completion: completion)
|
||||
self.updateNavigation(transition: .immediate)
|
||||
case .all:
|
||||
self.selectionNode?.animateOut(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,7 +670,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
let proceed: (Bool) -> Void = { convertToJpeg in
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
return
|
||||
}
|
||||
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
||||
@ -804,6 +904,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
let cleanGridInsets = UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
|
||||
let gridInsets = UIEdgeInsets(top: insets.top + manageHeight, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right)
|
||||
transition.updateFrame(node: self.gridNode, frame: innerBounds)
|
||||
self.scrollingArea.frame = innerBounds
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: innerBounds)
|
||||
self.backgroundNode.update(size: bounds.size, transition: transition)
|
||||
@ -834,7 +935,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
if selectedItems.count < 1 && self.currentDisplayMode == .selected {
|
||||
self.updateMode(.all)
|
||||
self.updateDisplayMode(.all)
|
||||
Queue.mainQueue().after(0.3, updateSelectionNode)
|
||||
} else {
|
||||
updateSelectionNode()
|
||||
@ -949,7 +1050,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
self.titleView.indexUpdated = { [weak self] index in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.updateMode(index == 0 ? .all : .selected)
|
||||
strongSelf.controllerNode.updateDisplayMode(index == 0 ? .all : .selected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -994,9 +1095,23 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, toggleSelection: { [weak self] item, value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
|
||||
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
var showUndo = false
|
||||
if suggestUndo {
|
||||
if !value {
|
||||
selectionState.saveState()
|
||||
showUndo = true
|
||||
} else {
|
||||
selectionState.clearSavedState()
|
||||
}
|
||||
}
|
||||
|
||||
selectionState.setItem(item, selected: value)
|
||||
|
||||
if showUndo {
|
||||
strongSelf.showSelectionUndo(item: item, count: Int32(selectionState.savedStateDifference()))
|
||||
}
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
@ -1037,6 +1152,51 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private weak var undoController: UndoOverlayController?
|
||||
private func showSelectionUndo(item: TGMediaSelectableItem, count: Int32) {
|
||||
var asset: PHAsset?
|
||||
if let item = item as? TGMediaAsset {
|
||||
asset = item.backingAsset
|
||||
} else if let item = item as? TGCameraCapturedVideo {
|
||||
asset = item.originalAsset.backingAsset
|
||||
}
|
||||
|
||||
guard let asset = asset else {
|
||||
return
|
||||
}
|
||||
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
let targetSize = CGSize(width: 64.0 * scale, height: 64.0 * scale)
|
||||
let _ = (assetImage(asset: asset, targetSize: targetSize, exact: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.presentationData
|
||||
if let undoController = strongSelf.undoController {
|
||||
let text = presentationData.strings.Attachment_DeselectedItems(count)
|
||||
undoController.content = .image(image: image ?? UIImage(), text: text)
|
||||
} else {
|
||||
let text = presentationData.strings.Attachment_DeselectedItems(count)
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), text: text), elevatedLayout: true, action: { [weak self] action in
|
||||
guard let strongSelf = self else {
|
||||
return true
|
||||
}
|
||||
switch action {
|
||||
case .undo:
|
||||
strongSelf.interaction?.selectionState?.restoreState()
|
||||
default:
|
||||
strongSelf.interaction?.selectionState?.clearSavedState()
|
||||
}
|
||||
return true
|
||||
})
|
||||
strongSelf.present(undoController, in: .window(.root))
|
||||
strongSelf.undoController = undoController
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private var selectionCount: Int32 = 0
|
||||
fileprivate func updateSelectionState(count: Int32) {
|
||||
self.selectionCount = count
|
||||
|
@ -118,7 +118,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay))
|
||||
checkNode.valueChanged = { [weak self] value in
|
||||
if let strongSelf = self, let interaction = strongSelf.interaction {
|
||||
interaction.toggleSelection(strongSelf.asset, value)
|
||||
interaction.toggleSelection(strongSelf.asset, value, true)
|
||||
}
|
||||
}
|
||||
self.addSubnode(checkNode)
|
||||
@ -189,6 +189,41 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
view.frame = self.convert(self.bounds, to: nil)
|
||||
return view
|
||||
}
|
||||
|
||||
func animateFrom(_ view: UIView) {
|
||||
view.alpha = 0.0
|
||||
|
||||
let frame = view.convert(view.bounds, to: self.supernode?.view)
|
||||
let targetFrame = self.frame
|
||||
|
||||
self.updateLayout(size: frame.size, transition: .immediate)
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in
|
||||
view?.alpha = 1.0
|
||||
})
|
||||
}
|
||||
|
||||
func animateTo(_ view: UIView) {
|
||||
view.alpha = 0.0
|
||||
|
||||
let frame = self.frame
|
||||
let targetFrame = view.convert(view.bounds, to: self.supernode?.view)
|
||||
|
||||
let corners = self.corners
|
||||
|
||||
self.corners = []
|
||||
self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring))
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||
view?.alpha = 1.0
|
||||
|
||||
self?.corners = corners
|
||||
self?.updateLayout(size: frame.size, transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.01) {
|
||||
self?.layer.removeAllAnimations()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class MessageBackgroundNode: ASDisplayNode {
|
||||
@ -288,7 +323,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
}
|
||||
return (false, false, nil)
|
||||
}, willBegin: { _ in
|
||||
// self?.willBeginReorder(point)
|
||||
|
||||
}, began: { [weak self] itemNode in
|
||||
self?.beginReordering(itemNode: itemNode)
|
||||
}, ended: { [weak self] point in
|
||||
@ -309,6 +344,74 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
})
|
||||
}
|
||||
|
||||
var getTransitionView: (String) -> UIView? = { _ in return nil }
|
||||
|
||||
func animateIn(completion: @escaping () -> Void = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateFrom(transitionView)
|
||||
} else {
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
if let topNode = self.messageNodes?.first, !topNode.alpha.isZero {
|
||||
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
if let bottomNode = self.messageNodes?.last, !bottomNode.alpha.isZero {
|
||||
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
completion()
|
||||
|
||||
if let strongSelf = self {
|
||||
Queue.mainQueue().after(0.01) {
|
||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||
backgroundNode.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
strongSelf.messageNodes?.first?.layer.removeAllAnimations()
|
||||
strongSelf.messageNodes?.last?.layer.removeAllAnimations()
|
||||
|
||||
strongSelf.wallpaperBackgroundNode.layer.removeAllAnimations()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateTo(transitionView)
|
||||
}
|
||||
}
|
||||
|
||||
self.messageNodes?.first?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
self.messageNodes?.first?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
|
||||
self.messageNodes?.last?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
self.messageNodes?.last?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: 30.0), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
@ -556,9 +659,14 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
self.backgroundNodes[groupIndex] = groupBackgroundNode
|
||||
self.scrollNode.insertSubnode(groupBackgroundNode, at: 0)
|
||||
}
|
||||
|
||||
var itemTransition = transition
|
||||
if groupBackgroundNode.frame.width.isZero {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
transition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
|
||||
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: transition)
|
||||
itemTransition.updateFrame(node: groupBackgroundNode, frame: groupRect.insetBy(dx: -5.0, dy: -2.0).offsetBy(dx: 3.0, dy: 0.0))
|
||||
groupBackgroundNode.update(size: groupBackgroundNode.frame.size, theme: theme, wallpaper: wallpaper, graphics: graphics, wallpaperBackgroundNode: self.wallpaperBackgroundNode, transition: itemTransition)
|
||||
|
||||
for (item, itemRect, itemPosition) in items {
|
||||
if let identifier = item.uniqueIdentifier, let itemNode = self.itemNodes[identifier] {
|
||||
@ -578,8 +686,18 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
itemNode.corners = corners
|
||||
itemNode.radius = bubbleCorners.mainRadius
|
||||
|
||||
itemNode.updateLayout(size: itemRect.size, transition: transition)
|
||||
transition.updateFrame(node: itemNode, frame: itemRect.offsetBy(dx: groupRect.minX, dy: groupRect.minY))
|
||||
var itemTransition = itemTransition
|
||||
if itemNode.frame.width.isZero {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
itemNode.updateLayout(size: itemRect.size, transition: itemTransition)
|
||||
itemTransition.updateFrame(node: itemNode, frame: itemRect.offsetBy(dx: groupRect.minX, dy: groupRect.minY))
|
||||
|
||||
if case .immediate = itemTransition, case .animated = transition {
|
||||
transition.animateTransformScale(node: itemNode, from: 0.01)
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user