mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Use modern sticker pack preview controller
This commit is contained in:
@@ -53,6 +53,7 @@ private enum StickerPackNextAction {
|
||||
}
|
||||
|
||||
private final class StickerPackContainer: ASDisplayNode {
|
||||
let index: Int
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private let stickerPack: StickerPackReference
|
||||
@@ -87,9 +88,12 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
var modalProgress: CGFloat = 0.0
|
||||
let expandProgressUpdated: (StickerPackContainer, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
private let interaction: StickerPackPreviewInteraction
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, stickerPack: StickerPackReference, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) {
|
||||
init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPack: StickerPackReference, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) {
|
||||
self.index = index
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.stickerPack = stickerPack
|
||||
@@ -139,19 +143,35 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}
|
||||
|
||||
self.gridNode.interactiveScrollingEnded = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||
return
|
||||
}
|
||||
let contentOffset = strongSelf.gridNode.scrollView.contentOffset
|
||||
let insets = strongSelf.gridNode.scrollView.contentInset
|
||||
|
||||
if contentOffset.y <= -insets.top - 30.0 {
|
||||
strongSelf.isDismissed = true
|
||||
DispatchQueue.main.async {
|
||||
self?.requestDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.gridNode.scrollingCompleted = { [weak self] in
|
||||
guard let strongSelf = self, !strongSelf.isDismissed else {
|
||||
return
|
||||
}
|
||||
let contentOffset = strongSelf.gridNode.scrollView.contentOffset
|
||||
let insets = strongSelf.gridNode.scrollView.contentInset
|
||||
if contentOffset.y <= 0.0 && contentOffset.y >= -insets.top {
|
||||
if contentOffset.y <= -insets.top / 2.0 {
|
||||
strongSelf.gridNode.scrollView.setContentOffset(CGPoint(x: 0.0, y: -insets.top), animated: true)
|
||||
} else {
|
||||
strongSelf.gridNode.scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.itemsDisposable = (loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: stickerPack, forceActualized: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] contents in
|
||||
guard let strongSelf = self else {
|
||||
@@ -336,7 +356,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
guard let (_, gridFrame, titleAreaInset, gridInsets) = self.validLayout else {
|
||||
return 0.0
|
||||
}
|
||||
return gridFrame.minY + gridInsets.top - titleAreaInset
|
||||
return min(self.backgroundNode.frame.minY, gridFrame.minY + gridInsets.top - titleAreaInset)
|
||||
}
|
||||
|
||||
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@@ -408,7 +428,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
var expandProgressTransition = transition
|
||||
var expandUpdated = false
|
||||
|
||||
let modalProgress: CGFloat = unclippedBackgroundY < minBackgroundY ? 1.0 : 0.0
|
||||
let modalProgress: CGFloat = unclippedBackgroundY <= minBackgroundY ? 1.0 : 0.0
|
||||
if abs(self.modalProgress - modalProgress) > CGFloat.ulpOfOne {
|
||||
self.modalProgress = modalProgress
|
||||
expandUpdated = true
|
||||
@@ -478,11 +498,13 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
private let stickerPacks: [StickerPackReference]
|
||||
private let modalProgressUpdated: (CGFloat, ContainedViewLayoutTransition) -> Void
|
||||
private let dismissed: () -> Void
|
||||
private let presentInGlobalOverlay: (ViewController, Any?) -> Void
|
||||
private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let containerContainingNode: ASDisplayNode
|
||||
|
||||
private var containers: [StickerPackContainer] = []
|
||||
private var containers: [Int: StickerPackContainer] = [:]
|
||||
private var selectedStickerPackIndex: Int
|
||||
private var relativeToSelectedStickerPackTransition: CGFloat = 0.0
|
||||
|
||||
@@ -501,6 +523,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
self.selectedStickerPackIndex = initialSelectedStickerPackIndex
|
||||
self.modalProgressUpdated = modalProgressUpdated
|
||||
self.dismissed = dismissed
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
self.sendSticker = sendSticker
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
@@ -510,71 +534,9 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.containers = self.stickerPacks.map { stickerPack in
|
||||
return StickerPackContainer(context: context, presentationData: self.presentationData, stickerPack: stickerPack, decideNextAction: { [weak self] container, action in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout, let index = strongSelf.containers.index(where: { $0 === container }) else {
|
||||
return .dismiss
|
||||
}
|
||||
if index == strongSelf.containers.count - 1 {
|
||||
return .dismiss
|
||||
} else {
|
||||
switch action {
|
||||
case .add:
|
||||
var allAdded = true
|
||||
for i in index + 1 ..< strongSelf.containers.count {
|
||||
if let (_, _, installed) = strongSelf.containers[i].currentStickerPack {
|
||||
if !installed {
|
||||
allAdded = false
|
||||
}
|
||||
} else {
|
||||
allAdded = false
|
||||
}
|
||||
}
|
||||
if allAdded {
|
||||
return .dismiss
|
||||
}
|
||||
case .remove:
|
||||
if strongSelf.containers.count == 1 {
|
||||
return .dismiss
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.selectedStickerPackIndex = strongSelf.selectedStickerPackIndex + 1
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
|
||||
return .navigatedNext
|
||||
}, requestDismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
}, expandProgressUpdated: { [weak self] container, transition in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout, let index = strongSelf.containers.index(where: { $0 === container }) else {
|
||||
return
|
||||
}
|
||||
if index == strongSelf.selectedStickerPackIndex {
|
||||
let modalProgress = strongSelf.containers[strongSelf.selectedStickerPackIndex].modalProgress
|
||||
strongSelf.modalProgressUpdated(modalProgress, transition)
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
}, presentInGlobalOverlay: presentInGlobalOverlay,
|
||||
sendSticker: sendSticker)
|
||||
}
|
||||
|
||||
for container in self.containers {
|
||||
self.containerContainingNode.addSubnode(container)
|
||||
}
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
|
||||
self.addSubnode(self.containerContainingNode)
|
||||
|
||||
self._ready.set(combineLatest(self.containers.map { $0.isReady })
|
||||
|> map { values -> Bool in
|
||||
for value in values {
|
||||
if !value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@@ -593,29 +555,107 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: self.containerContainingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let expandProgress: CGFloat
|
||||
if self.containers.count == 1 {
|
||||
if self.stickerPacks.count == 1 {
|
||||
expandProgress = 1.0
|
||||
} else {
|
||||
expandProgress = self.containers[self.selectedStickerPackIndex].expandProgress
|
||||
expandProgress = self.containers[self.selectedStickerPackIndex]?.expandProgress ?? 0.0
|
||||
}
|
||||
let scaledInset: CGFloat = 16.0
|
||||
let scaledInset: CGFloat = 12.0
|
||||
let scaledDistance: CGFloat = 4.0
|
||||
let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width
|
||||
let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale
|
||||
|
||||
let containerVerticalOffset: CGFloat = (1.0 - expandProgress) * scaledInset * 2.0
|
||||
|
||||
for i in 0 ..< self.containers.count {
|
||||
let container = self.containers[i]
|
||||
|
||||
for i in 0 ..< self.stickerPacks.count {
|
||||
let indexOffset = i - self.selectedStickerPackIndex
|
||||
var scaledOffset: CGFloat = 0.0
|
||||
scaledOffset = -CGFloat(indexOffset) * (1.0 - expandProgress) * (scaledInset * 2.0) + CGFloat(indexOffset) * scaledDistance
|
||||
|
||||
transition.updateFrame(node: container, frame: CGRect(origin: CGPoint(x: CGFloat(indexOffset) * layout.size.width + self.relativeToSelectedStickerPackTransition + scaledOffset, y: containerVerticalOffset), size: layout.size), beginWithCurrentState: true)
|
||||
transition.updateSublayerTransformScaleAndOffset(node: container, scale: containerScale, offset: CGPoint(), beginWithCurrentState: true)
|
||||
if container.validLayout?.0 != layout {
|
||||
container.updateLayout(layout: layout, transition: transition)
|
||||
if abs(indexOffset) <= 1 {
|
||||
let containerTransition: ContainedViewLayoutTransition
|
||||
let container: StickerPackContainer
|
||||
if let current = self.containers[i] {
|
||||
containerTransition = transition
|
||||
container = current
|
||||
} else {
|
||||
containerTransition = .immediate
|
||||
let index = i
|
||||
container = StickerPackContainer(index: index, context: context, presentationData: self.presentationData, stickerPack: self.stickerPacks[i], decideNextAction: { [weak self] container, action in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return .dismiss
|
||||
}
|
||||
if index == strongSelf.stickerPacks.count - 1 {
|
||||
return .dismiss
|
||||
} else {
|
||||
switch action {
|
||||
case .add:
|
||||
var allAdded = true
|
||||
for i in index + 1 ..< strongSelf.stickerPacks.count {
|
||||
if let container = strongSelf.containers[index], let (_, _, installed) = container.currentStickerPack {
|
||||
if !installed {
|
||||
allAdded = false
|
||||
}
|
||||
} else {
|
||||
allAdded = false
|
||||
}
|
||||
}
|
||||
if allAdded {
|
||||
return .dismiss
|
||||
}
|
||||
case .remove:
|
||||
if strongSelf.stickerPacks.count == 1 {
|
||||
return .dismiss
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.selectedStickerPackIndex = strongSelf.selectedStickerPackIndex + 1
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
|
||||
return .navigatedNext
|
||||
}, requestDismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
}, expandProgressUpdated: { [weak self] container, transition in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
if index == strongSelf.selectedStickerPackIndex, let container = strongSelf.containers[strongSelf.selectedStickerPackIndex] {
|
||||
let modalProgress = container.modalProgress
|
||||
strongSelf.modalProgressUpdated(modalProgress, transition)
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
}, presentInGlobalOverlay: presentInGlobalOverlay,
|
||||
sendSticker: sendSticker)
|
||||
self.containerContainingNode.addSubnode(container)
|
||||
self.containers[i] = container
|
||||
}
|
||||
|
||||
containerTransition.updateFrame(node: container, frame: CGRect(origin: CGPoint(x: CGFloat(indexOffset) * layout.size.width + self.relativeToSelectedStickerPackTransition + scaledOffset, y: containerVerticalOffset), size: layout.size), beginWithCurrentState: true)
|
||||
containerTransition.updateSublayerTransformScaleAndOffset(node: container, scale: containerScale, offset: CGPoint(), beginWithCurrentState: true)
|
||||
if container.validLayout?.0 != layout {
|
||||
container.updateLayout(layout: layout, transition: containerTransition)
|
||||
}
|
||||
} else {
|
||||
if let container = self.containers[i] {
|
||||
container.removeFromSupernode()
|
||||
self.containers.removeValue(forKey: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if firstTime {
|
||||
if !self.containers.isEmpty {
|
||||
self._ready.set(combineLatest(self.containers.map { (_, container) in container.isReady })
|
||||
|> map { values -> Bool in
|
||||
for value in values {
|
||||
if !value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
self._ready.set(.single(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,7 +670,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
if self.selectedStickerPackIndex == 0 {
|
||||
self.relativeToSelectedStickerPackTransition = min(0.0, self.relativeToSelectedStickerPackTransition)
|
||||
}
|
||||
if self.selectedStickerPackIndex == self.containers.count - 1 {
|
||||
if self.selectedStickerPackIndex == self.stickerPacks.count - 1 {
|
||||
self.relativeToSelectedStickerPackTransition = max(0.0, self.relativeToSelectedStickerPackTransition)
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
@@ -641,14 +681,14 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
let velocity = recognizer.velocity(in: self.view)
|
||||
if abs(translation.x) > 30.0 {
|
||||
let deltaIndex = translation.x > 0 ? -1 : 1
|
||||
self.selectedStickerPackIndex = max(0, min(self.containers.count - 1, Int(self.selectedStickerPackIndex + deltaIndex)))
|
||||
self.selectedStickerPackIndex = max(0, min(self.stickerPacks.count - 1, Int(self.selectedStickerPackIndex + deltaIndex)))
|
||||
} else if abs(velocity.x) > 100.0 {
|
||||
let deltaIndex = velocity.x > 0 ? -1 : 1
|
||||
self.selectedStickerPackIndex = max(0, min(self.containers.count - 1, Int(self.selectedStickerPackIndex + deltaIndex)))
|
||||
self.selectedStickerPackIndex = max(0, min(self.stickerPacks.count - 1, Int(self.selectedStickerPackIndex + deltaIndex)))
|
||||
}
|
||||
self.relativeToSelectedStickerPackTransition = 0.0
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -659,7 +699,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
self.dimNode.alpha = 1.0
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
let minInset: CGFloat = (self.containers.map { container -> CGFloat in container.topContentInset }).max() ?? 0.0
|
||||
let minInset: CGFloat = (self.containers.map { (_, container) -> CGFloat in container.topContentInset }).max() ?? 0.0
|
||||
self.containerContainingNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerContainingNode.bounds.height - minInset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
@@ -667,10 +707,12 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
self.dimNode.alpha = 0.0
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
|
||||
let minInset: CGFloat = (self.containers.map { container -> CGFloat in container.topContentInset }).max() ?? 0.0
|
||||
let minInset: CGFloat = (self.containers.map { (_, container) -> CGFloat in container.topContentInset }).max() ?? 0.0
|
||||
self.containerContainingNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.containerContainingNode.bounds.height - minInset), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
|
||||
self.modalProgressUpdated(0.0, .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
|
||||
func dismiss() {
|
||||
@@ -688,9 +730,10 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
let selectedContainer = self.containers[self.selectedStickerPackIndex]
|
||||
if selectedContainer.hitTest(self.view.convert(point, to: selectedContainer.view), with: event) == nil {
|
||||
return self.dimNode.view
|
||||
if let selectedContainer = self.containers[self.selectedStickerPackIndex] {
|
||||
if selectedContainer.hitTest(self.view.convert(point, to: selectedContainer.view), with: event) == nil {
|
||||
return self.dimNode.view
|
||||
}
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
@@ -708,6 +751,7 @@ public final class StickerPackScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let stickerPacks: [StickerPackReference]
|
||||
private let initialSelectedStickerPackIndex: Int
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
|
||||
private var controllerNode: StickerPackScreenNode {
|
||||
@@ -721,10 +765,11 @@ public final class StickerPackScreen: ViewController {
|
||||
|
||||
private var alreadyDidAppear: Bool = false
|
||||
|
||||
public init(context: AccountContext, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) {
|
||||
public init(context: AccountContext, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) {
|
||||
self.context = context
|
||||
self.stickerPacks = stickerPacks
|
||||
self.initialSelectedStickerPackIndex = selectedStickerPackIndex
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.sendSticker = sendSticker
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
Reference in New Issue
Block a user