mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
3206796f7c
commit
d66e1db6cc
@ -7585,9 +7585,7 @@ Sorry for the inconvenience.";
|
||||
"Premium.Stickers.Proceed" = "Unlock Premium Stickers";
|
||||
|
||||
"Premium.Reactions.Proceed" = "Unlock Premium Reactions";
|
||||
|
||||
"Premium.AppIcons.Proceed" = "Unlock Premium Icons";
|
||||
|
||||
"Premium.NoAds.Proceed" = "About Telegram Premium";
|
||||
|
||||
"AccessDenied.LocationPreciseDenied" = "To share your specific location in this chat, please go to Settings > Privacy > Location Services > Telegram and set Precise Location to On.";
|
||||
@ -8271,5 +8269,11 @@ Sorry for the inconvenience.";
|
||||
"EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons";
|
||||
"EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found";
|
||||
|
||||
"Username.UsernamePurchaseAvailable" = "Sorry, this username is occupied by someone. But it's available for purchase through official @auction.";
|
||||
"Channel.Username.UsernamePurchaseAvailable" = "Sorry, this link is occupied by someone. But it's available for purchase through official @auction.";
|
||||
"Username.UsernamePurchaseAvailable" = "Sorry, this username is occupied by someone. But it's available for purchase on [fragment.com]().";
|
||||
"Channel.Username.UsernamePurchaseAvailable" = "Sorry, this link is occupied by someone. But it's available for purchase on [fragment.com]().";
|
||||
|
||||
"DownloadList.IncreaseSpeed" = "Increase Speed";
|
||||
"Conversation.IncreaseSpeed" = "Increase Speed";
|
||||
|
||||
"Premium.ChatManagement.Proceed" = "About Telegram Premium";
|
||||
"Premium.FasterSpeed.Proceed" = "About Telegram Premium";
|
||||
|
@ -836,6 +836,7 @@ public enum PremiumIntroSource {
|
||||
case profile(PeerId)
|
||||
case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?)
|
||||
case voiceToText
|
||||
case fasterDownload
|
||||
}
|
||||
|
||||
#if ENABLE_WALLET
|
||||
|
@ -601,11 +601,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
context.sharedContext.hasOngoingCall.get(),
|
||||
itemNode.listNode.preloadItems.get()
|
||||
)
|
||||
|> map { hasOngoingCall, preloadItems -> [ChatHistoryPreloadItem] in
|
||||
|> map { hasOngoingCall, preloadItems -> Set<ChatHistoryPreloadItem> in
|
||||
if hasOngoingCall {
|
||||
return []
|
||||
return Set()
|
||||
} else {
|
||||
return preloadItems
|
||||
return Set(preloadItems)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import Postbox
|
||||
import TelegramAnimatedStickerNode
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import PremiumUI
|
||||
|
||||
private enum ChatListTokenId: Int32 {
|
||||
case archive
|
||||
@ -284,7 +285,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
isForum = true
|
||||
}
|
||||
|
||||
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: strongSelf.hasDownloads).map(\.filter)
|
||||
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: !isForum && strongSelf.hasDownloads).map(\.filter)
|
||||
}
|
||||
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
|
||||
}
|
||||
@ -896,13 +897,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
let items = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], messages: [message.id: message], peers: [:]),
|
||||
isCachedValue |> take(1)
|
||||
isCachedValue |> take(1),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak self] actions, isCachedValue -> [ContextMenuItem] in
|
||||
|> map { [weak self] actions, isCachedValue, accountPeer -> [ContextMenuItem] in
|
||||
guard let strongSelf = self else {
|
||||
return []
|
||||
}
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
@ -920,6 +923,31 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
})
|
||||
})))
|
||||
} else {
|
||||
if !isPremium, let size = downloadResource?.size, size >= 300 * 1024 * 1024 {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
f(.default)
|
||||
return
|
||||
}
|
||||
|
||||
let context = strongSelf.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: {})
|
||||
|
||||
f(.default)
|
||||
})))
|
||||
items.append(.separator)
|
||||
}
|
||||
|
||||
if let downloadResource = downloadResource, !downloadResource.isFirstInList {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Raise"), color: theme.contextMenu.primaryColor)
|
||||
@ -1032,7 +1060,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: { [weak self] in
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
|
||||
})
|
||||
})))
|
||||
|
||||
@ -1078,7 +1106,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
|
||||
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
|
||||
})
|
||||
})))
|
||||
|
||||
|
@ -908,6 +908,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
if case .reference = self.source {
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
let actionsVerticalTransitionDirection: CGFloat
|
||||
if let contentNode = contentNode {
|
||||
if contentNode.frame.minY < self.actionsStackNode.frame.minY {
|
||||
@ -1123,6 +1127,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
if case .center = actionsHorizontalAlignment {
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
if case .reference = self.source {
|
||||
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
|
||||
}
|
||||
|
||||
let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
|
||||
self.actionsStackNode.layer.animate(
|
||||
from: NSValue(cgPoint: CGPoint()),
|
||||
|
@ -196,9 +196,18 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
|
||||
self.highlight?(touchLocation)
|
||||
}
|
||||
|
||||
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton {
|
||||
self.state = .failed
|
||||
return
|
||||
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event) {
|
||||
var fail = false
|
||||
if let _ = hitResult as? UIButton {
|
||||
fail = true
|
||||
} else if let node = hitResult.asyncdisplaykit_node, node is ASControlNode {
|
||||
fail = true
|
||||
}
|
||||
|
||||
if fail {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
self.tapCount += 1
|
||||
|
@ -19,6 +19,7 @@ import Speak
|
||||
import TranslateUI
|
||||
import ShareController
|
||||
import UndoUI
|
||||
import ContextUI
|
||||
|
||||
enum ChatMediaGalleryThumbnail: Equatable {
|
||||
case image(ImageMediaReference)
|
||||
@ -201,6 +202,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
|
||||
|
||||
private let moreBarButton: MoreHeaderButton
|
||||
|
||||
private var tilingNode: TilingNode?
|
||||
fileprivate let _ready = Promise<Void>()
|
||||
fileprivate let _title = Promise<String>()
|
||||
@ -238,6 +241,10 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
|
||||
self.statusNode.isHidden = true
|
||||
|
||||
self.moreBarButton = MoreHeaderButton()
|
||||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
@ -275,6 +282,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
self?.openMoreMenu(sourceNode: sourceNode, gesture: gesture)
|
||||
}
|
||||
}
|
||||
|
||||
override func isPagingEnabled() -> Signal<Bool, NoError> {
|
||||
@ -326,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
case .medium, .full:
|
||||
strongSelf.statusNodeContainer.isHidden = true
|
||||
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
|
||||
strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
if let strongSelf = self {
|
||||
strongSelf.recognizedContentNode?.removeFromSupernode()
|
||||
if !results.isEmpty {
|
||||
let size = strongSelf.imageNode.bounds.size
|
||||
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
}, performAction: { [weak self] string, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .copy:
|
||||
UIPasteboard.general.string = string
|
||||
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
|
||||
controller.present(tooltipController, in: .window(.root))
|
||||
}
|
||||
case .share:
|
||||
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
|
||||
controller.present(shareController, in: .window(.root))
|
||||
}
|
||||
case .lookup:
|
||||
let controller = UIReferenceLibraryViewController(term: string)
|
||||
if let window = strongSelf.baseNavigationController()?.view.window {
|
||||
controller.popoverPresentationController?.sourceView = window
|
||||
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
window.rootViewController?.present(controller, animated: true)
|
||||
}
|
||||
case .speak:
|
||||
let _ = speakText(context: strongSelf.context, text: string)
|
||||
case .translate:
|
||||
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
|
||||
controller.pushController = { [weak parentController] c in
|
||||
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
parentController?.push(c)
|
||||
}
|
||||
controller.presentController = { [weak parentController] c in
|
||||
parentController?.present(c, in: .window(.root))
|
||||
}
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
}
|
||||
})
|
||||
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
||||
guard let strongSelf = self, let message = strongSelf.message else {
|
||||
return
|
||||
}
|
||||
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
|
||||
}
|
||||
recognizedContentNode.alpha = 0.0
|
||||
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
|
||||
strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
strongSelf.recognizedContentNode = recognizedContentNode
|
||||
strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
// Queue.concurrentDefaultQueue().async {
|
||||
// if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
|
||||
// strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.recognizedContentNode?.removeFromSupernode()
|
||||
// if !results.isEmpty {
|
||||
// let size = strongSelf.imageNode.bounds.size
|
||||
// let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
|
||||
// }
|
||||
// }, performAction: { [weak self] string, action in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// switch action {
|
||||
// case .copy:
|
||||
// UIPasteboard.general.string = string
|
||||
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
// let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
|
||||
// controller.present(tooltipController, in: .window(.root))
|
||||
// }
|
||||
// case .share:
|
||||
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
// let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
|
||||
// controller.present(shareController, in: .window(.root))
|
||||
// }
|
||||
// case .lookup:
|
||||
// let controller = UIReferenceLibraryViewController(term: string)
|
||||
// if let window = strongSelf.baseNavigationController()?.view.window {
|
||||
// controller.popoverPresentationController?.sourceView = window
|
||||
// controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
|
||||
// window.rootViewController?.present(controller, animated: true)
|
||||
// }
|
||||
// case .speak:
|
||||
// let _ = speakText(context: strongSelf.context, text: string)
|
||||
// case .translate:
|
||||
// if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
|
||||
// let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
|
||||
// controller.pushController = { [weak parentController] c in
|
||||
// (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
|
||||
// parentController?.push(c)
|
||||
// }
|
||||
// controller.presentController = { [weak parentController] c in
|
||||
// parentController?.present(c, in: .window(.root))
|
||||
// }
|
||||
// parentController.present(controller, in: .window(.root))
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// recognizedContentNode.barcodeAction = { [weak self] payload, rect in
|
||||
// guard let strongSelf = self, let message = strongSelf.message else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
|
||||
// }
|
||||
// recognizedContentNode.alpha = 0.0
|
||||
// recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
// recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
|
||||
// strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
// strongSelf.recognizedContentNode = recognizedContentNode
|
||||
// strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
// }
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
// }
|
||||
|
||||
case .none, .blurred:
|
||||
strongSelf.statusNodeContainer.isHidden = false
|
||||
@ -411,12 +423,17 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
} else {
|
||||
self._ready.set(.single(Void()))
|
||||
}
|
||||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
if imageReference.media.flags.contains(.hasStickers) {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
self._rightBarButtonItems.set(.single([rightBarButtonItem]))
|
||||
} else {
|
||||
self._rightBarButtonItems.set(.single([]))
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
}
|
||||
if self.message != nil {
|
||||
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
barButtonItems.append(moreMenuItem)
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
}
|
||||
self.contextAndMedia = (self.context, imageReference.abstract)
|
||||
}
|
||||
@ -453,6 +470,125 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self._ready.set(.single(Void()))
|
||||
}
|
||||
|
||||
@objc private func moreButtonPressed() {
|
||||
self.moreBarButton.play()
|
||||
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
|
||||
}
|
||||
|
||||
private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if let message = self.message {
|
||||
let context = self.context
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss()
|
||||
}
|
||||
}
|
||||
f(.default)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
// if #available(iOS 11.0, *) {
|
||||
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.beginAirPlaySetup()
|
||||
// })))
|
||||
// }
|
||||
|
||||
// if let (message, _, _) = strongSelf.contentInfo() {
|
||||
// for media in message.media {
|
||||
// if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
// let url = content.url
|
||||
//
|
||||
// let item = OpenInItem.url(url: url)
|
||||
// let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
|
||||
// items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
// f(.default)
|
||||
//
|
||||
// if let strongSelf = self, let controller = strongSelf.galleryController() {
|
||||
// var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
// if !presentationData.theme.overallDarkAppearance {
|
||||
// presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
// }
|
||||
// let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
|
||||
// }
|
||||
// })
|
||||
// controller.present(actionSheet, in: .window(.root))
|
||||
// }
|
||||
// })))
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() {
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in
|
||||
// f(.default)
|
||||
//
|
||||
// if let strongSelf = self {
|
||||
// switch strongSelf.fetchStatus {
|
||||
// case .Local:
|
||||
// let _ = (SaveToCameraRoll.saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, mediaReference: .message(message: MessageReference(message), media: file))
|
||||
// |> deliverOnMainQueue).start(completed: {
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// guard let controller = strongSelf.galleryController() else {
|
||||
// return
|
||||
// }
|
||||
// controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: strongSelf.presentationData.strings.Gallery_VideoSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
// })
|
||||
// default:
|
||||
// guard let controller = strongSelf.galleryController() else {
|
||||
// return
|
||||
// }
|
||||
// controller.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Gallery_WaitForVideoDownoad, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
// })]), in: .window(.root))
|
||||
// }
|
||||
// }
|
||||
// })))
|
||||
// }
|
||||
// if strongSelf.canDelete() {
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
// f(.default)
|
||||
//
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.footerContentNode.deleteButtonPressed()
|
||||
// }
|
||||
// })))
|
||||
// }
|
||||
|
||||
return .single(items)
|
||||
}
|
||||
|
||||
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
|
||||
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@objc func openStickersButtonPressed() {
|
||||
guard let (context, media) = self.contextAndMedia else {
|
||||
return
|
||||
|
@ -290,7 +290,7 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
|
||||
}
|
||||
|
||||
private func optionsCircleImage(dark: Bool) -> UIImage? {
|
||||
func optionsCircleImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
@ -339,7 +339,7 @@ private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .whi
|
||||
})
|
||||
}
|
||||
|
||||
private final class MoreHeaderButton: HighlightableButtonNode {
|
||||
final class MoreHeaderButton: HighlightableButtonNode {
|
||||
enum Content {
|
||||
case image(UIImage?)
|
||||
case more(UIImage?)
|
||||
@ -2475,6 +2475,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
let context = strongSelf.context
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss()
|
||||
}
|
||||
}
|
||||
f(.default)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
|
||||
var speedIconText: String = "1x"
|
||||
@ -2804,7 +2827,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
|
@ -13,6 +13,7 @@ public class ItemListActivityTextItem: ListViewItem, ItemListItem {
|
||||
case generic
|
||||
case constructive
|
||||
case destructive
|
||||
case warning
|
||||
}
|
||||
|
||||
let displayActivity: Bool
|
||||
@ -123,6 +124,8 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
|
||||
textColor = item.presentationData.theme.list.freeTextSuccessColor
|
||||
case .destructive:
|
||||
textColor = item.presentationData.theme.list.freeTextErrorColor
|
||||
case .warning:
|
||||
textColor = UIColor(rgb: 0xef8c00)
|
||||
}
|
||||
|
||||
let attributedString = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
|
@ -20,6 +20,8 @@ import ContextUI
|
||||
import FileMediaResourceStatus
|
||||
import ManagedAnimationNode
|
||||
import ShimmerEffect
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
|
||||
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
|
||||
|
||||
@ -159,6 +161,167 @@ final class CachedChatListSearchResult {
|
||||
}
|
||||
|
||||
public final class ListMessageFileItemNode: ListMessageNode {
|
||||
public final class DescriptionNode: ASDisplayNode {
|
||||
let descriptionNode: TextNode
|
||||
var titleTopicArrowNode: ASImageNode?
|
||||
var topicTitleNode: TextNode?
|
||||
var titleTopicIconView: ComponentHostView<Empty>?
|
||||
var titleTopicIconComponent: EmojiStatusComponent?
|
||||
|
||||
var visibilityStatus: Bool = false {
|
||||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent {
|
||||
let _ = titleTopicIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibilityStatus)),
|
||||
environment: {},
|
||||
containerSize: titleTopicIconView.bounds.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.descriptionNode = TextNode()
|
||||
self.descriptionNode.displaysAsynchronously = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.descriptionNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topic: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) {
|
||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
let makeTopicTitleLayout = TextNode.asyncLayout(self.topicTitleNode)
|
||||
|
||||
return { [weak self] context, constrainedWidth, theme, authorTitle, topic in
|
||||
var maxTitleWidth = constrainedWidth
|
||||
if let _ = topic {
|
||||
maxTitleWidth = floor(constrainedWidth * 0.7)
|
||||
}
|
||||
|
||||
let descriptionLayout = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: authorTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
|
||||
var remainingWidth = constrainedWidth - descriptionLayout.0.size.width
|
||||
|
||||
var topicTitleArguments: TextNodeLayoutArguments?
|
||||
var arrowIconImage: UIImage?
|
||||
if let topic = topic {
|
||||
remainingWidth -= 22.0 + 2.0
|
||||
|
||||
if authorTitle != nil {
|
||||
arrowIconImage = PresentationResourcesChatList.topicArrowIcon(theme)
|
||||
if let arrowIconImage = arrowIconImage {
|
||||
remainingWidth -= arrowIconImage.size.width + 6.0 * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
topicTitleArguments = TextNodeLayoutArguments(attributedString: topic.title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))
|
||||
}
|
||||
|
||||
let topicTitleLayout = topicTitleArguments.flatMap(makeTopicTitleLayout)
|
||||
|
||||
var size = descriptionLayout.0.size
|
||||
if let topicTitleLayout = topicTitleLayout {
|
||||
size.height = max(size.height, topicTitleLayout.0.size.height)
|
||||
size.width += 10.0 + topicTitleLayout.0.size.width
|
||||
}
|
||||
|
||||
return (size, {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = descriptionLayout.1()
|
||||
let authorFrame = CGRect(origin: CGPoint(), size: descriptionLayout.0.size)
|
||||
self.descriptionNode.frame = authorFrame
|
||||
|
||||
var nextX = authorFrame.maxX - 1.0
|
||||
if authorTitle == nil {
|
||||
nextX = 0.0
|
||||
}
|
||||
|
||||
if let arrowIconImage = arrowIconImage {
|
||||
let titleTopicArrowNode: ASImageNode
|
||||
if let current = self.titleTopicArrowNode {
|
||||
titleTopicArrowNode = current
|
||||
} else {
|
||||
titleTopicArrowNode = ASImageNode()
|
||||
self.titleTopicArrowNode = titleTopicArrowNode
|
||||
self.addSubnode(titleTopicArrowNode)
|
||||
}
|
||||
titleTopicArrowNode.image = arrowIconImage
|
||||
nextX += 6.0
|
||||
titleTopicArrowNode.frame = CGRect(origin: CGPoint(x: nextX, y: 5.0), size: arrowIconImage.size)
|
||||
nextX += arrowIconImage.size.width + 6.0
|
||||
} else {
|
||||
if let titleTopicArrowNode = self.titleTopicArrowNode {
|
||||
self.titleTopicArrowNode = nil
|
||||
titleTopicArrowNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let topic {
|
||||
let titleTopicIconView: ComponentHostView<Empty>
|
||||
if let current = self.titleTopicIconView {
|
||||
titleTopicIconView = current
|
||||
} else {
|
||||
titleTopicIconView = ComponentHostView<Empty>()
|
||||
self.titleTopicIconView = titleTopicIconView
|
||||
self.view.addSubview(titleTopicIconView)
|
||||
}
|
||||
|
||||
let titleTopicIconContent: EmojiStatusComponent.Content
|
||||
if let fileId = topic.iconId, fileId != 0 {
|
||||
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2))
|
||||
} else {
|
||||
titleTopicIconContent = .topic(title: String(topic.title.string.prefix(1)), color: topic.iconColor, size: CGSize(width: 22.0, height: 22.0))
|
||||
}
|
||||
|
||||
let titleTopicIconComponent = EmojiStatusComponent(
|
||||
context: context,
|
||||
animationCache: context.animationCache,
|
||||
animationRenderer: context.animationRenderer,
|
||||
content: titleTopicIconContent,
|
||||
isVisibleForAnimations: self.visibilityStatus,
|
||||
action: nil
|
||||
)
|
||||
self.titleTopicIconComponent = titleTopicIconComponent
|
||||
|
||||
let iconSize = titleTopicIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(titleTopicIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 22.0, height: 22.0)
|
||||
)
|
||||
titleTopicIconView.frame = CGRect(origin: CGPoint(x: nextX, y: UIScreenPixel), size: iconSize)
|
||||
nextX += iconSize.width + 2.0
|
||||
} else {
|
||||
if let titleTopicIconView = self.titleTopicIconView {
|
||||
self.titleTopicIconView = nil
|
||||
titleTopicIconView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if let topicTitleLayout = topicTitleLayout {
|
||||
let topicTitleNode = topicTitleLayout.1()
|
||||
if topicTitleNode.supernode == nil {
|
||||
self.addSubnode(topicTitleNode)
|
||||
self.topicTitleNode = topicTitleNode
|
||||
}
|
||||
|
||||
topicTitleNode.frame = CGRect(origin: CGPoint(x: nextX - 1.0, y: 0.0), size: topicTitleLayout.0.size)
|
||||
} else if let topicTitleNode = self.topicTitleNode {
|
||||
self.topicTitleNode = nil
|
||||
topicTitleNode.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let extractedBackgroundImageNode: ASImageNode
|
||||
@ -377,6 +540,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
|
||||
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
// let newDescriptionNodeMakeLayout = self.descriptionNode.asyncLayout()
|
||||
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
|
||||
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
|
||||
let iconImageLayout = self.iconImageNode.asyncLayout()
|
||||
@ -754,6 +918,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
// let forumThreadTitle: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)? = nil
|
||||
// let (newDescriptionNodeLayout, newDescriptionNodeApply) = newDescriptionNodeMakeLayout(item.context, params.width - leftInset - rightInset - 30.0, item.presentationData.theme.theme, descriptionText, forumThreadTitle)
|
||||
|
||||
var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
if extensionTextLayout.truncated, let text = extensionText?.string {
|
||||
extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center)
|
||||
|
@ -42,8 +42,9 @@ private final class ChannelVisibilityControllerArguments {
|
||||
let toggleApproveMembers: (Bool) -> Void
|
||||
let activateLink: (String) -> Void
|
||||
let deactivateLink: (String) -> Void
|
||||
let openAuction: (String) -> Void
|
||||
|
||||
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) {
|
||||
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
|
||||
self.context = context
|
||||
self.updateCurrentType = updateCurrentType
|
||||
self.updatePublicLinkText = updatePublicLinkText
|
||||
@ -60,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
|
||||
self.toggleApproveMembers = toggleApproveMembers
|
||||
self.activateLink = activateLink
|
||||
self.deactivateLink = deactivateLink
|
||||
self.openAuction = openAuction
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +111,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
case privateLinkManageInfo(PresentationTheme, String)
|
||||
|
||||
case publicLinkInfo(PresentationTheme, String)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
|
||||
|
||||
case existingLinksInfo(PresentationTheme, String)
|
||||
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
|
||||
@ -317,8 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus {
|
||||
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -671,7 +673,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .publicLinkInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .publicLinkStatus(_, text, status):
|
||||
case let .publicLinkStatus(_, text, status, username):
|
||||
var displayActivity = false
|
||||
let textColor: ItemListActivityTextItem.TextColor
|
||||
switch status {
|
||||
@ -686,13 +688,15 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
|
||||
case .taken:
|
||||
textColor = .destructive
|
||||
case .purchaseAvailable:
|
||||
textColor = .generic
|
||||
textColor = .warning
|
||||
}
|
||||
case .checking:
|
||||
textColor = .generic
|
||||
displayActivity = true
|
||||
}
|
||||
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section)
|
||||
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
|
||||
arguments.openAuction(username)
|
||||
}, sectionId: self.section)
|
||||
case let .existingLinksInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
|
||||
@ -1074,7 +1078,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
text = presentationData.strings.Channel_Username_CheckingUsername
|
||||
}
|
||||
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
|
||||
}
|
||||
if isGroup {
|
||||
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
|
||||
@ -1275,7 +1279,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
|
||||
text = presentationData.strings.Channel_Username_CheckingUsername
|
||||
}
|
||||
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status))
|
||||
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
|
||||
}
|
||||
|
||||
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
|
||||
@ -1742,6 +1746,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
|
||||
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
|
||||
})]), nil)
|
||||
})
|
||||
}, openAuction: { username in
|
||||
dismissInputImpl?()
|
||||
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||
})
|
||||
|
||||
let peerView = context.account.viewTracker.peerView(peerId)
|
||||
|
@ -252,7 +252,7 @@ final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0))
|
||||
)
|
||||
context.add(text
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 80.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 60.0 + text.size.height / 2.0))
|
||||
)
|
||||
context.add(content
|
||||
.position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0))
|
||||
@ -972,6 +972,10 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
buttonText = strings.Premium_Gift_GiftSubscription(price ?? "–").string
|
||||
case .other:
|
||||
switch component.subject {
|
||||
case .fasterDownload:
|
||||
buttonText = strings.Premium_FasterSpeed_Proceed
|
||||
case .advancedChatManagement:
|
||||
buttonText = strings.Premium_ChatManagement_Proceed
|
||||
case .uniqueReactions:
|
||||
buttonText = strings.Premium_Reactions_Proceed
|
||||
buttonAnimationName = "premium_unlock"
|
||||
@ -1030,6 +1034,10 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
var contentHeight: CGFloat = context.availableSize.width + 146.0
|
||||
if case .other = component.source {
|
||||
contentHeight -= 40.0
|
||||
|
||||
if [.advancedChatManagement, .fasterDownload].contains(component.subject) {
|
||||
contentHeight += 20.0
|
||||
}
|
||||
}
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 20.0), size: button.size)
|
||||
|
@ -159,6 +159,12 @@ public enum PremiumSource: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .fasterDownload:
|
||||
if case .fasterDownload = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,6 +190,7 @@ public enum PremiumSource: Equatable {
|
||||
case gift(from: PeerId, to: PeerId, duration: Int32)
|
||||
case giftTerms
|
||||
case voiceToText
|
||||
case fasterDownload
|
||||
|
||||
var identifier: String? {
|
||||
switch self {
|
||||
@ -225,6 +232,8 @@ public enum PremiumSource: Equatable {
|
||||
return "emoji_status"
|
||||
case .voiceToText:
|
||||
return "voice_to_text"
|
||||
case .fasterDownload:
|
||||
return "faster_download"
|
||||
case .gift, .giftTerms:
|
||||
return nil
|
||||
case let .deeplink(reference):
|
||||
|
@ -15,19 +15,19 @@ import TextFormat
|
||||
|
||||
private final class UsernameSetupControllerArguments {
|
||||
let account: Account
|
||||
|
||||
let updatePublicLinkText: (String?, String) -> Void
|
||||
let shareLink: () -> Void
|
||||
|
||||
let activateLink: (String) -> Void
|
||||
let deactivateLink: (String) -> Void
|
||||
let openAuction: (String) -> Void
|
||||
|
||||
init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) {
|
||||
init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
|
||||
self.account = account
|
||||
self.updatePublicLinkText = updatePublicLinkText
|
||||
self.shareLink = shareLink
|
||||
self.activateLink = activateLink
|
||||
self.deactivateLink = deactivateLink
|
||||
self.openAuction = openAuction
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ private enum UsernameSetupEntryId: Hashable {
|
||||
private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
case publicLinkHeader(PresentationTheme, String)
|
||||
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
|
||||
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String, String)
|
||||
case publicLinkInfo(PresentationTheme, String)
|
||||
|
||||
case additionalLinkHeader(PresentationTheme, String)
|
||||
@ -111,8 +111,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText {
|
||||
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
|
||||
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -208,7 +208,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
arguments.shareLink()
|
||||
}
|
||||
})
|
||||
case let .publicLinkStatus(_, _, status, text):
|
||||
case let .publicLinkStatus(_, _, status, text, username):
|
||||
var displayActivity = false
|
||||
let textColor: ItemListActivityTextItem.TextColor
|
||||
switch status {
|
||||
@ -219,7 +219,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
case .available:
|
||||
textColor = .constructive
|
||||
case .purchaseAvailable:
|
||||
textColor = .generic
|
||||
textColor = .warning
|
||||
case .invalid, .taken:
|
||||
textColor = .destructive
|
||||
}
|
||||
@ -227,7 +227,9 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
|
||||
textColor = .generic
|
||||
displayActivity = true
|
||||
}
|
||||
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section)
|
||||
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
|
||||
arguments.openAuction(username)
|
||||
}, sectionId: self.section)
|
||||
case let .additionalLinkHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .additionalLink(_, link, _):
|
||||
@ -343,7 +345,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
|
||||
case .checking:
|
||||
statusText = presentationData.strings.Username_CheckingUsername
|
||||
}
|
||||
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText))
|
||||
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText, currentUsername))
|
||||
}
|
||||
|
||||
var infoText = presentationData.strings.Username_Help
|
||||
@ -471,6 +473,10 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
|
||||
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: {
|
||||
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
|
||||
})]), nil)
|
||||
}, openAuction: { username in
|
||||
dismissInputImpl?()
|
||||
|
||||
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
|
||||
})
|
||||
|
||||
let temporaryOrder = Promise<[String]?>(nil)
|
||||
@ -680,6 +686,5 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -112,6 +112,10 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatBubbleVerticalLineIncomingImage
|
||||
case chatBubbleVerticalLineOutgoingImage
|
||||
|
||||
case chatBubbleArrowFreeImage
|
||||
case chatBubbleArrowIncomingImage
|
||||
case chatBubbleArrowOutgoingImage
|
||||
|
||||
case chatBubbleCheckBubbleFullImage
|
||||
case chatBubbleBubblePartialImage
|
||||
case checkBubbleMediaFullImage
|
||||
|
@ -122,6 +122,28 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatBubbleArrowImage(color: UIColor) -> UIImage? {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: color)
|
||||
}
|
||||
|
||||
public static func chatBubbleArrowFreeImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleArrowFreeImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: UIColor(white: 1.0, alpha: 0.3))
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatBubbleArrowIncomingImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleArrowIncomingImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: theme.chat.message.incoming.accentTextColor.withAlphaComponent(0.3))
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatBubbleArrowOutgoingImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleArrowOutgoingImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/HeaderArrow"), color: theme.chat.message.outgoing.accentTextColor.withAlphaComponent(0.3))
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatBubbleConsumableContentIncomingIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in
|
||||
return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor)
|
||||
|
@ -224,20 +224,7 @@ public final class EmojiStatusComponent: Component {
|
||||
iconImage = nil
|
||||
}
|
||||
case let .topic(title, color, realSize):
|
||||
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
|
||||
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
|
||||
}
|
||||
|
||||
let topicColors: [Int32: ([UInt32], [UInt32])] = [
|
||||
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
|
||||
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
|
||||
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
|
||||
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
|
||||
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
|
||||
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
|
||||
]
|
||||
let colors = topicColors[color] ?? generateTopicColors(color)
|
||||
|
||||
let colors = topicIconColors(for: color)
|
||||
if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) {
|
||||
iconImage = image
|
||||
} else {
|
||||
@ -575,3 +562,16 @@ public final class EmojiStatusComponent: Component {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public func topicIconColors(for color: Int32) -> ([UInt32], [UInt32]) {
|
||||
let topicColors: [Int32: ([UInt32], [UInt32])] = [
|
||||
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
|
||||
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
|
||||
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
|
||||
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
|
||||
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
|
||||
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
|
||||
]
|
||||
|
||||
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
|
||||
}
|
||||
|
@ -2944,7 +2944,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
case let .topic(title, color):
|
||||
let colors = self.getTopicColors(color)
|
||||
let colors = topicIconColors(for: color)
|
||||
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
|
||||
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
@ -2993,19 +2993,6 @@ public final class EmojiPagerContentComponent: Component {
|
||||
return nullAction
|
||||
}
|
||||
|
||||
private func getTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
|
||||
let topicColors: [Int32: ([UInt32], [UInt32])] = [
|
||||
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
|
||||
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
|
||||
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
|
||||
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
|
||||
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
|
||||
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
|
||||
]
|
||||
|
||||
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
|
||||
}
|
||||
|
||||
func update(content: ItemContent) {
|
||||
if self.content != content {
|
||||
if case let .icon(icon) = content, case let .topic(title, color) = icon {
|
||||
@ -3014,7 +3001,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
let colors = self.getTopicColors(color)
|
||||
let colors = topicIconColors(for: color)
|
||||
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
|
||||
let imageSize = image.size
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
|
@ -430,8 +430,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
|
||||
self.title = ""
|
||||
self.fileId = 0
|
||||
|
||||
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F]
|
||||
self.iconColor = colors.randomElement() ?? 0x0
|
||||
self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
|
||||
case let .edit(info):
|
||||
self.title = info.title
|
||||
self.fileId = info.icon ?? 0
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "iOS Speed.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
150
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/iOS Speed.pdf
vendored
Normal file
150
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Speed.imageset/iOS Speed.pdf
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.005127 2.995117 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.330000 9.000000 m
|
||||
1.330000 13.236024 4.763976 16.670000 9.000000 16.670000 c
|
||||
13.236024 16.670000 16.670000 13.236024 16.670000 9.000000 c
|
||||
16.670000 4.763976 13.236024 1.330000 9.000000 1.330000 c
|
||||
4.763976 1.330000 1.330000 4.763976 1.330000 9.000000 c
|
||||
h
|
||||
9.000000 18.000000 m
|
||||
4.029438 18.000000 0.000000 13.970562 0.000000 9.000000 c
|
||||
0.000000 4.029437 4.029438 -0.000002 9.000000 -0.000002 c
|
||||
13.970563 -0.000002 18.000002 4.029437 18.000002 9.000000 c
|
||||
18.000002 13.970562 13.970563 18.000000 9.000000 18.000000 c
|
||||
h
|
||||
6.012677 4.763371 m
|
||||
5.846505 4.591045 5.641757 4.504883 5.398434 4.504883 c
|
||||
5.161045 4.504883 4.956297 4.591045 4.784191 4.763371 c
|
||||
4.612084 4.941640 4.526031 5.149619 4.526031 5.387310 c
|
||||
4.526031 5.625002 4.612084 5.830010 4.784191 6.002336 c
|
||||
4.956297 6.174662 5.161045 6.260825 5.398434 6.260825 c
|
||||
5.641757 6.260825 5.846505 6.174662 6.012677 6.002336 c
|
||||
6.184784 5.830010 6.270837 5.625002 6.270837 5.387310 c
|
||||
6.270837 5.149619 6.184784 4.941640 6.012677 4.763371 c
|
||||
h
|
||||
4.490422 8.355477 m
|
||||
4.318315 8.183151 4.110600 8.096988 3.867277 8.096988 c
|
||||
3.629888 8.096988 3.425140 8.183151 3.253033 8.355477 c
|
||||
3.080926 8.527802 2.994873 8.732811 2.994873 8.970503 c
|
||||
2.994873 9.214136 3.080926 9.419145 3.253033 9.585527 c
|
||||
3.425140 9.757854 3.629888 9.844017 3.867277 9.844017 c
|
||||
4.110600 9.844017 4.318315 9.757854 4.490422 9.585527 c
|
||||
4.662529 9.419145 4.748582 9.214136 4.748582 8.970503 c
|
||||
4.748582 8.732811 4.662529 8.527802 4.490422 8.355477 c
|
||||
h
|
||||
5.994873 11.938669 m
|
||||
5.828701 11.766342 5.623953 11.680180 5.380630 11.680180 c
|
||||
5.143241 11.680180 4.938493 11.766342 4.766387 11.938669 c
|
||||
4.594280 12.110994 4.508226 12.316004 4.508226 12.553694 c
|
||||
4.508226 12.797327 4.594280 13.002337 4.766387 13.168720 c
|
||||
4.938493 13.341045 5.143241 13.427209 5.380630 13.427209 c
|
||||
5.623953 13.427209 5.828701 13.341045 5.994873 13.168720 c
|
||||
6.166980 13.002337 6.253033 12.797327 6.253033 12.553694 c
|
||||
6.253033 12.316004 6.166980 12.110994 5.994873 11.938669 c
|
||||
h
|
||||
9.609117 13.507429 m
|
||||
9.437010 13.335104 9.232262 13.248940 8.994873 13.248940 c
|
||||
8.751550 13.248940 8.543835 13.335104 8.371728 13.507429 c
|
||||
8.205556 13.679756 8.122470 13.884764 8.122470 14.122455 c
|
||||
8.122470 14.366088 8.205556 14.574068 8.371728 14.746394 c
|
||||
8.543835 14.918720 8.751550 15.004883 8.994873 15.004883 c
|
||||
9.232262 15.004883 9.437010 14.918720 9.609117 14.746394 c
|
||||
9.781223 14.574068 9.867277 14.366088 9.867277 14.122455 c
|
||||
9.867277 13.884764 9.781223 13.679756 9.609117 13.507429 c
|
||||
h
|
||||
14.736713 8.355477 m
|
||||
14.564607 8.183151 14.359859 8.096988 14.122470 8.096988 c
|
||||
13.879147 8.096988 13.671432 8.183151 13.499325 8.355477 c
|
||||
13.333152 8.527802 13.250066 8.732811 13.250066 8.970503 c
|
||||
13.250066 9.214136 13.333152 9.419145 13.499325 9.585527 c
|
||||
13.671432 9.757854 13.879147 9.844017 14.122470 9.844017 c
|
||||
14.359859 9.844017 14.564607 9.757854 14.736713 9.585527 c
|
||||
14.908820 9.419145 14.994873 9.214136 14.994873 8.970503 c
|
||||
14.994873 8.732811 14.908820 8.527802 14.736713 8.355477 c
|
||||
h
|
||||
13.205556 4.763371 m
|
||||
13.033449 4.591045 12.828701 4.504883 12.591312 4.504883 c
|
||||
12.353924 4.504883 12.149176 4.591045 11.977069 4.763371 c
|
||||
11.804962 4.941640 11.718909 5.149619 11.718909 5.387310 c
|
||||
11.718909 5.625002 11.804962 5.830010 11.977069 6.002336 c
|
||||
12.149176 6.174662 12.353924 6.260825 12.591312 6.260825 c
|
||||
12.828701 6.260825 13.033449 6.174662 13.205556 6.002336 c
|
||||
13.377663 5.830010 13.463717 5.625002 13.463717 5.387310 c
|
||||
13.463717 5.149619 13.377663 4.941640 13.205556 4.763371 c
|
||||
h
|
||||
8.362825 7.169993 m
|
||||
8.083894 7.217531 7.822766 7.360146 7.579443 7.597836 c
|
||||
7.342054 7.841470 7.196653 8.102930 7.143241 8.382217 c
|
||||
7.095763 8.667446 7.134339 8.943762 7.258968 9.211164 c
|
||||
7.383597 9.484509 7.585378 9.719229 7.864309 9.915324 c
|
||||
12.769354 13.355902 l
|
||||
12.917722 13.456921 13.057188 13.483660 13.187752 13.436122 c
|
||||
13.318316 13.394526 13.407336 13.308363 13.454814 13.177633 c
|
||||
13.502292 13.046904 13.472618 12.907259 13.365793 12.758703 c
|
||||
9.885081 7.891979 l
|
||||
9.683301 7.612693 9.448879 7.410655 9.181817 7.285868 c
|
||||
8.914755 7.161079 8.641757 7.122455 8.362825 7.169993 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4148
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004238 00000 n
|
||||
0000004261 00000 n
|
||||
0000004434 00000 n
|
||||
0000004508 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4567
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
92
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/arrow.pdf
vendored
Normal file
92
submodules/TelegramUI/Images.xcassets/Chat/Message/HeaderArrow.imageset/arrow.pdf
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 2.000000 -0.459961 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.470226 9.930187 m
|
||||
0.210527 10.189886 -0.210527 10.189886 -0.470226 9.930187 c
|
||||
-0.729925 9.670488 -0.729925 9.249434 -0.470226 8.989735 c
|
||||
0.470226 9.930187 l
|
||||
h
|
||||
4.000000 5.459961 m
|
||||
4.470226 4.989735 l
|
||||
4.729925 5.249434 4.729925 5.670488 4.470226 5.930187 c
|
||||
4.000000 5.459961 l
|
||||
h
|
||||
-0.470226 1.930187 m
|
||||
-0.729925 1.670488 -0.729925 1.249434 -0.470226 0.989735 c
|
||||
-0.210527 0.730036 0.210527 0.730036 0.470226 0.989735 c
|
||||
-0.470226 1.930187 l
|
||||
h
|
||||
-0.470226 8.989735 m
|
||||
3.529774 4.989735 l
|
||||
4.470226 5.930187 l
|
||||
0.470226 9.930187 l
|
||||
-0.470226 8.989735 l
|
||||
h
|
||||
3.529774 5.930187 m
|
||||
-0.470226 1.930187 l
|
||||
0.470226 0.989735 l
|
||||
4.470226 4.989735 l
|
||||
3.529774 5.930187 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
772
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 8.000000 10.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000862 00000 n
|
||||
0000000884 00000 n
|
||||
0000001056 00000 n
|
||||
0000001130 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1189
|
||||
%%EOF
|
@ -1820,6 +1820,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
|
||||
}, navigateToMessageStandalone: { [weak self] id in
|
||||
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
|
||||
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
|
||||
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
|
||||
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
|
||||
}
|
||||
}, tapMessage: nil, clickThroughMessage: { [weak self] in
|
||||
self?.chatDisplayNode.dismissInput()
|
||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||
|
@ -70,6 +70,7 @@ public final class ChatControllerInteraction {
|
||||
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
|
||||
let navigateToMessage: (MessageId, MessageId) -> Void
|
||||
let navigateToMessageStandalone: (MessageId) -> Void
|
||||
let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void
|
||||
let tapMessage: ((Message) -> Void)?
|
||||
let clickThroughMessage: () -> Void
|
||||
let toggleMessagesSelection: ([MessageId], Bool) -> Void
|
||||
@ -178,6 +179,7 @@ public final class ChatControllerInteraction {
|
||||
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
|
||||
navigateToMessage: @escaping (MessageId, MessageId) -> Void,
|
||||
navigateToMessageStandalone: @escaping (MessageId) -> Void,
|
||||
navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void,
|
||||
tapMessage: ((Message) -> Void)?,
|
||||
clickThroughMessage: @escaping () -> Void,
|
||||
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
|
||||
@ -269,6 +271,7 @@ public final class ChatControllerInteraction {
|
||||
self.openMessageContextActions = openMessageContextActions
|
||||
self.navigateToMessage = navigateToMessage
|
||||
self.navigateToMessageStandalone = navigateToMessageStandalone
|
||||
self.navigateToThreadMessage = navigateToThreadMessage
|
||||
self.tapMessage = tapMessage
|
||||
self.clickThroughMessage = clickThroughMessage
|
||||
self.toggleMessagesSelection = toggleMessagesSelection
|
||||
|
@ -614,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
|
||||
let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
|
||||
|
||||
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?), NoError> = combineLatest(
|
||||
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
|
||||
loadLimits,
|
||||
loadStickerSaveStatusSignal,
|
||||
loadResourceStatusSignal,
|
||||
@ -626,9 +626,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
|
||||
context.engine.stickers.availableReactions(),
|
||||
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]),
|
||||
context.engine.peers.notificationSoundList() |> take(1)
|
||||
context.engine.peers.notificationSoundList() |> take(1),
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
)
|
||||
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?) in
|
||||
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
|
||||
let (limitsConfiguration, appConfig) = limitsAndAppConfig
|
||||
var canEdit = false
|
||||
if !isAction {
|
||||
@ -652,12 +653,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
loggingSettings = LoggingSettings.defaultSettings
|
||||
}
|
||||
|
||||
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList)
|
||||
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer)
|
||||
}
|
||||
|
||||
return dataSignal
|
||||
|> deliverOnMainQueue
|
||||
|> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList -> ContextController.Items in
|
||||
|> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer -> ContextController.Items in
|
||||
let isPremium = accountPeer?.isPremium ?? false
|
||||
|
||||
var actions: [ContextMenuItem] = []
|
||||
|
||||
var isPinnedMessages = false
|
||||
@ -794,7 +797,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
|
||||
} else {
|
||||
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
controllerInteraction.displayUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_SaveSuccess_Text, action: {
|
||||
controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil))
|
||||
}))
|
||||
@ -937,6 +940,53 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
var isDownloading = false
|
||||
let resourceAvailable: Bool
|
||||
if let resourceStatus = data.resourceStatus {
|
||||
if case .Local = resourceStatus {
|
||||
resourceAvailable = true
|
||||
} else {
|
||||
resourceAvailable = false
|
||||
}
|
||||
if case .Fetching = resourceStatus {
|
||||
isDownloading = true
|
||||
}
|
||||
} else {
|
||||
resourceAvailable = false
|
||||
}
|
||||
|
||||
|
||||
if !isPremium && isDownloading {
|
||||
var isLargeFile = false
|
||||
for media in message.media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
if let size = file.size, size >= 300 * 1024 * 1024 {
|
||||
isLargeFile = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if isLargeFile {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_IncreaseSpeed, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
let context = context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
actions.append(.separator)
|
||||
}
|
||||
}
|
||||
|
||||
var isReplyThreadHead = false
|
||||
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
|
||||
@ -970,12 +1020,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
})))
|
||||
}
|
||||
|
||||
let resourceAvailable: Bool
|
||||
if let resourceStatus = data.resourceStatus, case .Local = resourceStatus {
|
||||
resourceAvailable = true
|
||||
} else {
|
||||
resourceAvailable = false
|
||||
}
|
||||
|
||||
|
||||
var messageText: String = ""
|
||||
for message in messages {
|
||||
|
@ -58,8 +58,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
|
||||
|
||||
class ChatMessageShareButton: HighlightableButtonNode {
|
||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private var iconOffset = CGPoint()
|
||||
|
||||
@ -242,7 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
|
||||
@ -472,6 +474,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.updateVisibility()
|
||||
self.haptic?.enabled = self.visibilityStatus == true
|
||||
|
||||
self.threadInfoNode?.visibility = self.visibilityStatus == true
|
||||
self.replyInfoNode?.visibility = self.visibilityStatus == true
|
||||
}
|
||||
}
|
||||
@ -789,6 +792,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
|
||||
}
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode {
|
||||
var threadInfoNodeFrame = threadInfoNode.frame
|
||||
threadInfoNodeFrame.origin.x += rect.minX
|
||||
threadInfoNodeFrame.origin.y += rect.minY
|
||||
|
||||
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
|
||||
}
|
||||
|
||||
if let shareButtonNode = self.shareButtonNode {
|
||||
var shareButtonNodeFrame = shareButtonNode.frame
|
||||
shareButtonNodeFrame.origin.x += rect.minX
|
||||
shareButtonNodeFrame.origin.y += rect.minY
|
||||
|
||||
shareButtonNode.updateAbsoluteRect(shareButtonNodeFrame, within: containerSize)
|
||||
}
|
||||
|
||||
if let actionButtonsNode = self.actionButtonsNode {
|
||||
var actionButtonsNodeFrame = actionButtonsNode.frame
|
||||
actionButtonsNodeFrame.origin.x += rect.minX
|
||||
actionButtonsNodeFrame.origin.y += rect.minY
|
||||
|
||||
actionButtonsNode.updateAbsoluteRect(actionButtonsNodeFrame, within: containerSize)
|
||||
}
|
||||
|
||||
if let reactionButtonsNode = self.reactionButtonsNode {
|
||||
var reactionButtonsNodeFrame = reactionButtonsNode.frame
|
||||
reactionButtonsNodeFrame.origin.x += rect.minX
|
||||
@ -796,6 +823,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
|
||||
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
|
||||
}
|
||||
|
||||
if let replyBackgroundContent = self.replyBackgroundContent {
|
||||
var replyBackgroundContentFrame = replyBackgroundContent.frame
|
||||
replyBackgroundContentFrame.origin.x += rect.minX
|
||||
replyBackgroundContentFrame.origin.y += rect.minY
|
||||
|
||||
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -855,6 +890,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
|
||||
|
||||
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
|
||||
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
@ -1124,11 +1160,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
|
||||
var needsReplyBackground = false
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
|
||||
let availableContentWidth = min(120.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left))
|
||||
|
||||
var ignoreForward = false
|
||||
@ -1166,8 +1202,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
|
||||
var hasReply = true
|
||||
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||
} else {
|
||||
hasReply = false
|
||||
} else if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
hasReply = false
|
||||
}
|
||||
|
||||
if case .peer = item.chatLocation, replyMessage.threadId != nil {
|
||||
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
controllerInteraction: item.controllerInteraction,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
}
|
||||
|
||||
if hasReply {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
@ -1425,9 +1483,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyBackgroundNode = replyBackgroundNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
|
||||
}
|
||||
|
||||
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
|
||||
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundContent.clipsToBounds = true
|
||||
strongSelf.replyBackgroundContent = backgroundContent
|
||||
strongSelf.insertSubnode(backgroundContent, at: 0)
|
||||
}
|
||||
} else {
|
||||
strongSelf.replyBackgroundContent?.removeFromSupernode()
|
||||
strongSelf.replyBackgroundContent = nil
|
||||
}
|
||||
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
strongSelf.replyBackgroundNode = nil
|
||||
replyBackgroundNode.removeFromSupernode()
|
||||
|
||||
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
|
||||
replyBackgroundContent.removeFromSupernode()
|
||||
strongSelf.replyBackgroundContent = nil
|
||||
}
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
|
||||
let threadInfoNode = threadInfoApply(synchronousLoads)
|
||||
if strongSelf.threadInfoNode == nil {
|
||||
strongSelf.threadInfoNode = threadInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
|
||||
}
|
||||
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
|
||||
threadInfoNode.frame = threadInfoFrame
|
||||
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
} else if let replyInfoNode = strongSelf.replyInfoNode {
|
||||
replyInfoNode.removeFromSupernode()
|
||||
strongSelf.replyInfoNode = nil
|
||||
}
|
||||
|
||||
var messageInfoSize = CGSize()
|
||||
@ -1447,7 +1537,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.viaBotNode = viaBotNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
|
||||
}
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size)
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
|
||||
viaBotNode.frame = viaBotFrame
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
|
||||
@ -1466,7 +1556,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize)
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
|
||||
forwardInfoNode.frame = forwardInfoFrame
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
|
||||
@ -1490,7 +1580,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
}
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
replyInfoNode.frame = replyInfoFrame
|
||||
|
||||
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
|
||||
@ -1500,13 +1590,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
|
||||
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
|
||||
|
||||
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
|
||||
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
|
||||
|
||||
if let backgroundContent = strongSelf.replyBackgroundContent {
|
||||
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
|
||||
|
||||
replyBackgroundNode.isHidden = true
|
||||
backgroundContent.cornerRadius = cornerRadius
|
||||
backgroundContent.frame = replyBackgroundNode.frame
|
||||
if let (rect, containerSize) = strongSelf.absoluteRect {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
replyBackgroundNode.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
|
||||
strongSelf.threadInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.replyInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.viaBotNode?.alpha = panelsAlpha
|
||||
strongSelf.forwardInfoNode?.alpha = panelsAlpha
|
||||
|
@ -510,6 +510,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var forwardInfoReferenceNode: ASDisplayNode? {
|
||||
return self.forwardInfoNode
|
||||
}
|
||||
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
|
||||
private var contentContainersWrapperNode: ASDisplayNode
|
||||
@ -547,6 +549,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
contentNode.visibility = mapVisibility(self.visibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode)
|
||||
}
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode {
|
||||
threadInfoNode.visibility = self.visibility != .none
|
||||
}
|
||||
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
replyInfoNode.visibility = self.visibility != .none
|
||||
}
|
||||
@ -932,6 +938,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
if let threadInfoNode = strongSelf.threadInfoNode, threadInfoNode.frame.contains(point) {
|
||||
if let _ = threadInfoNode.hitTest(strongSelf.view.convert(point, to: threadInfoNode.view), with: nil) {
|
||||
return .fail
|
||||
}
|
||||
}
|
||||
if let replyInfoNode = strongSelf.replyInfoNode, replyInfoNode.frame.contains(point) {
|
||||
return .waitForSingleTap
|
||||
}
|
||||
@ -1054,6 +1065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
let authorNameLayout = TextNode.asyncLayout(self.nameNode)
|
||||
let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode)
|
||||
let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
|
||||
let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
|
||||
@ -1076,6 +1088,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
|
||||
authorNameLayout: authorNameLayout,
|
||||
adminBadgeLayout: adminBadgeLayout,
|
||||
threadInfoLayout: threadInfoLayout,
|
||||
forwardInfoLayout: forwardInfoLayout,
|
||||
replyInfoLayout: replyInfoLayout,
|
||||
actionButtonsLayout: actionButtonsLayout,
|
||||
@ -1093,6 +1106,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
|
||||
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
|
||||
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode),
|
||||
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
|
||||
@ -1834,6 +1848,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var nameNodeOriginY: CGFloat = 0.0
|
||||
var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
|
||||
var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
|
||||
|
||||
var threadInfoOriginY: CGFloat = 0.0
|
||||
var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
|
||||
|
||||
var replyInfoOriginY: CGFloat = 0.0
|
||||
var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil })
|
||||
@ -1945,7 +1962,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
headerSize.height += forwardInfoSizeApply.0.height
|
||||
}
|
||||
|
||||
if !isInstantVideo, let replyMessage = replyMessage {
|
||||
var hasReply = replyMessage != nil
|
||||
if !isInstantVideo, let replyMessage = replyMessage, replyMessage.threadId != nil, case .peer = item.chatLocation {
|
||||
if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
hasReply = false
|
||||
}
|
||||
|
||||
if headerSize.height.isZero {
|
||||
headerSize.height += 14.0
|
||||
} else {
|
||||
headerSize.height += 5.0
|
||||
}
|
||||
let sizeAndApply = threadInfoLayout(ChatMessageThreadInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
controllerInteraction: item.controllerInteraction,
|
||||
type: .bubble(incoming: incoming),
|
||||
message: replyMessage,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
threadInfoSizeApply = (sizeAndApply.0, { synchronousLoads in sizeAndApply.1(synchronousLoads) })
|
||||
|
||||
threadInfoOriginY = headerSize.height
|
||||
headerSize.width = max(headerSize.width, threadInfoSizeApply.0.width + layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right)
|
||||
headerSize.height += threadInfoSizeApply.0.height + 5.0
|
||||
}
|
||||
|
||||
if !isInstantVideo, let replyMessage = replyMessage, hasReply {
|
||||
if headerSize.height.isZero {
|
||||
headerSize.height += 6.0
|
||||
} else {
|
||||
@ -2401,6 +2448,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentCredibilityIcon: currentCredibilityIcon,
|
||||
adminNodeSizeApply: adminNodeSizeApply,
|
||||
contentUpperRightCorner: contentUpperRightCorner,
|
||||
threadInfoSizeApply: threadInfoSizeApply,
|
||||
threadInfoOriginY: threadInfoOriginY,
|
||||
forwardInfoSizeApply: forwardInfoSizeApply,
|
||||
forwardInfoOriginY: forwardInfoOriginY,
|
||||
replyInfoSizeApply: replyInfoSizeApply,
|
||||
@ -2449,6 +2498,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
currentCredibilityIcon: EmojiStatusComponent.Content?,
|
||||
adminNodeSizeApply: (CGSize, () -> TextNode?),
|
||||
contentUpperRightCorner: CGPoint,
|
||||
threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?),
|
||||
threadInfoOriginY: CGFloat,
|
||||
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
|
||||
forwardInfoOriginY: CGFloat,
|
||||
replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?),
|
||||
@ -2721,6 +2772,40 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
|
||||
if let threadInfoNode = threadInfoSizeApply.1(synchronousLoads) {
|
||||
strongSelf.threadInfoNode = threadInfoNode
|
||||
var animateFrame = true
|
||||
if threadInfoNode.supernode == nil {
|
||||
strongSelf.clippingNode.addSubnode(threadInfoNode)
|
||||
animateFrame = false
|
||||
|
||||
threadInfoNode.visibility = strongSelf.visibility != .none
|
||||
|
||||
if animation.isAnimated {
|
||||
threadInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
let previousThreadInfoNodeFrame = threadInfoNode.frame
|
||||
threadInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + threadInfoOriginY), size: threadInfoSizeApply.0)
|
||||
if case let .System(duration, _) = animation {
|
||||
if animateFrame {
|
||||
threadInfoNode.layer.animateFrame(from: previousThreadInfoNodeFrame, to: threadInfoNode.frame, duration: duration, timingFunction: timingFunction)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if animation.isAnimated {
|
||||
if let threadInfoNode = strongSelf.threadInfoNode {
|
||||
strongSelf.threadInfoNode = nil
|
||||
threadInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak threadInfoNode] _ in
|
||||
threadInfoNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
strongSelf.threadInfoNode?.removeFromSupernode()
|
||||
strongSelf.threadInfoNode = nil
|
||||
}
|
||||
}
|
||||
|
||||
if let replyInfoNode = replyInfoSizeApply.1(synchronousLoads) {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
var animateFrame = true
|
||||
@ -3498,6 +3583,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
|
||||
if let item = self.item {
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? ReplyMessageAttribute, let threadId = attribute.threadMessageId {
|
||||
return .optionalAction({
|
||||
item.controllerInteraction.navigateToThreadMessage(item.message.id.peerId, Int64(clamping: threadId.id), item.message.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
|
||||
if let item = self.item, let forwardInfo = item.message.forwardInfo {
|
||||
@ -3652,6 +3747,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if let item = self.item, self.backgroundNode.frame.contains(location) {
|
||||
let message = item.message
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
|
||||
return .action({})
|
||||
}
|
||||
|
||||
var tapMessage: Message? = item.content.firstMessage
|
||||
var selectAll = true
|
||||
var hasFiles = false
|
||||
@ -3773,6 +3872,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
return nil
|
||||
}
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
|
||||
return shareButtonNode.view
|
||||
}
|
||||
@ -3803,7 +3906,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
|
@ -495,7 +495,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
return
|
||||
}
|
||||
var messageId: MessageId?
|
||||
if let messageReference = messageReference, case let .message(_, id, _, _, _) = messageReference.content {
|
||||
if let messageReference = messageReference, case let .message(_, _, id, _, _, _) = messageReference.content {
|
||||
messageId = id
|
||||
}
|
||||
strongSelf.controllerInteraction.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture)
|
||||
@ -641,7 +641,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
|
||||
|
||||
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content {
|
||||
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _) = self.messageReference?.content {
|
||||
self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
|
||||
} else if let peer = self.peer {
|
||||
if let adMessageId = self.adMessageId {
|
||||
|
@ -112,12 +112,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
|
||||
|
||||
if let threadId = arguments.parentMessage.threadId, Int64(arguments.message.id.id) == threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
|
||||
titleString = "\(threadInfo.title)"
|
||||
textString = NSAttributedString()
|
||||
}
|
||||
let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
|
||||
|
||||
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||
let titleColor: UIColor
|
||||
|
@ -38,7 +38,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
private var viaBotNode: TextNode?
|
||||
private let dateAndStatusNode: ChatMessageDateAndStatusNode
|
||||
private var threadInfoNode: ChatMessageThreadInfoNode?
|
||||
private var replyInfoNode: ChatMessageReplyInfoNode?
|
||||
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var replyBackgroundNode: NavigationBackgroundNode?
|
||||
private var forwardInfoNode: ChatMessageForwardInfoNode?
|
||||
|
||||
@ -71,6 +73,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
private var visibilityStatus: Bool? {
|
||||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
self.threadInfoNode?.visibility = self.visibilityStatus == true
|
||||
self.replyInfoNode?.visibility = self.visibilityStatus == true
|
||||
}
|
||||
}
|
||||
@ -277,6 +280,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
|
||||
}
|
||||
|
||||
if let threadInfoNode = self.threadInfoNode {
|
||||
var threadInfoNodeFrame = threadInfoNode.frame
|
||||
threadInfoNodeFrame.origin.x += rect.minX
|
||||
threadInfoNodeFrame.origin.y += rect.minY
|
||||
|
||||
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
|
||||
}
|
||||
|
||||
if let shareButtonNode = self.shareButtonNode {
|
||||
var shareButtonNodeFrame = shareButtonNode.frame
|
||||
shareButtonNodeFrame.origin.x += rect.minX
|
||||
@ -300,6 +311,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
|
||||
}
|
||||
|
||||
if let replyBackgroundContent = self.replyBackgroundContent {
|
||||
var replyBackgroundContentFrame = replyBackgroundContent.frame
|
||||
replyBackgroundContentFrame.origin.x += rect.minX
|
||||
replyBackgroundContentFrame.origin.y += rect.minY
|
||||
|
||||
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,6 +376,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
|
||||
|
||||
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
|
||||
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
|
||||
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
|
||||
let currentShareButtonNode = self.shareButtonNode
|
||||
let currentForwardInfo = self.appliedForwardInfo
|
||||
@ -567,6 +587,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
|
||||
|
||||
var viaBotApply: (TextNodeLayout, () -> TextNode)?
|
||||
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
|
||||
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
|
||||
var replyMarkup: ReplyMarkupMessageAttribute?
|
||||
|
||||
@ -610,8 +631,30 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
|
||||
var hasReply = true
|
||||
|
||||
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
|
||||
} else {
|
||||
hasReply = false
|
||||
} else if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
hasReply = false
|
||||
}
|
||||
|
||||
if case .peer = item.chatLocation, replyMessage.threadId != nil {
|
||||
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
context: item.context,
|
||||
controllerInteraction: item.controllerInteraction,
|
||||
type: .standalone,
|
||||
message: replyMessage,
|
||||
parentMessage: item.message,
|
||||
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
animationCache: item.controllerInteraction.presentationContext.animationCache,
|
||||
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
|
||||
))
|
||||
}
|
||||
|
||||
if hasReply {
|
||||
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
@ -764,9 +807,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
if let (threadInfoSize, _) = threadInfoApply {
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
}
|
||||
|
||||
var viaBotFrame: CGRect?
|
||||
if let (viaBotLayout, _) = viaBotApply {
|
||||
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size)
|
||||
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
|
||||
}
|
||||
|
||||
var replyInfoFrame: CGRect?
|
||||
@ -775,7 +823,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
if let viaBotFrame = viaBotFrame {
|
||||
viaBotSize = viaBotFrame.size
|
||||
}
|
||||
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize)
|
||||
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: headersOffset + 8.0 + viaBotSize.height), size: replyInfoSize)
|
||||
replyInfoFrame = replyInfoFrameValue
|
||||
if let viaBotFrameValue = viaBotFrame {
|
||||
if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
|
||||
@ -791,7 +839,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
viaBotSize = viaBotFrame.size
|
||||
}
|
||||
|
||||
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
|
||||
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: headersOffset + replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
|
||||
}
|
||||
|
||||
if let replyBackgroundFrameValue = replyBackgroundFrame {
|
||||
@ -875,9 +923,41 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyBackgroundNode = replyBackgroundNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
|
||||
}
|
||||
|
||||
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
|
||||
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundContent.clipsToBounds = true
|
||||
strongSelf.replyBackgroundContent = backgroundContent
|
||||
strongSelf.insertSubnode(backgroundContent, at: 0)
|
||||
}
|
||||
} else {
|
||||
strongSelf.replyBackgroundContent?.removeFromSupernode()
|
||||
strongSelf.replyBackgroundContent = nil
|
||||
}
|
||||
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
replyBackgroundNode.removeFromSupernode()
|
||||
strongSelf.replyBackgroundNode = nil
|
||||
|
||||
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
|
||||
replyBackgroundContent.removeFromSupernode()
|
||||
strongSelf.replyBackgroundContent = nil
|
||||
}
|
||||
}
|
||||
|
||||
var headersOffset: CGFloat = 0.0
|
||||
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
|
||||
let threadInfoNode = threadInfoApply(synchronousLoads)
|
||||
if strongSelf.threadInfoNode == nil {
|
||||
strongSelf.threadInfoNode = threadInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
|
||||
}
|
||||
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
|
||||
threadInfoNode.frame = threadInfoFrame
|
||||
|
||||
headersOffset += threadInfoSize.height + 10.0
|
||||
} else if let replyInfoNode = strongSelf.replyInfoNode {
|
||||
replyInfoNode.removeFromSupernode()
|
||||
strongSelf.replyInfoNode = nil
|
||||
}
|
||||
|
||||
var messageInfoSize = CGSize()
|
||||
@ -897,7 +977,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.viaBotNode = viaBotNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
|
||||
}
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size)
|
||||
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
|
||||
viaBotNode.frame = viaBotFrame
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
|
||||
@ -916,7 +996,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize)
|
||||
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
|
||||
forwardInfoNode.frame = forwardInfoFrame
|
||||
|
||||
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
|
||||
@ -940,7 +1020,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = replyInfoNode
|
||||
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
}
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
|
||||
replyInfoNode.frame = replyInfoFrame
|
||||
|
||||
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
|
||||
@ -949,15 +1029,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
strongSelf.replyInfoNode = nil
|
||||
}
|
||||
|
||||
|
||||
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
|
||||
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
|
||||
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
|
||||
|
||||
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
|
||||
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
|
||||
|
||||
if let backgroundContent = strongSelf.replyBackgroundContent {
|
||||
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
|
||||
|
||||
replyBackgroundNode.isHidden = true
|
||||
backgroundContent.cornerRadius = cornerRadius
|
||||
backgroundContent.frame = replyBackgroundNode.frame
|
||||
if let (rect, containerSize) = strongSelf.absoluteRect {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
replyBackgroundNode.isHidden = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
|
||||
strongSelf.threadInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.replyInfoNode?.alpha = panelsAlpha
|
||||
strongSelf.viaBotNode?.alpha = panelsAlpha
|
||||
strongSelf.forwardInfoNode?.alpha = panelsAlpha
|
||||
|
505
submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift
Normal file
505
submodules/TelegramUI/Sources/ChatMessageThreadInfoNode.swift
Normal file
@ -0,0 +1,505 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import LocalizedPeerData
|
||||
import PhotoResources
|
||||
import TelegramStringFormatting
|
||||
import TextFormat
|
||||
import InvisibleInkDustNode
|
||||
import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import ComponentFlow
|
||||
import EmojiStatusComponent
|
||||
import WallpaperBackgroundNode
|
||||
|
||||
private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) {
|
||||
enum CornerType {
|
||||
case topLeft
|
||||
case topRight
|
||||
case bottomLeft
|
||||
case bottomRight
|
||||
}
|
||||
|
||||
func drawFullCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) {
|
||||
if radius.isZero {
|
||||
return
|
||||
}
|
||||
context.setFillColor(color.cgColor)
|
||||
switch type {
|
||||
case .topLeft:
|
||||
context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius)))
|
||||
context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .topRight:
|
||||
context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .bottomLeft:
|
||||
context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .bottomRight:
|
||||
context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
}
|
||||
}
|
||||
|
||||
func drawConnectingCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) {
|
||||
context.setFillColor(color.cgColor)
|
||||
switch type {
|
||||
case .topLeft:
|
||||
context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius)))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .topRight:
|
||||
context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius)))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .bottomLeft:
|
||||
context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius)))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
case .bottomRight:
|
||||
context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius)))
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0)))
|
||||
}
|
||||
}
|
||||
|
||||
if rects.isEmpty {
|
||||
return (CGPoint(), nil)
|
||||
}
|
||||
|
||||
var topLeft = rects[0].origin
|
||||
var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY)
|
||||
for i in 1 ..< rects.count {
|
||||
topLeft.x = min(topLeft.x, rects[i].origin.x)
|
||||
topLeft.y = min(topLeft.y, rects[i].origin.y)
|
||||
bottomRight.x = max(bottomRight.x, rects[i].maxX)
|
||||
bottomRight.y = max(bottomRight.y, rects[i].maxY)
|
||||
}
|
||||
|
||||
topLeft.x -= inset
|
||||
topLeft.y -= inset
|
||||
bottomRight.x += inset * 2.0
|
||||
bottomRight.y += inset * 2.0
|
||||
|
||||
return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
for i in 0 ..< rects.count {
|
||||
let rect = rects[i].insetBy(dx: -inset, dy: -inset)
|
||||
context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y))
|
||||
}
|
||||
|
||||
for i in 0 ..< rects.count {
|
||||
let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
|
||||
|
||||
var previous: CGRect?
|
||||
if i != 0 {
|
||||
previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
|
||||
}
|
||||
|
||||
var next: CGRect?
|
||||
if i != rects.count - 1 {
|
||||
next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y)
|
||||
}
|
||||
|
||||
if let previous = previous {
|
||||
if previous.contains(rect.topLeft) {
|
||||
if abs(rect.topLeft.x - previous.minX) >= innerRadius {
|
||||
var radius = innerRadius
|
||||
if let next = next {
|
||||
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
|
||||
}
|
||||
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius)
|
||||
}
|
||||
if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) {
|
||||
if abs(rect.topRight.x - previous.maxX) >= innerRadius {
|
||||
var radius = innerRadius
|
||||
if let next = next {
|
||||
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
|
||||
}
|
||||
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius)
|
||||
drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius)
|
||||
}
|
||||
|
||||
if let next = next {
|
||||
if next.contains(rect.bottomLeft) {
|
||||
if abs(rect.bottomRight.x - next.maxX) >= innerRadius {
|
||||
var radius = innerRadius
|
||||
if let previous = previous {
|
||||
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
|
||||
}
|
||||
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius)
|
||||
}
|
||||
if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) {
|
||||
if abs(rect.bottomRight.x - next.maxX) >= innerRadius {
|
||||
var radius = innerRadius
|
||||
if let previous = previous {
|
||||
radius = min(radius, floor((next.minY - previous.maxY) / 2.0))
|
||||
}
|
||||
drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius)
|
||||
}
|
||||
} else {
|
||||
drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius)
|
||||
drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
enum ChatMessageThreadInfoType {
|
||||
case bubble(incoming: Bool)
|
||||
case standalone
|
||||
}
|
||||
|
||||
class ChatMessageThreadInfoNode: ASDisplayNode {
|
||||
class Arguments {
|
||||
let presentationData: ChatPresentationData
|
||||
let strings: PresentationStrings
|
||||
let context: AccountContext
|
||||
let controllerInteraction: ChatControllerInteraction
|
||||
let type: ChatMessageThreadInfoType
|
||||
let message: Message
|
||||
let parentMessage: Message
|
||||
let constrainedSize: CGSize
|
||||
let animationCache: AnimationCache?
|
||||
let animationRenderer: MultiAnimationRenderer?
|
||||
|
||||
init(
|
||||
presentationData: ChatPresentationData,
|
||||
strings: PresentationStrings,
|
||||
context: AccountContext,
|
||||
controllerInteraction: ChatControllerInteraction,
|
||||
type: ChatMessageThreadInfoType,
|
||||
message: Message,
|
||||
parentMessage: Message,
|
||||
constrainedSize: CGSize,
|
||||
animationCache: AnimationCache?,
|
||||
animationRenderer: MultiAnimationRenderer?
|
||||
) {
|
||||
self.presentationData = presentationData
|
||||
self.strings = strings
|
||||
self.context = context
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.type = type
|
||||
self.message = message
|
||||
self.parentMessage = parentMessage
|
||||
self.constrainedSize = constrainedSize
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
}
|
||||
}
|
||||
|
||||
var visibility: Bool = false {
|
||||
didSet {
|
||||
if self.visibility != oldValue {
|
||||
self.textNode?.visibilityRect = self.visibility ? CGRect.infinite : nil
|
||||
|
||||
if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent {
|
||||
let _ = titleTopicIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibility)),
|
||||
environment: {},
|
||||
containerSize: titleTopicIconView.bounds.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var backgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private var backgroundNode: NavigationBackgroundNode?
|
||||
|
||||
private let contentNode: HighlightTrackingButtonNode
|
||||
private let contentBackgroundNode: ASImageNode
|
||||
private var textNode: TextNodeWithEntities?
|
||||
private let arrowNode: ASImageNode
|
||||
|
||||
private var titleTopicIconView: ComponentHostView<Empty>?
|
||||
private var titleTopicIconComponent: EmojiStatusComponent?
|
||||
|
||||
private var lineRects: [CGRect] = []
|
||||
|
||||
private var pressed = { }
|
||||
|
||||
private var absolutePosition: (CGRect, CGSize)?
|
||||
|
||||
override init() {
|
||||
self.contentNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.contentBackgroundNode = ASImageNode()
|
||||
self.contentBackgroundNode.alpha = 0.1
|
||||
self.contentBackgroundNode.displaysAsynchronously = false
|
||||
self.contentBackgroundNode.displayWithoutProcessing = true
|
||||
self.contentBackgroundNode.isLayerBacked = true
|
||||
self.contentBackgroundNode.isUserInteractionEnabled = false
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
self.arrowNode.isLayerBacked = true
|
||||
self.arrowNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.contentNode.isUserInteractionEnabled = true
|
||||
|
||||
self.addSubnode(self.contentNode)
|
||||
self.contentNode.addSubnode(self.contentBackgroundNode)
|
||||
self.contentNode.addSubnode(self.arrowNode)
|
||||
|
||||
self.contentNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.contentNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.15, removeOnCompletion: false)
|
||||
|
||||
strongSelf.contentBackgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.contentBackgroundNode.alpha = 0.2
|
||||
} else if let presentationLayer = strongSelf.contentNode.layer.presentation() {
|
||||
strongSelf.contentNode.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
strongSelf.contentBackgroundNode.alpha = 0.1
|
||||
strongSelf.contentBackgroundNode.layer.animateAlpha(from: 0.2, to: 0.1, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.contentNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.absolutePosition = (rect, containerSize)
|
||||
if let backgroundContent = self.backgroundContent {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
class func asyncLayout(_ maybeNode: ChatMessageThreadInfoNode?) -> (_ arguments: Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode) {
|
||||
let textNodeLayout = TextNodeWithEntities.asyncLayout(maybeNode?.textNode)
|
||||
|
||||
return { arguments in
|
||||
let fontSize = floor(arguments.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)
|
||||
let textFont = Font.medium(fontSize)
|
||||
|
||||
var topicTitle = ""
|
||||
var topicIconId: Int64?
|
||||
var topicIconColor: Int32 = 0
|
||||
if let _ = arguments.parentMessage.threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
|
||||
|
||||
topicTitle = threadInfo.title
|
||||
topicIconId = threadInfo.icon
|
||||
topicIconColor = threadInfo.iconColor
|
||||
}
|
||||
|
||||
let backgroundColor: UIColor
|
||||
let textColor: UIColor
|
||||
let arrowIcon: UIImage?
|
||||
switch arguments.type {
|
||||
case let .bubble(incoming):
|
||||
if topicIconId == nil, topicIconColor != 0, incoming {
|
||||
let colors = topicIconColors(for: topicIconColor)
|
||||
backgroundColor = UIColor(rgb: colors.0.last ?? 0x000000)
|
||||
textColor = UIColor(rgb: colors.1.first ?? 0x000000)
|
||||
arrowIcon = PresentationResourcesChat.chatBubbleArrowImage(color: textColor)
|
||||
} else {
|
||||
backgroundColor = (incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor)
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
arrowIcon = incoming ? PresentationResourcesChat.chatBubbleArrowIncomingImage(arguments.presentationData.theme.theme) : PresentationResourcesChat.chatBubbleArrowOutgoingImage(arguments.presentationData.theme.theme)
|
||||
}
|
||||
case .standalone:
|
||||
textColor = .white
|
||||
backgroundColor = .white
|
||||
arrowIcon = PresentationResourcesChat.chatBubbleArrowFreeImage(arguments.presentationData.theme.theme)
|
||||
}
|
||||
|
||||
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
|
||||
|
||||
let text = NSAttributedString(string: topicTitle, font: textFont, textColor: textColor)
|
||||
|
||||
let lineInset: CGFloat = 7.0
|
||||
let iconSize = CGSize(width: 22.0, height: 22.0)
|
||||
let insets = UIEdgeInsets(top: 2.0, left: 4.0, bottom: 2.0, right: 4.0)
|
||||
let spacing: CGFloat = 4.0
|
||||
|
||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: arguments.constrainedSize.width - insets.left - insets.right - iconSize.width - spacing, height: arguments.constrainedSize.height), alignment: .natural, cutout: nil, insets: .zero))
|
||||
|
||||
var lineRects = textLayout.linesRects().map { rect in
|
||||
return CGRect(origin: rect.origin.offsetBy(dx: insets.left, dy: 0.0), size: CGSize(width: rect.width + iconSize.width + spacing + 3.0, height: rect.size.height))
|
||||
}
|
||||
let lastRect = lineRects[lineRects.count - 1]
|
||||
lineRects[lineRects.count - 1] = CGRect(origin: lastRect.origin, size: CGSize(width: lastRect.width + 11.0, height: lastRect.height))
|
||||
|
||||
let size = CGSize(width: insets.left + iconSize.width + spacing + textLayout.size.width + insets.right + lineInset * 2.0, height: insets.top + textLayout.size.height + insets.bottom)
|
||||
|
||||
return (size, { attemptSynchronous in
|
||||
let node: ChatMessageThreadInfoNode
|
||||
if let maybeNode = maybeNode {
|
||||
node = maybeNode
|
||||
} else {
|
||||
node = ChatMessageThreadInfoNode()
|
||||
}
|
||||
|
||||
node.pressed = {
|
||||
if let threadId = arguments.message.threadId {
|
||||
arguments.controllerInteraction.navigateToThreadMessage(arguments.parentMessage.id.peerId, threadId, arguments.parentMessage.id)
|
||||
}
|
||||
}
|
||||
|
||||
if node.lineRects != lineRects {
|
||||
let (offset, image) = generateRectsImage(color: backgroundColor, rects: lineRects, inset: 5.0, outerRadius: 13.0, innerRadius: 8.0)
|
||||
if let image = image {
|
||||
if case .standalone = arguments.type {
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -3.0), size: CGSize(width: size.width + 5.0, height: size.height + 10.0))
|
||||
|
||||
if arguments.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
|
||||
if node.backgroundContent == nil, let backgroundContent = arguments.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
backgroundContent.clipsToBounds = true
|
||||
backgroundContent.isUserInteractionEnabled = false
|
||||
node.backgroundContent = backgroundContent
|
||||
node.contentNode.insertSubnode(backgroundContent, at: 0)
|
||||
|
||||
let backgroundMask = UIImageView(image: image)
|
||||
backgroundContent.view.mask = backgroundMask
|
||||
}
|
||||
|
||||
if let backgroundContent = node.backgroundContent {
|
||||
backgroundContent.view.mask?.bounds = CGRect(origin: .zero, size: image.size)
|
||||
(backgroundContent.view.mask as? UIImageView)?.image = image
|
||||
|
||||
backgroundContent.frame = backgroundFrame
|
||||
if let (rect, containerSize) = node.absolutePosition {
|
||||
var backgroundFrame = backgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node.backgroundContent?.removeFromSupernode()
|
||||
node.backgroundContent = nil
|
||||
|
||||
let backgroundNode: NavigationBackgroundNode
|
||||
if let current = node.backgroundNode {
|
||||
backgroundNode = current
|
||||
} else {
|
||||
backgroundNode = NavigationBackgroundNode(color: .clear)
|
||||
backgroundNode.isUserInteractionEnabled = false
|
||||
node.backgroundNode = backgroundNode
|
||||
node.contentNode.insertSubnode(backgroundNode, at: 0)
|
||||
|
||||
let backgroundMask = UIImageView(image: image)
|
||||
backgroundNode.view.mask = backgroundMask
|
||||
}
|
||||
|
||||
backgroundNode.view.mask?.bounds = CGRect(origin: .zero, size: image.size)
|
||||
(backgroundNode.view.mask as? UIImageView)?.image = image
|
||||
|
||||
backgroundNode.frame = backgroundFrame
|
||||
backgroundNode.update(size: backgroundNode.bounds.size, cornerRadius: 0.0, transition: .immediate)
|
||||
backgroundNode.updateColor(color: selectDateFillStaticColor(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper), transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
node.contentBackgroundNode.frame = CGRect(origin: offset.offsetBy(dx: 0.0, dy: -11.0), size: image.size)
|
||||
node.contentBackgroundNode.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.textNode?.textNode.displaysAsynchronously = !arguments.presentationData.isPreview
|
||||
|
||||
var textArguments: TextNodeWithEntities.Arguments?
|
||||
if let cache = arguments.animationCache, let renderer = arguments.animationRenderer {
|
||||
textArguments = TextNodeWithEntities.Arguments(context: arguments.context, cache: cache, renderer: renderer, placeholderColor: placeholderColor, attemptSynchronous: attemptSynchronous)
|
||||
}
|
||||
let textNode = textApply(textArguments)
|
||||
textNode.visibilityRect = node.visibility ? CGRect.infinite : nil
|
||||
|
||||
if node.textNode == nil {
|
||||
textNode.textNode.isUserInteractionEnabled = false
|
||||
node.textNode = textNode
|
||||
node.contentNode.addSubnode(textNode.textNode)
|
||||
}
|
||||
|
||||
let titleTopicIconView: ComponentHostView<Empty>
|
||||
if let current = node.titleTopicIconView {
|
||||
titleTopicIconView = current
|
||||
} else {
|
||||
titleTopicIconView = ComponentHostView<Empty>()
|
||||
node.titleTopicIconView = titleTopicIconView
|
||||
node.contentNode.view.addSubview(titleTopicIconView)
|
||||
}
|
||||
|
||||
let titleTopicIconContent: EmojiStatusComponent.Content
|
||||
if let fileId = topicIconId, fileId != 0 {
|
||||
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: arguments.presentationData.theme.theme.list.mediaPlaceholderColor, themeColor: arguments.presentationData.theme.theme.list.itemAccentColor, loopMode: .count(1))
|
||||
} else {
|
||||
titleTopicIconContent = .topic(title: String(topicTitle.prefix(1)), color: topicIconColor, size: CGSize(width: 22.0, height: 22.0))
|
||||
}
|
||||
|
||||
if let animationCache = arguments.animationCache, let animationRenderer = arguments.animationRenderer {
|
||||
let titleTopicIconComponent = EmojiStatusComponent(
|
||||
context: arguments.context,
|
||||
animationCache: animationCache,
|
||||
animationRenderer: animationRenderer,
|
||||
content: titleTopicIconContent,
|
||||
isVisibleForAnimations: node.visibility,
|
||||
action: nil
|
||||
)
|
||||
node.titleTopicIconComponent = titleTopicIconComponent
|
||||
|
||||
let iconSize = titleTopicIconView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(titleTopicIconComponent),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 22.0, height: 22.0)
|
||||
)
|
||||
titleTopicIconView.frame = CGRect(origin: CGPoint(x: insets.left, y: 0.0), size: iconSize)
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: iconSize.width + 2.0 + insets.left, y: insets.top), size: textLayout.size)
|
||||
textNode.textNode.frame = textFrame
|
||||
|
||||
if let arrowIcon = arrowIcon, let lastRect = lineRects.last {
|
||||
node.arrowNode.image = arrowIcon
|
||||
node.arrowNode.frame = CGRect(origin: CGPoint(x: lastRect.maxX - arrowIcon.size.width - 1.0, y: floorToScreenPixels(lastRect.midY - arrowIcon.size.height / 2.0) - 11.0 + UIScreenPixel), size: arrowIcon.size)
|
||||
}
|
||||
|
||||
node.contentNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
return node
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -263,6 +263,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
|
||||
self?.openUrl(url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
|
@ -112,6 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
|
||||
}, presentController: { _, _ in
|
||||
}, presentControllerInCurrent: { _, _ in
|
||||
|
@ -76,6 +76,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
}, toggleMessagesSelection: { _, _ in
|
||||
}, sendCurrentMessage: { _ in
|
||||
|
@ -2431,6 +2431,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
})
|
||||
}, navigateToMessage: { fromId, id in
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
}, toggleMessagesSelection: { [weak self] ids, value in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -173,16 +173,16 @@ private final class PrefetchManagerInnerImpl {
|
||||
|
||||
if case .full = automaticDownload {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
||||
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
|
||||
} else if let _ = media as? TelegramMediaWebFile {
|
||||
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
||||
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
|
||||
context.fetchDisposable.set(fetchSignal.start())
|
||||
}
|
||||
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
if let file = media as? TelegramMediaFile, let _ = file.size {
|
||||
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
||||
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1293,6 +1293,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
|
||||
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { _, _, _ in
|
||||
}, tapMessage: { message in
|
||||
tapMessage?(message)
|
||||
}, clickThroughMessage: {
|
||||
@ -1523,6 +1524,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
|
||||
case .voiceToText:
|
||||
mappedSource = .voiceToText
|
||||
case .fasterDownload:
|
||||
mappedSource = .fasterDownload
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user