Merge commit 'c583d07cbd9d9b0e3e1a2d6ee8fab6aa87ea7f09'

This commit is contained in:
Peter 2019-05-29 16:16:21 +02:00
commit 173f9e7d37
13 changed files with 466 additions and 625 deletions

View File

@ -78,8 +78,7 @@
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */; };
096C98C221787C6700C211FF /* TGBridgeAudioDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */; };
09749BC321F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC221F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift */; };
09749BC521F0E024008FDDE9 /* StickersChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */; };
09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */; };
09749BC921F1BBA1008FDDE9 /* CallFeedbackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC821F1BBA1008FDDE9 /* CallFeedbackController.swift */; };
09749BCD21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BCC21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift */; };
09749BCF21F236F2008FDDE9 /* ModernCheckNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BCE21F236F2008FDDE9 /* ModernCheckNode.swift */; };
@ -989,8 +988,7 @@
D0EC6DCB1EB9F58900EBF1C3 /* ChatMediaInputTrendingPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AEC1E9FF1AD006F2541 /* ChatMediaInputTrendingPane.swift */; };
D0EC6DCC1EB9F58900EBF1C3 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */; };
D0EC6DCE1EB9F58900EBF1C3 /* HorizontalStickersChatContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */; };
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */; };
D0EC6DCE1EB9F58900EBF1C3 /* StickersChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */; };
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */; };
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA01D821B28008AEB01 /* HashtagChatInputPanelItem.swift */; };
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.swift */; };
@ -1283,8 +1281,7 @@
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioDecoder.h; sourceTree = "<group>"; };
096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TGBridgeAudioDecoder.mm; sourceTree = "<group>"; };
09749BC221F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelNode.swift; sourceTree = "<group>"; };
09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputPanelItem.swift; sourceTree = "<group>"; };
09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelItem.swift; sourceTree = "<group>"; };
09749BC821F1BBA1008FDDE9 /* CallFeedbackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallFeedbackController.swift; sourceTree = "<group>"; };
09749BCC21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperGalleryDecorationNode.swift; sourceTree = "<group>"; };
09749BCE21F236F2008FDDE9 /* ModernCheckNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModernCheckNode.swift; sourceTree = "<group>"; };
@ -1679,8 +1676,7 @@
D048EA8A1F4F298A00188713 /* InstantPageSettingsThemeItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsThemeItemNode.swift; sourceTree = "<group>"; };
D048EA8C1F4F299A00188713 /* InstantPageSettingsSwitchItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsSwitchItemNode.swift; sourceTree = "<group>"; };
D048EA8E1F4F2A9C00188713 /* InstantPageSettingsItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageSettingsItemNode.swift; sourceTree = "<group>"; };
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickersChatContextPanelNode.swift; sourceTree = "<group>"; };
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickerGridItem.swift; sourceTree = "<group>"; };
D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelNode.swift; sourceTree = "<group>"; };
D049EAE51E44AD5600A2CD3A /* ChatMediaInputMetaSectionItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputMetaSectionItemNode.swift; sourceTree = "<group>"; };
D049EAED1E44BB3200A2CD3A /* ChatListRecentPeersListItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatListRecentPeersListItem.swift; sourceTree = "<group>"; };
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceController.swift; sourceTree = "<group>"; };
@ -3196,10 +3192,8 @@
D049EAE01E447AB700A2CD3A /* Stickers */ = {
isa = PBXGroup;
children = (
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */,
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */,
09749BC221F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift */,
09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */,
D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */,
09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */,
);
name = Stickers;
sourceTree = "<group>";
@ -5458,7 +5452,6 @@
092F36902157AB46001A9F49 /* ItemListCallListItem.swift in Sources */,
D0EC6CC61EB9F58800EBF1C3 /* PresenceStrings.swift in Sources */,
D0EC6CC71EB9F58800EBF1C3 /* PeerNotificationSoundStrings.swift in Sources */,
09749BC321F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift in Sources */,
D01C06C01FBF118A001561AB /* MessageUtils.swift in Sources */,
D0104F281F47171F004E4881 /* InstantPageGalleryController.swift in Sources */,
D0EC6CC81EB9F58800EBF1C3 /* ProgressiveImage.swift in Sources */,
@ -6057,10 +6050,9 @@
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */,
D0F8C399201774AF00236FC5 /* FeedGroupingControllerNode.swift in Sources */,
D0EEE9A12165585F001292A6 /* DocumentPreviewController.swift in Sources */,
D0EC6DCE1EB9F58900EBF1C3 /* HorizontalStickersChatContextPanelNode.swift in Sources */,
D0EC6DCE1EB9F58900EBF1C3 /* StickersChatInputContextPanelNode.swift in Sources */,
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
09749BC521F0E024008FDDE9 /* StickersChatInputPanelItem.swift in Sources */,
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */,
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
09B4EE5621A8149C00847FA6 /* ItemListInfoItem.swift in Sources */,
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,

View File

@ -77,11 +77,11 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
switch inputQueryResult {
case let .stickers(results):
if !results.isEmpty {
if let currentPanel = currentPanel as? HorizontalStickersChatContextPanelNode {
if let currentPanel = currentPanel as? StickersChatInputContextPanelNode {
currentPanel.updateResults(results.map({ $0.file }))
return currentPanel
} else {
let panel = HorizontalStickersChatContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
let panel = StickersChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
panel.controllerInteraction = controllerInteraction
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results.map({ $0.file }))

View File

@ -224,16 +224,17 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let titleSize = self.titleNode.measure(size)
let titleSize = self.titleNode.measure(measureSize)
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
origin.y += titleSize.height + 9.0
let textSize = self.textNode.measure(size)
let textSize = self.textNode.measure(measureSize)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
origin.y += textSize.height + 16.0

View File

@ -656,8 +656,6 @@ func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedStri
let substring = string.substring(with: match.range(at: 1)) + text + string.substring(with: match.range(at: 5))
result.append(NSAttributedString(string: substring, attributes: [ChatTextInputAttributes.monospace: true as NSNumber]))
//newText.append()
//attributes.append(.pre(matchIndex + match.range(at: 1).length ..< matchIndex + match.range(at: 1).length + text.length))
offsetRanges.append((NSMakeRange(matchIndex + match.range(at: 1).length, text.count), 6))
}
@ -690,7 +688,7 @@ func convertMarkdownToAttributes(_ text: NSAttributedString) -> NSAttributedStri
}
if string.length > 0 {
result.append(text.attributedSubstring(from: NSMakeRange(stringOffset, string.length - stringOffset)))
result.append(text.attributedSubstring(from: NSMakeRange(text.length - string.length, string.length)))
}
return result

View File

@ -6,7 +6,6 @@ import Display
private struct EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
let symbol: String
let text: String
}
private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
@ -16,7 +15,7 @@ private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
let text: String
var stableId: EmojisChatInputContextPanelEntryStableId {
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol, text: self.text)
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol)
}
func withUpdatedTheme(_ theme: PresentationTheme) -> EmojisChatInputContextPanelEntry {

View File

@ -94,7 +94,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
entries.append(entry)
index += 1
}
self.prepareTransition(from: self.currentEntries ?? [], to: entries)
self.prepareTransition(from: self.currentEntries, to: entries)
}
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {

View File

@ -1,136 +0,0 @@
import Foundation
import Display
import TelegramCore
import SwiftSignalKit
import AsyncDisplayKit
import Postbox
final class HorizontalStickerGridItem: GridItem {
let account: Account
let file: TelegramMediaFile
let stickersInteraction: HorizontalStickersChatContextPanelInteraction
let interfaceInteraction: ChatPanelInterfaceInteraction
let section: GridSection? = nil
init(account: Account, file: TelegramMediaFile, stickersInteraction: HorizontalStickersChatContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) {
self.account = account
self.file = file
self.stickersInteraction = stickersInteraction
self.interfaceInteraction = interfaceInteraction
}
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
let node = HorizontalStickerGridItemNode()
node.setup(account: self.account, item: self)
node.interfaceInteraction = self.interfaceInteraction
return node
}
func update(node: GridItemNode) {
guard let node = node as? HorizontalStickerGridItemNode else {
assertionFailure()
return
}
node.setup(account: self.account, item: self)
node.interfaceInteraction = self.interfaceInteraction
}
}
final class HorizontalStickerGridItemNode: GridItemNode {
private var currentState: (Account, HorizontalStickerGridItem, CGSize)?
private let imageNode: TransformImageNode
private let stickerFetchedDisposable = MetaDisposable()
var interfaceInteraction: ChatPanelInterfaceInteraction?
private var currentIsPreviewing: Bool = false
var stickerItem: StickerPackItem? {
if let (_, item, _) = self.currentState {
return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.file, indexKeys: [])
} else {
return nil
}
}
override init() {
self.imageNode = TransformImageNode()
super.init()
self.addSubnode(self.imageNode)
}
deinit {
stickerFetchedDisposable.dispose()
}
override func didLoad() {
super.didLoad()
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
}
func setup(account: Account, item: HorizontalStickerGridItem) {
if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1.file.id != item.file.id {
if let dimensions = item.file.dimensions {
self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: true))
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: chatMessageStickerResource(file: item.file, small: true)).start())
self.currentState = (account, item, dimensions)
self.setNeedsLayout()
}
}
self.updatePreviewing(animated: false)
}
override func layout() {
super.layout()
let bounds = self.bounds
let boundingSize = bounds.insetBy(dx: 2.0, dy: 2.0).size
if let (_, _, mediaDimensions) = self.currentState {
let imageSize = mediaDimensions.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: CGSize(width: imageSize.width, height: imageSize.height))
self.imageNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: imageSize.width, height: imageSize.height))
self.imageNode.position = CGPoint(x: imageFrame.midX, y: imageFrame.midY)
}
}
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state {
interfaceInteraction.sendSticker(.standalone(media: item.file))
}
}
func transitionNode() -> ASDisplayNode? {
return self.imageNode
}
func updatePreviewing(animated: Bool) {
var isPreviewing = false
if let (_, item, _) = self.currentState {
isPreviewing = item.stickersInteraction.previewedStickerItem == self.stickerItem
}
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)
}
}
}
}
}

View File

@ -1,280 +0,0 @@
import Foundation
import AsyncDisplayKit
import Postbox
import TelegramCore
import Display
import SwiftSignalKit
final class HorizontalStickersChatContextPanelInteraction {
var previewedStickerItem: StickerPackItem?
}
private struct StickerEntry: Identifiable, Comparable {
let index: Int
let file: TelegramMediaFile
var stableId: MediaId {
return self.file.fileId
}
static func ==(lhs: StickerEntry, rhs: StickerEntry) -> Bool {
return lhs.index == rhs.index && lhs.stableId == rhs.stableId
}
static func <(lhs: StickerEntry, rhs: StickerEntry) -> Bool {
return lhs.index < rhs.index
}
func item(account: Account, stickersInteraction: HorizontalStickersChatContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> GridItem {
return HorizontalStickerGridItem(account: account, file: self.file, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction)
}
}
private struct StickerEntryTransition {
let deletions: [Int]
let insertions: [GridNodeInsertItem]
let updates: [GridNodeUpdateItem]
let updateFirstIndexInSectionOffset: Int?
let stationaryItems: GridNodeStationaryItems
let scrollToItem: GridNodeScrollToItem?
}
private func preparedGridEntryTransition(account: Account, from fromEntries: [StickerEntry], to toEntries: [StickerEntry], stickersInteraction: HorizontalStickersChatContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> StickerEntryTransition {
let stationaryItems: GridNodeStationaryItems = .none
let scrollToItem: GridNodeScrollToItem? = nil
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction), previousIndex: $0.2) }
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction)) }
return StickerEntryTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: nil, stationaryItems: stationaryItems, scrollToItem: scrollToItem)
}
final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
private var strings: PresentationStrings
private let gridNode: GridNode
private let backgroundNode: ASDisplayNode
private var validLayout: (CGSize, CGFloat, CGFloat, ChatPresentationInterfaceState)?
private var currentEntries: [StickerEntry]?
private var queuedTransitions: [(StickerEntryTransition, Bool)] = []
public var controllerInteraction: ChatControllerInteraction?
private let stickersInteraction: HorizontalStickersChatContextPanelInteraction
private var stickerPreviewController: StickerPreviewController?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
self.strings = strings
self.gridNode = GridNode()
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.gridNode.scrollView.alwaysBounceVertical = true
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor
self.stickersInteraction = HorizontalStickersChatContextPanelInteraction()
super.init(context: context, theme: theme, strings: strings)
self.placement = .overTextInput
self.isOpaque = false
self.clipsToBounds = true
self.addSubnode(self.gridNode)
self.gridNode.addSubnode(self.backgroundNode)
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
if let strongSelf = self {
let convertedPoint = strongSelf.gridNode.view.convert(point, from: strongSelf.view)
guard strongSelf.gridNode.bounds.contains(convertedPoint) else {
return nil
}
if let itemNode = strongSelf.gridNode.itemNodeAtPoint(strongSelf.view.convert(point, to: strongSelf.gridNode.view)) as? HorizontalStickerGridItemNode, let item = itemNode.stickerItem {
return strongSelf.context.account.postbox.transaction { transaction -> Bool in
return getIsStickerSaved(transaction: transaction, fileId: item.file.fileId)
}
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: {
controllerInteraction.sendSticker(.standalone(media: item.file), true)
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: {
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()
}
}
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: {
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes {
switch attribute {
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
controller.sendSticker = { file in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
controllerInteraction.sendSticker(file, true)
}
}
controllerInteraction.navigationController()?.view.window?.endEditing(true)
controllerInteraction.presentController(controller, nil)
}
break loop
default:
break
}
}
}
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {})
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
return nil
}
}
}
}
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
return sourceNode
})
strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil)
return controller
}
return nil
}, updateContent: { [weak self] content in
if let strongSelf = self {
var item: StickerPackItem?
if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item {
item = contentItem
}
strongSelf.updatePreviewingItem(item: item, animated: true)
}
}))
}
func updateResults(_ results: [TelegramMediaFile]) {
let firstTime = self.currentEntries == nil
let previousEntries = self.currentEntries ?? []
var entries: [StickerEntry] = []
for i in 0 ..< results.count {
entries.append(StickerEntry(index: i, file: results[i]))
}
self.currentEntries = entries
if let validLayout = self.validLayout {
self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate, interfaceState: validLayout.3)
}
let transition = preparedGridEntryTransition(account: self.context.account, from: previousEntries, to: entries, stickersInteraction: self.stickersInteraction, interfaceInteraction: self.interfaceInteraction!)
self.enqueueTransition(transition, firstTime: firstTime)
}
private func enqueueTransition(_ transition: StickerEntryTransition, firstTime: Bool) {
self.queuedTransitions.append((transition, firstTime))
if self.validLayout != nil {
self.dequeueTransitions()
}
}
private func dequeueTransitions() {
while !self.queuedTransitions.isEmpty {
let (transition, firstTime) = self.queuedTransitions.removeFirst()
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: .immediate, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.backgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: strongSelf.bounds.width, height: strongSelf.gridNode.scrollView.contentSize.height + 500.0)
if firstTime {
let position = strongSelf.gridNode.layer.position
let offset = strongSelf.gridNode.frame.height + strongSelf.gridNode.scrollView.contentOffset.y
strongSelf.gridNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + offset), to: position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in })
}
}
})
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
let minimumItemHeights: CGFloat = floor(66.0 * 1.5)
return max(size.height - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset, interfaceState)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
insets.left = leftInset
insets.right = rightInset
transition.updateFrame(node: self.gridNode, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let updateSizeAndInsets = GridNodeUpdateLayout(layout: GridNodeLayout(size: size, insets: insets, preloadSize: 100.0, type: .fixed(itemSize: CGSize(width: 66.0, height: 66.0), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition)
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: updateSizeAndInsets, itemTransition: .immediate, stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { [weak self] _ in
if let strongSelf = self {
strongSelf.backgroundNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: strongSelf.gridNode.scrollView.contentSize.height + 500.0)
}
})
if !hadValidLayout {
self.dequeueTransitions()
}
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
}
}
override func animateOut(completion: @escaping () -> Void) {
let position = self.gridNode.layer.position
let offset = self.gridNode.frame.height + self.gridNode.scrollView.contentOffset.y
self.gridNode.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let convertedPoint = self.convert(point, to: self.gridNode)
if convertedPoint.y > 0.0 {
return super.hitTest(point, with: event)
} else {
return nil
}
}
private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) {
if self.stickersInteraction.previewedStickerItem != item {
self.stickersInteraction.previewedStickerItem = item
self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? HorizontalStickerGridItemNode {
itemNode.updatePreviewing(animated: animated)
}
}
}
}
}

View File

@ -177,6 +177,7 @@ public final class PresentationCall {
private var receptionDisposable: Disposable?
private var reportedIncomingCall = false
private var callWasActive = false
private var shouldPresentCallRating = false
private var sessionStateDisposable: Disposable?
@ -423,14 +424,16 @@ public final class PresentationCall {
}
}
case .accepting:
self.callWasActive = true
presentationState = .connecting(nil)
case .dropping:
presentationState = .terminating
case let .terminated(id, reason, options):
presentationState = .terminated(id, reason, options.contains(.reportRating) || self.shouldPresentCallRating)
presentationState = .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating))
case let .requesting(ringing):
presentationState = .requesting(ringing)
case let .active(_, _, keyVisualHash, _, _, _):
self.callWasActive = true
if let callContextState = callContextState {
switch callContextState {
case .initializing:

View File

@ -0,0 +1,238 @@
import Foundation
import AsyncDisplayKit
import Display
import TelegramCore
import SwiftSignalKit
import Postbox
final class StickersChatInputContextPanelItem: ListViewItem {
let account: Account
let theme: PresentationTheme
let index: Int
let files: [TelegramMediaFile]
let itemsInRow: Int
let stickersInteraction: StickersChatInputContextPanelInteraction
let interfaceInteraction: ChatPanelInterfaceInteraction
let selectable: Bool = false
public init(account: Account, theme: PresentationTheme, index: Int, files: [TelegramMediaFile], itemsInRow: Int, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) {
self.account = account
self.theme = theme
self.index = index
self.files = files
self.itemsInRow = itemsInRow
self.stickersInteraction = stickersInteraction
self.interfaceInteraction = interfaceInteraction
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
let configure = { () -> Void in
let node = StickersChatInputContextPanelItemNode()
let nodeLayout = node.asyncLayout()
let (top, bottom) = (previousItem != nil, nextItem != nil)
let (layout, apply) = nodeLayout(self, params, top, bottom)
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply(.None) })
})
}
}
if Thread.isMainThread {
async {
configure()
}
} else {
configure()
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? StickersChatInputContextPanelItemNode {
let nodeLayout = nodeValue.asyncLayout()
async {
let (top, bottom) = (previousItem != nil, nextItem != nil)
let (layout, apply) = nodeLayout(self, params, top, bottom)
Queue.mainQueue().async {
completion(layout, { _ in
apply(animation)
})
}
}
} else {
assertionFailure()
}
}
}
}
private let itemSize = CGSize(width: 66.0, height: 66.0)
private let inset: CGFloat = 3.0
final class StickersChatInputContextPanelItemNode: ListViewItemNode {
private let topSeparatorNode: ASDisplayNode
private var nodes: [TransformImageNode] = []
private var item: StickersChatInputContextPanelItem?
private let disposables = DisposableSet()
private var currentPreviewingIndex: Int?
init() {
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
}
deinit {
self.disposables.dispose()
}
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
if let item = item as? StickersChatInputContextPanelItem {
let doLayout = self.asyncLayout()
let merged = (top: previousItem != nil, bottom: nextItem != nil)
let (layout, apply) = doLayout(item, params, merged.top, merged.bottom)
self.contentSize = layout.contentSize
self.insets = layout.insets
apply(.None)
}
}
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
@objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
guard let item = self.item else {
return
}
let location = gestureRecognizer.location(in: gestureRecognizer.view)
for i in 0 ..< self.nodes.count {
if self.nodes[i].frame.contains(location) {
let file = item.files[i]
item.interfaceInteraction.sendSticker(.standalone(media: file))
break
}
}
}
func stickerItem(at index: Int) -> StickerPackItem? {
guard let item = self.item else {
return nil
}
if index < item.files.count {
return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.files[index], indexKeys: [])
} else {
return nil
}
}
func stickerItem(at location: CGPoint) -> (StickerPackItem, ASDisplayNode)? {
guard let item = self.item else {
return nil
}
for i in 0 ..< self.nodes.count {
if self.nodes[i].frame.contains(location) {
return (StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.files[i], indexKeys: []), self.nodes[i])
}
}
return nil
}
// func transitionNode() -> ASDisplayNode? {
// return self.imageNode
// }
//
func updatePreviewing(animated: Bool) {
guard let item = self.item else {
return
}
var previewingIndex: Int? = nil
for i in 0 ..< item.files.count {
if item.stickersInteraction.previewedStickerItem == self.stickerItem(at: i) {
previewingIndex = i
break
}
}
if self.currentPreviewingIndex != previewingIndex {
self.currentPreviewingIndex = previewingIndex
for i in 0 ..< self.nodes.count {
let layer = self.nodes[i].layer
if i == previewingIndex {
layer.transform = CATransform3DMakeScale(0.8, 0.8, 1.0)
if animated {
let scale = ((layer.presentation()?.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue ?? (layer.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue) ?? 1.0
layer.animateSpring(from: scale as NSNumber, to: 0.8 as NSNumber, keyPath: "transform.scale", duration: 0.4)
}
} else {
layer.transform = CATransform3DIdentity
if animated {
let scale = ((layer.presentation()?.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue ?? (layer.value(forKeyPath: "transform.scale") as? NSNumber)?.floatValue) ?? 0.8
layer.animateSpring(from: scale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
}
}
}
}
}
func asyncLayout() -> (_ item: StickersChatInputContextPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
return { [weak self] item, params, mergedTop, mergedBottom in
let baseWidth = params.width - params.leftInset - params.rightInset
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 66.0), insets: UIEdgeInsets())
return (nodeLayout, { _ in
if let strongSelf = self {
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.item = item
if item.index == 0 && strongSelf.topSeparatorNode.supernode == nil {
strongSelf.addSubnode(strongSelf.topSeparatorNode)
}
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
let spacing = (baseWidth - itemSize.width * CGFloat(item.itemsInRow)) / (CGFloat(max(1, item.itemsInRow + 1)))
var i = 0
for file in item.files {
let imageNode: TransformImageNode
if strongSelf.nodes.count > i {
imageNode = strongSelf.nodes[i]
} else {
imageNode = TransformImageNode()
strongSelf.nodes.append(imageNode)
strongSelf.addSubnode(imageNode)
}
imageNode.setSignal(chatMessageSticker(account: item.account, file: file, small: true))
strongSelf.disposables.add(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: true)).start())
var imageSize = itemSize
if let dimensions = file.dimensions {
imageSize = dimensions.aspectFitted(CGSize(width: itemSize.width - 4.0, height: itemSize.height - 4.0))
imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
}
imageNode.frame = CGRect(x: spacing + params.leftInset + (itemSize.width + spacing) * CGFloat(i) + floor((itemSize.width - imageSize.width) / 2.0), y: floor((itemSize.height - imageSize.height) / 2.0), width: imageSize.width, height: imageSize.height)
i += 1
}
}
})
}
}
}

View File

@ -3,69 +3,96 @@ import AsyncDisplayKit
import Postbox
import TelegramCore
import Display
import SwiftSignalKit
private struct StickersChatInputContextPanelEntryStableId: Hashable {
let text: String
let ids: [MediaId]
var hashValue: Int {
return self.text.hashValue
var hash: Int = 0
for i in 0 ..< self.ids.count {
if i == 0 {
hash = self.ids[i].hashValue
} else {
hash = hash &* 31 &+ self.ids[i].hashValue
}
}
return hash
}
static func ==(lhs: StickersChatInputContextPanelEntryStableId, rhs: StickersChatInputContextPanelEntryStableId) -> Bool {
return lhs.text == rhs.text
return lhs.ids == rhs.ids
}
}
private struct StickersChatInputContextPanelEntry: Comparable, Identifiable {
final class StickersChatInputContextPanelInteraction {
var previewedStickerItem: StickerPackItem?
}
private struct StickersChatInputContextPanelEntry: Identifiable, Comparable {
let index: Int
let theme: PresentationTheme
let text: String
let files: [TelegramMediaFile]
let itemsInRow: Int
var stableId: StickersChatInputContextPanelEntryStableId {
return StickersChatInputContextPanelEntryStableId(text: self.text)
return StickersChatInputContextPanelEntryStableId(ids: files.compactMap { $0.id })
}
func withUpdatedTheme(_ theme: PresentationTheme) -> StickersChatInputContextPanelEntry {
return StickersChatInputContextPanelEntry(index: self.index, theme: theme, text: self.text)
}
static func ==(lhs: StickersChatInputContextPanelEntry, rhs: StickersChatInputContextPanelEntry) -> Bool {
return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme
return lhs.index == rhs.index && lhs.stableId == rhs.stableId
}
static func <(lhs: StickersChatInputContextPanelEntry, rhs: StickersChatInputContextPanelEntry) -> Bool {
return lhs.index < rhs.index
}
func item(account: Account, hashtagSelected: @escaping (String) -> Void) -> ListViewItem {
return StickersChatInputPanelItem(theme: self.theme, text: self.text, hashtagSelected: hashtagSelected)
func withUpdatedTheme(_ theme: PresentationTheme) -> StickersChatInputContextPanelEntry {
return StickersChatInputContextPanelEntry(index: self.index, theme: theme, files: self.files, itemsInRow: itemsInRow)
}
func item(account: Account, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> ListViewItem {
return StickersChatInputContextPanelItem(account: account, theme: self.theme, index: self.index, files: self.files, itemsInRow: self.itemsInRow, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction)
}
}
private struct StickersChatInputContextPanelTransition {
let deletions: [ListViewDeleteItem]
let insertions: [ListViewInsertItem]
let updates: [ListViewUpdateItem]
}
private func preparedTransition(from fromEntries: [StickersChatInputContextPanelEntry], to toEntries: [StickersChatInputContextPanelEntry], account: Account, hashtagSelected: @escaping (String) -> Void) -> StickersChatInputContextPanelTransition {
private func preparedTransition(from fromEntries: [StickersChatInputContextPanelEntry], to toEntries: [StickersChatInputContextPanelEntry], account: Account, stickersInteraction: StickersChatInputContextPanelInteraction, interfaceInteraction: ChatPanelInterfaceInteraction) -> StickersChatInputContextPanelTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, hashtagSelected: hashtagSelected), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, hashtagSelected: hashtagSelected), directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, stickersInteraction: stickersInteraction, interfaceInteraction: interfaceInteraction), directionHint: nil) }
return StickersChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
}
private let itemSize = CGSize(width: 66.0, height: 66.0)
final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
private let strings: PresentationStrings
private let listView: ListView
private var results: [TelegramMediaFile] = []
private var currentEntries: [StickersChatInputContextPanelEntry]?
private var enqueuedTransitions: [(StickersChatInputContextPanelTransition, Bool)] = []
private var validLayout: (CGSize, CGFloat, CGFloat)?
private var validLayout: (CGSize, CGFloat, CGFloat, ChatPresentationInterfaceState)?
public var controllerInteraction: ChatControllerInteraction?
private let stickersInteraction: StickersChatInputContextPanelInteraction
private var stickerPreviewController: StickerPreviewController?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
self.strings = strings
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
@ -73,6 +100,8 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.stickersInteraction = StickersChatInputContextPanelInteraction()
super.init(context: context, theme: theme, strings: strings)
self.isOpaque = false
@ -81,27 +110,152 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
self.addSubnode(self.listView)
}
func updateResults(_ results: [String]) {
var entries: [StickersChatInputContextPanelEntry] = []
var index = 0
var stableIds = Set<StickersChatInputContextPanelEntryStableId>()
for text in results {
let entry = StickersChatInputContextPanelEntry(index: index, theme: self.theme, text: text)
if stableIds.contains(entry.stableId) {
continue
override func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
if let strongSelf = self {
let convertedPoint = strongSelf.listView.view.convert(point, from: strongSelf.view)
guard strongSelf.listView.bounds.contains(convertedPoint) else {
return nil
}
var stickersNode: StickersChatInputContextPanelItemNode?
strongSelf.listView.forEachVisibleItemNode({ itemNode in
if itemNode.frame.contains(convertedPoint), let node = itemNode as? StickersChatInputContextPanelItemNode {
stickersNode = node
}
})
if let stickersNode = stickersNode {
let point = strongSelf.listView.view.convert(point, to: stickersNode.view)
if let (item, itemNode) = stickersNode.stickerItem(at: point) {
return strongSelf.context.account.postbox.transaction { transaction -> Bool in
return getIsStickerSaved(transaction: transaction, fileId: item.file.fileId)
}
|> deliverOnMainQueue
|> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
var menuItems: [PeekControllerMenuItem] = []
menuItems = [
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: {
controllerInteraction.sendSticker(.standalone(media: item.file), true)
}),
PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: {
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()
}
}
}),
PeekControllerMenuItem(title: strongSelf.strings.StickerPack_ViewPack, color: .accent, action: {
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
loop: for attribute in item.file.attributes {
switch attribute {
case let .Sticker(_, packReference, _):
if let packReference = packReference {
let controller = StickerPackPreviewController(context: strongSelf.context, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController())
controller.sendSticker = { file in
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
controllerInteraction.sendSticker(file, true)
}
}
controllerInteraction.navigationController()?.view.window?.endEditing(true)
controllerInteraction.presentController(controller, nil)
}
break loop
default:
break
}
}
}
}),
PeekControllerMenuItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: {})
]
return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems))
} else {
return nil
}
}
}
}
}
return nil
}, present: { [weak self] content, sourceNode in
if let strongSelf = self {
let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.theme), content: content, sourceNode: {
return sourceNode
})
strongSelf.interfaceInteraction?.presentGlobalOverlayController(controller, nil)
return controller
}
return nil
}, updateContent: { [weak self] content in
if let strongSelf = self {
var item: StickerPackItem?
if let content = content as? StickerPreviewPeekContent, case let .pack(contentItem) = content.item {
item = contentItem
}
strongSelf.updatePreviewingItem(item: item, animated: true)
}
}))
}
private func updatePreviewingItem(item: StickerPackItem?, animated: Bool) {
if self.stickersInteraction.previewedStickerItem != item {
self.stickersInteraction.previewedStickerItem = item
self.listView.forEachItemNode { itemNode in
if let itemNode = itemNode as? StickersChatInputContextPanelItemNode {
itemNode.updatePreviewing(animated: animated)
}
}
stableIds.insert(entry.stableId)
entries.append(entry)
index += 1
}
self.prepareTransition(from: self.currentEntries ?? [], to: entries)
}
func updateResults(_ results: [TelegramMediaFile]) {
self.results = results
self.commitResults(updateLayout: true)
}
private func commitResults(updateLayout: Bool = false) {
guard let validLayout = self.validLayout else {
return
}
var entries: [StickersChatInputContextPanelEntry] = []
let itemsInRow = Int(floor((validLayout.0.width - validLayout.1 - validLayout.2) / itemSize.width))
var files: [TelegramMediaFile] = []
var index = entries.count
for i in 0 ..< self.results.count {
files.append(results[i])
if files.count == itemsInRow {
entries.append(StickersChatInputContextPanelEntry(index: index, theme: self.theme, files: files, itemsInRow: itemsInRow))
index += 1
files.removeAll()
}
}
if !files.isEmpty {
entries.append(StickersChatInputContextPanelEntry(index: index, theme: self.theme, files: files, itemsInRow: itemsInRow))
}
if updateLayout {
self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate, interfaceState: validLayout.3)
}
self.prepareTransition(from: self.currentEntries, to: entries)
}
private func prepareTransition(from: [StickersChatInputContextPanelEntry]? , to: [StickersChatInputContextPanelEntry]) {
let firstTime = from == nil
let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, hashtagSelected: { [weak self] text in
})
let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, stickersInteraction: self.stickersInteraction, interfaceInteraction: self.interfaceInteraction!)
self.currentEntries = to
self.enqueueTransition(transition, firstTime: firstTime)
}
@ -155,14 +309,14 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 3.5)
let minimumItemHeights: CGFloat = floor(itemSize.height * 1.5)
return max(size.height - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset)
self.validLayout = (size, leftInset, rightInset, interfaceState)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
@ -174,16 +328,16 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
var duration: Double = 0.0
var curve: UInt = 0
switch transition {
case .immediate:
break
case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve {
case .easeInOut, .custom:
break
case .spring:
curve = 7
}
case .immediate:
break
case let .animated(animationDuration, animationCurve):
duration = animationDuration
switch animationCurve {
case .easeInOut, .custom:
break
case .spring:
curve = 7
}
}
let listViewCurve: ListViewAnimationCurve
@ -197,6 +351,8 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.commitResults(updateLayout: false)
if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty {
self.dequeueTransition()
@ -235,4 +391,3 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
return self.listView.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event)
}
}

View File

@ -1,134 +0,0 @@
import Foundation
import AsyncDisplayKit
import Display
import TelegramCore
import SwiftSignalKit
import Postbox
final class StickersChatInputPanelItem: ListViewItem {
fileprivate let theme: PresentationTheme
fileprivate let text: String
private let hashtagSelected: (String) -> Void
let selectable: Bool = true
public init(theme: PresentationTheme, text: String, hashtagSelected: @escaping (String) -> Void) {
self.theme = theme
self.text = text
self.hashtagSelected = hashtagSelected
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
let configure = { () -> Void in
let node = StickersChatInputPanelItemNode()
let nodeLayout = node.asyncLayout()
let (top, bottom) = (previousItem != nil, nextItem != nil)
let (layout, apply) = nodeLayout(self, params, top, bottom)
node.contentSize = layout.contentSize
node.insets = layout.insets
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in apply(.None) })
})
}
}
if Thread.isMainThread {
async {
configure()
}
} else {
configure()
}
}
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
if let nodeValue = node() as? StickersChatInputPanelItemNode {
let nodeLayout = nodeValue.asyncLayout()
async {
let (top, bottom) = (previousItem != nil, nextItem != nil)
let (layout, apply) = nodeLayout(self, params, top, bottom)
Queue.mainQueue().async {
completion(layout, { _ in
apply(animation)
})
}
}
} else {
assertionFailure()
}
}
}
}
private let textFont = Font.medium(14.0)
final class StickersChatInputPanelItemNode: ListViewItemNode {
static let itemHeight: CGFloat = 42.0
private let textNode: TextNode
private let topSeparatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
init() {
self.textNode = TextNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isLayerBacked = true
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.textNode)
}
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
if let item = item as? StickersChatInputPanelItem {
let doLayout = self.asyncLayout()
let merged = (top: previousItem != nil, bottom: nextItem != nil)
let (layout, apply) = doLayout(item, params, merged.top, merged.bottom)
self.contentSize = layout.contentSize
self.insets = layout.insets
apply(.None)
}
}
func asyncLayout() -> (_ item: StickersChatInputPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
return { [weak self] item, params, mergedTop, mergedBottom in
let baseWidth = params.width - params.leftInset - params.rightInset
let leftInset: CGFloat = 15.0 + params.leftInset
let rightInset: CGFloat = 10.0 + params.rightInset
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "#\(item.text)", font: textFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets())
return (nodeLayout, { _ in
if let strongSelf = self {
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
let _ = textApply()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size)
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
}
})
}
}
}

View File

@ -32,7 +32,12 @@ extension CharacterSet {
}
func isValidUrl(_ url: String) -> Bool {
if let url = URL(string: url), ["http", "https"].contains(url.scheme), let host = url.host, host.contains(".") {
if let url = URL(string: url), ["http", "https"].contains(url.scheme), let host = url.host, host.contains(".") && url.user == nil {
let components = host.components(separatedBy: ".")
let domain = (components.first ?? "")
if domain.isEmpty {
return false
}
return true
} else {
return false