mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1411 lines
81 KiB
Swift
1411 lines
81 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import AccountContext
|
|
import TelegramPresentationData
|
|
import TelegramUIPreferences
|
|
import MergeLists
|
|
import StickerPackPreviewUI
|
|
import OverlayStatusController
|
|
import PresentationDataUtils
|
|
import SearchBarNode
|
|
import UndoUI
|
|
import SegmentedControlNode
|
|
import LegacyComponents
|
|
import ChatPresentationInterfaceState
|
|
|
|
private enum DrawingPaneType {
|
|
case stickers
|
|
case masks
|
|
}
|
|
|
|
private struct DrawingPaneArrangement {
|
|
let panes: [DrawingPaneType]
|
|
let currentIndex: Int
|
|
let indexTransition: CGFloat
|
|
|
|
func withIndexTransition(_ indexTransition: CGFloat) -> DrawingPaneArrangement {
|
|
return DrawingPaneArrangement(panes: self.panes, currentIndex: currentIndex, indexTransition: indexTransition)
|
|
}
|
|
|
|
func withCurrentIndex(_ currentIndex: Int) -> DrawingPaneArrangement {
|
|
return DrawingPaneArrangement(panes: self.panes, currentIndex: currentIndex, indexTransition: self.indexTransition)
|
|
}
|
|
}
|
|
|
|
private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
|
private let context: AccountContext
|
|
private var presentationData: PresentationData
|
|
fileprivate var selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
|
private var searchItemContext = StickerPaneSearchGlobalItemContext()
|
|
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings)>
|
|
|
|
private let controllerInteraction: ChatControllerInteraction
|
|
private var stickersNodeInteraction: ChatMediaInputNodeInteraction!
|
|
private var masksNodeInteraction: ChatMediaInputNodeInteraction!
|
|
|
|
private let collectionListPanel: ASDisplayNode
|
|
private let collectionListContainer: ASDisplayNode
|
|
|
|
private let blurView: UIView
|
|
|
|
private let topPanel: ASDisplayNode
|
|
private let segmentedControlNode: SegmentedControlNode
|
|
private let cancelButton: HighlightableButtonNode
|
|
private let topSeparatorNode: ASDisplayNode
|
|
private let bottomSeparatorNode: ASDisplayNode
|
|
|
|
private let stickerListView: ListView
|
|
private let maskListView: ListView
|
|
private var hiddenListView: ListView?
|
|
|
|
private var searchContainerNode: PaneSearchContainerNode?
|
|
private let searchContainerNodeLoadedDisposable = MetaDisposable()
|
|
|
|
private let stickerPane: ChatMediaInputStickerPane
|
|
private let maskPane: ChatMediaInputStickerPane
|
|
private var hiddenPane: ChatMediaInputStickerPane?
|
|
|
|
private let stickerItemCollectionsViewPosition = Promise<StickerPacksCollectionPosition>()
|
|
private var currentStickerPacksCollectionPosition: StickerPacksCollectionPosition?
|
|
private var currentStickerView: ItemCollectionsView?
|
|
|
|
private let maskItemCollectionsViewPosition = Promise<StickerPacksCollectionPosition>()
|
|
private var currentMaskPacksCollectionPosition: StickerPacksCollectionPosition?
|
|
private var currentMaskView: ItemCollectionsView?
|
|
|
|
private var paneArrangement: DrawingPaneArrangement
|
|
|
|
private var animatingStickerPaneOut = false
|
|
private var animatingMaskPaneOut = false
|
|
|
|
private var panRecognizer: UIPanGestureRecognizer?
|
|
|
|
private var validLayout: ContainerViewLayout?
|
|
|
|
private var disposable = MetaDisposable()
|
|
private var maskDisposable = MetaDisposable()
|
|
|
|
private let _ready = Promise<Bool>()
|
|
var ready: Promise<Bool> {
|
|
return self._ready
|
|
}
|
|
private var didSetReady: Bool = false
|
|
|
|
fileprivate var dismiss: (() -> Void)?
|
|
|
|
init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) {
|
|
self.context = context
|
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
self.presentationData = presentationData
|
|
self.selectSticker = selectSticker
|
|
|
|
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings))
|
|
|
|
var selectStickerImpl: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
|
|
|
self.controllerInteraction = ChatControllerInteraction(openMessage: { _, _ in
|
|
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
|
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
|
|
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
|
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
|
}, presentController: { _, _ in
|
|
}, presentControllerInCurrent: { _, _ in
|
|
}, navigationController: {
|
|
return nil
|
|
}, chatControllerNode: {
|
|
return nil
|
|
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _, _ in }, longTap: { _, _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in
|
|
}, canSetupReply: { _ in
|
|
return .none
|
|
}, navigateToFirstDateMessage: { _, _ in
|
|
}, requestRedeliveryOfFailedMessages: { _ in
|
|
}, addContact: { _ in
|
|
}, rateCall: { _, _, _ in
|
|
}, requestSelectMessagePollOptions: { _, _ in
|
|
}, requestOpenMessagePollResults: { _, _ in
|
|
}, openAppStorePage: {
|
|
}, displayMessageTooltip: { _, _, _, _ in
|
|
}, seekToTimecode: { _, _, _ in
|
|
}, scheduleCurrentMessage: {
|
|
}, sendScheduledMessagesNow: { _ in
|
|
}, editScheduledMessagesTime: { _ in
|
|
}, performTextSelectionAction: { _, _, _ in
|
|
}, displayImportedMessageTooltip: { _ in
|
|
}, displaySwipeToReplyHint: {
|
|
}, dismissReplyMarkupMessage: { _ in
|
|
}, openMessagePollResults: { _, _ in
|
|
}, openPollCreation: { _ in
|
|
}, displayPollSolution: { _, _ in
|
|
}, displayPsa: { _, _ in
|
|
}, displayDiceTooltip: { _ in
|
|
}, animateDiceSuccess: { _ in
|
|
}, displayPremiumStickerTooltip: { _, _ in
|
|
}, openPeerContextMenu: { _, _, _, _, _ in
|
|
}, openMessageReplies: { _, _, _ in
|
|
}, openReplyThreadOriginalMessage: { _ in
|
|
}, openMessageStats: { _ in
|
|
}, editMessageMedia: { _, _ in
|
|
}, copyText: { _ in
|
|
}, displayUndo: { _ in
|
|
}, isAnimatingMessage: { _ in
|
|
return false
|
|
}, getMessageTransitionNode: {
|
|
return nil
|
|
}, updateChoosingSticker: { _ in
|
|
}, commitEmojiInteraction: { _, _, _, _ in
|
|
}, openLargeEmojiInfo: { _, _, _ in
|
|
}, openJoinLink: { _ in
|
|
}, openWebView: { _, _, _, _ in
|
|
}, requestMessageUpdate: { _ in
|
|
}, cancelInteractiveKeyboardGestures: {
|
|
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,
|
|
pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: true), presentationContext: ChatPresentationContext(context: context, backgroundNode: nil))
|
|
|
|
self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
|
|
|
self.topPanel = ASDisplayNode()
|
|
self.topPanel.clipsToBounds = true
|
|
self.topPanel.backgroundColor = UIColor(rgb: 0x151515)
|
|
self.topPanel.alpha = 0.3
|
|
|
|
let segmentedTheme = SegmentedControlTheme(backgroundColor: UIColor(rgb: 0x2c2d2d), foregroundColor: UIColor(rgb: 0x656565), shadowColor: UIColor.clear, textColor: .white, dividerColor: .white)
|
|
self.segmentedControlNode = SegmentedControlNode(theme: segmentedTheme, items: [SegmentedControlItem(title: self.presentationData.strings.Paint_Stickers), SegmentedControlItem(title: self.presentationData.strings.Paint_Masks)], selectedIndex: 0)
|
|
|
|
self.cancelButton = HighlightableButtonNode()
|
|
self.cancelButton.setAttributedTitle(NSAttributedString(string: self.presentationData.strings.Common_Cancel, font: Font.regular(17.0), textColor: .white), for: .normal)
|
|
|
|
self.collectionListPanel = ASDisplayNode()
|
|
self.collectionListPanel.clipsToBounds = true
|
|
self.collectionListPanel.backgroundColor = UIColor(rgb: 0x151515)
|
|
self.collectionListPanel.alpha = 0.3
|
|
|
|
self.collectionListContainer = ASDisplayNode()
|
|
self.collectionListContainer.clipsToBounds = true
|
|
|
|
self.stickerListView = ListView()
|
|
self.stickerListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
|
self.stickerListView.accessibilityPageScrolledString = { row, count in
|
|
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
|
}
|
|
|
|
self.maskListView = ListView()
|
|
self.maskListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
|
self.maskListView.accessibilityPageScrolledString = { row, count in
|
|
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
|
}
|
|
|
|
self.topSeparatorNode = ASDisplayNode()
|
|
self.topSeparatorNode.backgroundColor = UIColor(rgb: 0x2c2d2d)
|
|
|
|
self.bottomSeparatorNode = ASDisplayNode()
|
|
self.bottomSeparatorNode.backgroundColor = UIColor(rgb: 0x2c2d2d)
|
|
|
|
var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)?
|
|
self.stickerPane = ChatMediaInputStickerPane(theme: self.presentationData.theme, strings: self.presentationData.strings, paneDidScroll: { pane, state, transition in
|
|
paneDidScrollImpl?(pane, state, transition)
|
|
}, fixPaneScroll: { pane, state in
|
|
|
|
})
|
|
|
|
self.maskPane = ChatMediaInputStickerPane(theme: self.presentationData.theme, strings: self.presentationData.strings, paneDidScroll: { pane, state, transition in
|
|
paneDidScrollImpl?(pane, state, transition)
|
|
}, fixPaneScroll: { pane, state in
|
|
|
|
})
|
|
|
|
self.paneArrangement = DrawingPaneArrangement(panes: [.stickers, .masks], currentIndex: 0, indexTransition: 0.0)
|
|
|
|
super.init()
|
|
|
|
self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
|
|
|
self.view.addSubview(self.blurView)
|
|
|
|
self.stickersNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in
|
|
if let strongSelf = self, let currentView = strongSelf.currentStickerView, (collectionId != strongSelf.stickersNodeInteraction.highlightedItemCollectionId || true) {
|
|
var index: Int32 = 0
|
|
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
|
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
|
context: strongSelf.context,
|
|
highlightedPackId: nil,
|
|
sendSticker: {
|
|
fileReference, sourceNode, sourceRect in
|
|
if let strongSelf = self {
|
|
return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
|
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
|
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue {
|
|
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
|
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue {
|
|
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring))
|
|
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else {
|
|
strongSelf.setCurrentPane(.stickers, transition: .animated(duration: 0.25, curve: .spring))
|
|
for (id, _, _) in currentView.collectionInfos {
|
|
if id.namespace == collectionId.namespace {
|
|
if id == collectionId {
|
|
let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id)
|
|
strongSelf.currentStickerPacksCollectionPosition = .navigate(index: itemIndex, collectionId: nil)
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(.navigate(index: itemIndex, collectionId: nil)))
|
|
break
|
|
}
|
|
index += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, navigateBackToStickers: {
|
|
}, setGifMode: { _ in
|
|
}, openSettings: {
|
|
}, openTrending: { _ in
|
|
}, dismissTrendingPacks: { _ in
|
|
}, toggleSearch: { [weak self] value, searchMode, query in
|
|
if let strongSelf = self {
|
|
if let searchMode = searchMode, value {
|
|
var searchContainerNode: PaneSearchContainerNode?
|
|
if let current = strongSelf.searchContainerNode {
|
|
searchContainerNode = current
|
|
} else {
|
|
searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.stickersNodeInteraction, mode: searchMode, trendingGifsPromise: Promise(nil), cancel: {
|
|
self?.searchContainerNode?.deactivate()
|
|
self?.stickersNodeInteraction.toggleSearch(false, nil, "")
|
|
})
|
|
strongSelf.searchContainerNode = searchContainerNode
|
|
if !query.isEmpty {
|
|
DispatchQueue.main.async {
|
|
searchContainerNode?.updateQuery(query)
|
|
}
|
|
}
|
|
}
|
|
if let searchContainerNode = searchContainerNode {
|
|
strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready
|
|
|> deliverOnMainQueue).start(next: {
|
|
if let strongSelf = self {
|
|
strongSelf.controllerInteraction.updateInputMode { current in
|
|
switch current {
|
|
case let .media(mode, _, focused):
|
|
return .media(mode: mode, expanded: .search(searchMode), focused: focused)
|
|
default:
|
|
return current
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
} else {
|
|
strongSelf.controllerInteraction.updateInputMode { current in
|
|
switch current {
|
|
case let .media(mode, _, focused):
|
|
return .media(mode: mode, expanded: nil, focused: focused)
|
|
default:
|
|
return current
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, openPeerSpecificSettings: {
|
|
}, dismissPeerSpecificSettings: {
|
|
}, clearRecentlyUsedStickers: { [weak self] in
|
|
if let strongSelf = self {
|
|
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: strongSelf.presentationData.theme, fontSize: strongSelf.presentationData.listsFontSize))
|
|
var items: [ActionSheetItem] = []
|
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
let _ = context.engine.stickers.clearRecentlyUsedStickers().start()
|
|
}))
|
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
})
|
|
])])
|
|
strongSelf.controllerInteraction.presentController(actionSheet, nil)
|
|
}
|
|
})
|
|
self.stickersNodeInteraction.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: true)
|
|
self.stickersNodeInteraction.displayStickerPlaceholder = false
|
|
|
|
self.masksNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in
|
|
if let strongSelf = self, let currentView = strongSelf.currentMaskView, (collectionId != strongSelf.masksNodeInteraction.highlightedItemCollectionId || true) {
|
|
var index: Int32 = 0
|
|
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
|
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
|
context: strongSelf.context,
|
|
highlightedPackId: nil,
|
|
sendSticker: {
|
|
fileReference, sourceNode, sourceRect in
|
|
if let strongSelf = self {
|
|
return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
|
strongSelf.setCurrentPane(.masks, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
|
strongSelf.currentMaskPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue {
|
|
strongSelf.setCurrentPane(.masks, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: collectionId.namespace)
|
|
strongSelf.currentMaskPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue {
|
|
strongSelf.setCurrentPane(.masks, transition: .animated(duration: 0.25, curve: .spring))
|
|
strongSelf.currentMaskPacksCollectionPosition = .navigate(index: nil, collectionId: collectionId)
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(.navigate(index: nil, collectionId: collectionId)))
|
|
} else {
|
|
strongSelf.setCurrentPane(.masks, transition: .animated(duration: 0.25, curve: .spring))
|
|
for (id, _, _) in currentView.collectionInfos {
|
|
if id.namespace == collectionId.namespace {
|
|
if id == collectionId {
|
|
let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id)
|
|
strongSelf.currentMaskPacksCollectionPosition = .navigate(index: itemIndex, collectionId: nil)
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(.navigate(index: itemIndex, collectionId: nil)))
|
|
break
|
|
}
|
|
index += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, navigateBackToStickers: {
|
|
}, setGifMode: { _ in
|
|
}, openSettings: {
|
|
}, openTrending: { _ in
|
|
}, dismissTrendingPacks: { _ in
|
|
}, toggleSearch: { [weak self] value, searchMode, query in
|
|
if let strongSelf = self {
|
|
if let searchMode = searchMode, value {
|
|
var searchContainerNode: PaneSearchContainerNode?
|
|
if let current = strongSelf.searchContainerNode {
|
|
searchContainerNode = current
|
|
} else {
|
|
searchContainerNode = PaneSearchContainerNode(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, inputNodeInteraction: strongSelf.masksNodeInteraction, mode: searchMode, trendingGifsPromise: Promise(nil), cancel: {
|
|
self?.searchContainerNode?.deactivate()
|
|
self?.masksNodeInteraction.toggleSearch(false, nil, "")
|
|
})
|
|
strongSelf.searchContainerNode = searchContainerNode
|
|
if !query.isEmpty {
|
|
DispatchQueue.main.async {
|
|
searchContainerNode?.updateQuery(query)
|
|
}
|
|
}
|
|
}
|
|
if let searchContainerNode = searchContainerNode {
|
|
strongSelf.searchContainerNodeLoadedDisposable.set((searchContainerNode.ready
|
|
|> deliverOnMainQueue).start(next: {
|
|
if let strongSelf = self {
|
|
strongSelf.controllerInteraction.updateInputMode { current in
|
|
switch current {
|
|
case let .media(mode, _, focused):
|
|
return .media(mode: mode, expanded: .search(searchMode), focused: focused)
|
|
default:
|
|
return current
|
|
}
|
|
}
|
|
}
|
|
}))
|
|
}
|
|
} else {
|
|
strongSelf.controllerInteraction.updateInputMode { current in
|
|
switch current {
|
|
case let .media(mode, _, focused):
|
|
return .media(mode: mode, expanded: nil, focused: focused)
|
|
default:
|
|
return current
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, openPeerSpecificSettings: {
|
|
}, dismissPeerSpecificSettings: {
|
|
}, clearRecentlyUsedStickers: { [weak self] in
|
|
if let strongSelf = self {
|
|
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: strongSelf.presentationData.theme, fontSize: strongSelf.presentationData.listsFontSize))
|
|
var items: [ActionSheetItem] = []
|
|
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
let _ = context.engine.stickers.clearRecentlyUsedStickers().start()
|
|
}))
|
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
actionSheet?.dismissAnimated()
|
|
})
|
|
])])
|
|
strongSelf.controllerInteraction.presentController(actionSheet, nil)
|
|
}
|
|
})
|
|
self.masksNodeInteraction.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: true)
|
|
self.masksNodeInteraction.displayStickerPlaceholder = false
|
|
|
|
self.addSubnode(self.topPanel)
|
|
|
|
self.collectionListContainer.addSubnode(self.collectionListPanel)
|
|
self.addSubnode(self.collectionListContainer)
|
|
|
|
self.addSubnode(self.segmentedControlNode)
|
|
self.addSubnode(self.cancelButton)
|
|
|
|
self.addSubnode(self.topSeparatorNode)
|
|
self.addSubnode(self.bottomSeparatorNode)
|
|
|
|
let trendingInteraction = TrendingPaneInteraction(installPack: { info in
|
|
}, openPack: { info in
|
|
}, getItemIsPreviewed: { item in
|
|
return false
|
|
}, openSearch: {
|
|
})
|
|
|
|
let stickerItemCollectionsView = self.stickerItemCollectionsViewPosition.get()
|
|
|> distinctUntilChanged
|
|
|> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in
|
|
switch position {
|
|
case .initial:
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: nil, count: 50)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .initial
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
case let .scroll(aroundIndex):
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: aroundIndex, count: 300)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .scroll
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
case let .navigate(index, collectionId):
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 300)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .navigate(index, collectionId)
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
}
|
|
}
|
|
|
|
let maskItemCollectionsView = self.maskItemCollectionsViewPosition.get()
|
|
|> distinctUntilChanged
|
|
|> mapToSignal { position -> Signal<(ItemCollectionsView, StickerPacksCollectionUpdate), NoError> in
|
|
switch position {
|
|
case .initial:
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudMaskPacks], aroundIndex: nil, count: 50)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .initial
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
case let .scroll(aroundIndex):
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudMaskPacks], aroundIndex: aroundIndex, count: 300)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .scroll
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
case let .navigate(index, collectionId):
|
|
var firstTime = true
|
|
return context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers], namespaces: [Namespaces.ItemCollection.CloudMaskPacks], aroundIndex: index, count: 300)
|
|
|> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in
|
|
let update: StickerPacksCollectionUpdate
|
|
if firstTime {
|
|
firstTime = false
|
|
update = .navigate(index, collectionId)
|
|
} else {
|
|
update = .generic
|
|
}
|
|
return (view, update)
|
|
}
|
|
}
|
|
}
|
|
|
|
let controllerInteraction = self.controllerInteraction
|
|
let stickersInputNodeInteraction = self.stickersNodeInteraction!
|
|
|
|
let previousStickerEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], []))
|
|
let previousStickerView = Atomic<ItemCollectionsView?>(value: nil)
|
|
|
|
let stickerTransitions = combineLatest(queue: Queue(), stickerItemCollectionsView, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get())
|
|
|> map { viewAndUpdate, trendingPacks, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
|
let (view, viewUpdate) = viewAndUpdate
|
|
let previous = previousStickerView.swap(view)
|
|
var update = viewUpdate
|
|
if previous === view {
|
|
update = .generic
|
|
}
|
|
let (theme, strings) = themeAndStrings
|
|
|
|
var savedStickers: OrderedItemListView?
|
|
var recentStickers: OrderedItemListView?
|
|
for orderedView in view.orderedItemListsViews {
|
|
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
|
|
recentStickers = orderedView
|
|
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers {
|
|
savedStickers = orderedView
|
|
}
|
|
}
|
|
|
|
var installedPacks = Set<ItemCollectionId>()
|
|
for info in view.collectionInfos {
|
|
installedPacks.insert(info.0)
|
|
}
|
|
|
|
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, strings: strings, hasGifs: false, hasSettings: false)
|
|
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false, isPremiumDisabled: true, trendingIsPremium: false)
|
|
|
|
let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries))
|
|
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
|
}
|
|
|
|
self.disposable.set((stickerTransitions
|
|
|> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, panelFirstTime, gridTransition, gridFirstTime) in
|
|
if let strongSelf = self {
|
|
strongSelf.currentStickerView = view
|
|
strongSelf.enqueuePanelTransition(listView: strongSelf.stickerListView, pane: strongSelf.stickerPane, transition: panelTransition, firstTime: panelFirstTime, thenGridTransition: gridTransition, gridFirstTime: gridFirstTime)
|
|
}
|
|
}))
|
|
|
|
let masksInputNodeInteraction = self.masksNodeInteraction!
|
|
|
|
let previousMaskEntries = Atomic<([ChatMediaInputPanelEntry], [ChatMediaInputGridEntry])>(value: ([], []))
|
|
let previousMaskView = Atomic<ItemCollectionsView?>(value: nil)
|
|
|
|
let maskTransitions = combineLatest(queue: Queue(), maskItemCollectionsView, self.themeAndStringsPromise.get())
|
|
|> map { viewAndUpdate, themeAndStrings -> (ItemCollectionsView, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
|
let (view, viewUpdate) = viewAndUpdate
|
|
let previous = previousMaskView.swap(view)
|
|
var update = viewUpdate
|
|
if previous === view {
|
|
update = .generic
|
|
}
|
|
let (theme, strings) = themeAndStrings
|
|
|
|
var installedPacks = Set<ItemCollectionId>()
|
|
for info in view.collectionInfos {
|
|
installedPacks.insert(info.0)
|
|
}
|
|
|
|
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, strings: strings, hasGifs: false, hasSettings: false)
|
|
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], installedPacks: installedPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme, hasPremium: false, isPremiumDisabled: true, trendingIsPremium: false)
|
|
|
|
let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries))
|
|
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
|
}
|
|
|
|
self.maskDisposable.set((maskTransitions
|
|
|> deliverOnMainQueue).start(next: { [weak self] (view, panelTransition, panelFirstTime, gridTransition, gridFirstTime) in
|
|
if let strongSelf = self {
|
|
strongSelf.currentMaskView = view
|
|
strongSelf.enqueuePanelTransition(listView: strongSelf.maskListView, pane: strongSelf.maskPane, transition: panelTransition, firstTime: panelFirstTime, thenGridTransition: gridTransition, gridFirstTime: gridFirstTime)
|
|
}
|
|
}))
|
|
|
|
self.stickerPane.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
|
if let strongSelf = self {
|
|
var topVisibleCollectionId: ItemCollectionId?
|
|
|
|
if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection {
|
|
topVisibleCollectionId = topVisibleSection.collectionId
|
|
} else if let topVisible = visibleItems.topVisible {
|
|
if let item = topVisible.1 as? ChatMediaInputStickerGridItem {
|
|
topVisibleCollectionId = item.index.collectionId
|
|
} else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem {
|
|
topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0)
|
|
}
|
|
}
|
|
if let collectionId = topVisibleCollectionId {
|
|
if strongSelf.stickersNodeInteraction.highlightedItemCollectionId != collectionId {
|
|
strongSelf.setHighlightedStickerItemCollectionId(collectionId)
|
|
}
|
|
}
|
|
|
|
if let currentView = strongSelf.currentStickerView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom {
|
|
if topIndex <= 10 && currentView.lower != nil {
|
|
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index))
|
|
if strongSelf.currentStickerPacksCollectionPosition != position {
|
|
strongSelf.currentStickerPacksCollectionPosition = position
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(position))
|
|
}
|
|
} else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil {
|
|
var position: StickerPacksCollectionPosition?
|
|
if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem {
|
|
position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index))
|
|
}
|
|
|
|
if let position = position, strongSelf.currentStickerPacksCollectionPosition != position {
|
|
strongSelf.currentStickerPacksCollectionPosition = position
|
|
strongSelf.stickerItemCollectionsViewPosition.set(.single(position))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.maskPane.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
|
if let strongSelf = self {
|
|
var topVisibleCollectionId: ItemCollectionId?
|
|
|
|
if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection {
|
|
topVisibleCollectionId = topVisibleSection.collectionId
|
|
} else if let topVisible = visibleItems.topVisible {
|
|
if let item = topVisible.1 as? ChatMediaInputStickerGridItem {
|
|
topVisibleCollectionId = item.index.collectionId
|
|
} else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem {
|
|
topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0)
|
|
}
|
|
}
|
|
if let collectionId = topVisibleCollectionId {
|
|
if strongSelf.masksNodeInteraction.highlightedItemCollectionId != collectionId {
|
|
strongSelf.setHighlightedMaskItemCollectionId(collectionId)
|
|
}
|
|
}
|
|
|
|
if let currentView = strongSelf.currentMaskView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom {
|
|
if topIndex <= 10 && currentView.lower != nil {
|
|
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index))
|
|
if strongSelf.currentMaskPacksCollectionPosition != position {
|
|
strongSelf.currentMaskPacksCollectionPosition = position
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(position))
|
|
}
|
|
} else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil {
|
|
var position: StickerPacksCollectionPosition?
|
|
if let bottomItem = bottomItem as? ChatMediaInputStickerGridItem {
|
|
position = clipScrollPosition(.scroll(aroundIndex: bottomItem.index))
|
|
}
|
|
|
|
if let position = position, strongSelf.currentMaskPacksCollectionPosition != position {
|
|
strongSelf.currentMaskPacksCollectionPosition = position
|
|
strongSelf.maskItemCollectionsViewPosition.set(.single(position))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.currentStickerPacksCollectionPosition = .initial
|
|
self.stickerItemCollectionsViewPosition.set(.single(.initial))
|
|
|
|
self.currentMaskPacksCollectionPosition = .initial
|
|
self.maskItemCollectionsViewPosition.set(.single(.initial))
|
|
|
|
self.stickerPane.inputNodeInteraction = self.stickersNodeInteraction
|
|
self.maskPane.inputNodeInteraction = self.masksNodeInteraction
|
|
|
|
paneDidScrollImpl = { [weak self] pane, state, transition in
|
|
self?.updatePaneDidScroll(pane: pane, state: state, transition: transition)
|
|
}
|
|
|
|
selectStickerImpl = { [weak self] fileReference, node, rect in
|
|
return self?.selectSticker?(fileReference, node, rect) ?? false
|
|
}
|
|
|
|
self.segmentedControlNode.selectedIndexChanged = { [weak self] index in
|
|
if let strongSelf = self {
|
|
strongSelf.setCurrentPane(index == 0 ? .stickers : .masks, transition: .animated(duration: 0.25, curve: .spring), collectionIdHint: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
self.disposable.dispose()
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
|
self.panRecognizer = panRecognizer
|
|
self.view.addGestureRecognizer(panRecognizer)
|
|
}
|
|
|
|
@objc private func cancelPressed() {
|
|
self.animateOut()
|
|
}
|
|
|
|
private func setHighlightedStickerItemCollectionId(_ collectionId: ItemCollectionId) {
|
|
self.stickersNodeInteraction.highlightedStickerItemCollectionId = collectionId
|
|
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .stickers {
|
|
self.stickersNodeInteraction.highlightedItemCollectionId = collectionId
|
|
}
|
|
var ensuredNodeVisible = false
|
|
var firstVisibleCollectionId: ItemCollectionId?
|
|
self.stickerListView.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode {
|
|
if firstVisibleCollectionId == nil {
|
|
firstVisibleCollectionId = itemNode.currentCollectionId
|
|
}
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if let currentView = self.currentStickerView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible {
|
|
let targetIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == collectionId })
|
|
let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId })
|
|
if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex {
|
|
let toRight = targetIndex > firstVisibleIndex
|
|
self.stickerListView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func setHighlightedMaskItemCollectionId(_ collectionId: ItemCollectionId) {
|
|
self.masksNodeInteraction.highlightedStickerItemCollectionId = collectionId
|
|
if self.paneArrangement.panes[self.paneArrangement.currentIndex] == .masks {
|
|
self.masksNodeInteraction.highlightedItemCollectionId = collectionId
|
|
}
|
|
var ensuredNodeVisible = false
|
|
var firstVisibleCollectionId: ItemCollectionId?
|
|
self.maskListView.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode {
|
|
if firstVisibleCollectionId == nil {
|
|
firstVisibleCollectionId = itemNode.currentCollectionId
|
|
}
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
} else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode {
|
|
itemNode.updateIsHighlighted()
|
|
if itemNode.currentCollectionId == collectionId {
|
|
self.stickerListView.ensureItemNodeVisible(itemNode)
|
|
ensuredNodeVisible = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if let currentView = self.currentMaskView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible {
|
|
let targetIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == collectionId })
|
|
let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId })
|
|
if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex {
|
|
let toRight = targetIndex > firstVisibleIndex
|
|
self.maskListView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func setCurrentPane(_ pane: DrawingPaneType, transition: ContainedViewLayoutTransition, collectionIdHint: Int32? = nil) {
|
|
if let index = self.paneArrangement.panes.firstIndex(of: pane), index != self.paneArrangement.currentIndex, let layout = self.validLayout {
|
|
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0).withCurrentIndex(index)
|
|
|
|
switch pane {
|
|
case .stickers:
|
|
if let highlightedStickerCollectionId = self.stickersNodeInteraction.highlightedStickerItemCollectionId {
|
|
self.setHighlightedStickerItemCollectionId(highlightedStickerCollectionId)
|
|
} else if let collectionIdHint = collectionIdHint {
|
|
self.setHighlightedStickerItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0))
|
|
}
|
|
self.maskListView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: layout.size.width, y: 0.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak self] completed in
|
|
guard let strongSelf = self, completed else {
|
|
return
|
|
}
|
|
strongSelf.maskListView.isHidden = true
|
|
strongSelf.maskListView.layer.removeAllAnimations()
|
|
})
|
|
self.stickerListView.layer.removeAllAnimations()
|
|
self.stickerListView.isHidden = false
|
|
self.stickerListView.layer.animatePosition(from: CGPoint(x: -layout.size.width, y: 0.0), to: CGPoint(), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
case .masks:
|
|
if let highlightedStickerCollectionId = self.stickersNodeInteraction.highlightedStickerItemCollectionId {
|
|
self.setHighlightedMaskItemCollectionId(highlightedStickerCollectionId)
|
|
} else if let collectionIdHint = collectionIdHint {
|
|
self.setHighlightedMaskItemCollectionId(ItemCollectionId(namespace: collectionIdHint, id: 0))
|
|
}
|
|
self.stickerListView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -layout.size.width, y: 0.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true, completion: { [weak self] completed in
|
|
guard let strongSelf = self, completed else {
|
|
return
|
|
}
|
|
strongSelf.stickerListView.isHidden = true
|
|
strongSelf.stickerListView.layer.removeAllAnimations()
|
|
})
|
|
self.maskListView.layer.removeAllAnimations()
|
|
self.maskListView.isHidden = false
|
|
self.maskListView.layer.animatePosition(from: CGPoint(x: layout.size.width, y: 0.0), to: CGPoint(), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
|
}
|
|
}
|
|
if let layout = self.validLayout {
|
|
self.updateLayout(layout, transition: transition)
|
|
}
|
|
}
|
|
|
|
private func currentCollectionListPanelOffset() -> CGFloat {
|
|
let paneOffsets = self.paneArrangement.panes.map { pane -> CGFloat in
|
|
return self.stickerPane.collectionListPanelOffset
|
|
}
|
|
|
|
let mainOffset = paneOffsets[self.paneArrangement.currentIndex]
|
|
if self.paneArrangement.indexTransition.isZero {
|
|
return mainOffset
|
|
} else {
|
|
var sideOffset: CGFloat?
|
|
if self.paneArrangement.indexTransition < 0.0 {
|
|
if self.paneArrangement.currentIndex != 0 {
|
|
sideOffset = paneOffsets[self.paneArrangement.currentIndex - 1]
|
|
}
|
|
} else {
|
|
if self.paneArrangement.currentIndex != paneOffsets.count - 1 {
|
|
sideOffset = paneOffsets[self.paneArrangement.currentIndex + 1]
|
|
}
|
|
}
|
|
if let sideOffset = sideOffset {
|
|
let interpolator = CGFloat.interpolator()
|
|
let value = interpolator(mainOffset, sideOffset, abs(self.paneArrangement.indexTransition)) as! CGFloat
|
|
return value
|
|
} else {
|
|
return mainOffset
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
self.validLayout = layout
|
|
|
|
let insets = layout.insets(options: [.statusBar])
|
|
let height = layout.size.height
|
|
|
|
let _ = self.updateLayout(width: layout.size.width, topInset: insets.top, leftInset: insets.left, rightInset: insets.right, bottomInset: insets.bottom, standardInputHeight: height, inputHeight: height, maximumHeight: height, inputPanelHeight: height, transition: transition, deviceMetrics: layout.deviceMetrics, isVisible: true)
|
|
}
|
|
|
|
func updateLayout(width: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
|
|
let searchMode: ChatMediaInputSearchMode? = nil
|
|
|
|
let displaySearch = !"".isEmpty //silence warning
|
|
let separatorHeight = max(UIScreenPixel, 1.0 - UIScreenPixel)
|
|
let topPanelHeight: CGFloat = 56.0
|
|
let panelHeight: CGFloat
|
|
|
|
let isExpanded: Bool = true
|
|
panelHeight = maximumHeight
|
|
|
|
self.stickerPane.collectionListPanelOffset = 0.0
|
|
|
|
transition.updateFrame(node: self.topPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: topInset + topPanelHeight)))
|
|
|
|
var cancelSize = self.cancelButton.measure(CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
cancelSize.width += 16.0 * 2.0
|
|
transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: width - cancelSize.width, y: topInset + floorToScreenPixels((topPanelHeight - cancelSize.height) / 2.0)), size: cancelSize))
|
|
|
|
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: width - cancelSize.width - 16.0 * 2.0), transition: transition)
|
|
transition.updateFrame(node: self.segmentedControlNode, frame: CGRect(origin: CGPoint(x: 16.0, y: topInset + floorToScreenPixels((topPanelHeight - controlSize.height) / 2.0)), size: controlSize))
|
|
|
|
if displaySearch {
|
|
if let searchContainerNode = self.searchContainerNode {
|
|
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight))
|
|
if searchContainerNode.supernode != nil {
|
|
transition.updateFrame(node: searchContainerNode, frame: containerFrame)
|
|
searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: transition)
|
|
} else {
|
|
self.searchContainerNode = searchContainerNode
|
|
self.insertSubnode(searchContainerNode, belowSubnode: self.collectionListContainer)
|
|
searchContainerNode.frame = containerFrame
|
|
searchContainerNode.updateLayout(size: containerFrame.size, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, inputHeight: inputHeight, deviceMetrics: deviceMetrics, transition: .immediate)
|
|
var placeholderNode: PaneSearchBarPlaceholderNode?
|
|
let anchorTop = CGPoint(x: 0.0, y: 0.0)
|
|
let anchorTopView: UIView = self.view
|
|
if let searchMode = searchMode {
|
|
switch searchMode {
|
|
case .sticker:
|
|
self.stickerPane.gridNode.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? PaneSearchBarPlaceholderNode {
|
|
placeholderNode = itemNode
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
if let placeholderNode = placeholderNode {
|
|
searchContainerNode.animateIn(from: placeholderNode, anchorTop: anchorTop, anhorTopView: anchorTopView, transition: transition, completion: {
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let bottomPanelHeight: CGFloat = 49.0
|
|
let contentVerticalOffset: CGFloat = displaySearch ? -(inputPanelHeight + 41.0) : 0.0
|
|
|
|
let collectionListPanelOffset: CGFloat = 0.0
|
|
|
|
transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: maximumHeight)))
|
|
|
|
transition.updateFrame(node: self.collectionListContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: maximumHeight + contentVerticalOffset - bottomPanelHeight - bottomInset), size: CGSize(width: width, height: max(0.0, bottomPanelHeight + UIScreenPixel + bottomInset))))
|
|
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: width, height: bottomPanelHeight + bottomInset)))
|
|
|
|
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + topPanelHeight), size: CGSize(width: width, height: separatorHeight)))
|
|
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: maximumHeight + contentVerticalOffset - bottomPanelHeight - bottomInset), size: CGSize(width: width, height: separatorHeight)))
|
|
|
|
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
|
|
|
let listPosition = CGPoint(x: width / 2.0, y: (bottomPanelHeight - collectionListPanelOffset) / 2.0 + 15.0)
|
|
self.stickerListView.bounds = CGRect(x: 0.0, y: 0.0, width: bottomPanelHeight + 31.0, height: width)
|
|
transition.updatePosition(node: self.stickerListView, position: listPosition)
|
|
|
|
self.maskListView.bounds = CGRect(x: 0.0, y: 0.0, width: bottomPanelHeight + 31.0, height: width)
|
|
transition.updatePosition(node: self.maskListView, position: listPosition)
|
|
|
|
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: bottomPanelHeight + 31.0, height: width), insets: UIEdgeInsets(top: 4.0 + leftInset, left: 0.0, bottom: 4.0 + rightInset, right: 0.0), duration: duration, curve: curve)
|
|
self.stickerListView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
|
self.maskListView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
|
|
|
var visiblePanes: [(DrawingPaneType, CGFloat)] = []
|
|
|
|
var paneIndex = 0
|
|
for pane in self.paneArrangement.panes {
|
|
let paneOrigin = CGFloat(paneIndex - self.paneArrangement.currentIndex) * width - self.paneArrangement.indexTransition * width
|
|
if paneOrigin.isLess(than: width) && CGFloat(0.0).isLess(than: (paneOrigin + width)) {
|
|
visiblePanes.append((pane, paneOrigin))
|
|
}
|
|
paneIndex += 1
|
|
}
|
|
|
|
var stickersVisible = false
|
|
var masksVisible = false
|
|
|
|
for (pane, paneOrigin) in visiblePanes {
|
|
let paneFrame = CGRect(origin: CGPoint(x: paneOrigin + leftInset, y: topInset + topPanelHeight), size: CGSize(width: width - leftInset - rightInset, height: panelHeight - topInset - topPanelHeight - bottomInset - bottomPanelHeight))
|
|
switch pane {
|
|
case .stickers:
|
|
if self.stickerPane.supernode == nil {
|
|
self.insertSubnode(self.stickerPane, belowSubnode: self.collectionListContainer)
|
|
self.stickerPane.frame = CGRect(origin: CGPoint(x: -width, y: topInset + topPanelHeight), size: CGSize(width: width, height: panelHeight - topInset - topPanelHeight - bottomInset - bottomPanelHeight))
|
|
}
|
|
if self.stickerListView.supernode == nil {
|
|
self.collectionListContainer.addSubnode(self.stickerListView)
|
|
}
|
|
if self.stickerPane.frame != paneFrame {
|
|
self.stickerPane.layer.removeAnimation(forKey: "position")
|
|
transition.updateFrame(node: self.stickerPane, frame: paneFrame)
|
|
}
|
|
stickersVisible = true
|
|
case .masks:
|
|
if self.maskPane.supernode == nil {
|
|
self.insertSubnode(self.maskPane, belowSubnode: self.collectionListContainer)
|
|
self.maskPane.frame = CGRect(origin: CGPoint(x: width, y: topInset + topPanelHeight), size: CGSize(width: width, height: panelHeight - topInset - topPanelHeight - bottomInset - bottomPanelHeight))
|
|
}
|
|
if self.maskListView.supernode == nil {
|
|
self.collectionListContainer.addSubnode(self.maskListView)
|
|
}
|
|
if self.maskPane.frame != paneFrame {
|
|
self.maskPane.layer.removeAnimation(forKey: "position")
|
|
transition.updateFrame(node: self.maskPane, frame: paneFrame)
|
|
}
|
|
masksVisible = true
|
|
}
|
|
}
|
|
|
|
self.stickerPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight - topInset - topPanelHeight - bottomInset - bottomPanelHeight), topInset: 0.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: stickersVisible, deviceMetrics: deviceMetrics, transition: transition)
|
|
|
|
self.maskPane.updateLayout(size: CGSize(width: width - leftInset - rightInset, height: panelHeight - topInset - topPanelHeight - bottomInset - bottomPanelHeight), topInset: 0.0, bottomInset: bottomInset, isExpanded: isExpanded, isVisible: masksVisible, deviceMetrics: deviceMetrics, transition: transition)
|
|
|
|
if self.stickerPane.supernode != nil {
|
|
if !visiblePanes.contains(where: { $0.0 == .stickers }) {
|
|
if case .animated = transition {
|
|
if !self.animatingStickerPaneOut {
|
|
self.animatingStickerPaneOut = true
|
|
transition.animatePosition(node: self.stickerPane, to: CGPoint(x: -width + width / 2.0, y: self.stickerPane.layer.position.y), removeOnCompletion: false, completion: { [weak self] value in
|
|
if let strongSelf = self, value {
|
|
strongSelf.animatingStickerPaneOut = false
|
|
strongSelf.stickerPane.removeFromSupernode()
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
self.animatingStickerPaneOut = false
|
|
self.stickerPane.removeFromSupernode()
|
|
self.stickerListView.removeFromSupernode()
|
|
}
|
|
}
|
|
} else {
|
|
self.animatingStickerPaneOut = false
|
|
}
|
|
|
|
if self.maskPane.supernode != nil {
|
|
if !visiblePanes.contains(where: { $0.0 == .masks }) {
|
|
if case .animated = transition {
|
|
if !self.animatingMaskPaneOut {
|
|
self.animatingMaskPaneOut = true
|
|
transition.animatePosition(node: self.maskPane, to: CGPoint(x: width + width / 2.0, y: self.maskPane.layer.position.y), removeOnCompletion: false, completion: { [weak self] value in
|
|
if let strongSelf = self, value {
|
|
strongSelf.animatingMaskPaneOut = false
|
|
strongSelf.maskPane.removeFromSupernode()
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
self.animatingMaskPaneOut = false
|
|
self.maskPane.removeFromSupernode()
|
|
self.maskListView.removeFromSupernode()
|
|
}
|
|
}
|
|
} else {
|
|
self.animatingMaskPaneOut = false
|
|
}
|
|
|
|
if !displaySearch, let searchContainerNode = self.searchContainerNode {
|
|
self.searchContainerNode = nil
|
|
self.searchContainerNodeLoadedDisposable.set(nil)
|
|
|
|
var paneIsEmpty = false
|
|
var placeholderNode: PaneSearchBarPlaceholderNode?
|
|
if let searchMode = searchMode {
|
|
switch searchMode {
|
|
case .sticker:
|
|
paneIsEmpty = true
|
|
self.stickerPane.gridNode.forEachItemNode { itemNode in
|
|
if let itemNode = itemNode as? PaneSearchBarPlaceholderNode {
|
|
placeholderNode = itemNode
|
|
}
|
|
if let _ = itemNode as? ChatMediaInputStickerGridItemNode {
|
|
paneIsEmpty = false
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
if let placeholderNode = placeholderNode {
|
|
searchContainerNode.animateOut(to: placeholderNode, animateOutSearchBar: !paneIsEmpty, transition: transition, completion: { [weak searchContainerNode] in
|
|
searchContainerNode?.removeFromSupernode()
|
|
})
|
|
} else {
|
|
searchContainerNode.removeFromSupernode()
|
|
}
|
|
}
|
|
|
|
// if let panRecognizer = self.panRecognizer, panRecognizer.isEnabled != !displaySearch {
|
|
// panRecognizer.isEnabled = !displaySearch
|
|
// }
|
|
//
|
|
|
|
return (standardInputHeight, max(0.0, panelHeight - standardInputHeight))
|
|
}
|
|
|
|
private func enqueuePanelTransition(listView: ListView, pane: ChatMediaInputStickerPane, transition: ChatMediaInputPanelTransition, firstTime: Bool, thenGridTransition gridTransition: ChatMediaInputGridTransition, gridFirstTime: Bool) {
|
|
var options = ListViewDeleteAndInsertOptions()
|
|
if firstTime {
|
|
options.insert(.Synchronous)
|
|
options.insert(.LowLatency)
|
|
} else {
|
|
options.insert(.AnimateInsertion)
|
|
}
|
|
listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
strongSelf.enqueueGridTransition(pane: pane, transition: gridTransition, firstTime: gridFirstTime)
|
|
if !strongSelf.didSetReady && pane === strongSelf.stickerPane {
|
|
strongSelf.didSetReady = true
|
|
strongSelf._ready.set(.single(true))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
private func enqueueGridTransition(pane: ChatMediaInputStickerPane, transition: ChatMediaInputGridTransition, firstTime: Bool) {
|
|
var itemTransition: ContainedViewLayoutTransition = .immediate
|
|
if transition.animated {
|
|
itemTransition = .animated(duration: 0.3, curve: .spring)
|
|
}
|
|
pane.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset, updateOpaqueState: transition.updateOpaqueState), completion: { _ in })
|
|
}
|
|
|
|
private func updatePaneDidScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState, transition: ContainedViewLayoutTransition) {
|
|
pane.collectionListPanelOffset = 0.0
|
|
}
|
|
|
|
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
|
switch recognizer.state {
|
|
case .began:
|
|
self.stickerPane.layer.removeAllAnimations()
|
|
if self.animatingStickerPaneOut {
|
|
self.animatingStickerPaneOut = false
|
|
self.stickerPane.removeFromSupernode()
|
|
}
|
|
self.maskPane.layer.removeAllAnimations()
|
|
if self.animatingMaskPaneOut {
|
|
self.animatingMaskPaneOut = false
|
|
self.maskPane.removeFromSupernode()
|
|
}
|
|
case .changed:
|
|
if let layout = self.validLayout {
|
|
let translationX = -recognizer.translation(in: self.view).x
|
|
var indexTransition = translationX / layout.size.width
|
|
if self.paneArrangement.currentIndex == 0 {
|
|
indexTransition = max(0.0, indexTransition)
|
|
} else if self.paneArrangement.currentIndex == self.paneArrangement.panes.count - 1 {
|
|
indexTransition = min(0.0, indexTransition)
|
|
}
|
|
self.paneArrangement = self.paneArrangement.withIndexTransition(indexTransition)
|
|
self.updateLayout(layout, transition: .immediate)
|
|
}
|
|
case .ended:
|
|
if let layout = self.validLayout {
|
|
var updatedIndex = self.paneArrangement.currentIndex
|
|
if abs(self.paneArrangement.indexTransition * layout.size.width) > 30.0 {
|
|
if self.paneArrangement.indexTransition < 0.0 {
|
|
updatedIndex = max(0, self.paneArrangement.currentIndex - 1)
|
|
} else {
|
|
updatedIndex = min(self.paneArrangement.panes.count - 1, self.paneArrangement.currentIndex + 1)
|
|
}
|
|
}
|
|
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0)
|
|
self.setCurrentPane(self.paneArrangement.panes[updatedIndex], transition: .animated(duration: 0.25, curve: .spring))
|
|
self.segmentedControlNode.setSelectedIndex(updatedIndex, animated: true)
|
|
}
|
|
case .cancelled:
|
|
if let layout = self.validLayout {
|
|
self.paneArrangement = self.paneArrangement.withIndexTransition(0.0)
|
|
self.updateLayout(layout, transition: .animated(duration: 0.25, curve: .spring))
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
fileprivate var didAppear: (() -> Void)?
|
|
fileprivate var willDisappear: (() -> Void)?
|
|
|
|
func animateIn() {
|
|
self.isUserInteractionEnabled = true
|
|
self.isHidden = false
|
|
|
|
if let hiddenPane = self.hiddenPane {
|
|
self.insertSubnode(hiddenPane, belowSubnode: self.collectionListContainer)
|
|
self.hiddenPane = nil
|
|
}
|
|
if let hiddenListView = self.hiddenListView {
|
|
self.collectionListContainer.addSubnode(hiddenListView)
|
|
self.hiddenListView = nil
|
|
}
|
|
|
|
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
strongSelf.didAppear?()
|
|
}
|
|
})
|
|
}
|
|
|
|
func animateOut() {
|
|
self.willDisappear?()
|
|
|
|
self.isUserInteractionEnabled = false
|
|
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak self] _ in
|
|
if let strongSelf = self {
|
|
if strongSelf.stickerPane.supernode != nil {
|
|
strongSelf.hiddenPane = strongSelf.stickerPane
|
|
strongSelf.hiddenListView = strongSelf.stickerListView
|
|
} else if strongSelf.maskPane.supernode != nil {
|
|
strongSelf.hiddenPane = strongSelf.maskPane
|
|
strongSelf.hiddenListView = strongSelf.maskListView
|
|
}
|
|
strongSelf.hiddenPane?.removeFromSupernode()
|
|
strongSelf.hiddenListView?.removeFromSupernode()
|
|
strongSelf.isHidden = true
|
|
}
|
|
})
|
|
}
|
|
|
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
self.validLayout = layout
|
|
|
|
self.updateLayout(layout, transition: transition)
|
|
}
|
|
}
|
|
|
|
final class DrawingStickersScreen: ViewController, TGPhotoPaintStickersScreen {
|
|
public var screenDidAppear: (() -> Void)?
|
|
public var screenWillDisappear: (() -> Void)?
|
|
|
|
private let context: AccountContext
|
|
var selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
|
|
|
private var controllerNode: DrawingStickersScreenNode {
|
|
return self.displayNode as! DrawingStickersScreenNode
|
|
}
|
|
|
|
private var presentationData: PresentationData
|
|
private var presentationDataDisposable: Disposable?
|
|
|
|
private var didPlayPresentationAnimation = false
|
|
|
|
private let _ready = Promise<Bool>()
|
|
override public var ready: Promise<Bool> {
|
|
return self._ready
|
|
}
|
|
|
|
public init(context: AccountContext, selectSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) {
|
|
self.context = context
|
|
self.selectSticker = selectSticker
|
|
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
super.init(navigationBarPresentationData: nil)
|
|
|
|
self.navigationPresentation = .modal
|
|
|
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
|
|
|
self.presentationDataDisposable = (context.sharedContext.presentationData
|
|
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
|
if let strongSelf = self {
|
|
let previous = strongSelf.presentationData
|
|
strongSelf.presentationData = presentationData
|
|
|
|
if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings {
|
|
strongSelf.updatePresentationData()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
required init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit {
|
|
self.presentationDataDisposable?.dispose()
|
|
}
|
|
|
|
private func updatePresentationData() {
|
|
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
|
|
}
|
|
|
|
override public func loadDisplayNode() {
|
|
self.displayNode = DrawingStickersScreenNode(
|
|
context: self.context,
|
|
selectSticker: { [weak self] file, sourceNode, sourceRect in
|
|
if let strongSelf = self, let selectSticker = strongSelf.selectSticker {
|
|
(strongSelf.displayNode as! DrawingStickersScreenNode).animateOut()
|
|
return selectSticker(file, sourceNode, sourceRect)
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
)
|
|
(self.displayNode as! DrawingStickersScreenNode).dismiss = { [weak self] in
|
|
self?.dismiss()
|
|
}
|
|
(self.displayNode as! DrawingStickersScreenNode).didAppear = { [weak self] in
|
|
self?.screenDidAppear?()
|
|
}
|
|
(self.displayNode as! DrawingStickersScreenNode).willDisappear = { [weak self] in
|
|
self?.screenWillDisappear?()
|
|
}
|
|
self._ready.set(self.controllerNode.ready.get())
|
|
|
|
super.displayNodeDidLoad()
|
|
}
|
|
|
|
override public func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
|
|
if !self.didPlayPresentationAnimation {
|
|
self.didPlayPresentationAnimation = true
|
|
(self.displayNode as! DrawingStickersScreenNode).animateIn()
|
|
}
|
|
}
|
|
|
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
self.controllerNode.containerLayoutUpdated(layout, navigationHeight: 0.0, transition: transition)
|
|
}
|
|
|
|
func restore() {
|
|
(self.displayNode as! DrawingStickersScreenNode).animateIn()
|
|
}
|
|
|
|
func invalidate() {
|
|
self.dismiss()
|
|
}
|
|
}
|