mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Add external sticker set import
This commit is contained in:
parent
e2584bc99c
commit
d9f3dba292
@ -186,6 +186,7 @@ public enum ResolvedUrl {
|
||||
#endif
|
||||
case settings(ResolvedUrlSettingsSection)
|
||||
case joinVoiceChat(PeerId, String?)
|
||||
case importStickers
|
||||
}
|
||||
|
||||
public enum NavigateToChatKeepStack {
|
||||
|
||||
35
submodules/ImportStickerPackUI/BUILD
Normal file
35
submodules/ImportStickerPackUI/BUILD
Normal file
@ -0,0 +1,35 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ImportStickerPackUI",
|
||||
module_name = "ImportStickerPackUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/ShareController:ShareController",
|
||||
"//submodules/ItemListUI:ItemListUI",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/MergeLists:MergeLists",
|
||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -0,0 +1,49 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public class ImportStickerPack {
|
||||
public class Sticker: Equatable {
|
||||
public static func == (lhs: ImportStickerPack.Sticker, rhs: ImportStickerPack.Sticker) -> Bool {
|
||||
return lhs.uuid == rhs.uuid
|
||||
}
|
||||
|
||||
let image: UIImage
|
||||
let emojis: [String]
|
||||
let uuid: UUID
|
||||
|
||||
init(image: UIImage, emojis: [String], uuid: UUID = UUID()) {
|
||||
self.image = image
|
||||
self.emojis = emojis
|
||||
self.uuid = uuid
|
||||
}
|
||||
}
|
||||
|
||||
public var identifier: String
|
||||
public var name: String
|
||||
public let software: String
|
||||
public var thumbnail: String?
|
||||
public let isAnimated: Bool
|
||||
|
||||
public var stickers: [Sticker]
|
||||
|
||||
public init?(data: Data) {
|
||||
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.name = json["name"] as? String ?? ""
|
||||
self.identifier = json["identifier"] as? String ?? ""
|
||||
self.software = json["software"] as? String ?? ""
|
||||
self.isAnimated = json["isAnimated"] as? Bool ?? false
|
||||
|
||||
var stickers: [Sticker] = []
|
||||
if let stickersArray = json["stickers"] as? [[String: Any]] {
|
||||
for sticker in stickersArray {
|
||||
if let dataString = sticker["data"] as? String, let data = Data(base64Encoded: dataString), let image = UIImage(data: data) {
|
||||
stickers.append(Sticker(image: image, emojis: sticker["emojis"] as? [String] ?? []))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.stickers = stickers
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import ShareController
|
||||
import StickerResources
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
|
||||
public final class ImportStickerPackController: ViewController, StandalonePresentableController {
|
||||
private var controllerNode: ImportStickerPackControllerNode {
|
||||
return self.displayNode as! ImportStickerPackControllerNode
|
||||
}
|
||||
|
||||
private var animatedIn = false
|
||||
private var isDismissed = false
|
||||
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
private let context: AccountContext
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
|
||||
private let stickerPack: ImportStickerPack
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
|
||||
public init(context: AccountContext, stickerPack: ImportStickerPack, parentNavigationController: NavigationController?) {
|
||||
self.context = context
|
||||
self.parentNavigationController = parentNavigationController
|
||||
|
||||
self.stickerPack = stickerPack
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = ImportStickerPackControllerNode(context: self.context)
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.dismissed?()
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
self.controllerNode.cancel = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in
|
||||
self?.presentInGlobalOverlay(controller, with: arguments)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.controllerNode.updateStickerPack(self.stickerPack)
|
||||
}
|
||||
self.ready.set(self.controllerNode.ready.get())
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
} else {
|
||||
return
|
||||
}
|
||||
self.acceptsFocusWhenInOverlay = false
|
||||
self.requestUpdateParameters()
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,554 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import ActivityIndicator
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let stickerItem: ImportStickerPack.Sticker
|
||||
|
||||
var stableId: Int {
|
||||
return self.index
|
||||
// return self.stickerItem.file.fileId
|
||||
}
|
||||
|
||||
static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem {
|
||||
return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isEmpty: false)
|
||||
}
|
||||
}
|
||||
|
||||
private struct StickerPackPreviewGridTransaction {
|
||||
let deletions: [Int]
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
|
||||
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
|
||||
|
||||
self.deletions = deleteIndices
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, theme: theme), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, theme: theme)) }
|
||||
}
|
||||
}
|
||||
|
||||
final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var stickerPack: ImportStickerPack?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
|
||||
private let wrappingScrollNode: ASScrollNode
|
||||
private let cancelButtonNode: ASButtonNode
|
||||
|
||||
private let contentContainerNode: ASDisplayNode
|
||||
private let contentBackgroundNode: ASImageNode
|
||||
private let contentGridNode: GridNode
|
||||
private let installActionButtonNode: ASButtonNode
|
||||
private let installActionSeparatorNode: ASDisplayNode
|
||||
private let contentTitleNode: ImmediateTextNode
|
||||
private let contentSeparatorNode: ASDisplayNode
|
||||
|
||||
private var interaction: StickerPackPreviewInteraction!
|
||||
|
||||
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
|
||||
let ready = Promise<Bool>()
|
||||
private var didSetReady = false
|
||||
|
||||
private var currentItems: [StickerPackPreviewGridEntry] = []
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.wrappingScrollNode = ASScrollNode()
|
||||
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
self.cancelButtonNode = ASButtonNode()
|
||||
self.cancelButtonNode.displaysAsynchronously = false
|
||||
|
||||
self.contentContainerNode = ASDisplayNode()
|
||||
self.contentContainerNode.isOpaque = false
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
|
||||
self.contentBackgroundNode = ASImageNode()
|
||||
self.contentBackgroundNode.displaysAsynchronously = false
|
||||
self.contentBackgroundNode.displayWithoutProcessing = true
|
||||
|
||||
self.contentGridNode = GridNode()
|
||||
|
||||
self.installActionButtonNode = HighlightTrackingButtonNode()
|
||||
self.installActionButtonNode.displaysAsynchronously = false
|
||||
self.installActionButtonNode.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.contentTitleNode = ImmediateTextNode()
|
||||
self.contentTitleNode.displaysAsynchronously = false
|
||||
self.contentTitleNode.maximumNumberOfLines = 1
|
||||
|
||||
self.contentSeparatorNode = ASDisplayNode()
|
||||
self.contentSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.installActionSeparatorNode = ASDisplayNode()
|
||||
self.installActionSeparatorNode.isLayerBacked = true
|
||||
self.installActionSeparatorNode.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false)
|
||||
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
self.addSubnode(self.dimNode)
|
||||
|
||||
self.wrappingScrollNode.view.delegate = self
|
||||
self.addSubnode(self.wrappingScrollNode)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.cancelButtonNode)
|
||||
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.installActionButtonNode.addTarget(self, action: #selector(self.installActionButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.contentBackgroundNode)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.addSubnode(self.contentGridNode)
|
||||
self.contentContainerNode.addSubnode(self.installActionSeparatorNode)
|
||||
self.contentContainerNode.addSubnode(self.installActionButtonNode)
|
||||
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
|
||||
self.wrappingScrollNode.addSubnode(self.contentSeparatorNode)
|
||||
|
||||
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in
|
||||
if let strongSelf = self {
|
||||
if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
||||
// var menuItems: [PeekControllerMenuItem] = []
|
||||
// if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
// if strongSelf.sendSticker != nil {
|
||||
// menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
|
||||
// if let strongSelf = self {
|
||||
// return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false
|
||||
// } else {
|
||||
// return false
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
// menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in
|
||||
// if let strongSelf = self {
|
||||
// if isStarred {
|
||||
// let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start()
|
||||
// } else {
|
||||
// let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start()
|
||||
// }
|
||||
// }
|
||||
// return true
|
||||
// }))
|
||||
// menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true }))
|
||||
// }
|
||||
// return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
|
||||
return .single((itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: item, menu: [])))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, present: { [weak self] content, sourceNode in
|
||||
if let strongSelf = self {
|
||||
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: {
|
||||
return sourceNode
|
||||
})
|
||||
controller.visibilityUpdated = { [weak self] visible in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contentGridNode.forceHidden = visible
|
||||
}
|
||||
}
|
||||
strongSelf.presentInGlobalOverlay?(controller, nil)
|
||||
return controller
|
||||
}
|
||||
return nil
|
||||
}, updateContent: { [weak self] content in
|
||||
if let strongSelf = self {
|
||||
var item: ImportStickerPack.Sticker?
|
||||
if let content = content as? StickerPreviewPeekContent {
|
||||
item = content.item
|
||||
}
|
||||
strongSelf.updatePreviewingItem(item: item, animated: true)
|
||||
}
|
||||
}, activateBySingleTap: true))
|
||||
|
||||
self.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
let theme = presentationData.theme
|
||||
let solidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let highlightedSolidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor)
|
||||
|
||||
self.contentBackgroundNode.image = roundedBackground
|
||||
|
||||
self.cancelButtonNode.setBackgroundImage(roundedBackground, for: .normal)
|
||||
self.cancelButtonNode.setBackgroundImage(highlightedRoundedBackground, for: .highlighted)
|
||||
|
||||
self.installActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal)
|
||||
self.installActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
|
||||
|
||||
self.contentSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
||||
self.installActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
||||
|
||||
self.cancelButtonNode.setTitle(presentationData.strings.Common_Cancel, with: Font.medium(20.0), with: presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
||||
|
||||
self.contentTitleNode.linkHighlightColor = presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
if let (layout, navigationBarHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var insets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
if insets.bottom > 0 {
|
||||
bottomInset -= 12.0
|
||||
}
|
||||
|
||||
let buttonHeight: CGFloat = 57.0
|
||||
let sectionSpacing: CGFloat = 8.0
|
||||
let titleAreaHeight: CGFloat = 51.0
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
|
||||
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
|
||||
transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight)))
|
||||
|
||||
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
|
||||
|
||||
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
|
||||
let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0)
|
||||
|
||||
var transaction: StickerPackPreviewGridTransaction?
|
||||
|
||||
var itemCount = 0
|
||||
var animateIn = false
|
||||
|
||||
if let stickerPack = self.stickerPack {
|
||||
var updatedItems: [StickerPackPreviewGridEntry] = []
|
||||
for item in stickerPack.stickers {
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
|
||||
}
|
||||
|
||||
if self.currentItems.isEmpty && !updatedItems.isEmpty {
|
||||
let entities = generateTextEntities(stickerPack.name, enabledTypes: [.mention])
|
||||
let font = Font.medium(20.0)
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(stickerPack.name, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font)
|
||||
animateIn = true
|
||||
itemCount = updatedItems.count
|
||||
}
|
||||
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme)
|
||||
self.currentItems = updatedItems
|
||||
}
|
||||
|
||||
let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: contentContainerFrame.minX + floor((contentContainerFrame.size.width - titleSize.width) / 2.0), y: self.contentBackgroundNode.frame.minY + 15.0), size: titleSize)
|
||||
let deltaTitlePosition = CGPoint(x: titleFrame.midX - self.contentTitleNode.frame.midX, y: titleFrame.midY - self.contentTitleNode.frame.midY)
|
||||
self.contentTitleNode.frame = titleFrame
|
||||
transition.animatePosition(node: self.contentTitleNode, from: CGPoint(x: titleFrame.midX + deltaTitlePosition.x, y: titleFrame.midY + deltaTitlePosition.y))
|
||||
|
||||
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentContainerFrame.minX, y: self.contentBackgroundNode.frame.minY + titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
let itemsPerRow = 4
|
||||
let itemWidth = floor(contentFrame.size.width / CGFloat(itemsPerRow))
|
||||
let rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0)
|
||||
|
||||
let minimallyRevealedRowCount: CGFloat = 3.5
|
||||
let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount))
|
||||
|
||||
let bottomGridInset = buttonHeight
|
||||
let topInset = max(0.0, contentFrame.size.height - initiallyRevealedRowCount * itemWidth - titleAreaHeight - bottomGridInset)
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
|
||||
let installButtonOffset = buttonHeight
|
||||
transition.updateFrame(node: self.installActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.installActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight))
|
||||
|
||||
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
|
||||
if animateIn {
|
||||
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) {
|
||||
if let (layout, _) = self.containerLayout {
|
||||
var insets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
|
||||
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
if insets.bottom > 0 {
|
||||
bottomInset -= 12.0
|
||||
}
|
||||
|
||||
let buttonHeight: CGFloat = 57.0
|
||||
let sectionSpacing: CGFloat = 8.0
|
||||
let titleAreaHeight: CGFloat = 51.0
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
|
||||
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
|
||||
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
|
||||
let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY - presentationLayout.contentOffset.y), size: contentFrame.size)
|
||||
if backgroundFrame.minY < contentFrame.minY {
|
||||
backgroundFrame.origin.y = contentFrame.minY
|
||||
}
|
||||
if backgroundFrame.maxY > contentFrame.maxY {
|
||||
backgroundFrame.size.height += contentFrame.maxY - backgroundFrame.maxY
|
||||
}
|
||||
if backgroundFrame.size.height < buttonHeight + 32.0 {
|
||||
backgroundFrame.origin.y -= buttonHeight + 32.0 - backgroundFrame.size.height
|
||||
backgroundFrame.size.height = buttonHeight + 32.0
|
||||
}
|
||||
var compactFrame = false
|
||||
let backgroundDeltaY = backgroundFrame.minY - self.contentBackgroundNode.frame.minY
|
||||
transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame)
|
||||
transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -backgroundDeltaY))
|
||||
|
||||
let titleSize = self.contentTitleNode.bounds.size
|
||||
let titleFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.size.width - titleSize.width) / 2.0), y: backgroundFrame.minY + 15.0), size: titleSize)
|
||||
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
|
||||
|
||||
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: backgroundFrame.minY + titleAreaHeight), size: CGSize(width: contentFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
if !compactFrame && CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) {
|
||||
self.contentSeparatorNode.alpha = 1.0
|
||||
} else {
|
||||
self.contentSeparatorNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.cancelButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
self.cancel?()
|
||||
}
|
||||
|
||||
@objc func installActionButtonPressed() {
|
||||
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
|
||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
var dimCompleted = false
|
||||
var offsetCompleted = false
|
||||
|
||||
let internalCompletion: () -> Void = { [weak self] in
|
||||
if let strongSelf = self, dimCompleted && offsetCompleted {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
dimCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
|
||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
offsetCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
func updateStickerPack(_ stickerPack: ImportStickerPack) {
|
||||
self.stickerPack = stickerPack
|
||||
|
||||
// self.interaction.playAnimatedStickers = stickerSettings.loopAnimatedStickers
|
||||
|
||||
if let _ = self.containerLayout {
|
||||
self.dequeueUpdateStickerPack()
|
||||
}
|
||||
self.installActionButtonNode.setTitle("Create Sticker Set", with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
// switch stickerPack {
|
||||
// case .none, .fetching:
|
||||
// self.installActionSeparatorNode.alpha = 0.0
|
||||
// self.shareActionSeparatorNode.alpha = 0.0
|
||||
// self.shareActionButtonNode.alpha = 0.0
|
||||
// self.installActionButtonNode.alpha = 0.0
|
||||
// self.installActionButtonNode.setTitle("", with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
||||
// case let .result(info, _, installed):
|
||||
// if self.stickerPackInitiallyInstalled == nil {
|
||||
// self.stickerPackInitiallyInstalled = installed
|
||||
// }
|
||||
// self.installActionSeparatorNode.alpha = 1.0
|
||||
// self.shareActionSeparatorNode.alpha = 1.0
|
||||
// self.shareActionButtonNode.alpha = 1.0
|
||||
// self.installActionButtonNode.alpha = 1.0
|
||||
// if installed {
|
||||
// let text: String
|
||||
// if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
// text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count)
|
||||
// } else {
|
||||
// text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count)
|
||||
// }
|
||||
// self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal)
|
||||
// } else {
|
||||
// let text: String
|
||||
// if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
// text = self.presentationData.strings.StickerPack_AddStickerCount(info.count)
|
||||
// } else {
|
||||
// text = self.presentationData.strings.StickerPack_AddMaskCount(info.count)
|
||||
// }
|
||||
// self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
func dequeueUpdateStickerPack() {
|
||||
if let (layout, navigationBarHeight) = self.containerLayout, let _ = self.stickerPack {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if self.didSetReady {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
self.ready.set(.single(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.installActionButtonNode.hitTest(self.installActionButtonNode.convert(point, from: self), with: event) {
|
||||
return result
|
||||
}
|
||||
if self.bounds.contains(point) {
|
||||
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) && !self.cancelButtonNode.bounds.contains(self.convert(point, to: self.cancelButtonNode)) {
|
||||
return self.dimNode.view
|
||||
}
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let additionalTopHeight = max(0.0, -contentOffset.y)
|
||||
|
||||
if additionalTopHeight >= 30.0 {
|
||||
self.cancelButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePreviewingItem(item: ImportStickerPack.Sticker?, animated: Bool) {
|
||||
if self.interaction.previewedItem !== item {
|
||||
self.interaction.previewedItem = item
|
||||
|
||||
self.contentGridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? StickerPackPreviewGridItemNode {
|
||||
itemNode.updatePreviewing(animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import StickerResources
|
||||
import AccountContext
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
|
||||
final class StickerPackPreviewInteraction {
|
||||
var previewedItem: ImportStickerPack.Sticker?
|
||||
var playAnimatedStickers: Bool
|
||||
|
||||
init(playAnimatedStickers: Bool) {
|
||||
self.playAnimatedStickers = playAnimatedStickers
|
||||
}
|
||||
}
|
||||
|
||||
final class StickerPackPreviewGridItem: GridItem {
|
||||
let account: Account
|
||||
let stickerItem: ImportStickerPack.Sticker
|
||||
let interaction: StickerPackPreviewInteraction
|
||||
let theme: PresentationTheme
|
||||
let isEmpty: Bool
|
||||
|
||||
let section: GridSection? = nil
|
||||
|
||||
init(account: Account, stickerItem: ImportStickerPack.Sticker, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isEmpty: Bool) {
|
||||
self.account = account
|
||||
self.stickerItem = stickerItem
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
self.isEmpty = isEmpty
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
let node = StickerPackPreviewGridItemNode()
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isEmpty: self.isEmpty)
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? StickerPackPreviewGridItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(account: self.account, stickerItem: self.stickerItem, interaction: self.interaction, theme: self.theme, isEmpty: self.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
private let textFont = Font.regular(20.0)
|
||||
|
||||
final class StickerPackPreviewGridItemNode: GridItemNode {
|
||||
private var currentState: (Account, ImportStickerPack.Sticker?)?
|
||||
private var isEmpty: Bool?
|
||||
// private let imageNode: TransformImageNode
|
||||
private let imageNode: ASImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override var isVisibleInGrid: Bool {
|
||||
didSet {
|
||||
self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
}
|
||||
}
|
||||
|
||||
private var currentIsPreviewing = false
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
var interaction: StickerPackPreviewInteraction?
|
||||
|
||||
var selected: (() -> Void)?
|
||||
|
||||
var stickerPackItem: ImportStickerPack.Sticker? {
|
||||
return self.currentState?.1
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.imageNode = ASImageNode()
|
||||
// self.imageNode = TransformImageNode()
|
||||
// self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
// self.addSubnode(placeholderNode)
|
||||
}
|
||||
|
||||
var firstTime = true
|
||||
// self.imageNode.imageUpdated = { [weak self] image in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// if image != nil {
|
||||
// strongSelf.removePlaceholder(animated: !firstTime)
|
||||
// }
|
||||
// firstTime = false
|
||||
// }
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.allowsGroupOpacity = true
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
placeholderNode?.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func setup(account: Account, stickerItem: ImportStickerPack.Sticker?, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, isEmpty: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 !== stickerItem || self.isEmpty != isEmpty {
|
||||
if let stickerItem = stickerItem {
|
||||
self.imageNode.image = stickerItem.image
|
||||
// if stickerItem.file.isAnimatedSticker {
|
||||
// let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
// self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: account.postbox, file: stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
||||
//
|
||||
// if self.animationNode == nil {
|
||||
// let animationNode = AnimatedStickerNode()
|
||||
// self.animationNode = animationNode
|
||||
// self.addSubnode(animationNode)
|
||||
// animationNode.started = { [weak self] in
|
||||
// self?.imageNode.isHidden = true
|
||||
// self?.removePlaceholder(animated: false)
|
||||
// }
|
||||
// }
|
||||
// let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
// self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: stickerItem.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .cached)
|
||||
// self.animationNode?.visibility = self.isVisibleInGrid && self.interaction?.playAnimatedStickers ?? true
|
||||
// self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
|
||||
// } else {
|
||||
// if let animationNode = self.animationNode {
|
||||
// animationNode.visibility = false
|
||||
// self.animationNode = nil
|
||||
// animationNode.removeFromSupernode()
|
||||
// }
|
||||
// self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true))
|
||||
// self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
|
||||
// }
|
||||
} else {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
if isEmpty {
|
||||
if !placeholderNode.alpha.isZero {
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
|
||||
}
|
||||
} else {
|
||||
placeholderNode.alpha = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
self.currentState = (account, stickerItem)
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
self.isEmpty = isEmpty
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let bounds = self.bounds
|
||||
let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0)
|
||||
let boundingSize = CGSize(width: boundsSide, height: boundsSide)
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
let placeholderFrame = CGRect(origin: CGPoint(x: floor((bounds.width - boundingSize.width) / 2.0), y: floor((bounds.height - boundingSize.height) / 2.0)), size: boundingSize)
|
||||
placeholderNode.frame = bounds
|
||||
|
||||
// if let theme = self.theme, let (_, stickerItem) = self.currentState, let item = stickerItem {
|
||||
// placeholderNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.file.immediateThumbnailData, size: placeholderFrame.size)
|
||||
// }
|
||||
}
|
||||
|
||||
self.imageNode.frame = bounds
|
||||
// if let (_, item) = self.currentState {
|
||||
// if let item = item, let dimensions = item.file.dimensions?.cgSize {
|
||||
// let imageSize = dimensions.aspectFitted(boundingSize)
|
||||
// self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
// self.imageNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
// if let animationNode = self.animationNode {
|
||||
// animationNode.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
// animationNode.updateLayout(size: imageSize)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
func transitionNode() -> ASDisplayNode? {
|
||||
return self
|
||||
}
|
||||
|
||||
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||
}
|
||||
|
||||
func updatePreviewing(animated: Bool) {
|
||||
var isPreviewing = false
|
||||
if let (_, maybeItem) = self.currentState, let interaction = self.interaction, let item = maybeItem {
|
||||
isPreviewing = interaction.previewedItem === item
|
||||
}
|
||||
if self.currentIsPreviewing != isPreviewing {
|
||||
self.currentIsPreviewing = isPreviewing
|
||||
|
||||
if isPreviewing {
|
||||
self.layer.sublayerTransform = CATransform3DMakeScale(0.8, 0.8, 1.0)
|
||||
if animated {
|
||||
self.layer.animateSpring(from: 1.0 as NSNumber, to: 0.8 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4)
|
||||
}
|
||||
} else {
|
||||
self.layer.sublayerTransform = CATransform3DIdentity
|
||||
if animated {
|
||||
self.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,142 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import StickerResources
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
|
||||
public final class StickerPreviewPeekContent: PeekControllerContent {
|
||||
let account: Account
|
||||
public let item: ImportStickerPack.Sticker
|
||||
let menu: [PeekControllerMenuItem]
|
||||
|
||||
public init(account: Account, item: ImportStickerPack.Sticker, menu: [PeekControllerMenuItem]) {
|
||||
self.account = account
|
||||
self.item = item
|
||||
self.menu = menu
|
||||
}
|
||||
|
||||
public func presentation() -> PeekControllerContentPresentation {
|
||||
return .freeform
|
||||
}
|
||||
|
||||
public func menuActivation() -> PeerkControllerMenuActivation {
|
||||
return .press
|
||||
}
|
||||
|
||||
public func menuItems() -> [PeekControllerMenuItem] {
|
||||
return self.menu
|
||||
}
|
||||
|
||||
public func node() -> PeekControllerContentNode & ASDisplayNode {
|
||||
return StickerPreviewPeekContentNode(account: self.account, item: self.item)
|
||||
}
|
||||
|
||||
public func topAccessoryNode() -> ASDisplayNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func isEqual(to: PeekControllerContent) -> Bool {
|
||||
if let to = to as? StickerPreviewPeekContent {
|
||||
return self.item === to.item
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerContentNode {
|
||||
private let account: Account
|
||||
private let item: ImportStickerPack.Sticker
|
||||
|
||||
private var textNode: ASTextNode
|
||||
private var imageNode: ASImageNode
|
||||
private var animationNode: AnimatedStickerNode?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
init(account: Account, item: ImportStickerPack.Sticker) {
|
||||
self.account = account
|
||||
self.item = item
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.image = item.image
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: item.emojis.joined(separator: " "), font: Font.regular(32.0), textColor: .black)
|
||||
|
||||
// for case let .Sticker(text, _, _) in item.file.attributes {
|
||||
// self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(32.0), textColor: .black)
|
||||
// break
|
||||
// }
|
||||
|
||||
// if item.file.isAnimatedSticker {
|
||||
// let animationNode = AnimatedStickerNode()
|
||||
// self.animationNode = animationNode
|
||||
//
|
||||
// let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
// let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
||||
//
|
||||
// self.animationNode?.setup(source: AnimatedStickerResourceSource(account: account, resource: item.file.resource), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), mode: .direct(cachePathPrefix: nil))
|
||||
// self.animationNode?.visibility = true
|
||||
// self.animationNode?.addSubnode(self.textNode)
|
||||
// } else {
|
||||
// self.imageNode.addSubnode(self.textNode)
|
||||
// self.animationNode = nil
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: false, fetched: true))
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
self.addSubnode(animationNode)
|
||||
} else {
|
||||
self.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let boundingSize = CGSize(width: 180.0, height: 180.0).fitted(size)
|
||||
let imageFrame = CGRect(origin: CGPoint(), size: boundingSize)
|
||||
|
||||
let textSpacing: CGFloat = 10.0
|
||||
let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0), y: -textSize.height - textSpacing), size: textSize)
|
||||
|
||||
self.imageNode.frame = imageFrame
|
||||
return boundingSize
|
||||
|
||||
// if let dimensitons = self.item.file.dimensions {
|
||||
// let textSpacing: CGFloat = 10.0
|
||||
// let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0))
|
||||
//
|
||||
// let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
|
||||
// self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
// let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing), size: imageSize)
|
||||
// self.imageNode.frame = imageFrame
|
||||
// if let animationNode = self.animationNode {
|
||||
// animationNode.frame = imageFrame
|
||||
// animationNode.updateLayout(size: imageSize)
|
||||
// }
|
||||
//
|
||||
// self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0), y: -textSize.height - textSpacing), size: textSize)
|
||||
//
|
||||
// return CGSize(width: size.width, height: imageFrame.height + textSize.height + textSpacing)
|
||||
// } else {
|
||||
// return CGSize(width: size.width, height: 10.0)
|
||||
// }
|
||||
}
|
||||
}
|
||||
447
submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift
Normal file
447
submodules/InstantPageUI/Sources/InstantPageSubContentNode.swift
Normal file
@ -0,0 +1,447 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
|
||||
final class InstantPageSubContentNode : ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private let nameDisplayOrder: PresentationPersonNameOrder
|
||||
private let sourcePeerType: MediaAutoDownloadPeerType
|
||||
private let theme: InstantPageTheme
|
||||
|
||||
private let openMedia: (InstantPageMedia) -> Void
|
||||
private let longPressMedia: (InstantPageMedia) -> Void
|
||||
private let openPeer: (PeerId) -> Void
|
||||
private let openUrl: (InstantPageUrlItem) -> Void
|
||||
|
||||
var currentLayoutTiles: [InstantPageTile] = []
|
||||
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
||||
var distanceThresholdGroupCount: [Int: Int] = [:]
|
||||
|
||||
var visibleTiles: [Int: InstantPageTileNode] = [:]
|
||||
var visibleItemsWithNodes: [Int: InstantPageNode] = [:]
|
||||
|
||||
var currentWebEmbedHeights: [Int : CGFloat] = [:]
|
||||
var currentExpandedDetails: [Int : Bool]?
|
||||
var currentDetailsItems: [InstantPageDetailsItem] = []
|
||||
|
||||
var requestLayoutUpdate: ((Bool) -> Void)?
|
||||
|
||||
var currentLayout: InstantPageLayout
|
||||
let contentSize: CGSize
|
||||
let inOverlayPanel: Bool
|
||||
|
||||
private var previousVisibleBounds: CGRect?
|
||||
|
||||
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourcePeerType: MediaAutoDownloadPeerType, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (PeerId) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.sourcePeerType = sourcePeerType
|
||||
self.theme = theme
|
||||
|
||||
self.openMedia = openMedia
|
||||
self.longPressMedia = longPressMedia
|
||||
self.openPeer = openPeer
|
||||
self.openUrl = openUrl
|
||||
|
||||
self.currentLayout = InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||
self.contentSize = contentSize
|
||||
self.inOverlayPanel = inOverlayPanel
|
||||
|
||||
super.init()
|
||||
|
||||
self.updateLayout()
|
||||
}
|
||||
|
||||
private func updateLayout() {
|
||||
for (_, tileNode) in self.visibleTiles {
|
||||
tileNode.removeFromSupernode()
|
||||
}
|
||||
self.visibleTiles.removeAll()
|
||||
|
||||
let currentLayoutTiles = instantPageTilesFromLayout(currentLayout, boundingWidth: contentSize.width)
|
||||
|
||||
var currentDetailsItems: [InstantPageDetailsItem] = []
|
||||
var currentLayoutItemsWithViews: [InstantPageItem] = []
|
||||
var distanceThresholdGroupCount: [Int: Int] = [:]
|
||||
|
||||
var expandedDetails: [Int: Bool] = [:]
|
||||
|
||||
var detailsIndex = -1
|
||||
for item in self.currentLayout.items {
|
||||
if item.wantsNode {
|
||||
currentLayoutItemsWithViews.append(item)
|
||||
if let group = item.distanceThresholdGroup() {
|
||||
let count: Int
|
||||
if let currentCount = distanceThresholdGroupCount[Int(group)] {
|
||||
count = currentCount
|
||||
} else {
|
||||
count = 0
|
||||
}
|
||||
distanceThresholdGroupCount[Int(group)] = count + 1
|
||||
}
|
||||
if let detailsItem = item as? InstantPageDetailsItem {
|
||||
detailsIndex += 1
|
||||
expandedDetails[detailsIndex] = detailsItem.initiallyExpanded
|
||||
currentDetailsItems.append(detailsItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.currentExpandedDetails == nil {
|
||||
self.currentExpandedDetails = expandedDetails
|
||||
}
|
||||
|
||||
self.currentLayoutTiles = currentLayoutTiles
|
||||
self.currentLayoutItemsWithNodes = currentLayoutItemsWithViews
|
||||
self.currentDetailsItems = currentDetailsItems
|
||||
self.distanceThresholdGroupCount = distanceThresholdGroupCount
|
||||
}
|
||||
|
||||
var effectiveContentSize: CGSize {
|
||||
var contentSize = self.contentSize
|
||||
for item in self.currentDetailsItems {
|
||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
||||
contentSize.height += -item.frame.height + (expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight)
|
||||
}
|
||||
return contentSize
|
||||
}
|
||||
|
||||
func updateVisibleItems(visibleBounds: CGRect, animated: Bool = false) {
|
||||
var visibleTileIndices = Set<Int>()
|
||||
var visibleItemIndices = Set<Int>()
|
||||
|
||||
self.previousVisibleBounds = visibleBounds
|
||||
|
||||
var topNode: ASDisplayNode?
|
||||
let topTileNode = topNode
|
||||
if let scrollSubnodes = self.subnodes {
|
||||
for node in scrollSubnodes.reversed() {
|
||||
if let node = node as? InstantPageTileNode {
|
||||
topNode = node
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var collapseOffset: CGFloat = 0.0
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if animated {
|
||||
transition = .animated(duration: 0.3, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
var itemIndex = -1
|
||||
var embedIndex = -1
|
||||
var detailsIndex = -1
|
||||
|
||||
for item in self.currentLayoutItemsWithNodes {
|
||||
itemIndex += 1
|
||||
if item is InstantPageWebEmbedItem {
|
||||
embedIndex += 1
|
||||
}
|
||||
if item is InstantPageDetailsItem {
|
||||
detailsIndex += 1
|
||||
}
|
||||
|
||||
var itemThreshold: CGFloat = 0.0
|
||||
if let group = item.distanceThresholdGroup() {
|
||||
var count: Int = 0
|
||||
if let currentCount = self.distanceThresholdGroupCount[group] {
|
||||
count = currentCount
|
||||
}
|
||||
itemThreshold = item.distanceThresholdWithGroupCount(count)
|
||||
}
|
||||
|
||||
var itemFrame = item.frame.offsetBy(dx: 0.0, dy: -collapseOffset)
|
||||
var thresholdedItemFrame = itemFrame
|
||||
thresholdedItemFrame.origin.y -= itemThreshold
|
||||
thresholdedItemFrame.size.height += itemThreshold * 2.0
|
||||
|
||||
if let detailsItem = item as? InstantPageDetailsItem, let expanded = self.currentExpandedDetails?[detailsIndex] {
|
||||
let height = expanded ? self.effectiveSizeForDetails(detailsItem).height : detailsItem.titleHeight
|
||||
collapseOffset += itemFrame.height - height
|
||||
itemFrame = CGRect(origin: itemFrame.origin, size: CGSize(width: itemFrame.width, height: height))
|
||||
}
|
||||
|
||||
if visibleBounds.intersects(thresholdedItemFrame) {
|
||||
visibleItemIndices.insert(itemIndex)
|
||||
|
||||
var itemNode = self.visibleItemsWithNodes[itemIndex]
|
||||
if let currentItemNode = itemNode {
|
||||
if !item.matchesNode(currentItemNode) {
|
||||
(currentItemNode as! ASDisplayNode).removeFromSupernode()
|
||||
self.visibleItemsWithNodes.removeValue(forKey: itemIndex)
|
||||
itemNode = nil
|
||||
}
|
||||
}
|
||||
|
||||
if itemNode == nil {
|
||||
let itemIndex = itemIndex
|
||||
let detailsIndex = detailsIndex
|
||||
if let newNode = item.node(context: self.context, strings: self.strings, nameDisplayOrder: self.nameDisplayOrder, theme: theme, sourcePeerType: self.sourcePeerType, openMedia: { [weak self] media in
|
||||
self?.openMedia(media)
|
||||
}, longPressMedia: { [weak self] media in
|
||||
self?.longPressMedia(media)
|
||||
}, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { [weak self] peerId in
|
||||
self?.openPeer(peerId)
|
||||
}, openUrl: { [weak self] url in
|
||||
self?.openUrl(url)
|
||||
}, updateWebEmbedHeight: { _ in
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
if let topNode = topNode {
|
||||
self.insertSubnode(newNode, aboveSubnode: topNode)
|
||||
} else {
|
||||
self.insertSubnode(newNode, at: 0)
|
||||
}
|
||||
topNode = newNode
|
||||
self.visibleItemsWithNodes[itemIndex] = newNode
|
||||
itemNode = newNode
|
||||
|
||||
if let itemNode = itemNode as? InstantPageDetailsNode {
|
||||
itemNode.requestLayoutUpdate = { [weak self] animated in
|
||||
self?.requestLayoutUpdate?(animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (itemNode as! ASDisplayNode).frame != itemFrame {
|
||||
transition.updateFrame(node: (itemNode as! ASDisplayNode), frame: itemFrame)
|
||||
itemNode?.updateLayout(size: itemFrame.size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let itemNode = itemNode as? InstantPageDetailsNode {
|
||||
itemNode.updateVisibleItems(visibleBounds: visibleBounds.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY), animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
topNode = topTileNode
|
||||
|
||||
var tileIndex = -1
|
||||
for tile in self.currentLayoutTiles {
|
||||
tileIndex += 1
|
||||
|
||||
let tileFrame = effectiveFrameForTile(tile)
|
||||
var tileVisibleFrame = tileFrame
|
||||
tileVisibleFrame.origin.y -= 400.0
|
||||
tileVisibleFrame.size.height += 400.0 * 2.0
|
||||
if tileVisibleFrame.intersects(visibleBounds) {
|
||||
visibleTileIndices.insert(tileIndex)
|
||||
|
||||
if self.visibleTiles[tileIndex] == nil {
|
||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: self.inOverlayPanel ? self.theme.overlayPanelColor : self.theme.pageBackgroundColor)
|
||||
tileNode.frame = tileFrame
|
||||
if let topNode = topNode {
|
||||
self.insertSubnode(tileNode, aboveSubnode: topNode)
|
||||
} else {
|
||||
self.insertSubnode(tileNode, at: 0)
|
||||
}
|
||||
topNode = tileNode
|
||||
self.visibleTiles[tileIndex] = tileNode
|
||||
} else {
|
||||
if visibleTiles[tileIndex]!.frame != tileFrame {
|
||||
transition.updateFrame(node: self.visibleTiles[tileIndex]!, frame: tileFrame)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removeTileIndices: [Int] = []
|
||||
for (index, tileNode) in self.visibleTiles {
|
||||
if !visibleTileIndices.contains(index) {
|
||||
removeTileIndices.append(index)
|
||||
tileNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for index in removeTileIndices {
|
||||
self.visibleTiles.removeValue(forKey: index)
|
||||
}
|
||||
|
||||
var removeItemIndices: [Int] = []
|
||||
for (index, itemNode) in self.visibleItemsWithNodes {
|
||||
if !visibleItemIndices.contains(index) {
|
||||
removeItemIndices.append(index)
|
||||
(itemNode as! ASDisplayNode).removeFromSupernode()
|
||||
} else {
|
||||
var itemFrame = (itemNode as! ASDisplayNode).frame
|
||||
let itemThreshold: CGFloat = 200.0
|
||||
itemFrame.origin.y -= itemThreshold
|
||||
itemFrame.size.height += itemThreshold * 2.0
|
||||
itemNode.updateIsVisible(visibleBounds.intersects(itemFrame))
|
||||
}
|
||||
}
|
||||
for index in removeItemIndices {
|
||||
self.visibleItemsWithNodes.removeValue(forKey: index)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateWebEmbedHeight(_ index: Int, _ height: CGFloat) {
|
||||
// let currentHeight = self.currentWebEmbedHeights[index]
|
||||
// if height != currentHeight {
|
||||
// if let currentHeight = currentHeight, currentHeight > height {
|
||||
// return
|
||||
// }
|
||||
// self.currentWebEmbedHeights[index] = height
|
||||
//
|
||||
// let signal: Signal<Void, NoError> = (.complete() |> delay(0.08, queue: Queue.mainQueue()))
|
||||
// self.updateLayoutDisposable.set(signal.start(completed: { [weak self] in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.updateLayout()
|
||||
// strongSelf.updateVisibleItems()
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
}
|
||||
|
||||
func updateDetailsExpanded(_ index: Int, _ expanded: Bool, animated: Bool = true, requestLayout: Bool = true) {
|
||||
if var currentExpandedDetails = self.currentExpandedDetails {
|
||||
currentExpandedDetails[index] = expanded
|
||||
self.currentExpandedDetails = currentExpandedDetails
|
||||
}
|
||||
self.requestLayoutUpdate?(animated)
|
||||
}
|
||||
|
||||
func transitionNode(media: InstantPageMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let transitionNode = itemNode.transitionNode(media: media) {
|
||||
return transitionNode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateHiddenMedia(media: InstantPageMedia?) {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
itemNode.updateHiddenMedia(media: media)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollableContentOffset(item: InstantPageScrollableItem) -> CGPoint {
|
||||
var contentOffset = CGPoint()
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let itemNode = itemNode as? InstantPageScrollableNode, itemNode.item === item {
|
||||
contentOffset = itemNode.contentOffset
|
||||
break
|
||||
}
|
||||
}
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
|
||||
return detailsNode
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
|
||||
if let node = nodeForDetailsItem(item) {
|
||||
return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
|
||||
} else {
|
||||
return item.frame.size
|
||||
}
|
||||
}
|
||||
|
||||
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
|
||||
let layoutOrigin = tile.frame.origin
|
||||
var origin = layoutOrigin
|
||||
for item in self.currentDetailsItems {
|
||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
||||
if layoutOrigin.y >= item.frame.maxY {
|
||||
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
|
||||
origin.y += height - item.frame.height
|
||||
}
|
||||
}
|
||||
return CGRect(origin: origin, size: tile.frame.size)
|
||||
}
|
||||
|
||||
func effectiveFrameForItem(_ item: InstantPageItem) -> CGRect {
|
||||
let layoutOrigin = item.frame.origin
|
||||
var origin = layoutOrigin
|
||||
|
||||
for item in self.currentDetailsItems {
|
||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
||||
if layoutOrigin.y >= item.frame.maxY {
|
||||
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
|
||||
origin.y += height - item.frame.height
|
||||
}
|
||||
}
|
||||
|
||||
if let item = item as? InstantPageDetailsItem {
|
||||
let expanded = self.currentExpandedDetails?[item.index] ?? item.initiallyExpanded
|
||||
let height = expanded ? self.effectiveSizeForDetails(item).height : item.titleHeight
|
||||
return CGRect(origin: origin, size: CGSize(width: item.frame.width, height: height))
|
||||
} else {
|
||||
return CGRect(origin: origin, size: item.frame.size)
|
||||
}
|
||||
}
|
||||
|
||||
func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
|
||||
for item in self.currentLayout.items {
|
||||
let itemFrame = self.effectiveFrameForItem(item)
|
||||
if itemFrame.contains(location) {
|
||||
if let item = item as? InstantPageTextItem, item.selectable {
|
||||
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
|
||||
} else if let item = item as? InstantPageScrollableItem {
|
||||
let contentOffset = scrollableContentOffset(item: item)
|
||||
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) {
|
||||
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
|
||||
}
|
||||
} else if let item = item as? InstantPageDetailsItem {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
|
||||
if let (textItem, parentOffset) = itemNode.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX, dy: -itemFrame.minY)) {
|
||||
return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x, dy: parentOffset.y))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func tapActionAtPoint(_ point: CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction {
|
||||
for item in self.currentLayout.items {
|
||||
let frame = self.effectiveFrameForItem(item)
|
||||
if frame.contains(point) {
|
||||
if item is InstantPagePeerReferenceItem {
|
||||
return .fail
|
||||
} else if item is InstantPageAudioItem {
|
||||
return .fail
|
||||
} else if item is InstantPageArticleItem {
|
||||
return .fail
|
||||
} else if item is InstantPageFeedbackItem {
|
||||
return .fail
|
||||
} else if let item = item as? InstantPageDetailsItem {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let itemNode = itemNode as? InstantPageDetailsNode, itemNode.item === item {
|
||||
return itemNode.tapActionAtPoint(point.offsetBy(dx: -itemNode.frame.minX, dy: -itemNode.frame.minY))
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return .waitForSingleTap
|
||||
}
|
||||
}
|
||||
@ -843,6 +843,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
self.optionsButton = VoiceChatHeaderButton(context: self.context)
|
||||
self.optionsButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
self.closeButton = VoiceChatHeaderButton(context: self.context)
|
||||
self.closeButton.setContent(.image(closeButtonImage(dark: false)))
|
||||
|
||||
@ -2566,6 +2567,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
@objc private func optionsPressed() {
|
||||
if self.optionsButton.isUserInteractionEnabled {
|
||||
self.optionsButton.play()
|
||||
self.optionsButton.contextAction?(self.optionsButton.containerNode, nil)
|
||||
}
|
||||
}
|
||||
@ -3219,7 +3221,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: isFullscreen)
|
||||
|
||||
if !self.optionsButtonIsAvatar {
|
||||
self.optionsButton.setContent(.image(optionsButtonImage(dark: isFullscreen)), animated: transition.isAnimated)
|
||||
self.optionsButton.setContent(.more(optionsCircleImage(dark: isFullscreen)), animated: transition.isAnimated)
|
||||
}
|
||||
self.closeButton.setContent(.image(closeButtonImage(dark: isFullscreen)), animated: transition.isAnimated)
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import Postbox
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import AvatarNode
|
||||
import AnimationUI
|
||||
|
||||
func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
@ -30,6 +31,15 @@ func optionsButtonImage(dark: Bool) -> UIImage? {
|
||||
})
|
||||
}
|
||||
|
||||
func optionsCircleImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
})
|
||||
}
|
||||
|
||||
func closeButtonImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
@ -51,6 +61,7 @@ func closeButtonImage(dark: Bool) -> UIImage? {
|
||||
final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
enum Content {
|
||||
case image(UIImage?)
|
||||
case more(UIImage?)
|
||||
case avatar(Peer)
|
||||
}
|
||||
|
||||
@ -60,6 +71,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
private var animationNode: AnimationNode?
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
@ -109,6 +121,15 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
|
||||
private var content: Content?
|
||||
func setContent(_ content: Content, animated: Bool = false) {
|
||||
if case .more = content, self.animationNode == nil {
|
||||
let iconColor = UIColor(rgb: 0xffffff)
|
||||
let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor,
|
||||
"Point 3.Group 1.Fill 1": iconColor,
|
||||
"Point 1.Group 1.Fill 1": iconColor], scale: 1.0)
|
||||
animationNode.frame = self.containerNode.bounds
|
||||
self.addSubnode(animationNode)
|
||||
self.animationNode = animationNode
|
||||
}
|
||||
if animated {
|
||||
switch content {
|
||||
case let .image(image):
|
||||
@ -127,7 +148,12 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer)
|
||||
self.iconNode.isHidden = true
|
||||
self.avatarNode.isHidden = false
|
||||
|
||||
self.animationNode?.isHidden = true
|
||||
case let .more(image):
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.avatarNode.isHidden = true
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
} else {
|
||||
self.content = content
|
||||
@ -140,6 +166,12 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
self.avatarNode.setPeer(context: self.context, theme: self.theme, peer: peer)
|
||||
self.iconNode.isHidden = true
|
||||
self.avatarNode.isHidden = false
|
||||
self.animationNode?.isHidden = true
|
||||
case let .more(image):
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.avatarNode.isHidden = true
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,4 +187,8 @@ final class VoiceChatHeaderButton: HighlightableButtonNode {
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func play() {
|
||||
self.animationNode?.playOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
import Foundation
|
||||
@ -56,7 +56,7 @@ public struct PresentationResourcesSettings {
|
||||
|
||||
public static let support = renderIcon(name: "Settings/MenuIcons/Support")
|
||||
public static let faq = renderIcon(name: "Settings/MenuIcons/Faq")
|
||||
public static let tips = renderIcon(name: "Settings/MenuIcons/Faq")
|
||||
public static let tips = renderIcon(name: "Settings/MenuIcons/Tips")
|
||||
|
||||
public static let addAccount = renderIcon(name: "Settings/MenuIcons/AddAccount")
|
||||
public static let setPasscode = renderIcon(name: "Settings/MenuIcons/SetPasscode")
|
||||
|
||||
@ -458,8 +458,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||
}
|
||||
} else if let duration = duration {
|
||||
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
let titleString = strings.Notification_VoiceChatEnded(callDurationString(strings: strings, value: duration)).0
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
let attributePeerIds: [(Int, PeerId?)] = [(0, message.author?.id)]
|
||||
let titleString = strings.Notification_VoiceChatEndedGroup(authorName, callDurationString(strings: strings, value: duration))
|
||||
attributedString = addAttributesToStringWithRanges(titleString, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds))
|
||||
}
|
||||
} else {
|
||||
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
let titleString = strings.Notification_VoiceChatStartedChannel
|
||||
|
||||
@ -220,6 +220,7 @@ swift_library(
|
||||
"//submodules/Speak:Speak",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
"//submodules/DebugSettingsUI:DebugSettingsUI",
|
||||
"//submodules/ImportStickerPackUI:ImportStickerPackUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Tips.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Tips.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "tips_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
113
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Tips.imageset/tips_30.pdf
vendored
Normal file
113
submodules/TelegramUI/Images.xcassets/Settings/MenuIcons/Tips.imageset/tips_30.pdf
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||
0.964706 0.768627 0.262745 scn
|
||||
0.000000 18.799999 m
|
||||
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
|
||||
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
|
||||
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
|
||||
18.799999 30.000000 l
|
||||
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
|
||||
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
|
||||
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
|
||||
30.000000 11.200001 l
|
||||
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
|
||||
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
|
||||
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
|
||||
11.200000 0.000000 l
|
||||
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
|
||||
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
|
||||
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
|
||||
0.000000 18.799999 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 8.500000 5.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
0.000000 12.500000 m
|
||||
0.000000 16.089851 2.910149 19.000000 6.500000 19.000000 c
|
||||
10.089851 19.000000 13.000000 16.089851 13.000000 12.500000 c
|
||||
13.000000 9.326989 11.698906 7.884006 10.621044 6.688600 c
|
||||
10.292052 6.323730 9.983858 5.981926 9.739806 5.621033 c
|
||||
9.164136 4.769756 8.876301 4.344118 8.752400 4.243239 c
|
||||
8.663695 4.171017 8.625642 4.138249 8.582385 4.115283 c
|
||||
8.539128 4.092316 8.490668 4.079148 8.381149 4.046125 c
|
||||
8.228177 4.000000 7.977118 4.000000 7.475001 4.000000 c
|
||||
5.525000 4.000000 l
|
||||
5.022882 4.000000 4.771823 4.000000 4.618851 4.046125 c
|
||||
4.509332 4.079148 4.460872 4.092316 4.417615 4.115283 c
|
||||
4.374358 4.138249 4.336305 4.171017 4.247600 4.243239 c
|
||||
4.123699 4.344119 3.835864 4.769756 3.260195 5.621032 c
|
||||
3.016143 5.981926 2.707948 6.323730 2.378956 6.688601 c
|
||||
1.301094 7.884007 0.000000 9.326990 0.000000 12.500000 c
|
||||
h
|
||||
5.500000 3.000000 m
|
||||
4.947715 3.000000 4.500000 2.552284 4.500000 2.000000 c
|
||||
4.500000 0.895432 5.395431 0.000000 6.500000 0.000000 c
|
||||
7.604569 0.000000 8.500000 0.895430 8.500000 2.000000 c
|
||||
8.500000 2.552284 8.052285 3.000000 7.500000 3.000000 c
|
||||
5.500000 3.000000 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2149
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002239 00000 n
|
||||
0000002262 00000 n
|
||||
0000002435 00000 n
|
||||
0000002509 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2568
|
||||
%%EOF
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "ic_wallet.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@ -949,8 +949,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
break
|
||||
case let .joinVoiceChat(peerId, invite):
|
||||
strongSelf.presentController(VoiceChatJoinScreen(context: strongSelf.context, peerId: peerId, invite: invite, join: { call in
|
||||
|
||||
}), .window(.root), nil)
|
||||
case .importStickers:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@ -23,6 +23,7 @@ import ShareController
|
||||
import ChatInterfaceState
|
||||
import TelegramCallsUI
|
||||
import UndoUI
|
||||
import ImportStickerPackUI
|
||||
|
||||
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
|
||||
if case .default = navigation {
|
||||
@ -480,5 +481,16 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}), on: .root, blockInteraction: false, completion: {})
|
||||
}))
|
||||
}
|
||||
case .importStickers:
|
||||
dismissInput()
|
||||
if let navigationController = navigationController, let data = UIPasteboard.general.data(forPasteboardType: "org.telegram.third-party.stickerpack"), let stickerPack = ImportStickerPack(data: data) {
|
||||
for controller in navigationController.overlayControllers {
|
||||
if controller is ImportStickerPackController {
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
let controller = ImportStickerPackController(context: context, stickerPack: stickerPack, parentNavigationController: navigationController)
|
||||
present(controller, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,7 +678,9 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if parsedUrl.host == "settings" {
|
||||
if parsedUrl.host == "importStickers" {
|
||||
handleResolvedUrl(.importStickers)
|
||||
} else if parsedUrl.host == "settings" {
|
||||
if let path = parsedUrl.pathComponents.last {
|
||||
var section: ResolvedUrlSettingsSection?
|
||||
switch path {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user