mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge commit 'c583d07cbd9d9b0e3e1a2d6ee8fab6aa87ea7f09'
This commit is contained in:
commit
173f9e7d37
@ -78,8 +78,7 @@
|
|||||||
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
|
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
|
||||||
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */; };
|
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BD21787C6700C211FF /* TGBridgeAudioDecoder.h */; };
|
||||||
096C98C221787C6700C211FF /* TGBridgeAudioDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */; };
|
096C98C221787C6700C211FF /* TGBridgeAudioDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BE21787C6700C211FF /* TGBridgeAudioDecoder.mm */; };
|
||||||
09749BC321F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC221F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift */; };
|
09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */; };
|
||||||
09749BC521F0E024008FDDE9 /* StickersChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */; };
|
|
||||||
09749BC921F1BBA1008FDDE9 /* CallFeedbackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC821F1BBA1008FDDE9 /* CallFeedbackController.swift */; };
|
09749BC921F1BBA1008FDDE9 /* CallFeedbackController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BC821F1BBA1008FDDE9 /* CallFeedbackController.swift */; };
|
||||||
09749BCD21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BCC21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift */; };
|
09749BCD21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BCC21F23139008FDDE9 /* WallpaperGalleryDecorationNode.swift */; };
|
||||||
09749BCF21F236F2008FDDE9 /* ModernCheckNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09749BCE21F236F2008FDDE9 /* ModernCheckNode.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 */; };
|
D0EC6DCB1EB9F58900EBF1C3 /* ChatMediaInputTrendingPane.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AEC1E9FF1AD006F2541 /* ChatMediaInputTrendingPane.swift */; };
|
||||||
D0EC6DCC1EB9F58900EBF1C3 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
|
D0EC6DCC1EB9F58900EBF1C3 /* ChatButtonKeyboardInputNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932351E0988C60074F044 /* ChatButtonKeyboardInputNode.swift */; };
|
||||||
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */; };
|
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C991D81FF3F008AEB01 /* ChatInputContextPanelNode.swift */; };
|
||||||
D0EC6DCE1EB9F58900EBF1C3 /* HorizontalStickersChatContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */; };
|
D0EC6DCE1EB9F58900EBF1C3 /* StickersChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */; };
|
||||||
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */; };
|
|
||||||
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */; };
|
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C971D81FF28008AEB01 /* HashtagChatInputContextPanelNode.swift */; };
|
||||||
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA01D821B28008AEB01 /* HashtagChatInputPanelItem.swift */; };
|
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA01D821B28008AEB01 /* HashtagChatInputPanelItem.swift */; };
|
||||||
D0EC6DD21EB9F58900EBF1C3 /* MentionChatInputContextPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA31D82BCD0008AEB01 /* MentionChatInputContextPanelNode.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>"; };
|
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>"; };
|
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>"; };
|
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 /* StickersChatInputContextPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelItem.swift; sourceTree = "<group>"; };
|
||||||
09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickersChatInputPanelItem.swift; sourceTree = "<group>"; };
|
|
||||||
09749BC821F1BBA1008FDDE9 /* CallFeedbackController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallFeedbackController.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickersChatInputContextPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalStickerGridItem.swift; sourceTree = "<group>"; };
|
|
||||||
D049EAE51E44AD5600A2CD3A /* ChatMediaInputMetaSectionItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMediaInputMetaSectionItemNode.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>"; };
|
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>"; };
|
D049EAF21E44DE2500A2CD3A /* AuthorizationSequenceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthorizationSequenceController.swift; sourceTree = "<group>"; };
|
||||||
@ -3196,10 +3192,8 @@
|
|||||||
D049EAE01E447AB700A2CD3A /* Stickers */ = {
|
D049EAE01E447AB700A2CD3A /* Stickers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
D049EAE11E447AD500A2CD3A /* HorizontalStickersChatContextPanelNode.swift */,
|
D049EAE11E447AD500A2CD3A /* StickersChatInputContextPanelNode.swift */,
|
||||||
D049EAE31E44949F00A2CD3A /* HorizontalStickerGridItem.swift */,
|
09749BC421F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift */,
|
||||||
09749BC221F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift */,
|
|
||||||
09749BC421F0E024008FDDE9 /* StickersChatInputPanelItem.swift */,
|
|
||||||
);
|
);
|
||||||
name = Stickers;
|
name = Stickers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -5458,7 +5452,6 @@
|
|||||||
092F36902157AB46001A9F49 /* ItemListCallListItem.swift in Sources */,
|
092F36902157AB46001A9F49 /* ItemListCallListItem.swift in Sources */,
|
||||||
D0EC6CC61EB9F58800EBF1C3 /* PresenceStrings.swift in Sources */,
|
D0EC6CC61EB9F58800EBF1C3 /* PresenceStrings.swift in Sources */,
|
||||||
D0EC6CC71EB9F58800EBF1C3 /* PeerNotificationSoundStrings.swift in Sources */,
|
D0EC6CC71EB9F58800EBF1C3 /* PeerNotificationSoundStrings.swift in Sources */,
|
||||||
09749BC321F0DFFD008FDDE9 /* StickersChatInputContextPanelNode.swift in Sources */,
|
|
||||||
D01C06C01FBF118A001561AB /* MessageUtils.swift in Sources */,
|
D01C06C01FBF118A001561AB /* MessageUtils.swift in Sources */,
|
||||||
D0104F281F47171F004E4881 /* InstantPageGalleryController.swift in Sources */,
|
D0104F281F47171F004E4881 /* InstantPageGalleryController.swift in Sources */,
|
||||||
D0EC6CC81EB9F58800EBF1C3 /* ProgressiveImage.swift in Sources */,
|
D0EC6CC81EB9F58800EBF1C3 /* ProgressiveImage.swift in Sources */,
|
||||||
@ -6057,10 +6050,9 @@
|
|||||||
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */,
|
D0EC6DCD1EB9F58900EBF1C3 /* ChatInputContextPanelNode.swift in Sources */,
|
||||||
D0F8C399201774AF00236FC5 /* FeedGroupingControllerNode.swift in Sources */,
|
D0F8C399201774AF00236FC5 /* FeedGroupingControllerNode.swift in Sources */,
|
||||||
D0EEE9A12165585F001292A6 /* DocumentPreviewController.swift in Sources */,
|
D0EEE9A12165585F001292A6 /* DocumentPreviewController.swift in Sources */,
|
||||||
D0EC6DCE1EB9F58900EBF1C3 /* HorizontalStickersChatContextPanelNode.swift in Sources */,
|
D0EC6DCE1EB9F58900EBF1C3 /* StickersChatInputContextPanelNode.swift in Sources */,
|
||||||
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
|
D0BCC3D2203F0A6C008126C2 /* StringForMessageTimestampStatus.swift in Sources */,
|
||||||
09749BC521F0E024008FDDE9 /* StickersChatInputPanelItem.swift in Sources */,
|
09749BC521F0E024008FDDE9 /* StickersChatInputContextPanelItem.swift in Sources */,
|
||||||
D0EC6DCF1EB9F58900EBF1C3 /* HorizontalStickerGridItem.swift in Sources */,
|
|
||||||
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
D0EC6DD01EB9F58900EBF1C3 /* HashtagChatInputContextPanelNode.swift in Sources */,
|
||||||
09B4EE5621A8149C00847FA6 /* ItemListInfoItem.swift in Sources */,
|
09B4EE5621A8149C00847FA6 /* ItemListInfoItem.swift in Sources */,
|
||||||
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
|
D0EC6DD11EB9F58900EBF1C3 /* HashtagChatInputPanelItem.swift in Sources */,
|
||||||
|
@ -77,11 +77,11 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
|
|||||||
switch inputQueryResult {
|
switch inputQueryResult {
|
||||||
case let .stickers(results):
|
case let .stickers(results):
|
||||||
if !results.isEmpty {
|
if !results.isEmpty {
|
||||||
if let currentPanel = currentPanel as? HorizontalStickersChatContextPanelNode {
|
if let currentPanel = currentPanel as? StickersChatInputContextPanelNode {
|
||||||
currentPanel.updateResults(results.map({ $0.file }))
|
currentPanel.updateResults(results.map({ $0.file }))
|
||||||
return currentPanel
|
return currentPanel
|
||||||
} else {
|
} 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.controllerInteraction = controllerInteraction
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.updateResults(results.map({ $0.file }))
|
panel.updateResults(results.map({ $0.file }))
|
||||||
|
@ -224,16 +224,17 @@ private final class ChatMessageActionUrlAuthAlertContentNode: AlertContentNode {
|
|||||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
var size = size
|
var size = size
|
||||||
size.width = min(size.width, 270.0)
|
size.width = min(size.width, 270.0)
|
||||||
|
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||||
|
|
||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
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))
|
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
|
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))
|
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
|
origin.y += textSize.height + 16.0
|
||||||
|
|
||||||
|
@ -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))
|
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]))
|
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))
|
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 {
|
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
|
return result
|
||||||
|
@ -6,7 +6,6 @@ import Display
|
|||||||
|
|
||||||
private struct EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
|
private struct EmojisChatInputContextPanelEntryStableId: Hashable, Equatable {
|
||||||
let symbol: String
|
let symbol: String
|
||||||
let text: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
|
private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
|
||||||
@ -16,7 +15,7 @@ private struct EmojisChatInputContextPanelEntry: Comparable, Identifiable {
|
|||||||
let text: String
|
let text: String
|
||||||
|
|
||||||
var stableId: EmojisChatInputContextPanelEntryStableId {
|
var stableId: EmojisChatInputContextPanelEntryStableId {
|
||||||
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol, text: self.text)
|
return EmojisChatInputContextPanelEntryStableId(symbol: self.symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedTheme(_ theme: PresentationTheme) -> EmojisChatInputContextPanelEntry {
|
func withUpdatedTheme(_ theme: PresentationTheme) -> EmojisChatInputContextPanelEntry {
|
||||||
|
@ -94,7 +94,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
self.prepareTransition(from: self.currentEntries ?? [], to: entries)
|
self.prepareTransition(from: self.currentEntries, to: entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -177,6 +177,7 @@ public final class PresentationCall {
|
|||||||
private var receptionDisposable: Disposable?
|
private var receptionDisposable: Disposable?
|
||||||
private var reportedIncomingCall = false
|
private var reportedIncomingCall = false
|
||||||
|
|
||||||
|
private var callWasActive = false
|
||||||
private var shouldPresentCallRating = false
|
private var shouldPresentCallRating = false
|
||||||
|
|
||||||
private var sessionStateDisposable: Disposable?
|
private var sessionStateDisposable: Disposable?
|
||||||
@ -423,14 +424,16 @@ public final class PresentationCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .accepting:
|
case .accepting:
|
||||||
|
self.callWasActive = true
|
||||||
presentationState = .connecting(nil)
|
presentationState = .connecting(nil)
|
||||||
case .dropping:
|
case .dropping:
|
||||||
presentationState = .terminating
|
presentationState = .terminating
|
||||||
case let .terminated(id, reason, options):
|
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):
|
case let .requesting(ringing):
|
||||||
presentationState = .requesting(ringing)
|
presentationState = .requesting(ringing)
|
||||||
case let .active(_, _, keyVisualHash, _, _, _):
|
case let .active(_, _, keyVisualHash, _, _, _):
|
||||||
|
self.callWasActive = true
|
||||||
if let callContextState = callContextState {
|
if let callContextState = callContextState {
|
||||||
switch callContextState {
|
switch callContextState {
|
||||||
case .initializing:
|
case .initializing:
|
||||||
|
238
TelegramUI/StickersChatInputContextPanelItem.swift
Normal file
238
TelegramUI/StickersChatInputContextPanelItem.swift
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,69 +3,96 @@ import AsyncDisplayKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import Display
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
private struct StickersChatInputContextPanelEntryStableId: Hashable {
|
private struct StickersChatInputContextPanelEntryStableId: Hashable {
|
||||||
let text: String
|
let ids: [MediaId]
|
||||||
|
|
||||||
var hashValue: Int {
|
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 {
|
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 index: Int
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let text: String
|
let files: [TelegramMediaFile]
|
||||||
|
let itemsInRow: Int
|
||||||
|
|
||||||
var stableId: StickersChatInputContextPanelEntryStableId {
|
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 {
|
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 {
|
static func <(lhs: StickersChatInputContextPanelEntry, rhs: StickersChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, hashtagSelected: @escaping (String) -> Void) -> ListViewItem {
|
func withUpdatedTheme(_ theme: PresentationTheme) -> StickersChatInputContextPanelEntry {
|
||||||
return StickersChatInputPanelItem(theme: self.theme, text: self.text, hashtagSelected: hashtagSelected)
|
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 {
|
private struct StickersChatInputContextPanelTransition {
|
||||||
let deletions: [ListViewDeleteItem]
|
let deletions: [ListViewDeleteItem]
|
||||||
let insertions: [ListViewInsertItem]
|
let insertions: [ListViewInsertItem]
|
||||||
let updates: [ListViewUpdateItem]
|
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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
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 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, hashtagSelected: hashtagSelected), 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)
|
return StickersChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let itemSize = CGSize(width: 66.0, height: 66.0)
|
||||||
|
|
||||||
final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||||
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
private let listView: ListView
|
private let listView: ListView
|
||||||
|
private var results: [TelegramMediaFile] = []
|
||||||
private var currentEntries: [StickersChatInputContextPanelEntry]?
|
private var currentEntries: [StickersChatInputContextPanelEntry]?
|
||||||
|
|
||||||
private var enqueuedTransitions: [(StickersChatInputContextPanelTransition, Bool)] = []
|
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) {
|
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
|
self.strings = strings
|
||||||
|
|
||||||
self.listView = ListView()
|
self.listView = ListView()
|
||||||
self.listView.isOpaque = false
|
self.listView.isOpaque = false
|
||||||
self.listView.stackFromBottom = true
|
self.listView.stackFromBottom = true
|
||||||
@ -73,6 +100,8 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.listView.limitHitTestToNodes = true
|
self.listView.limitHitTestToNodes = true
|
||||||
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
|
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
|
|
||||||
|
self.stickersInteraction = StickersChatInputContextPanelInteraction()
|
||||||
|
|
||||||
super.init(context: context, theme: theme, strings: strings)
|
super.init(context: context, theme: theme, strings: strings)
|
||||||
|
|
||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
@ -81,27 +110,152 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.addSubnode(self.listView)
|
self.addSubnode(self.listView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateResults(_ results: [String]) {
|
override func didLoad() {
|
||||||
var entries: [StickersChatInputContextPanelEntry] = []
|
super.didLoad()
|
||||||
var index = 0
|
|
||||||
var stableIds = Set<StickersChatInputContextPanelEntryStableId>()
|
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
|
||||||
for text in results {
|
if let strongSelf = self {
|
||||||
let entry = StickersChatInputContextPanelEntry(index: index, theme: self.theme, text: text)
|
let convertedPoint = strongSelf.listView.view.convert(point, from: strongSelf.view)
|
||||||
if stableIds.contains(entry.stableId) {
|
guard strongSelf.listView.bounds.contains(convertedPoint) else {
|
||||||
continue
|
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]) {
|
private func prepareTransition(from: [StickersChatInputContextPanelEntry]? , to: [StickersChatInputContextPanelEntry]) {
|
||||||
let firstTime = from == nil
|
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.currentEntries = to
|
||||||
self.enqueueTransition(transition, firstTime: firstTime)
|
self.enqueueTransition(transition, firstTime: firstTime)
|
||||||
}
|
}
|
||||||
@ -155,14 +309,14 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func topInsetForLayout(size: CGSize) -> CGFloat {
|
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)
|
return max(size.height - minimumItemHeights, 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
|
||||||
let hadValidLayout = self.validLayout != nil
|
let hadValidLayout = self.validLayout != nil
|
||||||
self.validLayout = (size, leftInset, rightInset)
|
self.validLayout = (size, leftInset, rightInset, interfaceState)
|
||||||
|
|
||||||
var insets = UIEdgeInsets()
|
var insets = UIEdgeInsets()
|
||||||
insets.top = self.topInsetForLayout(size: size)
|
insets.top = self.topInsetForLayout(size: size)
|
||||||
@ -174,16 +328,16 @@ final class StickersChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
var duration: Double = 0.0
|
var duration: Double = 0.0
|
||||||
var curve: UInt = 0
|
var curve: UInt = 0
|
||||||
switch transition {
|
switch transition {
|
||||||
case .immediate:
|
case .immediate:
|
||||||
break
|
break
|
||||||
case let .animated(animationDuration, animationCurve):
|
case let .animated(animationDuration, animationCurve):
|
||||||
duration = animationDuration
|
duration = animationDuration
|
||||||
switch animationCurve {
|
switch animationCurve {
|
||||||
case .easeInOut, .custom:
|
case .easeInOut, .custom:
|
||||||
break
|
break
|
||||||
case .spring:
|
case .spring:
|
||||||
curve = 7
|
curve = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let listViewCurve: ListViewAnimationCurve
|
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.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|
||||||
|
self.commitResults(updateLayout: false)
|
||||||
|
|
||||||
if !hadValidLayout {
|
if !hadValidLayout {
|
||||||
while !self.enqueuedTransitions.isEmpty {
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
self.dequeueTransition()
|
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)
|
return self.listView.hitTest(CGPoint(x: point.x - listViewFrame.minX, y: point.y - listViewFrame.minY), with: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -32,7 +32,12 @@ extension CharacterSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isValidUrl(_ url: String) -> Bool {
|
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
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user