mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'ae3c81cccda962a1ef21d60c1c5c837608beb60a'
This commit is contained in:
commit
73b227040a
BIN
Telegram/Telegram-iOS/Resources/UserAvatarMask.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/UserAvatarMask.tgs
Normal file
Binary file not shown.
@ -871,6 +871,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}, beginCall: { _ in
|
}, beginCall: { _ in
|
||||||
}, toggleMessageStickerStarred: { _ in
|
}, toggleMessageStickerStarred: { _ in
|
||||||
}, presentController: { _, _ in
|
}, presentController: { _, _ in
|
||||||
|
}, presentControllerInCurrent: { _, _ in
|
||||||
}, getNavigationController: {
|
}, getNavigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
}, presentGlobalOverlayController: { _, _ in
|
||||||
|
@ -890,6 +890,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var compoundTextButtonNode: HighlightTrackingButtonNode?
|
private var compoundTextButtonNode: HighlightTrackingButtonNode?
|
||||||
let measureNode: TextNode
|
let measureNode: TextNode
|
||||||
private var currentItemHeight: CGFloat?
|
private var currentItemHeight: CGFloat?
|
||||||
|
let forwardedIconNode: ASImageNode
|
||||||
let textNode: TextNodeWithEntities
|
let textNode: TextNodeWithEntities
|
||||||
var dustNode: InvisibleInkDustNode?
|
var dustNode: InvisibleInkDustNode?
|
||||||
let inputActivitiesNode: ChatListInputActivitiesNode
|
let inputActivitiesNode: ChatListInputActivitiesNode
|
||||||
@ -1160,6 +1161,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.mentionBadgeNode = ChatListBadgeNode()
|
self.mentionBadgeNode = ChatListBadgeNode()
|
||||||
self.onlineNode = PeerOnlineMarkerNode()
|
self.onlineNode = PeerOnlineMarkerNode()
|
||||||
|
|
||||||
|
self.forwardedIconNode = ASImageNode()
|
||||||
|
self.forwardedIconNode.isLayerBacked = true
|
||||||
|
self.forwardedIconNode.displaysAsynchronously = false
|
||||||
|
self.forwardedIconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.pinnedIconNode = ASImageNode()
|
self.pinnedIconNode = ASImageNode()
|
||||||
self.pinnedIconNode.isLayerBacked = true
|
self.pinnedIconNode.isLayerBacked = true
|
||||||
self.pinnedIconNode.displaysAsynchronously = false
|
self.pinnedIconNode.displaysAsynchronously = false
|
||||||
@ -1651,6 +1657,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var currentMutedIconImage: UIImage?
|
var currentMutedIconImage: UIImage?
|
||||||
var currentCredibilityIconContent: EmojiStatusComponent.Content?
|
var currentCredibilityIconContent: EmojiStatusComponent.Content?
|
||||||
var currentSecretIconImage: UIImage?
|
var currentSecretIconImage: UIImage?
|
||||||
|
var currentForwardedIcon: UIImage?
|
||||||
|
|
||||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||||
@ -1766,10 +1773,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
|
let contentImageSide: CGFloat = max(10.0, min(20.0, floor(item.presentationData.fontSize.baseDisplaySize * 18.0 / 17.0)))
|
||||||
let contentImageSize = CGSize(width: contentImageSide, height: contentImageSide)
|
let contentImageSize = CGSize(width: contentImageSide, height: contentImageSide)
|
||||||
let contentImageSpacing: CGFloat = 2.0
|
let contentImageSpacing: CGFloat = 2.0
|
||||||
|
let forwardedIconSpacing: CGFloat = 6.0
|
||||||
let contentImageTrailingSpace: CGFloat = 5.0
|
let contentImageTrailingSpace: CGFloat = 5.0
|
||||||
var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = []
|
var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = []
|
||||||
var forumThread: (id: Int64, title: String, iconId: Int64?, iconColor: Int32, isUnread: Bool)?
|
var forumThread: (id: Int64, title: String, iconId: Int64?, iconColor: Int32, isUnread: Bool)?
|
||||||
|
|
||||||
|
var displayForwardedIcon = false
|
||||||
|
|
||||||
switch contentData {
|
switch contentData {
|
||||||
case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges):
|
case let .chat(itemPeer, _, _, _, text, spoilers, customEmojiRanges):
|
||||||
var isUser = false
|
var isUser = false
|
||||||
@ -1928,6 +1938,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
attributedText = composedString
|
attributedText = composedString
|
||||||
|
|
||||||
|
if let forwardInfo = message.forwardInfo, !forwardInfo.flags.contains(.isImported) {
|
||||||
|
displayForwardedIcon = true
|
||||||
|
}
|
||||||
|
|
||||||
var displayMediaPreviews = true
|
var displayMediaPreviews = true
|
||||||
if message._asMessage().containsSecretMedia {
|
if message._asMessage().containsSecretMedia {
|
||||||
displayMediaPreviews = false
|
displayMediaPreviews = false
|
||||||
@ -2009,6 +2023,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
attributedText = textString
|
attributedText = textString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if displayForwardedIcon {
|
||||||
|
currentForwardedIcon = PresentationResourcesChatList.forwardedIcon(item.presentationData.theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentForwardedIcon {
|
||||||
|
textLeftCutout += currentForwardedIcon.size.width
|
||||||
|
if !contentImageSpecs.isEmpty {
|
||||||
|
textLeftCutout += forwardedIconSpacing
|
||||||
|
} else {
|
||||||
|
textLeftCutout += contentImageTrailingSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i in 0 ..< contentImageSpecs.count {
|
for i in 0 ..< contentImageSpecs.count {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
textLeftCutout += contentImageSpacing
|
textLeftCutout += contentImageSpacing
|
||||||
@ -3161,6 +3188,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.authorNode.alpha = 0.0
|
strongSelf.authorNode.alpha = 0.0
|
||||||
strongSelf.compoundHighlightingNode?.alpha = 0.0
|
strongSelf.compoundHighlightingNode?.alpha = 0.0
|
||||||
strongSelf.dustNode?.alpha = 0.0
|
strongSelf.dustNode?.alpha = 0.0
|
||||||
|
strongSelf.forwardedIconNode.alpha = 0.0
|
||||||
|
|
||||||
if animated || animateContent {
|
if animated || animateContent {
|
||||||
strongSelf.inputActivitiesNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
strongSelf.inputActivitiesNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
@ -3168,6 +3196,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.authorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
strongSelf.authorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||||
strongSelf.compoundHighlightingNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
strongSelf.compoundHighlightingNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||||
strongSelf.dustNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
strongSelf.dustNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||||
|
strongSelf.forwardedIconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -3177,6 +3206,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.authorNode.alpha = 1.0
|
strongSelf.authorNode.alpha = 1.0
|
||||||
strongSelf.compoundHighlightingNode?.alpha = 1.0
|
strongSelf.compoundHighlightingNode?.alpha = 1.0
|
||||||
strongSelf.dustNode?.alpha = 1.0
|
strongSelf.dustNode?.alpha = 1.0
|
||||||
|
strongSelf.forwardedIconNode.alpha = 1.0
|
||||||
if animated || animateContent {
|
if animated || animateContent {
|
||||||
strongSelf.inputActivitiesNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { value in
|
strongSelf.inputActivitiesNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { value in
|
||||||
if let strongSelf = self, value {
|
if let strongSelf = self, value {
|
||||||
@ -3187,6 +3217,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.authorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
strongSelf.authorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
strongSelf.compoundHighlightingNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
strongSelf.compoundHighlightingNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
strongSelf.dustNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
strongSelf.dustNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
strongSelf.forwardedIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.inputActivitiesNode.removeFromSupernode()
|
strongSelf.inputActivitiesNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -3203,6 +3234,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
inputActivitiesApply?()
|
inputActivitiesApply?()
|
||||||
|
|
||||||
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: floor((measureLayout.size.height - contentImageSize.height) / 2.0))
|
var mediaPreviewOffset = textNodeFrame.origin.offsetBy(dx: 1.0, dy: floor((measureLayout.size.height - contentImageSize.height) / 2.0))
|
||||||
|
|
||||||
|
if let currentForwardedIcon = currentForwardedIcon {
|
||||||
|
strongSelf.forwardedIconNode.image = currentForwardedIcon
|
||||||
|
if strongSelf.forwardedIconNode.supernode == nil {
|
||||||
|
strongSelf.mainContentContainerNode.addSubnode(strongSelf.forwardedIconNode)
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: strongSelf.forwardedIconNode, frame: CGRect(origin: CGPoint(x: mediaPreviewOffset.x, y: mediaPreviewOffset.y + 3.0), size: currentForwardedIcon.size))
|
||||||
|
mediaPreviewOffset.x += currentForwardedIcon.size.width + forwardedIconSpacing
|
||||||
|
} else if strongSelf.forwardedIconNode.supernode != nil {
|
||||||
|
strongSelf.forwardedIconNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
var validMediaIds: [EngineMedia.Id] = []
|
var validMediaIds: [EngineMedia.Id] = []
|
||||||
for (message, media, mediaSize) in contentImageSpecs {
|
for (message, media, mediaSize) in contentImageSpecs {
|
||||||
var mediaId = media.id
|
var mediaId = media.id
|
||||||
|
@ -125,6 +125,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
public let beginCall: (Bool) -> Void
|
public let beginCall: (Bool) -> Void
|
||||||
public let toggleMessageStickerStarred: (MessageId) -> Void
|
public let toggleMessageStickerStarred: (MessageId) -> Void
|
||||||
public let presentController: (ViewController, Any?) -> Void
|
public let presentController: (ViewController, Any?) -> Void
|
||||||
|
public let presentControllerInCurrent: (ViewController, Any?) -> Void
|
||||||
public let getNavigationController: () -> NavigationController?
|
public let getNavigationController: () -> NavigationController?
|
||||||
public let presentGlobalOverlayController: (ViewController, Any?) -> Void
|
public let presentGlobalOverlayController: (ViewController, Any?) -> Void
|
||||||
public let navigateFeed: () -> Void
|
public let navigateFeed: () -> Void
|
||||||
@ -228,6 +229,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
beginCall: @escaping (Bool) -> Void,
|
beginCall: @escaping (Bool) -> Void,
|
||||||
toggleMessageStickerStarred: @escaping (MessageId) -> Void,
|
toggleMessageStickerStarred: @escaping (MessageId) -> Void,
|
||||||
presentController: @escaping (ViewController, Any?) -> Void,
|
presentController: @escaping (ViewController, Any?) -> Void,
|
||||||
|
presentControllerInCurrent: @escaping (ViewController, Any?) -> Void,
|
||||||
getNavigationController: @escaping () -> NavigationController?,
|
getNavigationController: @escaping () -> NavigationController?,
|
||||||
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
|
presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void,
|
||||||
navigateFeed: @escaping () -> Void,
|
navigateFeed: @escaping () -> Void,
|
||||||
@ -330,6 +332,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
self.beginCall = beginCall
|
self.beginCall = beginCall
|
||||||
self.toggleMessageStickerStarred = toggleMessageStickerStarred
|
self.toggleMessageStickerStarred = toggleMessageStickerStarred
|
||||||
self.presentController = presentController
|
self.presentController = presentController
|
||||||
|
self.presentControllerInCurrent = presentControllerInCurrent
|
||||||
self.getNavigationController = getNavigationController
|
self.getNavigationController = getNavigationController
|
||||||
self.presentGlobalOverlayController = presentGlobalOverlayController
|
self.presentGlobalOverlayController = presentGlobalOverlayController
|
||||||
self.navigateFeed = navigateFeed
|
self.navigateFeed = navigateFeed
|
||||||
@ -440,6 +443,7 @@ public final class ChatPanelInterfaceInteraction {
|
|||||||
}, beginCall: { _ in
|
}, beginCall: { _ in
|
||||||
}, toggleMessageStickerStarred: { _ in
|
}, toggleMessageStickerStarred: { _ in
|
||||||
}, presentController: { _, _ in
|
}, presentController: { _, _ in
|
||||||
|
}, presentControllerInCurrent: { _, _ in
|
||||||
}, getNavigationController: {
|
}, getNavigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
}, presentGlobalOverlayController: { _, _ in
|
||||||
|
@ -138,7 +138,6 @@ public final class PinchSourceContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
private(set) var naturalContentFrame: CGRect?
|
private(set) var naturalContentFrame: CGRect?
|
||||||
|
|
||||||
fileprivate let gesture: PinchSourceGesture
|
fileprivate let gesture: PinchSourceGesture
|
||||||
fileprivate var panGesture: UIPanGestureRecognizer?
|
|
||||||
|
|
||||||
public var isPinchGestureEnabled: Bool = true {
|
public var isPinchGestureEnabled: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
@ -209,9 +208,6 @@ public final class PinchSourceContainerNode: ASDisplayNode, UIGestureRecognizerD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func panGestureRecognized(_ recognizer: UIPanGestureRecognizer) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,9 @@ import SwiftSignalKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
final class InstantPageContentNode : ASDisplayNode {
|
public final class InstantPageContentNode : ASDisplayNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let nameDisplayOrder: PresentationPersonNameOrder
|
private let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
@ -20,6 +21,8 @@ final class InstantPageContentNode : ASDisplayNode {
|
|||||||
private let longPressMedia: (InstantPageMedia) -> Void
|
private let longPressMedia: (InstantPageMedia) -> Void
|
||||||
private let openPeer: (EnginePeer) -> Void
|
private let openPeer: (EnginePeer) -> Void
|
||||||
private let openUrl: (InstantPageUrlItem) -> Void
|
private let openUrl: (InstantPageUrlItem) -> Void
|
||||||
|
private let activatePinchPreview: ((PinchSourceContainerNode) -> Void)?
|
||||||
|
private let pinchPreviewFinished: ((InstantPageNode) -> Void)?
|
||||||
|
|
||||||
var currentLayoutTiles: [InstantPageTile] = []
|
var currentLayoutTiles: [InstantPageTile] = []
|
||||||
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
var currentLayoutItemsWithNodes: [InstantPageItem] = []
|
||||||
@ -40,7 +43,7 @@ final class InstantPageContentNode : ASDisplayNode {
|
|||||||
|
|
||||||
private var previousVisibleBounds: CGRect?
|
private var previousVisibleBounds: CGRect?
|
||||||
|
|
||||||
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
|
init(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, items: [InstantPageItem], contentSize: CGSize, inOverlayPanel: Bool = false, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -49,6 +52,8 @@ final class InstantPageContentNode : ASDisplayNode {
|
|||||||
|
|
||||||
self.openMedia = openMedia
|
self.openMedia = openMedia
|
||||||
self.longPressMedia = longPressMedia
|
self.longPressMedia = longPressMedia
|
||||||
|
self.activatePinchPreview = activatePinchPreview
|
||||||
|
self.pinchPreviewFinished = pinchPreviewFinished
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.openUrl = openUrl
|
self.openUrl = openUrl
|
||||||
|
|
||||||
@ -193,8 +198,12 @@ final class InstantPageContentNode : ASDisplayNode {
|
|||||||
}, longPressMedia: { [weak self] media in
|
}, longPressMedia: { [weak self] media in
|
||||||
self?.longPressMedia(media)
|
self?.longPressMedia(media)
|
||||||
},
|
},
|
||||||
activatePinchPreview: nil,
|
activatePinchPreview: { [weak self] node in
|
||||||
pinchPreviewFinished: nil,
|
self?.activatePinchPreview?(node)
|
||||||
|
},
|
||||||
|
pinchPreviewFinished: { [weak self] node in
|
||||||
|
self?.pinchPreviewFinished?(node)
|
||||||
|
},
|
||||||
openPeer: { [weak self] peerId in
|
openPeer: { [weak self] peerId in
|
||||||
self?.openPeer(peerId)
|
self?.openPeer(peerId)
|
||||||
}, openUrl: { [weak self] url in
|
}, openUrl: { [weak self] url in
|
||||||
|
@ -166,14 +166,18 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
savedMessages = true
|
savedMessages = true
|
||||||
} else {
|
} else {
|
||||||
if peers.count == 1, let peer = peers.first {
|
if peers.count == 1, let peer = peers.first {
|
||||||
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
var peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
peerName = peerName.replacingOccurrences(of: "**", with: "")
|
||||||
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
|
text = presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string
|
||||||
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||||
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
var firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
firstPeerName = firstPeerName.replacingOccurrences(of: "**", with: "")
|
||||||
|
var secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
secondPeerName = secondPeerName.replacingOccurrences(of: "**", with: "")
|
||||||
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
|
text = presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).string
|
||||||
} else if let peer = peers.first {
|
} else if let peer = peers.first {
|
||||||
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
peerName = peerName.replacingOccurrences(of: "**", with: "")
|
||||||
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
|
text = presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").string
|
||||||
} else {
|
} else {
|
||||||
text = ""
|
text = ""
|
||||||
@ -1340,6 +1344,10 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
if let navigationController = strongSelf.getNavigationController() {
|
if let navigationController = strongSelf.getNavigationController() {
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
||||||
}
|
}
|
||||||
|
case let .withBotApp(botAppStart):
|
||||||
|
if let navigationController = strongSelf.getNavigationController() {
|
||||||
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botAppStart: botAppStart))
|
||||||
|
}
|
||||||
case .info:
|
case .info:
|
||||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
||||||
|> deliverOnMainQueue).start(next: { peer in
|
|> deliverOnMainQueue).start(next: { peer in
|
||||||
|
@ -45,7 +45,7 @@ final class InstantPageDetailsItem: InstantPageItem {
|
|||||||
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
|
if let expandedDetails = currentExpandedDetails, let currentlyExpanded = expandedDetails[self.index] {
|
||||||
expanded = currentlyExpanded
|
expanded = currentlyExpanded
|
||||||
}
|
}
|
||||||
return InstantPageDetailsNode(context: context, sourceLocation: sourceLocation, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, item: self, openMedia: openMedia, longPressMedia: longPressMedia, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded)
|
return InstantPageDetailsNode(context: context, sourceLocation: sourceLocation, strings: strings, nameDisplayOrder: nameDisplayOrder, theme: theme, item: self, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl, currentlyExpanded: expanded, updateDetailsExpanded: updateDetailsExpanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchesAnchor(_ anchor: String) -> Bool {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
|
@ -8,16 +8,17 @@ import SwiftSignalKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private let detailsInset: CGFloat = 17.0
|
private let detailsInset: CGFloat = 17.0
|
||||||
private let titleInset: CGFloat = 22.0
|
private let titleInset: CGFloat = 22.0
|
||||||
|
|
||||||
final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
public final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
private let nameDisplayOrder: PresentationPersonNameOrder
|
private let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
private let theme: InstantPageTheme
|
private let theme: InstantPageTheme
|
||||||
let item: InstantPageDetailsItem
|
public let item: InstantPageDetailsItem
|
||||||
|
|
||||||
private let titleTile: InstantPageTile
|
private let titleTile: InstantPageTile
|
||||||
private let titleTileNode: InstantPageTileNode
|
private let titleTileNode: InstantPageTileNode
|
||||||
@ -35,7 +36,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
|
|
||||||
var requestLayoutUpdate: ((Bool) -> Void)?
|
var requestLayoutUpdate: ((Bool) -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
|
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, item: InstantPageDetailsItem, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, currentlyExpanded: Bool?, updateDetailsExpanded: @escaping (Bool) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -65,7 +66,7 @@ final class InstantPageDetailsNode: ASDisplayNode, InstantPageNode {
|
|||||||
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
|
self.arrowNode = InstantPageDetailsArrowNode(color: theme.controlColor, open: self.expanded)
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
|
|
||||||
self.contentNode = InstantPageContentNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, sourceLocation: sourceLocation, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, longPressMedia: longPressMedia, openPeer: openPeer, openUrl: openUrl)
|
self.contentNode = InstantPageContentNode(context: context, strings: strings, nameDisplayOrder: nameDisplayOrder, sourceLocation: sourceLocation, theme: theme, items: item.items, contentSize: CGSize(width: item.frame.width, height: item.frame.height - item.titleHeight), openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished, openPeer: openPeer, openUrl: openUrl)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
|
|||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
|
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
|
||||||
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in })
|
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in })
|
||||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
||||||
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
||||||
self.contentNode = contentNode
|
self.contentNode = contentNode
|
||||||
|
@ -22,7 +22,7 @@ final class InstantPageSlideshowItem: InstantPageItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
func node(context: AccountContext, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, theme: InstantPageTheme, sourceLocation: InstantPageSourceLocation, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, openPeer: @escaping (EnginePeer) -> Void, openUrl: @escaping (InstantPageUrlItem) -> Void, updateWebEmbedHeight: @escaping (CGFloat) -> Void, updateDetailsExpanded: @escaping (Bool) -> Void, currentExpandedDetails: [Int : Bool]?) -> InstantPageNode? {
|
||||||
return InstantPageSlideshowNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia)
|
return InstantPageSlideshowNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, medias: self.medias, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchesAnchor(_ anchor: String) -> Bool {
|
func matchesAnchor(_ anchor: String) -> Bool {
|
||||||
|
@ -6,6 +6,7 @@ import Display
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private final class InstantPageSlideshowItemNode: ASDisplayNode {
|
private final class InstantPageSlideshowItemNode: ASDisplayNode {
|
||||||
private var _index: Int?
|
private var _index: Int?
|
||||||
@ -69,6 +70,8 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe
|
|||||||
private let webPage: TelegramMediaWebpage
|
private let webPage: TelegramMediaWebpage
|
||||||
private let openMedia: (InstantPageMedia) -> Void
|
private let openMedia: (InstantPageMedia) -> Void
|
||||||
private let longPressMedia: (InstantPageMedia) -> Void
|
private let longPressMedia: (InstantPageMedia) -> Void
|
||||||
|
private let activatePinchPreview: ((PinchSourceContainerNode) -> Void)?
|
||||||
|
private let pinchPreviewFinished: ((InstantPageNode) -> Void)?
|
||||||
private let pageGap: CGFloat
|
private let pageGap: CGFloat
|
||||||
|
|
||||||
private let scrollView: UIScrollView
|
private let scrollView: UIScrollView
|
||||||
@ -98,13 +101,15 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, pageGap: CGFloat = 0.0) {
|
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?, pageGap: CGFloat = 0.0) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.sourceLocation = sourceLocation
|
self.sourceLocation = sourceLocation
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.webPage = webPage
|
self.webPage = webPage
|
||||||
self.openMedia = openMedia
|
self.openMedia = openMedia
|
||||||
self.longPressMedia = longPressMedia
|
self.longPressMedia = longPressMedia
|
||||||
|
self.activatePinchPreview = activatePinchPreview
|
||||||
|
self.pinchPreviewFinished = pinchPreviewFinished
|
||||||
self.pageGap = pageGap
|
self.pageGap = pageGap
|
||||||
self.scrollView = UIScrollView()
|
self.scrollView = UIScrollView()
|
||||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
@ -182,7 +187,7 @@ private final class InstantPageSlideshowPagerNode: ASDisplayNode, UIScrollViewDe
|
|||||||
let media = self.items[index]
|
let media = self.items[index]
|
||||||
let contentNode: ASDisplayNode
|
let contentNode: ASDisplayNode
|
||||||
if let _ = media.media as? TelegramMediaImage {
|
if let _ = media.media as? TelegramMediaImage {
|
||||||
contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: nil, pinchPreviewFinished: nil)
|
contentNode = InstantPageImageNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, media: media, attributes: [], interactive: true, roundCorners: false, fit: false, openMedia: self.openMedia, longPressMedia: self.longPressMedia, activatePinchPreview: self.activatePinchPreview, pinchPreviewFinished: self.pinchPreviewFinished)
|
||||||
} else if let _ = media.media as? TelegramMediaFile {
|
} else if let _ = media.media as? TelegramMediaFile {
|
||||||
contentNode = ASDisplayNode()
|
contentNode = ASDisplayNode()
|
||||||
} else {
|
} else {
|
||||||
@ -381,10 +386,10 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode {
|
|||||||
private let pagerNode: InstantPageSlideshowPagerNode
|
private let pagerNode: InstantPageSlideshowPagerNode
|
||||||
private let pageControlNode: PageControlNode
|
private let pageControlNode: PageControlNode
|
||||||
|
|
||||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, medias: [InstantPageMedia], openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void) {
|
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, medias: [InstantPageMedia], openMedia: @escaping (InstantPageMedia) -> Void, longPressMedia: @escaping (InstantPageMedia) -> Void, activatePinchPreview: ((PinchSourceContainerNode) -> Void)?, pinchPreviewFinished: ((InstantPageNode) -> Void)?) {
|
||||||
self.medias = medias
|
self.medias = medias
|
||||||
|
|
||||||
self.pagerNode = InstantPageSlideshowPagerNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, openMedia: openMedia, longPressMedia: longPressMedia)
|
self.pagerNode = InstantPageSlideshowPagerNode(context: context, sourceLocation: sourceLocation, theme: theme, webPage: webPage, openMedia: openMedia, longPressMedia: longPressMedia, activatePinchPreview: activatePinchPreview, pinchPreviewFinished: pinchPreviewFinished)
|
||||||
self.pagerNode.replaceItems(medias, centralItemIndex: nil)
|
self.pagerNode.replaceItems(medias, centralItemIndex: nil)
|
||||||
|
|
||||||
self.pageControlNode = PageControlNode(dotColor: .white, inactiveDotColor: UIColor(white: 1.0, alpha: 0.5))
|
self.pageControlNode = PageControlNode(dotColor: .white, inactiveDotColor: UIColor(white: 1.0, alpha: 0.5))
|
||||||
|
@ -164,7 +164,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
private var fontSize: CGFloat
|
private var fontSize: CGFloat
|
||||||
private let gloss: Bool
|
private let gloss: Bool
|
||||||
|
|
||||||
private let buttonBackgroundNode: ASImageNode
|
public let buttonBackgroundNode: ASImageNode
|
||||||
private var buttonBackgroundAnimationView: UIImageView?
|
private var buttonBackgroundAnimationView: UIImageView?
|
||||||
|
|
||||||
private var shimmerView: ShimmerEffectForegroundView?
|
private var shimmerView: ShimmerEffectForegroundView?
|
||||||
@ -173,7 +173,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
private var borderShimmerView: ShimmerEffectForegroundView?
|
private var borderShimmerView: ShimmerEffectForegroundView?
|
||||||
|
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
private let buttonNode: HighlightTrackingButtonNode
|
||||||
private let titleNode: ImmediateTextNode
|
public let titleNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateTextNode
|
private let subtitleNode: ImmediateTextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private var animationNode: SimpleAnimationNode?
|
private var animationNode: SimpleAnimationNode?
|
||||||
@ -181,7 +181,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
private var badgeNode: BadgeNode?
|
private var badgeNode: BadgeNode?
|
||||||
|
|
||||||
private let buttonHeight: CGFloat
|
private let buttonHeight: CGFloat
|
||||||
private let buttonCornerRadius: CGFloat
|
public let buttonCornerRadius: CGFloat
|
||||||
|
|
||||||
public var pressed: (() -> Void)?
|
public var pressed: (() -> Void)?
|
||||||
public var validLayout: CGFloat?
|
public var validLayout: CGFloat?
|
||||||
@ -309,6 +309,23 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var progressType: SolidRoundedButtonProgressType = .fullSize
|
public var progressType: SolidRoundedButtonProgressType = .fullSize
|
||||||
|
|
||||||
|
public var highlightEnabled = true {
|
||||||
|
didSet {
|
||||||
|
if !self.highlightEnabled {
|
||||||
|
self.buttonBackgroundNode.alpha = 1.0
|
||||||
|
self.titleNode.alpha = 1.0
|
||||||
|
self.subtitleNode.alpha = 1.0
|
||||||
|
self.iconNode.alpha = 1.0
|
||||||
|
self.animationNode?.alpha = 1.0
|
||||||
|
self.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.subtitleNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.iconNode.layer.removeAnimation(forKey: "opacity")
|
||||||
|
self.animationNode?.layer.removeAnimation(forKey: "opacity")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.font = font
|
self.font = font
|
||||||
@ -366,7 +383,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||||
if let strongSelf = self, strongSelf.isEnabled {
|
if let strongSelf = self, strongSelf.isEnabled && strongSelf.highlightEnabled {
|
||||||
if highlighted {
|
if highlighted {
|
||||||
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||||
strongSelf.buttonBackgroundNode.alpha = 0.55
|
strongSelf.buttonBackgroundNode.alpha = 0.55
|
||||||
|
@ -106,6 +106,8 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
case chatListRecentStatusVoiceChatPinnedIcon
|
case chatListRecentStatusVoiceChatPinnedIcon
|
||||||
case chatListRecentStatusVoiceChatPanelIcon
|
case chatListRecentStatusVoiceChatPanelIcon
|
||||||
|
|
||||||
|
case chatListForwardedIcon
|
||||||
|
|
||||||
case chatListGeneralTopicIcon
|
case chatListGeneralTopicIcon
|
||||||
case chatListGeneralTopicSmallIcon
|
case chatListGeneralTopicSmallIcon
|
||||||
|
|
||||||
|
@ -247,6 +247,12 @@ public struct PresentationResourcesChatList {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func forwardedIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatListForwardedIcon.rawValue, { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/ForwardedIcon"), color: theme.chatList.muteIconColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
|
public static func verifiedIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatListVerifiedIcon.rawValue, { theme in
|
||||||
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
|
if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
|
||||||
|
@ -8051,7 +8051,6 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
searchCategories
|
searchCategories
|
||||||
)
|
)
|
||||||
|> map { view, hasPremium, featuredStickerPacks, featuredStickersConfiguration, dismissedTrendingStickerPacks, peerSpecificPack, searchCategories -> EmojiPagerContentComponent in
|
|> map { view, hasPremium, featuredStickerPacks, featuredStickersConfiguration, dismissedTrendingStickerPacks, peerSpecificPack, searchCategories -> EmojiPagerContentComponent in
|
||||||
let actuallyHasPremium = hasPremium
|
|
||||||
let hasPremium = forceHasPremium || hasPremium
|
let hasPremium = forceHasPremium || hasPremium
|
||||||
struct ItemGroup {
|
struct ItemGroup {
|
||||||
var supergroupId: AnyHashable
|
var supergroupId: AnyHashable
|
||||||
@ -8070,14 +8069,11 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
|
|
||||||
var savedStickers: OrderedItemListView?
|
var savedStickers: OrderedItemListView?
|
||||||
var recentStickers: OrderedItemListView?
|
var recentStickers: OrderedItemListView?
|
||||||
var cloudPremiumStickers: OrderedItemListView?
|
|
||||||
for orderedView in view.orderedItemListsViews {
|
for orderedView in view.orderedItemListsViews {
|
||||||
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
|
if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers {
|
||||||
recentStickers = orderedView
|
recentStickers = orderedView
|
||||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers {
|
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers {
|
||||||
savedStickers = orderedView
|
savedStickers = orderedView
|
||||||
} else if orderedView.collectionId == Namespaces.OrderedItemList.CloudAllPremiumStickers {
|
|
||||||
cloudPremiumStickers = orderedView
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -8226,63 +8222,6 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var premiumStickers: [StickerPackItem] = []
|
|
||||||
if hasPremium {
|
|
||||||
for entry in view.entries {
|
|
||||||
guard let item = entry.item as? StickerPackItem else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.file.isPremiumSticker {
|
|
||||||
premiumStickers.append(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let cloudPremiumStickers = cloudPremiumStickers, !cloudPremiumStickers.items.isEmpty, actuallyHasPremium {
|
|
||||||
premiumStickers.append(contentsOf: cloudPremiumStickers.items.compactMap { item -> StickerPackItem? in guard let item = item.contents.get(RecentMediaItem.self) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.media, indexKeys: [])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !premiumStickers.isEmpty {
|
|
||||||
var processedIds = Set<MediaId>()
|
|
||||||
for item in premiumStickers {
|
|
||||||
if isPremiumDisabled && item.file.isPremiumSticker {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if processedIds.contains(item.file.fileId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
processedIds.insert(item.file.fileId)
|
|
||||||
|
|
||||||
var tintMode: Item.TintMode = .none
|
|
||||||
if item.file.isCustomTemplateEmoji {
|
|
||||||
tintMode = .primary
|
|
||||||
}
|
|
||||||
|
|
||||||
let animationData = EntityKeyboardAnimationData(file: item.file)
|
|
||||||
let resultItem = EmojiPagerContentComponent.Item(
|
|
||||||
animationData: animationData,
|
|
||||||
content: .animation(animationData),
|
|
||||||
itemFile: item.file,
|
|
||||||
subgroupId: nil,
|
|
||||||
icon: .none,
|
|
||||||
tintMode: tintMode
|
|
||||||
)
|
|
||||||
|
|
||||||
let groupId = "premium"
|
|
||||||
if let groupIndex = itemGroupIndexById[groupId] {
|
|
||||||
itemGroups[groupIndex].items.append(resultItem)
|
|
||||||
} else {
|
|
||||||
itemGroupIndexById[groupId] = itemGroups.count
|
|
||||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitlePremiumStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var avatarPeer: EnginePeer?
|
var avatarPeer: EnginePeer?
|
||||||
if let peerSpecificPack = peerSpecificPack {
|
if let peerSpecificPack = peerSpecificPack {
|
||||||
avatarPeer = peerSpecificPack.peer
|
avatarPeer = peerSpecificPack.peer
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Chat List/ForwardedIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/ForwardedIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "forwarded.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
154
submodules/TelegramUI/Images.xcassets/Chat List/ForwardedIcon.imageset/forwarded.pdf
vendored
Normal file
154
submodules/TelegramUI/Images.xcassets/Chat List/ForwardedIcon.imageset/forwarded.pdf
vendored
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
%PDF-1.7
|
||||||
|
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /XObject
|
||||||
|
/Length 2 0 R
|
||||||
|
/Group << /Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
>>
|
||||||
|
/Subtype /Form
|
||||||
|
/Resources << >>
|
||||||
|
/BBox [ 0.000000 0.000000 14.000000 14.000000 ]
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 0.000000 2.376520 cm
|
||||||
|
0.000000 0.000000 0.000000 scn
|
||||||
|
7.214178 7.143479 m
|
||||||
|
7.214178 8.355392 l
|
||||||
|
7.214178 9.166631 7.214178 9.572250 7.376865 9.767084 c
|
||||||
|
7.518138 9.936272 7.730846 10.029038 7.950955 10.017458 c
|
||||||
|
8.204431 10.004122 8.501691 9.728144 9.096210 9.176188 c
|
||||||
|
13.115910 5.444274 l
|
||||||
|
13.423076 5.159099 13.576659 5.016511 13.633628 4.849649 c
|
||||||
|
13.683687 4.703024 13.683687 4.543934 13.633628 4.397310 c
|
||||||
|
13.576659 4.230447 13.423076 4.087859 13.115908 3.802683 c
|
||||||
|
9.096210 0.070769 l
|
||||||
|
8.501690 -0.481186 8.204431 -0.757164 7.950955 -0.770499 c
|
||||||
|
7.730846 -0.782080 7.518138 -0.689313 7.376865 -0.520126 c
|
||||||
|
7.214178 -0.325293 7.214178 0.080327 7.214178 0.891567 c
|
||||||
|
7.214178 2.103479 l
|
||||||
|
4.071625 2.103479 2.169120 0.875460 1.113997 -0.180696 c
|
||||||
|
0.644118 -0.651034 0.409179 -0.886204 0.299532 -0.890471 c
|
||||||
|
0.197423 -0.894444 0.118586 -0.855472 0.059738 -0.771932 c
|
||||||
|
-0.003455 -0.682224 0.035404 -0.392442 0.113122 0.187123 c
|
||||||
|
0.460188 2.775306 1.841749 7.143479 7.214178 7.143479 c
|
||||||
|
h
|
||||||
|
f*
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
1042
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
<< /Type /XObject
|
||||||
|
/Length 4 0 R
|
||||||
|
/Group << /Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
>>
|
||||||
|
/Subtype /Form
|
||||||
|
/Resources << >>
|
||||||
|
/BBox [ 0.000000 0.000000 14.000000 14.000000 ]
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
|
||||||
|
0.000000 0.000000 0.000000 scn
|
||||||
|
0.000000 14.000000 m
|
||||||
|
14.000000 14.000000 l
|
||||||
|
14.000000 0.000000 l
|
||||||
|
0.000000 0.000000 l
|
||||||
|
0.000000 14.000000 l
|
||||||
|
h
|
||||||
|
f
|
||||||
|
n
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
232
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<< /XObject << /X1 1 0 R >>
|
||||||
|
/ExtGState << /E1 << /SMask << /Type /Mask
|
||||||
|
/G 3 0 R
|
||||||
|
/S /Alpha
|
||||||
|
>>
|
||||||
|
/Type /ExtGState
|
||||||
|
>> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
6 0 obj
|
||||||
|
<< /Length 7 0 R >>
|
||||||
|
stream
|
||||||
|
/DeviceRGB CS
|
||||||
|
/DeviceRGB cs
|
||||||
|
q
|
||||||
|
/E1 gs
|
||||||
|
/X1 Do
|
||||||
|
Q
|
||||||
|
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
7 0 obj
|
||||||
|
46
|
||||||
|
endobj
|
||||||
|
|
||||||
|
8 0 obj
|
||||||
|
<< /Annots []
|
||||||
|
/Type /Page
|
||||||
|
/MediaBox [ 0.000000 0.000000 14.000000 14.000000 ]
|
||||||
|
/Resources 5 0 R
|
||||||
|
/Contents 6 0 R
|
||||||
|
/Parent 9 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
9 0 obj
|
||||||
|
<< /Kids [ 8 0 R ]
|
||||||
|
/Count 1
|
||||||
|
/Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
10 0 obj
|
||||||
|
<< /Pages 9 0 R
|
||||||
|
/Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
xref
|
||||||
|
0 11
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000010 00000 n
|
||||||
|
0000001300 00000 n
|
||||||
|
0000001323 00000 n
|
||||||
|
0000001803 00000 n
|
||||||
|
0000001825 00000 n
|
||||||
|
0000002123 00000 n
|
||||||
|
0000002225 00000 n
|
||||||
|
0000002246 00000 n
|
||||||
|
0000002419 00000 n
|
||||||
|
0000002493 00000 n
|
||||||
|
trailer
|
||||||
|
<< /ID [ (some) (id) ]
|
||||||
|
/Root 10 0 R
|
||||||
|
/Size 11
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
2553
|
||||||
|
%%EOF
|
@ -7,10 +7,11 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
import SolidRoundedButtonNode
|
||||||
|
import TooltipUI
|
||||||
|
|
||||||
final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
||||||
private let button: HighlightableButtonNode
|
private let button: SolidRoundedButtonNode
|
||||||
private let activityIndicator: UIActivityIndicatorView
|
|
||||||
|
|
||||||
private var statusDisposable: Disposable?
|
private var statusDisposable: Disposable?
|
||||||
|
|
||||||
@ -23,15 +24,7 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
|||||||
if let startingBot = self.interfaceInteraction?.statuses?.startingBot {
|
if let startingBot = self.interfaceInteraction?.statuses?.startingBot {
|
||||||
self.statusDisposable = (startingBot |> deliverOnMainQueue).start(next: { [weak self] value in
|
self.statusDisposable = (startingBot |> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if value != !strongSelf.activityIndicator.isHidden {
|
strongSelf.inProgress = value
|
||||||
if value {
|
|
||||||
strongSelf.activityIndicator.isHidden = false
|
|
||||||
strongSelf.activityIndicator.startAnimating()
|
|
||||||
} else {
|
|
||||||
strongSelf.activityIndicator.isHidden = true
|
|
||||||
strongSelf.activityIndicator.stopAnimating()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -40,28 +33,43 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var inProgress = false {
|
||||||
|
didSet {
|
||||||
|
if self.inProgress != oldValue {
|
||||||
|
if self.inProgress {
|
||||||
|
self.button.transitionToProgress()
|
||||||
|
} else {
|
||||||
|
self.button.transitionFromProgress()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var strings: PresentationStrings
|
private var strings: PresentationStrings
|
||||||
|
|
||||||
|
private var tooltipController: TooltipScreen?
|
||||||
|
private var tooltipDismissed = false
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings) {
|
init(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
|
||||||
self.button = HighlightableButtonNode()
|
self.button = SolidRoundedButtonNode(title: self.strings.Bot_Start, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0, gloss: true)
|
||||||
self.activityIndicator = UIActivityIndicatorView(style: .gray)
|
self.button.progressType = .embedded
|
||||||
self.activityIndicator.isHidden = true
|
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.button)
|
self.addSubnode(self.button)
|
||||||
self.view.addSubview(self.activityIndicator)
|
|
||||||
|
|
||||||
self.button.setAttributedTitle(NSAttributedString(string: strings.Bot_Start, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlAccentColor), for: [])
|
self.button.pressed = { [weak self] in
|
||||||
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: [.touchUpInside])
|
self?.buttonPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.statusDisposable?.dispose()
|
self.statusDisposable?.dispose()
|
||||||
|
self.tooltipController?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
@ -69,44 +77,84 @@ final class ChatBotStartInputPanelNode: ChatInputPanelNode {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
|
||||||
self.button.setAttributedTitle(NSAttributedString(string: strings.Bot_Start, font: Font.regular(17.0), textColor: theme.chat.inputPanel.panelControlAccentColor), for: [])
|
self.button.updateTheme(SolidRoundedButtonTheme(theme: theme))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
||||||
if self.bounds.contains(point) {
|
|
||||||
return self.button.view
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func buttonPressed() {
|
@objc func buttonPressed() {
|
||||||
guard let _ = self.context, let presentationInterfaceState = self.presentationInterfaceState, let _ = presentationInterfaceState.renderedPeer?.peer else {
|
guard let _ = self.context, let presentationInterfaceState = self.presentationInterfaceState else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interfaceInteraction?.sendBotStart(presentationInterfaceState.botStartPayload)
|
self.interfaceInteraction?.sendBotStart(presentationInterfaceState.botStartPayload)
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController {
|
||||||
|
self.tooltipDismissed = false
|
||||||
|
self.tooltipController = nil
|
||||||
|
tooltipController.dismiss()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.updateAbsoluteRect(rect, within: containerSize, transition: transition)
|
||||||
|
|
||||||
|
let absoluteFrame = self.button.view.convert(self.button.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController, self.view.window != nil {
|
||||||
|
tooltipController.location = .point(location, .bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||||
if self.presentationInterfaceState != interfaceState {
|
if self.presentationInterfaceState != interfaceState {
|
||||||
self.presentationInterfaceState = interfaceState
|
self.presentationInterfaceState = interfaceState
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonSize = self.button.measure(CGSize(width: width - 80.0, height: 100.0))
|
let inset: CGFloat = max(leftInset, 16.0)
|
||||||
|
let maximumWidth: CGFloat = min(430.0, width)
|
||||||
|
let proceedHeight = self.button.updateLayout(width: maximumWidth - inset * 2.0, transition: transition)
|
||||||
|
let buttonSize = CGSize(width: maximumWidth - inset * 2.0, height: proceedHeight)
|
||||||
|
|
||||||
let panelHeight = defaultHeight(metrics: metrics)
|
let panelHeight = defaultHeight(metrics: metrics) + 27.0
|
||||||
|
|
||||||
self.button.frame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) / 2.0)), size: buttonSize)
|
self.button.frame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - buttonSize.width) / 2.0), y: 8.0), size: buttonSize)
|
||||||
|
|
||||||
let indicatorSize = self.activityIndicator.bounds.size
|
if !self.tooltipDismissed, let context = self.context {
|
||||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width - 12.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
|
let absoluteFrame = self.button.view.convert(self.button.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController {
|
||||||
|
if self.view.window != nil {
|
||||||
|
tooltipController.location = .point(location, .bottom)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let controller = TooltipScreen(account: context.account, text: self.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
|
||||||
|
return .ignore
|
||||||
|
})
|
||||||
|
controller.alwaysVisible = true
|
||||||
|
self.tooltipController = controller
|
||||||
|
|
||||||
|
let delay: Double
|
||||||
|
if case .regular = metrics.widthClass {
|
||||||
|
delay = 0.1
|
||||||
|
} else {
|
||||||
|
delay = 0.35
|
||||||
|
}
|
||||||
|
Queue.mainQueue().after(delay, {
|
||||||
|
let absoluteFrame = self.button.view.convert(self.button.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
controller.location = .point(location, .bottom)
|
||||||
|
self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return panelHeight
|
return panelHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
|
||||||
return defaultHeight(metrics: metrics)
|
return defaultHeight(metrics: metrics) + 27.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
public let chatLocation: ChatLocation
|
public let chatLocation: ChatLocation
|
||||||
public let subject: ChatControllerSubject?
|
public let subject: ChatControllerSubject?
|
||||||
private let botStart: ChatControllerInitialBotStart?
|
private var botStart: ChatControllerInitialBotStart?
|
||||||
private var attachBotStart: ChatControllerInitialAttachBotStart?
|
private var attachBotStart: ChatControllerInitialAttachBotStart?
|
||||||
private var botAppStart: ChatControllerInitialBotAppStart?
|
private var botAppStart: ChatControllerInitialBotAppStart?
|
||||||
|
|
||||||
@ -7183,6 +7183,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: strongSelf.isViewLoaded && strongSelf.view.window != nil, {
|
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: strongSelf.isViewLoaded && strongSelf.view.window != nil, {
|
||||||
$0.updatedChatHistoryState(state)
|
$0.updatedChatHistoryState(state)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if let botStart = strongSelf.botStart, case let .loaded(isEmpty) = state {
|
||||||
|
strongSelf.botStart = nil
|
||||||
|
if !isEmpty {
|
||||||
|
strongSelf.startBot(botStart.payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -9596,6 +9603,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}, presentController: { [weak self] controller, arguments in
|
}, presentController: { [weak self] controller, arguments in
|
||||||
self?.present(controller, in: .window(.root), with: arguments)
|
self?.present(controller, in: .window(.root), with: arguments)
|
||||||
|
}, presentControllerInCurrent: { [weak self] controller, arguments in
|
||||||
|
self?.present(controller, in: .current, with: arguments)
|
||||||
}, getNavigationController: { [weak self] in
|
}, getNavigationController: { [weak self] in
|
||||||
return self?.navigationController as? NavigationController
|
return self?.navigationController as? NavigationController
|
||||||
}, presentGlobalOverlayController: { [weak self] controller, arguments in
|
}, presentGlobalOverlayController: { [weak self] controller, arguments in
|
||||||
@ -17670,7 +17679,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let controller = controller as? UndoOverlayController {
|
if let controller = controller as? UndoOverlayController {
|
||||||
controller.dismissWithCommitAction()
|
controller.dismissWithCommitAction()
|
||||||
}
|
}
|
||||||
if let controller = controller as? TooltipScreen {
|
if let controller = controller as? TooltipScreen, !controller.alwaysVisible {
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -535,6 +535,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
||||||
if case let .empty(type) = loadState {
|
if case let .empty(type) = loadState {
|
||||||
|
if case .botInfo = type {
|
||||||
|
} else {
|
||||||
emptyType = type
|
emptyType = type
|
||||||
if case .joined = type {
|
if case .joined = type {
|
||||||
if strongSelf.didDisplayEmptyGreeting {
|
if strongSelf.didDisplayEmptyGreeting {
|
||||||
@ -543,6 +545,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
strongSelf.didDisplayEmptyGreeting = true
|
strongSelf.didDisplayEmptyGreeting = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if case .messages = loadState {
|
} else if case .messages = loadState {
|
||||||
strongSelf.didDisplayEmptyGreeting = true
|
strongSelf.didDisplayEmptyGreeting = true
|
||||||
}
|
}
|
||||||
@ -1966,13 +1969,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if inputPanelNodeHandlesTransition {
|
if inputPanelNodeHandlesTransition {
|
||||||
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: .immediate)
|
|
||||||
inputPanelNode.frame = apparentInputPanelFrame
|
inputPanelNode.frame = apparentInputPanelFrame
|
||||||
inputPanelNode.alpha = 1.0
|
inputPanelNode.alpha = 1.0
|
||||||
|
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: transition)
|
|
||||||
transition.updateFrame(node: inputPanelNode, frame: apparentInputPanelFrame)
|
transition.updateFrame(node: inputPanelNode, frame: apparentInputPanelFrame)
|
||||||
transition.updateAlpha(node: inputPanelNode, alpha: 1.0)
|
transition.updateAlpha(node: inputPanelNode, alpha: 1.0)
|
||||||
|
inputPanelNode.updateAbsoluteRect(apparentInputPanelFrame, within: layout.size, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let viewForOverlayContent = inputPanelNode.viewForOverlayContent {
|
if let viewForOverlayContent = inputPanelNode.viewForOverlayContent {
|
||||||
|
@ -2739,15 +2739,20 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
} else {
|
} else {
|
||||||
loadState = .empty(.generic)
|
loadState = .empty(.generic)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if transition.historyView.filteredEntries.count == 1, let entry = transition.historyView.filteredEntries.first, case .ChatInfoEntry = entry {
|
||||||
|
loadState = .empty(.botInfo)
|
||||||
} else {
|
} else {
|
||||||
loadState = .messages
|
loadState = .messages
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if self.loadState != loadState {
|
if self.loadState != loadState {
|
||||||
self.loadState = loadState
|
self.loadState = loadState
|
||||||
self.loadStateUpdated?(loadState, transition.options.contains(.AnimateInsertion))
|
self.loadStateUpdated?(loadState, transition.options.contains(.AnimateInsertion))
|
||||||
}
|
}
|
||||||
|
|
||||||
let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty)
|
let isEmpty = transition.historyView.originalView.entries.isEmpty || loadState == .empty(.botInfo)
|
||||||
|
let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: isEmpty)
|
||||||
if self.currentHistoryState != historyState {
|
if self.currentHistoryState != historyState {
|
||||||
self.currentHistoryState = historyState
|
self.currentHistoryState = historyState
|
||||||
self.historyState.set(historyState)
|
self.historyState.set(historyState)
|
||||||
@ -2924,10 +2929,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
} else {
|
} else {
|
||||||
if historyView.originalView.isLoadingEarlier && strongSelf.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser {
|
if historyView.originalView.isLoadingEarlier && strongSelf.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser {
|
||||||
loadState = .loading(true)
|
loadState = .loading(true)
|
||||||
|
} else {
|
||||||
|
if historyView.filteredEntries.count == 1, let entry = historyView.filteredEntries.first, case .ChatInfoEntry = entry {
|
||||||
|
loadState = .empty(.botInfo)
|
||||||
} else {
|
} else {
|
||||||
loadState = .messages
|
loadState = .messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
loadState = .loading(false)
|
loadState = .loading(false)
|
||||||
}
|
}
|
||||||
@ -2989,7 +2998,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
strongSelf._initialData.set(.single(ChatHistoryCombinedInitialData(initialData: transition.initialData, buttonKeyboardMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData)))
|
strongSelf._initialData.set(.single(ChatHistoryCombinedInitialData(initialData: transition.initialData, buttonKeyboardMessage: transition.keyboardButtonsMessage, cachedData: transition.cachedData, cachedDataMessages: transition.cachedDataMessages, readStateData: transition.readStateData)))
|
||||||
}
|
}
|
||||||
strongSelf._cachedPeerDataAndMessages.set(.single((transition.cachedData, transition.cachedDataMessages)))
|
strongSelf._cachedPeerDataAndMessages.set(.single((transition.cachedData, transition.cachedDataMessages)))
|
||||||
let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: transition.historyView.originalView.entries.isEmpty)
|
let isEmpty = transition.historyView.originalView.entries.isEmpty || loadState == .empty(.botInfo)
|
||||||
|
let historyState: ChatHistoryNodeHistoryState = .loaded(isEmpty: isEmpty)
|
||||||
if strongSelf.currentHistoryState != historyState {
|
if strongSelf.currentHistoryState != historyState {
|
||||||
strongSelf.currentHistoryState = historyState
|
strongSelf.currentHistoryState = historyState
|
||||||
strongSelf.historyState.set(historyState)
|
strongSelf.historyState.set(historyState)
|
||||||
|
@ -12,6 +12,7 @@ public enum ChatHistoryNodeLoadState: Equatable {
|
|||||||
case joined
|
case joined
|
||||||
case clearedHistory
|
case clearedHistory
|
||||||
case topic
|
case topic
|
||||||
|
case botInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
case loading(Bool)
|
case loading(Bool)
|
||||||
|
@ -86,7 +86,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chatPresentationInterfaceState.peerIsBlocked {
|
if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
|
||||||
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
|
||||||
currentPanel.interfaceInteraction = interfaceInteraction
|
currentPanel.interfaceInteraction = interfaceInteraction
|
||||||
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
@ -311,7 +311,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if displayBotStartPanel {
|
if displayBotStartPanel, !"".isEmpty {
|
||||||
if let currentPanel = (currentPanel as? ChatBotStartInputPanelNode) ?? (currentSecondaryPanel as? ChatBotStartInputPanelNode) {
|
if let currentPanel = (currentPanel as? ChatBotStartInputPanelNode) ?? (currentSecondaryPanel as? ChatBotStartInputPanelNode) {
|
||||||
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||||
return (currentPanel, nil)
|
return (currentPanel, nil)
|
||||||
|
@ -120,6 +120,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
|||||||
}, beginCall: { _ in
|
}, beginCall: { _ in
|
||||||
}, toggleMessageStickerStarred: { _ in
|
}, toggleMessageStickerStarred: { _ in
|
||||||
}, presentController: { _, _ in
|
}, presentController: { _, _ in
|
||||||
|
}, presentControllerInCurrent: { _, _ in
|
||||||
}, getNavigationController: {
|
}, getNavigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
}, presentGlobalOverlayController: { _, _ in
|
||||||
|
@ -113,7 +113,7 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
|
|||||||
|
|
||||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||||
let makeLayout = TextNode.asyncLayout(self.textNode)
|
let makeLayout = TextNode.asyncLayout(self.textNode)
|
||||||
let (size, apply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "00:00,00", font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 200.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (size, apply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "0:00:00,00", font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 200.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
let _ = apply()
|
let _ = apply()
|
||||||
self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 1.0 + UIScreenPixel), size: size.size)
|
self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 1.0 + UIScreenPixel), size: size.size)
|
||||||
return size.size
|
return size.size
|
||||||
@ -135,7 +135,12 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode {
|
|||||||
if let parameters = parameters as? ChatTextInputAudioRecordingTimeNodeParameters {
|
if let parameters = parameters as? ChatTextInputAudioRecordingTimeNodeParameters {
|
||||||
let currentAudioDurationSeconds = Int(parameters.timestamp)
|
let currentAudioDurationSeconds = Int(parameters.timestamp)
|
||||||
let currentAudioDurationMilliseconds = Int(parameters.timestamp * 100.0) % 100
|
let currentAudioDurationMilliseconds = Int(parameters.timestamp * 100.0) % 100
|
||||||
let text = String(format: "%d:%02d,%02d", currentAudioDurationSeconds / 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds)
|
let text: String
|
||||||
|
if currentAudioDurationSeconds >= 60 * 60 {
|
||||||
|
text = String(format: "%d:%02d:%02d,%02d", currentAudioDurationSeconds / 3600, currentAudioDurationSeconds / 60 % 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds)
|
||||||
|
} else {
|
||||||
|
text = String(format: "%d:%02d,%02d", currentAudioDurationSeconds / 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds)
|
||||||
|
}
|
||||||
let string = NSAttributedString(string: text, font: textFont, textColor: parameters.theme.chat.inputPanel.primaryTextColor)
|
let string = NSAttributedString(string: text, font: textFont, textColor: parameters.theme.chat.inputPanel.primaryTextColor)
|
||||||
string.draw(at: CGPoint())
|
string.draw(at: CGPoint())
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,8 @@ import UndoUI
|
|||||||
import PremiumUI
|
import PremiumUI
|
||||||
import StickerPeekUI
|
import StickerPeekUI
|
||||||
import LottieComponent
|
import LottieComponent
|
||||||
|
import SolidRoundedButtonNode
|
||||||
|
import TooltipUI
|
||||||
|
|
||||||
private let accessoryButtonFont = Font.medium(14.0)
|
private let accessoryButtonFont = Font.medium(14.0)
|
||||||
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
|
||||||
@ -488,6 +490,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
private let menuButtonIconNode: MenuIconNode
|
private let menuButtonIconNode: MenuIconNode
|
||||||
private let menuButtonTextNode: ImmediateTextNode
|
private let menuButtonTextNode: ImmediateTextNode
|
||||||
|
|
||||||
|
private let startButton: SolidRoundedButtonNode
|
||||||
|
|
||||||
let sendAsAvatarButtonNode: HighlightableButtonNode
|
let sendAsAvatarButtonNode: HighlightableButtonNode
|
||||||
let sendAsAvatarReferenceNode: ContextReferenceContentNode
|
let sendAsAvatarReferenceNode: ContextReferenceContentNode
|
||||||
let sendAsAvatarContainerNode: ContextControllerSourceNode
|
let sendAsAvatarContainerNode: ContextControllerSourceNode
|
||||||
@ -575,6 +579,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
return self.actionButtons.micButton
|
return self.actionButtons.micButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let startingBotDisposable = MetaDisposable()
|
||||||
private let statusDisposable = MetaDisposable()
|
private let statusDisposable = MetaDisposable()
|
||||||
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
|
||||||
didSet {
|
didSet {
|
||||||
@ -585,6 +590,25 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self?.updateIsProcessingInlineRequest(value)
|
self?.updateIsProcessingInlineRequest(value)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
if let startingBot = self.interfaceInteraction?.statuses?.startingBot {
|
||||||
|
self.startingBotDisposable.set((startingBot |> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.startingBotProgress = value
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var startingBotProgress = false {
|
||||||
|
didSet {
|
||||||
|
// if self.startingBotProgress != oldValue {
|
||||||
|
// if self.startingBotProgress {
|
||||||
|
// self.startButton.transitionToProgress()
|
||||||
|
// } else {
|
||||||
|
// self.startButton.transitionFromProgress()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -695,6 +719,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
private let presentationContext: ChatPresentationContext?
|
private let presentationContext: ChatPresentationContext?
|
||||||
|
|
||||||
|
private var tooltipController: TooltipScreen?
|
||||||
|
|
||||||
init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
init(context: AccountContext, presentationInterfaceState: ChatPresentationInterfaceState, presentationContext: ChatPresentationContext?, presentController: @escaping (ViewController) -> Void) {
|
||||||
self.presentationInterfaceState = presentationInterfaceState
|
self.presentationInterfaceState = presentationInterfaceState
|
||||||
self.presentationContext = presentationContext
|
self.presentationContext = presentationContext
|
||||||
@ -738,6 +764,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.menuButtonIconNode.customColor = presentationInterfaceState.theme.chat.inputPanel.actionControlForegroundColor
|
self.menuButtonIconNode.customColor = presentationInterfaceState.theme.chat.inputPanel.actionControlForegroundColor
|
||||||
self.menuButtonTextNode = ImmediateTextNode()
|
self.menuButtonTextNode = ImmediateTextNode()
|
||||||
|
|
||||||
|
self.startButton = SolidRoundedButtonNode(title: presentationInterfaceState.strings.Bot_Start, theme: SolidRoundedButtonTheme(theme: presentationInterfaceState.theme), height: 50.0, cornerRadius: 11.0, gloss: true)
|
||||||
|
self.startButton.progressType = .embedded
|
||||||
|
self.startButton.isHidden = true
|
||||||
|
|
||||||
self.sendAsAvatarButtonNode = HighlightableButtonNode()
|
self.sendAsAvatarButtonNode = HighlightableButtonNode()
|
||||||
self.sendAsAvatarReferenceNode = ContextReferenceContentNode()
|
self.sendAsAvatarReferenceNode = ContextReferenceContentNode()
|
||||||
self.sendAsAvatarContainerNode = ContextControllerSourceNode()
|
self.sendAsAvatarContainerNode = ContextControllerSourceNode()
|
||||||
@ -820,6 +850,22 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.startButton.pressed = { [weak self] in
|
||||||
|
guard let self, let presentationInterfaceState = self.presentationInterfaceState else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if presentationInterfaceState.peerIsBlocked {
|
||||||
|
self.interfaceInteraction?.unblockPeer()
|
||||||
|
} else {
|
||||||
|
self.interfaceInteraction?.sendBotStart(presentationInterfaceState.botStartPayload)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController {
|
||||||
|
self.tooltipController = nil
|
||||||
|
tooltipController.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
|
self.attachmentButton.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
|
||||||
self.attachmentButtonDisabledNode.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
|
self.attachmentButtonDisabledNode.addTarget(self, action: #selector(self.attachmentButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
@ -921,6 +967,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.clippingNode.addSubnode(self.attachmentButton)
|
self.clippingNode.addSubnode(self.attachmentButton)
|
||||||
self.clippingNode.addSubnode(self.attachmentButtonDisabledNode)
|
self.clippingNode.addSubnode(self.attachmentButtonDisabledNode)
|
||||||
|
|
||||||
|
self.clippingNode.addSubnode(self.startButton)
|
||||||
|
|
||||||
self.clippingNode.addSubnode(self.actionButtons)
|
self.clippingNode.addSubnode(self.actionButtons)
|
||||||
self.clippingNode.addSubnode(self.counterTextNode)
|
self.clippingNode.addSubnode(self.counterTextNode)
|
||||||
|
|
||||||
@ -972,6 +1020,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.statusDisposable.dispose()
|
self.statusDisposable.dispose()
|
||||||
|
self.tooltipController?.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTextInputNodeIfNeeded() {
|
func loadTextInputNodeIfNeeded() {
|
||||||
@ -1154,6 +1203,110 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
return minimalHeight
|
return minimalHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var animatingTransition = false
|
||||||
|
private func animateBotButtonInFromMenu(transition: ContainedViewLayoutTransition) {
|
||||||
|
guard !self.animatingTransition else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let menuIconSnapshotView = self.menuButtonIconNode.view.snapshotView(afterScreenUpdates: false), let menuTextSnapshotView = self.menuButtonTextNode.view.snapshotView(afterScreenUpdates: false) else {
|
||||||
|
self.startButton.highlightEnabled = true
|
||||||
|
self.menuButton.isHidden = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if transition.isAnimated {
|
||||||
|
self.animatingTransition = true
|
||||||
|
self.startButton.highlightEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
self.menuButton.isHidden = true
|
||||||
|
|
||||||
|
transition.animateFrame(layer: self.startButton.layer, from: self.menuButton.frame)
|
||||||
|
transition.animateFrame(layer: self.startButton.buttonBackgroundNode.layer, from: CGRect(origin: .zero, size: self.menuButton.frame.size))
|
||||||
|
transition.animatePosition(node: self.startButton.titleNode, from: CGPoint(x: self.menuButton.frame.width / 2.0, y: self.menuButton.frame.height / 2.0))
|
||||||
|
|
||||||
|
let targetButtonCornerRadius = self.startButton.buttonCornerRadius
|
||||||
|
self.startButton.buttonBackgroundNode.cornerRadius = self.menuButton.cornerRadius
|
||||||
|
transition.updateCornerRadius(node: self.startButton.buttonBackgroundNode, cornerRadius: targetButtonCornerRadius)
|
||||||
|
transition.animateTransformScale(node: self.startButton.titleNode, from: 0.4)
|
||||||
|
self.startButton.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
let menuContentDelta = (self.startButton.frame.width - self.menuButton.frame.width) / 2.0
|
||||||
|
menuIconSnapshotView.frame = self.menuButtonIconNode.frame.offsetBy(dx: self.menuButton.frame.minX, dy: self.menuButton.frame.minY)
|
||||||
|
self.view.addSubview(menuIconSnapshotView)
|
||||||
|
menuIconSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak menuIconSnapshotView] _ in
|
||||||
|
menuIconSnapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x + menuContentDelta, y: self.startButton.position.y))
|
||||||
|
|
||||||
|
menuTextSnapshotView.frame = self.menuButtonTextNode.frame.offsetBy(dx: self.menuButton.frame.minX + 19.0, dy: self.menuButton.frame.minY)
|
||||||
|
self.view.addSubview(menuTextSnapshotView)
|
||||||
|
menuTextSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak menuTextSnapshotView, weak self] _ in
|
||||||
|
menuTextSnapshotView?.removeFromSuperview()
|
||||||
|
self?.animatingTransition = false
|
||||||
|
self?.startButton.highlightEnabled = true
|
||||||
|
})
|
||||||
|
transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x + menuContentDelta, y: self.startButton.position.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateBotButtonOutToMenu(transition: ContainedViewLayoutTransition) {
|
||||||
|
guard !self.animatingTransition else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let menuIconSnapshotView = self.menuButtonIconNode.view.snapshotView(afterScreenUpdates: false), let menuTextSnapshotView = self.menuButtonTextNode.view.snapshotView(afterScreenUpdates: false) else {
|
||||||
|
self.startButton.highlightEnabled = true
|
||||||
|
self.menuButton.isHidden = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if transition.isAnimated {
|
||||||
|
self.animatingTransition = true
|
||||||
|
self.startButton.highlightEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceButtonFrame = self.startButton.frame
|
||||||
|
transition.updateFrame(node: self.startButton, frame: self.menuButton.frame)
|
||||||
|
transition.updateFrame(node: self.startButton.buttonBackgroundNode, frame: CGRect(origin: .zero, size: self.menuButton.frame.size))
|
||||||
|
let sourceButtonTextPosition = self.startButton.titleNode.position
|
||||||
|
transition.updatePosition(node: self.startButton.titleNode, position: CGPoint(x: self.menuButton.frame.width / 2.0, y: self.menuButton.frame.height / 2.0))
|
||||||
|
|
||||||
|
let sourceButtonCornerRadius = self.startButton.buttonCornerRadius
|
||||||
|
transition.updateCornerRadius(node: self.startButton.buttonBackgroundNode, cornerRadius: self.menuButton.cornerRadius)
|
||||||
|
transition.animateTransformScale(layer: self.startButton.titleNode.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.4, y: 0.4))
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
self.startButton.titleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
let menuContentDelta = (sourceButtonFrame.width - self.menuButton.frame.width) / 2.0
|
||||||
|
var menuIconSnapshotViewFrame = self.menuButtonIconNode.frame.offsetBy(dx: self.menuButton.frame.minX + menuContentDelta, dy: self.menuButton.frame.minY)
|
||||||
|
menuIconSnapshotViewFrame.origin.y = self.startButton.position.y - menuIconSnapshotViewFrame.height / 2.0
|
||||||
|
menuIconSnapshotView.frame = menuIconSnapshotViewFrame
|
||||||
|
self.view.addSubview(menuIconSnapshotView)
|
||||||
|
menuIconSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
transition.updatePosition(layer: menuIconSnapshotView.layer, position: CGPoint(x: menuIconSnapshotView.center.x - menuContentDelta, y: self.menuButton.position.y))
|
||||||
|
|
||||||
|
var menuTextSnapshotViewFrame = self.menuButtonTextNode.frame.offsetBy(dx: self.menuButton.frame.minX + 19.0 + menuContentDelta, dy: self.menuButton.frame.minY)
|
||||||
|
menuTextSnapshotViewFrame.origin.y = self.startButton.position.y - menuTextSnapshotViewFrame.height / 2.0
|
||||||
|
menuTextSnapshotView.frame = menuTextSnapshotViewFrame
|
||||||
|
self.view.addSubview(menuTextSnapshotView)
|
||||||
|
menuTextSnapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
transition.updatePosition(layer: menuTextSnapshotView.layer, position: CGPoint(x: menuTextSnapshotView.center.x - menuContentDelta, y: self.menuButton.position.y), completion: { [weak self, weak menuIconSnapshotView, weak menuTextSnapshotView] _ in
|
||||||
|
self?.animatingTransition = false
|
||||||
|
|
||||||
|
menuIconSnapshotView?.removeFromSuperview()
|
||||||
|
menuTextSnapshotView?.removeFromSuperview()
|
||||||
|
|
||||||
|
self?.menuButton.isHidden = false
|
||||||
|
self?.startButton.isHidden = true
|
||||||
|
self?.startButton.frame = sourceButtonFrame
|
||||||
|
self?.startButton.buttonBackgroundNode.frame = CGRect(origin: .zero, size: sourceButtonFrame.size)
|
||||||
|
self?.startButton.titleNode.position = sourceButtonTextPosition
|
||||||
|
self?.startButton.titleNode.layer.removeAllAnimations()
|
||||||
|
self?.startButton.buttonBackgroundNode.cornerRadius = sourceButtonCornerRadius
|
||||||
|
self?.startButton.highlightEnabled = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private var absoluteRect: (CGRect, CGSize)?
|
private var absoluteRect: (CGRect, CGSize)?
|
||||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
self.absoluteRect = (rect, containerSize)
|
self.absoluteRect = (rect, containerSize)
|
||||||
@ -1161,7 +1314,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
if !self.actionButtons.frame.width.isZero {
|
if !self.actionButtons.frame.width.isZero {
|
||||||
self.actionButtons.updateAbsoluteRect(CGRect(origin: rect.origin.offsetBy(dx: self.actionButtons.frame.minX, dy: self.actionButtons.frame.minY), size: self.actionButtons.frame.size), within: containerSize, transition: transition)
|
self.actionButtons.updateAbsoluteRect(CGRect(origin: rect.origin.offsetBy(dx: self.actionButtons.frame.minX, dy: self.actionButtons.frame.minY), size: self.actionButtons.frame.size), within: containerSize, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController, self.view.window != nil {
|
||||||
|
tooltipController.location = .point(location, .bottom)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||||
let previousAdditionalSideInsets = self.validLayout?.4
|
let previousAdditionalSideInsets = self.validLayout?.4
|
||||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||||
@ -1229,6 +1390,127 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
self.textInputNode?.isUserInteractionEnabled = !sendingTextDisabled
|
self.textInputNode?.isUserInteractionEnabled = !sendingTextDisabled
|
||||||
|
|
||||||
|
var displayBotStartButton = false
|
||||||
|
if case .scheduledMessages = interfaceState.subject {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if let chatHistoryState = interfaceState.chatHistoryState, case .loaded(true) = chatHistoryState {
|
||||||
|
if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil {
|
||||||
|
displayBotStartButton = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputHasText = false
|
||||||
|
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
|
||||||
|
inputHasText = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasMenuButton = false
|
||||||
|
var menuButtonExpanded = false
|
||||||
|
var isSendAsButton = false
|
||||||
|
|
||||||
|
var shouldDisplayMenuButton = false
|
||||||
|
if interfaceState.hasBotCommands {
|
||||||
|
shouldDisplayMenuButton = true
|
||||||
|
} else if case .webView = interfaceState.botMenuButton {
|
||||||
|
shouldDisplayMenuButton = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState
|
||||||
|
if let sendAsPeers = interfaceState.sendAsPeers, !sendAsPeers.isEmpty && interfaceState.editMessageState == nil {
|
||||||
|
hasMenuButton = true
|
||||||
|
menuButtonExpanded = false
|
||||||
|
isSendAsButton = true
|
||||||
|
self.sendAsAvatarNode.isHidden = false
|
||||||
|
|
||||||
|
var currentPeer = sendAsPeers.first(where: { $0.peer.id == interfaceState.currentSendAsPeerId})?.peer
|
||||||
|
if currentPeer == nil {
|
||||||
|
currentPeer = sendAsPeers.first?.peer
|
||||||
|
}
|
||||||
|
if let context = self.context, let peer = currentPeer {
|
||||||
|
self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: EnginePeer(peer), emptyColor: interfaceState.theme.list.mediaPlaceholderColor)
|
||||||
|
}
|
||||||
|
} else if let peer = interfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo, shouldDisplayMenuButton && interfaceState.editMessageState == nil {
|
||||||
|
hasMenuButton = true
|
||||||
|
|
||||||
|
if !inputHasText {
|
||||||
|
switch interfaceState.inputMode {
|
||||||
|
case .none, .inputButtons:
|
||||||
|
menuButtonExpanded = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sendAsAvatarNode.isHidden = true
|
||||||
|
} else {
|
||||||
|
self.sendAsAvatarNode.isHidden = true
|
||||||
|
}
|
||||||
|
if mediaRecordingState != nil {
|
||||||
|
hasMenuButton = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttonInset: CGFloat = max(leftInset, 16.0)
|
||||||
|
let maximumButtonWidth: CGFloat = min(430.0, width)
|
||||||
|
let buttonHeight = self.startButton.updateLayout(width: maximumButtonWidth - buttonInset * 2.0, transition: transition)
|
||||||
|
let buttonSize = CGSize(width: maximumButtonWidth - buttonInset * 2.0, height: buttonHeight)
|
||||||
|
self.startButton.frame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - buttonSize.width) / 2.0), y: 6.0), size: buttonSize)
|
||||||
|
|
||||||
|
var hideOffset: CGPoint = .zero
|
||||||
|
if displayBotStartButton {
|
||||||
|
if hasMenuButton {
|
||||||
|
hideOffset = CGPoint(x: width, y: 0.0)
|
||||||
|
} else {
|
||||||
|
hideOffset = CGPoint(x: 0.0, y: 80.0)
|
||||||
|
}
|
||||||
|
if self.startButton.isHidden {
|
||||||
|
self.startButton.isHidden = false
|
||||||
|
if hasMenuButton {
|
||||||
|
self.animateBotButtonInFromMenu(transition: transition)
|
||||||
|
} else {
|
||||||
|
transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let context = self.context {
|
||||||
|
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
|
||||||
|
if let tooltipController = self.tooltipController {
|
||||||
|
if self.view.window != nil {
|
||||||
|
tooltipController.location = .point(location, .bottom)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let controller = TooltipScreen(account: context.account, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
|
||||||
|
return .ignore
|
||||||
|
})
|
||||||
|
controller.alwaysVisible = true
|
||||||
|
self.tooltipController = controller
|
||||||
|
|
||||||
|
let delay: Double
|
||||||
|
if case .regular = metrics.widthClass {
|
||||||
|
delay = 0.1
|
||||||
|
} else {
|
||||||
|
delay = 0.35
|
||||||
|
}
|
||||||
|
Queue.mainQueue().after(delay, {
|
||||||
|
let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
|
||||||
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
|
||||||
|
controller.location = .point(location, .bottom)
|
||||||
|
self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !self.startButton.isHidden {
|
||||||
|
if hasMenuButton {
|
||||||
|
self.animateBotButtonOutToMenu(transition: transition)
|
||||||
|
} else {
|
||||||
|
transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), additive: true, completion: { _ in
|
||||||
|
self.startButton.isHidden = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var buttonTitleUpdated = false
|
var buttonTitleUpdated = false
|
||||||
var menuTextSize = self.menuButtonTextNode.frame.size
|
var menuTextSize = self.menuButtonTextNode.frame.size
|
||||||
if self.presentationInterfaceState != interfaceState {
|
if self.presentationInterfaceState != interfaceState {
|
||||||
@ -1243,6 +1525,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
let themeUpdated = previousState?.theme !== interfaceState.theme
|
let themeUpdated = previousState?.theme !== interfaceState.theme
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
self.menuButtonIconNode.customColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor
|
self.menuButtonIconNode.customColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor
|
||||||
|
self.startButton.updateTheme(SolidRoundedButtonTheme(theme: interfaceState.theme))
|
||||||
}
|
}
|
||||||
if let sendAsPeers = interfaceState.sendAsPeers, !sendAsPeers.isEmpty {
|
if let sendAsPeers = interfaceState.sendAsPeers, !sendAsPeers.isEmpty {
|
||||||
self.menuButtonIconNode.enqueueState(.close, animated: false)
|
self.menuButtonIconNode.enqueueState(.close, animated: false)
|
||||||
@ -1527,56 +1810,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.accessoryItemButtons = updatedButtons
|
self.accessoryItemButtons = updatedButtons
|
||||||
}
|
}
|
||||||
|
|
||||||
let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState
|
|
||||||
|
|
||||||
var inputHasText = false
|
|
||||||
if let textInputNode = self.textInputNode, let attributedText = textInputNode.attributedText, attributedText.length != 0 {
|
|
||||||
inputHasText = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasMenuButton = false
|
|
||||||
var menuButtonExpanded = false
|
|
||||||
var isSendAsButton = false
|
|
||||||
|
|
||||||
var shouldDisplayMenuButton = false
|
|
||||||
if interfaceState.hasBotCommands {
|
|
||||||
shouldDisplayMenuButton = true
|
|
||||||
} else if case .webView = interfaceState.botMenuButton {
|
|
||||||
shouldDisplayMenuButton = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if let sendAsPeers = interfaceState.sendAsPeers, !sendAsPeers.isEmpty && interfaceState.editMessageState == nil {
|
|
||||||
hasMenuButton = true
|
|
||||||
menuButtonExpanded = false
|
|
||||||
isSendAsButton = true
|
|
||||||
self.sendAsAvatarNode.isHidden = false
|
|
||||||
|
|
||||||
var currentPeer = sendAsPeers.first(where: { $0.peer.id == interfaceState.currentSendAsPeerId})?.peer
|
|
||||||
if currentPeer == nil {
|
|
||||||
currentPeer = sendAsPeers.first?.peer
|
|
||||||
}
|
|
||||||
if let context = self.context, let peer = currentPeer {
|
|
||||||
self.sendAsAvatarNode.setPeer(context: context, theme: interfaceState.theme, peer: EnginePeer(peer), emptyColor: interfaceState.theme.list.mediaPlaceholderColor)
|
|
||||||
}
|
|
||||||
} else if let peer = interfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo, shouldDisplayMenuButton && interfaceState.editMessageState == nil {
|
|
||||||
hasMenuButton = true
|
|
||||||
|
|
||||||
if !inputHasText {
|
|
||||||
switch interfaceState.inputMode {
|
|
||||||
case .none, .inputButtons:
|
|
||||||
menuButtonExpanded = true
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sendAsAvatarNode.isHidden = true
|
|
||||||
} else {
|
|
||||||
self.sendAsAvatarNode.isHidden = true
|
|
||||||
}
|
|
||||||
if mediaRecordingState != nil {
|
|
||||||
hasMenuButton = false
|
|
||||||
}
|
|
||||||
|
|
||||||
let leftMenuInset: CGFloat
|
let leftMenuInset: CGFloat
|
||||||
let menuButtonHeight: CGFloat = 33.0
|
let menuButtonHeight: CGFloat = 33.0
|
||||||
let menuCollapsedButtonWidth: CGFloat = isSendAsButton ? menuButtonHeight : 38.0
|
let menuCollapsedButtonWidth: CGFloat = isSendAsButton ? menuButtonHeight : 38.0
|
||||||
@ -1599,17 +1832,27 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
let baseWidth = width - leftInset - leftMenuInset - rightInset
|
let baseWidth = width - leftInset - leftMenuInset - rightInset
|
||||||
let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics)
|
let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: baseWidth, maxHeight: maxHeight, metrics: metrics)
|
||||||
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
|
var panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
|
||||||
|
if displayBotStartButton {
|
||||||
|
panelHeight += 27.0
|
||||||
|
}
|
||||||
|
|
||||||
let menuButtonFrame = CGRect(x: leftInset + 10.0, y: panelHeight - minimalHeight + floorToScreenPixels((minimalHeight - menuButtonHeight) / 2.0), width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
|
let menuButtonOriginY: CGFloat
|
||||||
|
if displayBotStartButton {
|
||||||
|
menuButtonOriginY = floorToScreenPixels((minimalHeight - menuButtonHeight) / 2.0)
|
||||||
|
} else {
|
||||||
|
menuButtonOriginY = panelHeight - minimalHeight + floorToScreenPixels((minimalHeight - menuButtonHeight) / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let menuButtonFrame = CGRect(x: leftInset + 10.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight)
|
||||||
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
|
transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame)
|
||||||
transition.updateFrame(node: self.menuButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
transition.updateFrame(node: self.menuButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size))
|
||||||
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
|
transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height)))
|
||||||
var buttonTitlteTransition = transition
|
var menuButtonTitleTransition = transition
|
||||||
if buttonTitleUpdated {
|
if buttonTitleUpdated {
|
||||||
buttonTitlteTransition = .immediate
|
menuButtonTitleTransition = .immediate
|
||||||
}
|
}
|
||||||
buttonTitlteTransition.updateFrame(node: self.menuButtonTextNode, frame: CGRect(origin: CGPoint(x: 16.0, y: 7.0 - UIScreenPixel), size: menuTextSize))
|
menuButtonTitleTransition.updateFrame(node: self.menuButtonTextNode, frame: CGRect(origin: CGPoint(x: 16.0, y: 7.0 - UIScreenPixel), size: menuTextSize))
|
||||||
transition.updateAlpha(node: self.menuButtonTextNode, alpha: menuButtonExpanded ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.menuButtonTextNode, alpha: menuButtonExpanded ? 1.0 : 0.0)
|
||||||
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: isSendAsButton ? 1.0 + UIScreenPixel : (4.0 + UIScreenPixel), y: 1.0 + UIScreenPixel, width: 30.0, height: 30.0))
|
transition.updateFrame(node: self.menuButtonIconNode, frame: CGRect(x: isSendAsButton ? 1.0 + UIScreenPixel : (4.0 + UIScreenPixel), y: 1.0 + UIScreenPixel, width: 30.0, height: 30.0))
|
||||||
|
|
||||||
@ -1917,7 +2160,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
var leftInset = leftInset
|
var leftInset = leftInset
|
||||||
leftInset += leftMenuInset
|
leftInset += leftMenuInset
|
||||||
|
|
||||||
transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(x: leftInset + 2.0 - UIScreenPixel, y: panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight)))
|
transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(x: hideOffset.x + leftInset + 2.0 - UIScreenPixel, y: hideOffset.y + panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight)))
|
||||||
transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame)
|
transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame)
|
||||||
|
|
||||||
var composeButtonsOffset: CGFloat = 0.0
|
var composeButtonsOffset: CGFloat = 0.0
|
||||||
@ -1929,7 +2172,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
self.updateCounterTextNode(transition: transition)
|
self.updateCounterTextNode(transition: transition)
|
||||||
|
|
||||||
let actionButtonsFrame = CGRect(origin: CGPoint(x: width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset, y: panelHeight - minimalHeight), size: CGSize(width: 44.0, height: minimalHeight))
|
let actionButtonsFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 43.0 - UIScreenPixel + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight), size: CGSize(width: 44.0, height: minimalHeight))
|
||||||
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
|
transition.updateFrame(node: self.actionButtons, frame: actionButtonsFrame)
|
||||||
if let (rect, containerSize) = self.absoluteRect {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
self.actionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + actionButtonsFrame.origin.x, y: rect.origin.y + actionButtonsFrame.origin.y, width: actionButtonsFrame.width, height: actionButtonsFrame.height), within: containerSize, transition: transition)
|
self.actionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + actionButtonsFrame.origin.x, y: rect.origin.y + actionButtonsFrame.origin.y, width: actionButtonsFrame.width, height: actionButtonsFrame.height), within: containerSize, transition: transition)
|
||||||
@ -1991,7 +2234,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth)
|
textInputViewRealInsets = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
let textInputFrame = CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
let textInputFrame = CGRect(x: hideOffset.x + leftInset + textFieldInsets.left, y: hideOffset.y + textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
||||||
transition.updateFrame(node: self.textInputContainer, frame: textInputFrame)
|
transition.updateFrame(node: self.textInputContainer, frame: textInputFrame)
|
||||||
transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: CGRect(origin: CGPoint(), size: textInputFrame.size))
|
transition.updateFrame(node: self.textInputContainerBackgroundNode, frame: CGRect(origin: CGPoint(), size: textInputFrame.size))
|
||||||
transition.updateAlpha(node: self.textInputContainer, alpha: audioRecordingItemsAlpha)
|
transition.updateAlpha(node: self.textInputContainer, alpha: audioRecordingItemsAlpha)
|
||||||
@ -2025,7 +2268,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
let _ = placeholderApply()
|
let _ = placeholderApply()
|
||||||
|
|
||||||
contextPlaceholderNode.frame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size)
|
transition.updateFrame(node: contextPlaceholderNode, frame: CGRect(origin: CGPoint(x: hideOffset.x + leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size))
|
||||||
contextPlaceholderNode.alpha = audioRecordingItemsAlpha
|
contextPlaceholderNode.alpha = audioRecordingItemsAlpha
|
||||||
} else if let contextPlaceholderNode = self.contextPlaceholderNode {
|
} else if let contextPlaceholderNode = self.contextPlaceholderNode {
|
||||||
self.contextPlaceholderNode = nil
|
self.contextPlaceholderNode = nil
|
||||||
@ -2060,7 +2303,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
self.slowmodePlaceholderNode?.isHidden = true
|
self.slowmodePlaceholderNode?.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight)
|
var nextButtonTopRight = CGPoint(x: hideOffset.x + width - rightInset - textFieldInsets.right - accessoryButtonInset, y: hideOffset.y + panelHeight - textFieldInsets.bottom - minimalInputHeight)
|
||||||
for (item, button) in self.accessoryItemButtons.reversed() {
|
for (item, button) in self.accessoryItemButtons.reversed() {
|
||||||
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
|
||||||
button.updateLayout(item: item, size: buttonSize)
|
button.updateLayout(item: item, size: buttonSize)
|
||||||
@ -2080,7 +2323,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
nextButtonTopRight.x -= accessoryButtonSpacing
|
nextButtonTopRight.x -= accessoryButtonSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let textInputBackgroundFrame = CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
let textInputBackgroundFrame = CGRect(x: hideOffset.x + leftInset + textFieldInsets.left, y: hideOffset.y + textFieldInsets.top, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom)
|
||||||
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputBackgroundFrame)
|
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: textInputBackgroundFrame)
|
||||||
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
transition.updateAlpha(node: self.textInputBackgroundNode, alpha: audioRecordingItemsAlpha)
|
||||||
|
|
||||||
@ -2105,7 +2348,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
textLockIconTransition.updateFrame(node: textLockIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 4.0, y: floor((textPlaceholderFrame.height - image.size.height) / 2.0)), size: image.size))
|
textLockIconTransition.updateFrame(node: textLockIconNode, frame: CGRect(origin: CGPoint(x: -image.size.width - 4.0, y: floor((textPlaceholderFrame.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
textPlaceholderFrame = CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size)
|
textPlaceholderFrame = CGRect(origin: CGPoint(x: hideOffset.x + leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: self.textPlaceholderNode.frame.size)
|
||||||
|
|
||||||
if let textLockIconNode = self.textLockIconNode {
|
if let textLockIconNode = self.textLockIconNode {
|
||||||
self.textLockIconNode = nil
|
self.textLockIconNode = nil
|
||||||
|
@ -1023,6 +1023,9 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
|
|||||||
final class PeerInfoAvatarListNode: ASDisplayNode {
|
final class PeerInfoAvatarListNode: ASDisplayNode {
|
||||||
private let isSettings: Bool
|
private let isSettings: Bool
|
||||||
let pinchSourceNode: PinchSourceContainerNode
|
let pinchSourceNode: PinchSourceContainerNode
|
||||||
|
let bottomCoverNode: ASDisplayNode
|
||||||
|
fileprivate let maskNode: DynamicIslandMaskNode
|
||||||
|
fileprivate let topCoverNode: DynamicIslandBlurNode
|
||||||
let avatarContainerNode: PeerInfoAvatarTransformContainerNode
|
let avatarContainerNode: PeerInfoAvatarTransformContainerNode
|
||||||
let listContainerTransformNode: ASDisplayNode
|
let listContainerTransformNode: ASDisplayNode
|
||||||
let listContainerNode: PeerInfoAvatarListContainerNode
|
let listContainerNode: PeerInfoAvatarListContainerNode
|
||||||
@ -1038,6 +1041,10 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
|
|||||||
init(context: AccountContext, readyWhenGalleryLoads: Bool, isSettings: Bool) {
|
init(context: AccountContext, readyWhenGalleryLoads: Bool, isSettings: Bool) {
|
||||||
self.isSettings = isSettings
|
self.isSettings = isSettings
|
||||||
|
|
||||||
|
self.bottomCoverNode = ASDisplayNode()
|
||||||
|
self.bottomCoverNode.backgroundColor = .black
|
||||||
|
|
||||||
|
self.maskNode = DynamicIslandMaskNode(size: CGSize(width: 512.0, height: 512.0))
|
||||||
self.pinchSourceNode = PinchSourceContainerNode()
|
self.pinchSourceNode = PinchSourceContainerNode()
|
||||||
|
|
||||||
self.avatarContainerNode = PeerInfoAvatarTransformContainerNode(context: context)
|
self.avatarContainerNode = PeerInfoAvatarTransformContainerNode(context: context)
|
||||||
@ -1046,12 +1053,16 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
|
|||||||
self.listContainerNode.clipsToBounds = true
|
self.listContainerNode.clipsToBounds = true
|
||||||
self.listContainerNode.isHidden = true
|
self.listContainerNode.isHidden = true
|
||||||
|
|
||||||
|
self.topCoverNode = DynamicIslandBlurNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.bottomCoverNode)
|
||||||
self.addSubnode(self.pinchSourceNode)
|
self.addSubnode(self.pinchSourceNode)
|
||||||
self.pinchSourceNode.contentNode.addSubnode(self.avatarContainerNode)
|
self.pinchSourceNode.contentNode.addSubnode(self.avatarContainerNode)
|
||||||
self.listContainerTransformNode.addSubnode(self.listContainerNode)
|
self.listContainerTransformNode.addSubnode(self.listContainerNode)
|
||||||
self.pinchSourceNode.contentNode.addSubnode(self.listContainerTransformNode)
|
self.pinchSourceNode.contentNode.addSubnode(self.listContainerTransformNode)
|
||||||
|
self.addSubnode(self.topCoverNode)
|
||||||
|
|
||||||
let avatarReady = (self.avatarContainerNode.avatarNode.ready
|
let avatarReady = (self.avatarContainerNode.avatarNode.ready
|
||||||
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
|> mapToSignal { _ -> Signal<Bool, NoError> in
|
||||||
@ -2542,7 +2553,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
private var currentCredibilityIcon: CredibilityIcon?
|
private var currentCredibilityIcon: CredibilityIcon?
|
||||||
|
|
||||||
private var currentPanelStatusData: PeerInfoStatusData?
|
private var currentPanelStatusData: PeerInfoStatusData?
|
||||||
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, notificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat {
|
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, notificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.threadData = threadData
|
self.threadData = threadData
|
||||||
@ -2714,7 +2725,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
var transitionSourceTitleFrame = CGRect()
|
var transitionSourceTitleFrame = CGRect()
|
||||||
var transitionSourceSubtitleFrame = CGRect()
|
var transitionSourceSubtitleFrame = CGRect()
|
||||||
|
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 13.0), size: CGSize(width: avatarSize, height: avatarSize))
|
let avatarFrame = CGRect(origin: CGPoint(x: floor((width - avatarSize) / 2.0), y: statusBarHeight + 22.0), size: CGSize(width: avatarSize, height: avatarSize))
|
||||||
|
|
||||||
self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
self.backgroundNode.updateColor(color: presentationData.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||||
|
|
||||||
@ -3103,7 +3114,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
let titleMinScale: CGFloat = 0.6
|
let titleMinScale: CGFloat = 0.6
|
||||||
let subtitleMinScale: CGFloat = 0.8
|
let subtitleMinScale: CGFloat = 0.8
|
||||||
let avatarMinScale: CGFloat = 0.7
|
let avatarMinScale: CGFloat = 0.55
|
||||||
|
|
||||||
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
|
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
|
||||||
|
|
||||||
@ -3197,13 +3208,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let avatarCornerRadius: CGFloat
|
var isForum = false
|
||||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
avatarCornerRadius = floor(avatarSize * 0.25)
|
isForum = true
|
||||||
} else {
|
|
||||||
avatarCornerRadius = avatarSize / 2.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let avatarCornerRadius: CGFloat = isForum ? floor(avatarSize * 0.25) : avatarSize / 2.0
|
||||||
|
|
||||||
if self.isAvatarExpanded {
|
if self.isAvatarExpanded {
|
||||||
self.avatarListNode.listContainerNode.isHidden = false
|
self.avatarListNode.listContainerNode.isHidden = false
|
||||||
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
if let transitionSourceAvatarFrame = transitionSourceAvatarFrame {
|
||||||
@ -3294,6 +3305,29 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
|
transition.updateSublayerTransformScale(node: self.avatarListNode.listContainerTransformNode, scale: avatarListContainerScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if deviceMetrics.hasDynamicIsland && !isForum && self.forumTopicThreadId == nil {
|
||||||
|
self.avatarListNode.maskNode.frame = CGRect(origin: CGPoint(x: -85.5, y: -self.avatarListNode.frame.minY + 48.0), size: CGSize(width: 171.0, height: 171.0))
|
||||||
|
self.avatarListNode.bottomCoverNode.frame = self.avatarListNode.maskNode.frame
|
||||||
|
self.avatarListNode.topCoverNode.frame = self.avatarListNode.maskNode.frame
|
||||||
|
|
||||||
|
let maskValue = max(0.0, min(1.0, contentOffset / 120.0))
|
||||||
|
if maskValue > 0.03 {
|
||||||
|
self.avatarListNode.bottomCoverNode.isHidden = false
|
||||||
|
self.avatarListNode.topCoverNode.isHidden = false
|
||||||
|
self.avatarListNode.view.mask = self.avatarListNode.maskNode.view
|
||||||
|
} else {
|
||||||
|
self.avatarListNode.bottomCoverNode.isHidden = true
|
||||||
|
self.avatarListNode.topCoverNode.isHidden = true
|
||||||
|
self.avatarListNode.view.mask = nil
|
||||||
|
}
|
||||||
|
self.avatarListNode.maskNode.update(maskValue)
|
||||||
|
self.avatarListNode.topCoverNode.update(maskValue)
|
||||||
|
} else {
|
||||||
|
self.avatarListNode.bottomCoverNode.isHidden = true
|
||||||
|
self.avatarListNode.topCoverNode.isHidden = true
|
||||||
|
self.avatarListNode.view.mask = nil
|
||||||
|
}
|
||||||
|
|
||||||
self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition)
|
self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition)
|
||||||
if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse {
|
if self.avatarListNode.listContainerNode.isCollapsing && !self.ignoreCollapse {
|
||||||
self.avatarListNode.avatarContainerNode.canAttachVideo = false
|
self.avatarListNode.avatarContainerNode.canAttachVideo = false
|
||||||
@ -3639,3 +3673,83 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DynamicIslandMaskNode: ManagedAnimationNode {
|
||||||
|
func update(_ value: CGFloat) {
|
||||||
|
let lowerBound = 0
|
||||||
|
let upperBound = 180
|
||||||
|
let frameIndex = lowerBound + Int(value * CGFloat(upperBound - lowerBound))
|
||||||
|
|
||||||
|
self.trackTo(item: ManagedAnimationItem(source: .local("UserAvatarMask"), frames: .range(startFrame: frameIndex, endFrame: frameIndex), duration: 0.001))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DynamicIslandBlurNode: ASDisplayNode {
|
||||||
|
private var effectView: UIVisualEffectView?
|
||||||
|
private let fadeNode = ASDisplayNode()
|
||||||
|
private let gradientNode = ASImageNode()
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.fadeNode.backgroundColor = .black
|
||||||
|
self.fadeNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.gradientNode.displaysAsynchronously = false
|
||||||
|
let gradientImage = generateImage(CGSize(width: 100.0, height: 100.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: .zero, size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.87, 1.0]
|
||||||
|
let colors: [CGColor] = [UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000, alpha: 0.0).cgColor, UIColor(rgb: 0x000000, alpha: 1.0).cgColor]
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
let endRadius: CGFloat = 90.0
|
||||||
|
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0 + 38.0)
|
||||||
|
context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: .drawsAfterEndLocation)
|
||||||
|
})
|
||||||
|
self.gradientNode.image = gradientImage
|
||||||
|
|
||||||
|
let effectView = UIVisualEffectView(effect: nil)
|
||||||
|
self.effectView = effectView
|
||||||
|
self.view.insertSubview(effectView, at: 0)
|
||||||
|
|
||||||
|
self.addSubnode(self.gradientNode)
|
||||||
|
self.addSubnode(self.fadeNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepare() {
|
||||||
|
guard let effectView = self.effectView, effectView.layer.animation(forKey: "effect") == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UIView.animate(withDuration: 1.0) {
|
||||||
|
effectView.effect = UIBlurEffect(style: .dark)
|
||||||
|
}
|
||||||
|
effectView.layer.speed = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(_ value: CGFloat) {
|
||||||
|
if value > 0.0 {
|
||||||
|
self.prepare()
|
||||||
|
self.effectView?.layer.timeOffset = max(0.0, -0.1 + value * 1.1)
|
||||||
|
} else {
|
||||||
|
self.effectView?.layer.removeAllAnimations()
|
||||||
|
self.effectView?.layer.speed = 1.0
|
||||||
|
self.effectView?.layer.timeOffset = 0.0
|
||||||
|
self.effectView?.effect = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fadeNode.alpha = min(1.0, max(0.0, -0.25 + value * 1.55))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
|
||||||
|
self.effectView?.frame = self.bounds
|
||||||
|
self.fadeNode.frame = self.bounds
|
||||||
|
|
||||||
|
let gradientSize = CGSize(width: 100.0, height: 100.0)
|
||||||
|
self.gradientNode.frame = CGRect(origin: CGPoint(x: (self.bounds.width - gradientSize.width) / 2.0, y: 0.0), size: gradientSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -350,6 +350,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
|||||||
}, beginCall: { _ in
|
}, beginCall: { _ in
|
||||||
}, toggleMessageStickerStarred: { _ in
|
}, toggleMessageStickerStarred: { _ in
|
||||||
}, presentController: { _, _ in
|
}, presentController: { _, _ in
|
||||||
|
}, presentControllerInCurrent: { _, _ in
|
||||||
}, getNavigationController: {
|
}, getNavigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
}, presentGlobalOverlayController: { _, _ in
|
||||||
@ -8687,7 +8688,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive)
|
var headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive)
|
||||||
if !self.isSettings && !self.state.isEditing {
|
if !self.isSettings && !self.state.isEditing {
|
||||||
headerHeight += 71.0
|
headerHeight += 71.0
|
||||||
}
|
}
|
||||||
@ -9052,7 +9053,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, transition: transition, additive: additive)
|
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, threadData: self.data?.threadData, notificationSettings: self.data?.notificationSettings, globalNotificationSettings: self.data?.globalNotificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: additive)
|
||||||
}
|
}
|
||||||
|
|
||||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||||
@ -10261,7 +10262,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
}
|
}
|
||||||
let headerInset = sectionInset
|
let headerInset = sectionInset
|
||||||
|
|
||||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, notificationSettings: self.screenNode.data?.notificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, transition: transition, additive: false)
|
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: headerInset, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, threadData: self.screenNode.data?.threadData, notificationSettings: self.screenNode.data?.notificationSettings, globalNotificationSettings: self.screenNode.data?.globalNotificationSettings, statusData: self.screenNode.data?.status, panelStatusData: (nil, nil, nil), isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, transition: transition, additive: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
let titleScale = (fraction * previousTitleNode.view.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.view.bounds.height
|
||||||
|
@ -597,6 +597,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}, beginCall: { _ in
|
}, beginCall: { _ in
|
||||||
}, toggleMessageStickerStarred: { _ in
|
}, toggleMessageStickerStarred: { _ in
|
||||||
}, presentController: { _, _ in
|
}, presentController: { _, _ in
|
||||||
|
}, presentControllerInCurrent: { _, _ in
|
||||||
}, getNavigationController: {
|
}, getNavigationController: {
|
||||||
return nil
|
return nil
|
||||||
}, presentGlobalOverlayController: { _, _ in
|
}, presentGlobalOverlayController: { _, _ in
|
||||||
|
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
@ -29,11 +30,93 @@ public enum TooltipActiveTextAction {
|
|||||||
case longTap
|
case longTap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func generateArrowImage() -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 14.0, height: 8.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: .zero, size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
context.setLineWidth(1.0 + UIScreenPixel)
|
||||||
|
context.setLineCap(.round)
|
||||||
|
|
||||||
|
let arrowBounds = bounds.insetBy(dx: 1.0, dy: 1.0)
|
||||||
|
context.move(to: arrowBounds.origin)
|
||||||
|
context.addLine(to: CGPoint(x: arrowBounds.midX, y: arrowBounds.maxY))
|
||||||
|
context.addLine(to: CGPoint(x: arrowBounds.maxX, y: arrowBounds.minY))
|
||||||
|
context.strokePath()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownArrowsIconNode: ASDisplayNode {
|
||||||
|
private let topArrow: ASImageNode
|
||||||
|
private let bottomArrow: ASImageNode
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.topArrow = ASImageNode()
|
||||||
|
self.topArrow.displaysAsynchronously = false
|
||||||
|
self.topArrow.image = generateArrowImage()
|
||||||
|
|
||||||
|
self.bottomArrow = ASImageNode()
|
||||||
|
self.bottomArrow.displaysAsynchronously = false
|
||||||
|
self.bottomArrow.image = self.topArrow.image
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.topArrow)
|
||||||
|
self.addSubnode(self.bottomArrow)
|
||||||
|
|
||||||
|
if let image = self.topArrow.image {
|
||||||
|
self.topArrow.frame = CGRect(origin: .zero, size: image.size)
|
||||||
|
self.bottomArrow.frame = CGRect(origin: CGPoint(x: 0.0, y: 7.0), size: image.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAnimations() {
|
||||||
|
guard self.bottomArrow.layer.animation(forKey: "position") == nil else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.supernode?.layer.animateKeyframes(values: [
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 1.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 1.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||||
|
], duration: 1.1, keyPath: "position", additive: true)
|
||||||
|
|
||||||
|
self.bottomArrow.layer.animateKeyframes(values: [
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 4.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 4.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||||
|
], duration: 1.1, keyPath: "position", additive: true, completion: { [weak self] _ in
|
||||||
|
Queue.mainQueue().after(2.9) {
|
||||||
|
self?.setupAnimations()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.topArrow.layer.animateKeyframes(values: [
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 6.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: -0.5)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 6.0)),
|
||||||
|
NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0))
|
||||||
|
], duration: 1.1, keyPath: "position", additive: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class TooltipScreenNode: ViewControllerTracingNode {
|
private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||||
private let tooltipStyle: TooltipScreen.Style
|
private let tooltipStyle: TooltipScreen.Style
|
||||||
private let icon: TooltipScreen.Icon?
|
private let icon: TooltipScreen.Icon?
|
||||||
private let customContentNode: TooltipCustomContentNode?
|
private let customContentNode: TooltipCustomContentNode?
|
||||||
private let location: TooltipScreen.Location
|
var location: TooltipScreen.Location {
|
||||||
|
didSet {
|
||||||
|
if let layout = self.validLayout {
|
||||||
|
self.updateLayout(layout: layout, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private let displayDuration: TooltipScreen.DisplayDuration
|
private let displayDuration: TooltipScreen.DisplayDuration
|
||||||
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
||||||
private let requestDismiss: () -> Void
|
private let requestDismiss: () -> Void
|
||||||
@ -50,6 +133,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
private let arrowContainer: ASDisplayNode
|
private let arrowContainer: ASDisplayNode
|
||||||
private var arrowEffectView: UIView?
|
private var arrowEffectView: UIView?
|
||||||
private let animatedStickerNode: AnimatedStickerNode
|
private let animatedStickerNode: AnimatedStickerNode
|
||||||
|
private var downArrowsNode: DownArrowsIconNode?
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
|
|
||||||
private var isArrowInverted: Bool = false
|
private var isArrowInverted: Bool = false
|
||||||
@ -138,6 +222,24 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||||
self.arrowContainer.view.addSubview(self.arrowEffectView!)
|
self.arrowContainer.view.addSubview(self.arrowEffectView!)
|
||||||
|
|
||||||
|
let maskLayer = CAShapeLayer()
|
||||||
|
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
|
||||||
|
maskLayer.path = path.cgPath
|
||||||
|
}
|
||||||
|
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
|
||||||
|
self.arrowContainer.layer.mask = maskLayer
|
||||||
|
} else if case .default = style {
|
||||||
|
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
self.backgroundContainerNode.clipsToBounds = true
|
||||||
|
self.backgroundContainerNode.cornerRadius = 14.0
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.backgroundContainerNode.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
fontSize = 14.0
|
||||||
|
|
||||||
|
self.arrowEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
self.arrowContainer.view.addSubview(self.arrowEffectView!)
|
||||||
|
|
||||||
let maskLayer = CAShapeLayer()
|
let maskLayer = CAShapeLayer()
|
||||||
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
|
if let path = try? svgPath("M85.882251,0 C79.5170552,0 73.4125613,2.52817247 68.9116882,7.02834833 L51.4264069,24.5109211 C46.7401154,29.1964866 39.1421356,29.1964866 34.4558441,24.5109211 L16.9705627,7.02834833 C12.4696897,2.52817247 6.36519576,0 0,0 L85.882251,0 ", scale: CGPoint(x: 0.333333, y: 0.333333), offset: CGPoint()) {
|
||||||
maskLayer.path = path.cgPath
|
maskLayer.path = path.cgPath
|
||||||
@ -179,7 +281,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
} else if case .top = location {
|
} else if case .top = location {
|
||||||
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
self.containerNode.clipsToBounds = true
|
self.containerNode.clipsToBounds = true
|
||||||
self.containerNode.cornerRadius = 9.0
|
self.containerNode.cornerRadius = 14.0
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
self.containerNode.layer.cornerCurve = .continuous
|
self.containerNode.layer.cornerCurve = .continuous
|
||||||
}
|
}
|
||||||
@ -204,6 +306,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
case .info:
|
case .info:
|
||||||
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_infotip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
self.animatedStickerNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "anim_infotip"), width: Int(70 * UIScreenScale), height: Int(70 * UIScreenScale), playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||||
self.animatedStickerNode.automaticallyLoadFirstFrame = true
|
self.animatedStickerNode.automaticallyLoadFirstFrame = true
|
||||||
|
case .downArrows:
|
||||||
|
self.downArrowsNode = DownArrowsIconNode()
|
||||||
}
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -227,6 +331,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
self.containerNode.addSubnode(self.textNode)
|
self.containerNode.addSubnode(self.textNode)
|
||||||
self.containerNode.addSubnode(self.animatedStickerNode)
|
self.containerNode.addSubnode(self.animatedStickerNode)
|
||||||
|
if let downArrowsNode = self.downArrowsNode {
|
||||||
|
self.containerNode.addSubnode(downArrowsNode)
|
||||||
|
}
|
||||||
self.scrollingContainer.addSubnode(self.containerNode)
|
self.scrollingContainer.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.scrollingContainer)
|
self.addSubnode(self.scrollingContainer)
|
||||||
|
|
||||||
@ -298,7 +405,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
let sideInset: CGFloat = self.inset + layout.safeInsets.left
|
let sideInset: CGFloat = self.inset + layout.safeInsets.left
|
||||||
let bottomInset: CGFloat = 10.0
|
let bottomInset: CGFloat = 10.0
|
||||||
let contentInset: CGFloat = 11.0
|
let contentInset: CGFloat = 11.0
|
||||||
let contentVerticalInset: CGFloat = 11.0
|
let contentVerticalInset: CGFloat = 8.0
|
||||||
let animationSize: CGSize
|
let animationSize: CGSize
|
||||||
let animationInset: CGFloat
|
let animationInset: CGFloat
|
||||||
let animationSpacing: CGFloat
|
let animationSpacing: CGFloat
|
||||||
@ -308,6 +415,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
animationSize = CGSize()
|
animationSize = CGSize()
|
||||||
animationInset = 0.0
|
animationInset = 0.0
|
||||||
animationSpacing = 0.0
|
animationSpacing = 0.0
|
||||||
|
case .downArrows:
|
||||||
|
animationSize = CGSize(width: 24.0, height: 32.0)
|
||||||
|
animationInset = (40.0 - animationSize.width) / 2.0
|
||||||
|
animationSpacing = 8.0
|
||||||
case .chatListPress:
|
case .chatListPress:
|
||||||
animationSize = CGSize(width: 32.0, height: 32.0)
|
animationSize = CGSize(width: 32.0, height: 32.0)
|
||||||
animationInset = (70.0 - animationSize.width) / 2.0
|
animationInset = (70.0 - animationSize.width) / 2.0
|
||||||
@ -412,8 +523,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize))
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize))
|
||||||
|
|
||||||
transition.updateFrame(node: self.animatedStickerNode, frame: CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)))
|
let animationFrame = CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
||||||
|
transition.updateFrame(node: self.animatedStickerNode, frame: animationFrame)
|
||||||
self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))
|
||||||
|
|
||||||
|
if let downArrowsNode = self.downArrowsNode {
|
||||||
|
let arrowsSize = CGSize(width: 16.0, height: 16.0)
|
||||||
|
transition.updateFrame(node: downArrowsNode, frame: CGRect(origin: CGPoint(x: animationFrame.midX - arrowsSize.width / 2.0, y: animationFrame.midY - arrowsSize.height / 2.0), size: arrowsSize))
|
||||||
|
downArrowsNode.setupAnimations()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
@ -472,7 +590,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
|||||||
animationDelay = 0.6
|
animationDelay = 0.6
|
||||||
case .info:
|
case .info:
|
||||||
animationDelay = 0.2
|
animationDelay = 0.2
|
||||||
case .none:
|
case .none, .downArrows:
|
||||||
animationDelay = 0.0
|
animationDelay = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,6 +645,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
public enum Icon {
|
public enum Icon {
|
||||||
case info
|
case info
|
||||||
case chatListPress
|
case chatListPress
|
||||||
|
case downArrows
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DismissOnTouch {
|
public enum DismissOnTouch {
|
||||||
@ -548,6 +667,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
public enum DisplayDuration {
|
public enum DisplayDuration {
|
||||||
case `default`
|
case `default`
|
||||||
case custom(Double)
|
case custom(Double)
|
||||||
|
case infinite
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Style {
|
public enum Style {
|
||||||
@ -562,7 +682,13 @@ public final class TooltipScreen: ViewController {
|
|||||||
private let style: TooltipScreen.Style
|
private let style: TooltipScreen.Style
|
||||||
private let icon: TooltipScreen.Icon?
|
private let icon: TooltipScreen.Icon?
|
||||||
private let customContentNode: TooltipCustomContentNode?
|
private let customContentNode: TooltipCustomContentNode?
|
||||||
private let location: TooltipScreen.Location
|
public var location: TooltipScreen.Location {
|
||||||
|
didSet {
|
||||||
|
if self.isNodeLoaded {
|
||||||
|
self.controllerNode.location = self.location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private let displayDuration: DisplayDuration
|
private let displayDuration: DisplayDuration
|
||||||
private let inset: CGFloat
|
private let inset: CGFloat
|
||||||
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch
|
||||||
@ -580,6 +706,8 @@ public final class TooltipScreen: ViewController {
|
|||||||
|
|
||||||
private var dismissTimer: Foundation.Timer?
|
private var dismissTimer: Foundation.Timer?
|
||||||
|
|
||||||
|
public var alwaysVisible = false
|
||||||
|
|
||||||
public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
|
public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.text = text
|
self.text = text
|
||||||
@ -615,6 +743,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
|
|
||||||
public func resetDismissTimeout(duration: TooltipScreen.DisplayDuration? = nil) {
|
public func resetDismissTimeout(duration: TooltipScreen.DisplayDuration? = nil) {
|
||||||
self.dismissTimer?.invalidate()
|
self.dismissTimer?.invalidate()
|
||||||
|
self.dismissTimer = nil
|
||||||
|
|
||||||
let timeout: Double
|
let timeout: Double
|
||||||
switch duration ?? self.displayDuration {
|
switch duration ?? self.displayDuration {
|
||||||
@ -622,6 +751,8 @@ public final class TooltipScreen: ViewController {
|
|||||||
timeout = 5.0
|
timeout = 5.0
|
||||||
case let .custom(value):
|
case let .custom(value):
|
||||||
timeout = value
|
timeout = value
|
||||||
|
case .infinite:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
final class TimerTarget: NSObject {
|
final class TimerTarget: NSObject {
|
||||||
@ -658,7 +789,7 @@ public final class TooltipScreen: ViewController {
|
|||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout, !self.alwaysVisible {
|
||||||
if validLayout.size.width != layout.size.width {
|
if validLayout.size.width != layout.size.width {
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if !pathComponents.isEmpty {
|
if !pathComponents.isEmpty {
|
||||||
pathComponents.removeFirst()
|
pathComponents.removeFirst()
|
||||||
}
|
}
|
||||||
|
if let lastComponent = pathComponents.last, lastComponent.isEmpty {
|
||||||
|
pathComponents.removeLast()
|
||||||
|
}
|
||||||
if !pathComponents.isEmpty && !pathComponents[0].isEmpty {
|
if !pathComponents.isEmpty && !pathComponents[0].isEmpty {
|
||||||
let peerName: String = pathComponents[0]
|
let peerName: String = pathComponents[0]
|
||||||
if pathComponents.count == 1 {
|
if pathComponents.count == 1 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user