Various improvements

This commit is contained in:
Ilya Laktyushin 2022-11-09 01:30:17 +04:00
parent 3206796f7c
commit d66e1db6cc
38 changed files with 1737 additions and 184 deletions

View File

@ -7585,9 +7585,7 @@ Sorry for the inconvenience.";
"Premium.Stickers.Proceed" = "Unlock Premium Stickers"; "Premium.Stickers.Proceed" = "Unlock Premium Stickers";
"Premium.Reactions.Proceed" = "Unlock Premium Reactions"; "Premium.Reactions.Proceed" = "Unlock Premium Reactions";
"Premium.AppIcons.Proceed" = "Unlock Premium Icons"; "Premium.AppIcons.Proceed" = "Unlock Premium Icons";
"Premium.NoAds.Proceed" = "About Telegram Premium"; "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."; "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.SearchTopicIconsPlaceholder" = "Search Topic Icons";
"EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found"; "EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found";
"Username.UsernamePurchaseAvailable" = "Sorry, this username 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 through official @auction."; "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";

View File

@ -836,6 +836,7 @@ public enum PremiumIntroSource {
case profile(PeerId) case profile(PeerId)
case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?) case emojiStatus(PeerId, Int64, TelegramMediaFile?, LoadedStickerPack?)
case voiceToText case voiceToText
case fasterDownload
} }
#if ENABLE_WALLET #if ENABLE_WALLET

View File

@ -601,11 +601,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
context.sharedContext.hasOngoingCall.get(), context.sharedContext.hasOngoingCall.get(),
itemNode.listNode.preloadItems.get() itemNode.listNode.preloadItems.get()
) )
|> map { hasOngoingCall, preloadItems -> [ChatHistoryPreloadItem] in |> map { hasOngoingCall, preloadItems -> Set<ChatHistoryPreloadItem> in
if hasOngoingCall { if hasOngoingCall {
return [] return Set()
} else { } else {
return preloadItems return Set(preloadItems)
} }
}) })
} }

View File

@ -33,6 +33,7 @@ import Postbox
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
import AnimationCache import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import PremiumUI
private enum ChatListTokenId: Int32 { private enum ChatListTokenId: Int32 {
case archive case archive
@ -284,7 +285,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
isForum = true 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) 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(), let items = combineLatest(queue: .mainQueue(),
context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], messages: [message.id: message], peers: [:]), 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 |> deliverOnMainQueue
|> map { [weak self] actions, isCachedValue -> [ContextMenuItem] in |> map { [weak self] actions, isCachedValue, accountPeer -> [ContextMenuItem] in
guard let strongSelf = self else { guard let strongSelf = self else {
return [] return []
} }
let isPremium = accountPeer?.isPremium ?? false
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
@ -920,6 +923,31 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}) })
}))) })))
} else { } 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 { if let downloadResource = downloadResource, !downloadResource.isFirstInList {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in 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) 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 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 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 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: { 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)
}) })
}))) })))

View File

@ -908,6 +908,10 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
} }
if case .reference = self.source {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX
}
let actionsVerticalTransitionDirection: CGFloat let actionsVerticalTransitionDirection: CGFloat
if let contentNode = contentNode { if let contentNode = contentNode {
if contentNode.frame.minY < self.actionsStackNode.frame.minY { if contentNode.frame.minY < self.actionsStackNode.frame.minY {
@ -1123,6 +1127,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if case .center = actionsHorizontalAlignment { if case .center = actionsHorizontalAlignment {
actionsPositionDeltaXDistance = currentContentScreenFrame.midX - self.actionsStackNode.frame.midX 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 let actionsPositionDeltaYDistance = -animationInContentDistance + actionsVerticalTransitionDirection * actionsSize.height / 2.0 - contentActionsSpacing
self.actionsStackNode.layer.animate( self.actionsStackNode.layer.animate(
from: NSValue(cgPoint: CGPoint()), from: NSValue(cgPoint: CGPoint()),

View File

@ -196,10 +196,19 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
self.highlight?(touchLocation) self.highlight?(touchLocation)
} }
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton { 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 self.state = .failed
return return
} }
}
self.tapCount += 1 self.tapCount += 1
if self.tapCount == 2 && self.touchCount == 1 { if self.tapCount == 2 && self.touchCount == 1 {

View File

@ -19,6 +19,7 @@ import Speak
import TranslateUI import TranslateUI
import ShareController import ShareController
import UndoUI import UndoUI
import ContextUI
enum ChatMediaGalleryThumbnail: Equatable { enum ChatMediaGalleryThumbnail: Equatable {
case image(ImageMediaReference) case image(ImageMediaReference)
@ -201,6 +202,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
private let moreBarButton: MoreHeaderButton
private var tilingNode: TilingNode? private var tilingNode: TilingNode?
fileprivate let _ready = Promise<Void>() fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>() 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.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
self.statusNode.isHidden = true self.statusNode.isHidden = true
self.moreBarButton = MoreHeaderButton()
self.moreBarButton.isUserInteractionEnabled = true
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
super.init() super.init()
self.clipsToBounds = true 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> { override func isPagingEnabled() -> Signal<Bool, NoError> {
@ -326,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .medium, .full: case .medium, .full:
strongSelf.statusNodeContainer.isHidden = true strongSelf.statusNodeContainer.isHidden = true
Queue.concurrentDefaultQueue().async { // Queue.concurrentDefaultQueue().async {
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) { // 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) // 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 // |> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self { // if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode() // strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty { // if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size // 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 // let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self { // if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a) // strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
} // }
}, performAction: { [weak self] string, action in // }, performAction: { [weak self] string, action in
guard let strongSelf = self else { // guard let strongSelf = self else {
return // return
} // }
switch action { // switch action {
case .copy: // case .copy:
UIPasteboard.general.string = string // UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController { // if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 }) // 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 }) // 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)) // controller.present(tooltipController, in: .window(.root))
} // }
case .share: // case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController { // 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)) // 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)) // controller.present(shareController, in: .window(.root))
} // }
case .lookup: // case .lookup:
let controller = UIReferenceLibraryViewController(term: string) // let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window { // if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = 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)) // 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) // window.rootViewController?.present(controller, animated: true)
} // }
case .speak: // case .speak:
let _ = speakText(context: strongSelf.context, text: string) // let _ = speakText(context: strongSelf.context, text: string)
case .translate: // case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController { // if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil) // let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
controller.pushController = { [weak parentController] c in // controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true // (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c) // parentController?.push(c)
} // }
controller.presentController = { [weak parentController] c in // controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root)) // parentController?.present(c, in: .window(.root))
} // }
parentController.present(controller, in: .window(.root)) // parentController.present(controller, in: .window(.root))
} // }
} // }
}) // })
recognizedContentNode.barcodeAction = { [weak self] payload, rect in // recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else { // guard let strongSelf = self, let message = strongSelf.message else {
return // return
} // }
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message) // strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
} // }
recognizedContentNode.alpha = 0.0 // recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size) // recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate) // recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode) // strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode // strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn() // strongSelf.recognitionOverlayContentNode.transitionIn()
} // }
} // }
})) // }))
} // }
} // }
case .none, .blurred: case .none, .blurred:
strongSelf.statusNodeContainer.isHidden = false strongSelf.statusNodeContainer.isHidden = false
@ -411,12 +423,17 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} else { } else {
self._ready.set(.single(Void())) self._ready.set(.single(Void()))
} }
var barButtonItems: [UIBarButtonItem] = []
if imageReference.media.flags.contains(.hasStickers) { 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)) 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])) barButtonItems.append(rightBarButtonItem)
} else {
self._rightBarButtonItems.set(.single([]))
} }
if self.message != nil {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
barButtonItems.append(moreMenuItem)
}
self._rightBarButtonItems.set(.single(barButtonItems))
} }
self.contextAndMedia = (self.context, imageReference.abstract) self.contextAndMedia = (self.context, imageReference.abstract)
} }
@ -453,6 +470,125 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self._ready.set(.single(Void())) 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() { @objc func openStickersButtonPressed() {
guard let (context, media) = self.contextAndMedia else { guard let (context, media) = self.contextAndMedia else {
return return

View File

@ -290,7 +290,7 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14) })?.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 return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) 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 { enum Content {
case image(UIImage?) case image(UIImage?)
case more(UIImage?) case more(UIImage?)
@ -2476,6 +2476,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
var items: [ContextMenuItem] = [] 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 speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
var speedIconText: String = "1x" var speedIconText: String = "1x"
for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) { for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
@ -2804,7 +2827,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController private let controller: ViewController
private let sourceNode: ContextReferenceContentNode private let sourceNode: ContextReferenceContentNode

View File

@ -13,6 +13,7 @@ public class ItemListActivityTextItem: ListViewItem, ItemListItem {
case generic case generic
case constructive case constructive
case destructive case destructive
case warning
} }
let displayActivity: Bool let displayActivity: Bool
@ -123,6 +124,8 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
textColor = item.presentationData.theme.list.freeTextSuccessColor textColor = item.presentationData.theme.list.freeTextSuccessColor
case .destructive: case .destructive:
textColor = item.presentationData.theme.list.freeTextErrorColor 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 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

View File

@ -20,6 +20,8 @@ import ContextUI
import FileMediaResourceStatus import FileMediaResourceStatus
import ManagedAnimationNode import ManagedAnimationNode
import ShimmerEffect import ShimmerEffect
import ComponentFlow
import EmojiStatusComponent
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:]) private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
@ -159,6 +161,167 @@ final class CachedChatListSearchResult {
} }
public final class ListMessageFileItemNode: ListMessageNode { 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 contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode private let extractedBackgroundImageNode: ASImageNode
@ -377,6 +540,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode) let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode) let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode) let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
// let newDescriptionNodeMakeLayout = self.descriptionNode.asyncLayout()
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText) let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode) let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
let iconImageLayout = self.iconImageNode.asyncLayout() 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 (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())) 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 { if extensionTextLayout.truncated, let text = extensionText?.string {
extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center) extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center)

View File

@ -42,8 +42,9 @@ private final class ChannelVisibilityControllerArguments {
let toggleApproveMembers: (Bool) -> Void let toggleApproveMembers: (Bool) -> Void
let activateLink: (String) -> Void let activateLink: (String) -> Void
let deactivateLink: (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.context = context
self.updateCurrentType = updateCurrentType self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
@ -60,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
self.toggleApproveMembers = toggleApproveMembers self.toggleApproveMembers = toggleApproveMembers
self.activateLink = activateLink self.activateLink = activateLink
self.deactivateLink = deactivateLink self.deactivateLink = deactivateLink
self.openAuction = openAuction
} }
} }
@ -109,7 +111,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case privateLinkManageInfo(PresentationTheme, String) case privateLinkManageInfo(PresentationTheme, String)
case publicLinkInfo(PresentationTheme, String) case publicLinkInfo(PresentationTheme, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus) case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case existingLinksInfo(PresentationTheme, String) case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool) case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
@ -317,8 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus): case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus { if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
return true return true
} else { } else {
return false return false
@ -671,7 +673,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkInfo(_, text): case let .publicLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkStatus(_, text, status): case let .publicLinkStatus(_, text, status, username):
var displayActivity = false var displayActivity = false
let textColor: ItemListActivityTextItem.TextColor let textColor: ItemListActivityTextItem.TextColor
switch status { switch status {
@ -686,13 +688,15 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case .taken: case .taken:
textColor = .destructive textColor = .destructive
case .purchaseAvailable: case .purchaseAvailable:
textColor = .generic textColor = .warning
} }
case .checking: case .checking:
textColor = .generic textColor = .generic
displayActivity = true 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): case let .existingLinksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
@ -1074,7 +1078,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
text = presentationData.strings.Channel_Username_CheckingUsername text = presentationData.strings.Channel_Username_CheckingUsername
} }
entries.append(.publicLinkStatus(presentationData.theme, text, status)) entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
} }
if isGroup { if isGroup {
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil { 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 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)) 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() let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
})]), nil) })]), 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) let peerView = context.account.viewTracker.peerView(peerId)

View File

@ -252,7 +252,7 @@ final class PageComponent<ChildEnvironment: Equatable>: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: content.size.height + 40.0))
) )
context.add(text 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 context.add(content
.position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0)) .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 buttonText = strings.Premium_Gift_GiftSubscription(price ?? "").string
case .other: case .other:
switch component.subject { switch component.subject {
case .fasterDownload:
buttonText = strings.Premium_FasterSpeed_Proceed
case .advancedChatManagement:
buttonText = strings.Premium_ChatManagement_Proceed
case .uniqueReactions: case .uniqueReactions:
buttonText = strings.Premium_Reactions_Proceed buttonText = strings.Premium_Reactions_Proceed
buttonAnimationName = "premium_unlock" buttonAnimationName = "premium_unlock"
@ -1030,6 +1034,10 @@ private final class DemoSheetContent: CombinedComponent {
var contentHeight: CGFloat = context.availableSize.width + 146.0 var contentHeight: CGFloat = context.availableSize.width + 146.0
if case .other = component.source { if case .other = component.source {
contentHeight -= 40.0 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) let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + 20.0), size: button.size)

View File

@ -159,6 +159,12 @@ public enum PremiumSource: Equatable {
} else { } else {
return false 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 gift(from: PeerId, to: PeerId, duration: Int32)
case giftTerms case giftTerms
case voiceToText case voiceToText
case fasterDownload
var identifier: String? { var identifier: String? {
switch self { switch self {
@ -225,6 +232,8 @@ public enum PremiumSource: Equatable {
return "emoji_status" return "emoji_status"
case .voiceToText: case .voiceToText:
return "voice_to_text" return "voice_to_text"
case .fasterDownload:
return "faster_download"
case .gift, .giftTerms: case .gift, .giftTerms:
return nil return nil
case let .deeplink(reference): case let .deeplink(reference):

View File

@ -15,19 +15,19 @@ import TextFormat
private final class UsernameSetupControllerArguments { private final class UsernameSetupControllerArguments {
let account: Account let account: Account
let updatePublicLinkText: (String?, String) -> Void let updatePublicLinkText: (String?, String) -> Void
let shareLink: () -> Void let shareLink: () -> Void
let activateLink: (String) -> Void let activateLink: (String) -> Void
let deactivateLink: (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.account = account
self.updatePublicLinkText = updatePublicLinkText self.updatePublicLinkText = updatePublicLinkText
self.shareLink = shareLink self.shareLink = shareLink
self.activateLink = activateLink self.activateLink = activateLink
self.deactivateLink = deactivateLink self.deactivateLink = deactivateLink
self.openAuction = openAuction
} }
} }
@ -56,7 +56,7 @@ private enum UsernameSetupEntryId: Hashable {
private enum UsernameSetupEntry: ItemListNodeEntry { private enum UsernameSetupEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String) case publicLinkHeader(PresentationTheme, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, 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 publicLinkInfo(PresentationTheme, String)
case additionalLinkHeader(PresentationTheme, String) case additionalLinkHeader(PresentationTheme, String)
@ -111,8 +111,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText): case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText { if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
return true return true
} else { } else {
return false return false
@ -208,7 +208,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
arguments.shareLink() arguments.shareLink()
} }
}) })
case let .publicLinkStatus(_, _, status, text): case let .publicLinkStatus(_, _, status, text, username):
var displayActivity = false var displayActivity = false
let textColor: ItemListActivityTextItem.TextColor let textColor: ItemListActivityTextItem.TextColor
switch status { switch status {
@ -219,7 +219,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
case .available: case .available:
textColor = .constructive textColor = .constructive
case .purchaseAvailable: case .purchaseAvailable:
textColor = .generic textColor = .warning
case .invalid, .taken: case .invalid, .taken:
textColor = .destructive textColor = .destructive
} }
@ -227,7 +227,9 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
textColor = .generic textColor = .generic
displayActivity = true 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): case let .additionalLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .additionalLink(_, link, _): case let .additionalLink(_, link, _):
@ -343,7 +345,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
case .checking: case .checking:
statusText = presentationData.strings.Username_CheckingUsername 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 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: { 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() let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
})]), nil) })]), 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) let temporaryOrder = Promise<[String]?>(nil)
@ -680,6 +686,5 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
presentControllerImpl = { [weak controller] c, a in presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
return controller return controller
} }

View File

@ -112,6 +112,10 @@ public enum PresentationResourceKey: Int32 {
case chatBubbleVerticalLineIncomingImage case chatBubbleVerticalLineIncomingImage
case chatBubbleVerticalLineOutgoingImage case chatBubbleVerticalLineOutgoingImage
case chatBubbleArrowFreeImage
case chatBubbleArrowIncomingImage
case chatBubbleArrowOutgoingImage
case chatBubbleCheckBubbleFullImage case chatBubbleCheckBubbleFullImage
case chatBubbleBubblePartialImage case chatBubbleBubblePartialImage
case checkBubbleMediaFullImage case checkBubbleMediaFullImage

View File

@ -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? { public static func chatBubbleConsumableContentIncomingIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in
return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor) return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor)

View File

@ -224,20 +224,7 @@ public final class EmojiStatusComponent: Component {
iconImage = nil iconImage = nil
} }
case let .topic(title, color, realSize): case let .topic(title, color, realSize):
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) { let colors = topicIconColors(for: color)
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)
if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) { if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) {
iconImage = image iconImage = image
} else { } else {
@ -575,3 +562,16 @@ public final class EmojiStatusComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) 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])
}

View File

@ -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)) 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): 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) { 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)) 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)) 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 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) { func update(content: ItemContent) {
if self.content != content { if self.content != content {
if case let .icon(icon) = content, case let .topic(title, color) = icon { if case let .icon(icon) = content, case let .topic(title, color) = icon {
@ -3014,7 +3001,7 @@ public final class EmojiPagerContentComponent: Component {
UIGraphicsPushContext(context) 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) { if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size 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)) image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))

View File

@ -430,8 +430,7 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent {
self.title = "" self.title = ""
self.fileId = 0 self.fileId = 0
let colors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98,0xFF93B2, 0xFB6F5F] self.iconColor = ForumCreateTopicScreen.iconColors.randomElement() ?? 0x0
self.iconColor = colors.randomElement() ?? 0x0
case let .edit(info): case let .edit(info):
self.title = info.title self.title = info.title
self.fileId = info.icon ?? 0 self.fileId = info.icon ?? 0

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "iOS Speed.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "arrow.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -1820,6 +1820,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId) self?.navigateToMessage(from: fromId, to: .id(id, nil), forceInCurrentChat: fromId.peerId == id.peerId)
}, navigateToMessageStandalone: { [weak self] id in }, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false) 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 }, tapMessage: nil, clickThroughMessage: { [weak self] in
self?.chatDisplayNode.dismissInput() self?.chatDisplayNode.dismissInput()
}, toggleMessagesSelection: { [weak self] ids, value in }, toggleMessagesSelection: { [weak self] ids, value in

View File

@ -70,6 +70,7 @@ public final class ChatControllerInteraction {
let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void let openMessageContextActions: (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void
let navigateToMessage: (MessageId, MessageId) -> Void let navigateToMessage: (MessageId, MessageId) -> Void
let navigateToMessageStandalone: (MessageId) -> Void let navigateToMessageStandalone: (MessageId) -> Void
let navigateToThreadMessage: (PeerId, Int64, MessageId?) -> Void
let tapMessage: ((Message) -> Void)? let tapMessage: ((Message) -> Void)?
let clickThroughMessage: () -> Void let clickThroughMessage: () -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void let toggleMessagesSelection: ([MessageId], Bool) -> Void
@ -178,6 +179,7 @@ public final class ChatControllerInteraction {
openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void,
navigateToMessage: @escaping (MessageId, MessageId) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void,
navigateToMessageStandalone: @escaping (MessageId) -> Void, navigateToMessageStandalone: @escaping (MessageId) -> Void,
navigateToThreadMessage: @escaping (PeerId, Int64, MessageId?) -> Void,
tapMessage: ((Message) -> Void)?, tapMessage: ((Message) -> Void)?,
clickThroughMessage: @escaping () -> Void, clickThroughMessage: @escaping () -> Void,
toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void,
@ -269,6 +271,7 @@ public final class ChatControllerInteraction {
self.openMessageContextActions = openMessageContextActions self.openMessageContextActions = openMessageContextActions
self.navigateToMessage = navigateToMessage self.navigateToMessage = navigateToMessage
self.navigateToMessageStandalone = navigateToMessageStandalone self.navigateToMessageStandalone = navigateToMessageStandalone
self.navigateToThreadMessage = navigateToThreadMessage
self.tapMessage = tapMessage self.tapMessage = tapMessage
self.clickThroughMessage = clickThroughMessage self.clickThroughMessage = clickThroughMessage
self.toggleMessagesSelection = toggleMessagesSelection self.toggleMessagesSelection = toggleMessagesSelection

View File

@ -614,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId)) 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, loadLimits,
loadStickerSaveStatusSignal, loadStickerSaveStatusSignal,
loadResourceStatusSignal, loadResourceStatusSignal,
@ -626,9 +626,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
context.engine.stickers.availableReactions(), context.engine.stickers.availableReactions(),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]), 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 let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false var canEdit = false
if !isAction { if !isAction {
@ -652,12 +653,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
loggingSettings = LoggingSettings.defaultSettings 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 return dataSignal
|> deliverOnMainQueue |> 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 actions: [ContextMenuItem] = []
var isPinnedMessages = false var isPinnedMessages = false
@ -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 var isReplyThreadHead = false
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation { if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId 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 = "" var messageText: String = ""
for message in messages { for message in messages {

View File

@ -58,8 +58,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
class ChatMessageShareButton: HighlightableButtonNode { class ChatMessageShareButton: HighlightableButtonNode {
private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
private var iconOffset = CGPoint() private var iconOffset = CGPoint()
@ -242,7 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode? private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode? private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -472,6 +474,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.updateVisibility() self.updateVisibility()
self.haptic?.enabled = self.visibilityStatus == true self.haptic?.enabled = self.visibilityStatus == true
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.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) 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 { if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX reactionButtonsNodeFrame.origin.x += rect.minX
@ -796,6 +823,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) 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 makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo let currentForwardInfo = self.appliedForwardInfo
@ -1124,11 +1160,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)? var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)? var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var needsReplyBackground = false var needsReplyBackground = false
var replyMarkup: ReplyMarkupMessageAttribute? 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)) 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 var ignoreForward = false
@ -1166,8 +1202,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { 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 { 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( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,
strings: item.presentationData.strings, strings: item.presentationData.strings,
@ -1425,9 +1483,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 { } else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
strongSelf.replyBackgroundNode = nil strongSelf.replyBackgroundNode = nil
replyBackgroundNode.removeFromSupernode() 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() var messageInfoSize = CGSize()
@ -1447,7 +1537,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) 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) 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 forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -1490,7 +1580,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) 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 { 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 let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) 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 let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha

View File

@ -510,6 +510,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var forwardInfoReferenceNode: ASDisplayNode? { var forwardInfoReferenceNode: ASDisplayNode? {
return self.forwardInfoNode return self.forwardInfoNode
} }
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var contentContainersWrapperNode: ASDisplayNode 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) 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 { if let replyInfoNode = self.replyInfoNode {
replyInfoNode.visibility = self.visibility != .none 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) { if let replyInfoNode = strongSelf.replyInfoNode, replyInfoNode.frame.contains(point) {
return .waitForSingleTap return .waitForSingleTap
} }
@ -1054,6 +1065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let authorNameLayout = TextNode.asyncLayout(self.nameNode) let authorNameLayout = TextNode.asyncLayout(self.nameNode)
let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode) let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode)
let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode) let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
@ -1076,6 +1088,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts, currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
authorNameLayout: authorNameLayout, authorNameLayout: authorNameLayout,
adminBadgeLayout: adminBadgeLayout, adminBadgeLayout: adminBadgeLayout,
threadInfoLayout: threadInfoLayout,
forwardInfoLayout: forwardInfoLayout, forwardInfoLayout: forwardInfoLayout,
replyInfoLayout: replyInfoLayout, replyInfoLayout: replyInfoLayout,
actionButtonsLayout: actionButtonsLayout, 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))))], 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), authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode),
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
@ -1835,6 +1849,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil }) var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var adminNodeSizeApply: (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 replyInfoOriginY: CGFloat = 0.0
var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil }) var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil })
@ -1945,7 +1962,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
headerSize.height += forwardInfoSizeApply.0.height 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 { if headerSize.height.isZero {
headerSize.height += 6.0 headerSize.height += 6.0
} else { } else {
@ -2401,6 +2448,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: currentCredibilityIcon, currentCredibilityIcon: currentCredibilityIcon,
adminNodeSizeApply: adminNodeSizeApply, adminNodeSizeApply: adminNodeSizeApply,
contentUpperRightCorner: contentUpperRightCorner, contentUpperRightCorner: contentUpperRightCorner,
threadInfoSizeApply: threadInfoSizeApply,
threadInfoOriginY: threadInfoOriginY,
forwardInfoSizeApply: forwardInfoSizeApply, forwardInfoSizeApply: forwardInfoSizeApply,
forwardInfoOriginY: forwardInfoOriginY, forwardInfoOriginY: forwardInfoOriginY,
replyInfoSizeApply: replyInfoSizeApply, replyInfoSizeApply: replyInfoSizeApply,
@ -2449,6 +2498,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: EmojiStatusComponent.Content?, currentCredibilityIcon: EmojiStatusComponent.Content?,
adminNodeSizeApply: (CGSize, () -> TextNode?), adminNodeSizeApply: (CGSize, () -> TextNode?),
contentUpperRightCorner: CGPoint, contentUpperRightCorner: CGPoint,
threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?),
threadInfoOriginY: CGFloat,
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?), forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
forwardInfoOriginY: CGFloat, forwardInfoOriginY: CGFloat,
replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?), 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) { if let replyInfoNode = replyInfoSizeApply.1(synchronousLoads) {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
var animateFrame = true 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 forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
if let item = self.item, let forwardInfo = item.message.forwardInfo { 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) { if let item = self.item, self.backgroundNode.frame.contains(location) {
let message = item.message 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 tapMessage: Message? = item.content.firstMessage
var selectAll = true var selectAll = true
var hasFiles = false var hasFiles = false
@ -3773,6 +3872,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return nil 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) { if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view return shareButtonNode.view
} }

View File

@ -495,7 +495,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
return return
} }
var messageId: MessageId? 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 messageId = id
} }
strongSelf.controllerInteraction.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) strongSelf.controllerInteraction.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture)
@ -641,7 +641,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) { @objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
if case .ended = recognizer.state { 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) self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
} else if let peer = self.peer { } else if let peer = self.peer {
if let adMessageId = self.adMessageId { if let adMessageId = self.adMessageId {

View File

@ -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) 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)
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 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 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 let titleColor: UIColor

View File

@ -38,7 +38,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode? private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode? private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode? private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode? private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -71,6 +73,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var visibilityStatus: Bool? { private var visibilityStatus: Bool? {
didSet { didSet {
if self.visibilityStatus != oldValue { if self.visibilityStatus != oldValue {
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.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) 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 { if let shareButtonNode = self.shareButtonNode {
var shareButtonNodeFrame = shareButtonNode.frame var shareButtonNodeFrame = shareButtonNode.frame
shareButtonNodeFrame.origin.x += rect.minX shareButtonNodeFrame.origin.x += rect.minX
@ -300,6 +311,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate) 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 makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode) let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode) let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo let currentForwardInfo = self.appliedForwardInfo
@ -567,6 +587,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0) let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)? var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)? var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var replyMarkup: ReplyMarkupMessageAttribute? var replyMarkup: ReplyMarkupMessageAttribute?
@ -610,8 +631,30 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
} }
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] { 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 { 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( replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData, presentationData: item.presentationData,
strings: item.presentationData.strings, strings: item.presentationData.strings,
@ -764,9 +807,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
} }
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, _) = threadInfoApply {
headersOffset += threadInfoSize.height + 10.0
}
var viaBotFrame: CGRect? var viaBotFrame: CGRect?
if let (viaBotLayout, _) = viaBotApply { 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? var replyInfoFrame: CGRect?
@ -775,7 +823,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let viaBotFrame = viaBotFrame { if let viaBotFrame = viaBotFrame {
viaBotSize = viaBotFrame.size 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 replyInfoFrame = replyInfoFrameValue
if let viaBotFrameValue = viaBotFrame { if let viaBotFrameValue = viaBotFrame {
if replyInfoFrameValue.minX < replyInfoFrameValue.minX { if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
@ -791,7 +839,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
viaBotSize = viaBotFrame.size 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 { if let replyBackgroundFrameValue = replyBackgroundFrame {
@ -875,9 +923,41 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 { } else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.removeFromSupernode() replyBackgroundNode.removeFromSupernode()
strongSelf.replyBackgroundNode = nil 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() var messageInfoSize = CGSize()
@ -897,7 +977,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) 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) 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 forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -940,7 +1020,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(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 replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
@ -949,15 +1029,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = nil strongSelf.replyInfoNode = nil
} }
if let replyBackgroundNode = strongSelf.replyBackgroundNode { 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 let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) 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 let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha strongSelf.forwardInfoNode?.alpha = panelsAlpha

View 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
})
}
}
}

View File

@ -263,6 +263,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, activateMessagePinch: { _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ 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 }, 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) self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in

View File

@ -112,6 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ 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 }, 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 }, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in }, presentControllerInCurrent: { _, _ in

View File

@ -76,6 +76,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, openMessageContextActions: { _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessage: { _, _ in
}, navigateToMessageStandalone: { _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, tapMessage: nil, clickThroughMessage: {
}, toggleMessagesSelection: { _, _ in }, toggleMessagesSelection: { _, _ in
}, sendCurrentMessage: { _ in }, sendCurrentMessage: { _ in

View File

@ -2431,6 +2431,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}) })
}, navigateToMessage: { fromId, id in }, navigateToMessage: { fromId, id in
}, navigateToMessageStandalone: { _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, tapMessage: nil, clickThroughMessage: {
}, toggleMessagesSelection: { [weak self] ids, value in }, toggleMessagesSelection: { [weak self] ids, value in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -173,16 +173,16 @@ private final class PrefetchManagerInnerImpl {
if case .full = automaticDownload { if case .full = automaticDownload {
if let image = media as? TelegramMediaImage { 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 { } else if let _ = media as? TelegramMediaWebFile {
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start()) //strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
} else if let file = media as? TelegramMediaFile { } 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()) context.fetchDisposable.set(fetchSignal.start())
} }
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat { } else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
if let file = media as? TelegramMediaFile, let _ = file.size { 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())
} }
} }
} }

View File

@ -1293,6 +1293,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: { message in }, tapMessage: { message in
tapMessage?(message) tapMessage?(message)
}, clickThroughMessage: { }, clickThroughMessage: {
@ -1523,6 +1524,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .emojiStatus(peerId, fileId, file, packTitle) mappedSource = .emojiStatus(peerId, fileId, file, packTitle)
case .voiceToText: case .voiceToText:
mappedSource = .voiceToText mappedSource = .voiceToText
case .fasterDownload:
mappedSource = .fasterDownload
} }
return PremiumIntroScreen(context: context, source: mappedSource) return PremiumIntroScreen(context: context, source: mappedSource)
} }