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.Reactions.Proceed" = "Unlock Premium Reactions";
"Premium.AppIcons.Proceed" = "Unlock Premium Icons";
"Premium.NoAds.Proceed" = "About Telegram Premium";
"AccessDenied.LocationPreciseDenied" = "To share your specific location in this chat, please go to Settings > Privacy > Location Services > Telegram and set Precise Location to On.";
@ -8271,5 +8269,11 @@ Sorry for the inconvenience.";
"EmojiSearch.SearchTopicIconsPlaceholder" = "Search Topic Icons";
"EmojiSearch.SearchTopicIconsEmptyResult" = "No emoji found";
"Username.UsernamePurchaseAvailable" = "Sorry, this username is occupied by someone. But it's available for purchase through official @auction.";
"Channel.Username.UsernamePurchaseAvailable" = "Sorry, this link is occupied by someone. But it's available for purchase through official @auction.";
"Username.UsernamePurchaseAvailable" = "Sorry, this username is occupied by someone. But it's available for purchase on [fragment.com]().";
"Channel.Username.UsernamePurchaseAvailable" = "Sorry, this link is occupied by someone. But it's available for purchase on [fragment.com]().";
"DownloadList.IncreaseSpeed" = "Increase Speed";
"Conversation.IncreaseSpeed" = "Increase Speed";
"Premium.ChatManagement.Proceed" = "About Telegram Premium";
"Premium.FasterSpeed.Proceed" = "About Telegram Premium";

View File

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

View File

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

View File

@ -33,6 +33,7 @@ import Postbox
import TelegramAnimatedStickerNode
import AnimationCache
import MultiAnimationRenderer
import PremiumUI
private enum ChatListTokenId: Int32 {
case archive
@ -284,7 +285,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
isForum = true
}
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: strongSelf.hasDownloads).map(\.filter)
filters = defaultAvailableSearchPanes(isForum: isForum, hasDownloads: !isForum && strongSelf.hasDownloads).map(\.filter)
}
strongSelf.filterContainerNode.update(size: CGSize(width: layout.size.width - 40.0, height: 38.0), sideInset: layout.safeInsets.left - 20.0, filters: filters.map { .filter($0) }, selectedFilter: strongSelf.selectedFilter?.id, transitionFraction: strongSelf.transitionFraction, presentationData: strongSelf.presentationData, transition: transition)
}
@ -896,13 +897,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let items = combineLatest(queue: .mainQueue(),
context.sharedContext.chatAvailableMessageActions(engine: context.engine, accountPeerId: context.account.peerId, messageIds: [message.id], messages: [message.id: message], peers: [:]),
isCachedValue |> take(1)
isCachedValue |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
|> deliverOnMainQueue
|> map { [weak self] actions, isCachedValue -> [ContextMenuItem] in
|> map { [weak self] actions, isCachedValue, accountPeer -> [ContextMenuItem] in
guard let strongSelf = self else {
return []
}
let isPremium = accountPeer?.isPremium ?? false
var items: [ContextMenuItem] = []
@ -920,6 +923,31 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
})))
} else {
if !isPremium, let size = downloadResource?.size, size >= 300 * 1024 * 1024 {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_IncreaseSpeed, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
guard let strongSelf = self else {
f(.default)
return
}
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.navigationController?.pushViewController(controller, animated: false, completion: {})
f(.default)
})))
items.append(.separator)
}
if let downloadResource = downloadResource, !downloadResource.isFirstInList {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DownloadList_RaisePriority, textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Raise"), color: theme.contextMenu.primaryColor)
@ -1032,7 +1060,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: { [weak self] in
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
})
})))
@ -1078,7 +1106,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), nil, message.id, false)
self?.openMessage(EnginePeer(message.peers[message.id.peerId]!), message.threadId, message.id, false)
})
})))

View File

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

View File

@ -196,9 +196,18 @@ public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer,
self.highlight?(touchLocation)
}
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton {
self.state = .failed
return
if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event) {
var fail = false
if let _ = hitResult as? UIButton {
fail = true
} else if let node = hitResult.asyncdisplaykit_node, node is ASControlNode {
fail = true
}
if fail {
self.state = .failed
return
}
}
self.tapCount += 1

View File

@ -19,6 +19,7 @@ import Speak
import TranslateUI
import ShareController
import UndoUI
import ContextUI
enum ChatMediaGalleryThumbnail: Equatable {
case image(ImageMediaReference)
@ -201,6 +202,8 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
private let moreBarButton: MoreHeaderButton
private var tilingNode: TilingNode?
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
@ -238,6 +241,10 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
self.statusNode.isHidden = true
self.moreBarButton = MoreHeaderButton()
self.moreBarButton.isUserInteractionEnabled = true
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
super.init()
self.clipsToBounds = true
@ -275,6 +282,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
}
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
self?.openMoreMenu(sourceNode: sourceNode, gesture: gesture)
}
}
override func isPagingEnabled() -> Signal<Bool, NoError> {
@ -326,75 +338,75 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .medium, .full:
strongSelf.statusNodeContainer.isHidden = true
Queue.concurrentDefaultQueue().async {
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
}
}, performAction: { [weak self] string, action in
guard let strongSelf = self else {
return
}
switch action {
case .copy:
UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
controller.present(tooltipController, in: .window(.root))
}
case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
controller.present(shareController, in: .window(.root))
}
case .lookup:
let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true)
}
case .speak:
let _ = speakText(context: strongSelf.context, text: string)
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root))
}
}
})
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else {
return
}
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
}
recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn()
}
}
}))
}
}
// Queue.concurrentDefaultQueue().async {
// if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
// strongSelf.recognitionDisposable.set((recognizedContent(engine: strongSelf.context.engine, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
// |> deliverOnMainQueue).start(next: { [weak self] results in
// if let strongSelf = self {
// strongSelf.recognizedContentNode?.removeFromSupernode()
// if !results.isEmpty {
// let size = strongSelf.imageNode.bounds.size
// let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
// if let strongSelf = self {
// strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
// }
// }, performAction: { [weak self] string, action in
// guard let strongSelf = self else {
// return
// }
// switch action {
// case .copy:
// UIPasteboard.general.string = string
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
// let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
// controller.present(tooltipController, in: .window(.root))
// }
// case .share:
// if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
// controller.present(shareController, in: .window(.root))
// }
// case .lookup:
// let controller = UIReferenceLibraryViewController(term: string)
// if let window = strongSelf.baseNavigationController()?.view.window {
// controller.popoverPresentationController?.sourceView = window
// controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
// window.rootViewController?.present(controller, animated: true)
// }
// case .speak:
// let _ = speakText(context: strongSelf.context, text: string)
// case .translate:
// if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
// let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
// controller.pushController = { [weak parentController] c in
// (parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
// parentController?.push(c)
// }
// controller.presentController = { [weak parentController] c in
// parentController?.present(c, in: .window(.root))
// }
// parentController.present(controller, in: .window(.root))
// }
// }
// })
// recognizedContentNode.barcodeAction = { [weak self] payload, rect in
// guard let strongSelf = self, let message = strongSelf.message else {
// return
// }
// strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
// }
// recognizedContentNode.alpha = 0.0
// recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
// recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
// strongSelf.imageNode.addSubnode(recognizedContentNode)
// strongSelf.recognizedContentNode = recognizedContentNode
// strongSelf.recognitionOverlayContentNode.transitionIn()
// }
// }
// }))
// }
// }
case .none, .blurred:
strongSelf.statusNodeContainer.isHidden = false
@ -411,12 +423,17 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
} else {
self._ready.set(.single(Void()))
}
var barButtonItems: [UIBarButtonItem] = []
if imageReference.media.flags.contains(.hasStickers) {
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
self._rightBarButtonItems.set(.single([rightBarButtonItem]))
} else {
self._rightBarButtonItems.set(.single([]))
barButtonItems.append(rightBarButtonItem)
}
if self.message != nil {
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
barButtonItems.append(moreMenuItem)
}
self._rightBarButtonItems.set(.single(barButtonItems))
}
self.contextAndMedia = (self.context, imageReference.abstract)
}
@ -453,6 +470,125 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
self._ready.set(.single(Void()))
}
@objc private func moreButtonPressed() {
self.moreBarButton.play()
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
}
private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> {
var items: [ContextMenuItem] = []
if let message = self.message {
let context = self.context
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
if let navigationController = strongSelf.baseNavigationController() {
strongSelf.beginCustomDismiss(true)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
Queue.mainQueue().after(0.3) {
strongSelf.completeCustomDismiss()
}
}
f(.default)
})
})))
}
// if #available(iOS 11.0, *) {
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
// f(.default)
// guard let strongSelf = self else {
// return
// }
// strongSelf.beginAirPlaySetup()
// })))
// }
// if let (message, _, _) = strongSelf.contentInfo() {
// for media in message.media {
// if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
// let url = content.url
//
// let item = OpenInItem.url(url: url)
// let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
// items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self, let controller = strongSelf.galleryController() {
// var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
// if !presentationData.theme.overallDarkAppearance {
// presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
// }
// let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
// if let strongSelf = self {
// strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
// }
// })
// controller.present(actionSheet, in: .window(.root))
// }
// })))
// break
// }
// }
// }
// if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() {
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self {
// switch strongSelf.fetchStatus {
// case .Local:
// let _ = (SaveToCameraRoll.saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, mediaReference: .message(message: MessageReference(message), media: file))
// |> deliverOnMainQueue).start(completed: {
// guard let strongSelf = self else {
// return
// }
// guard let controller = strongSelf.galleryController() else {
// return
// }
// controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: strongSelf.presentationData.strings.Gallery_VideoSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
// })
// default:
// guard let controller = strongSelf.galleryController() else {
// return
// }
// controller.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Gallery_WaitForVideoDownoad, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
// })]), in: .window(.root))
// }
// }
// })))
// }
// if strongSelf.canDelete() {
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
// f(.default)
//
// if let strongSelf = self {
// strongSelf.footerContentNode.deleteButtonPressed()
// }
// })))
// }
return .single(items)
}
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@objc func openStickersButtonPressed() {
guard let (context, media) = self.contextAndMedia else {
return

View File

@ -290,7 +290,7 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
}
private func optionsCircleImage(dark: Bool) -> UIImage? {
func optionsCircleImage(dark: Bool) -> UIImage? {
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -339,7 +339,7 @@ private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .whi
})
}
private final class MoreHeaderButton: HighlightableButtonNode {
final class MoreHeaderButton: HighlightableButtonNode {
enum Content {
case image(UIImage?)
case more(UIImage?)
@ -2475,6 +2475,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
var items: [ContextMenuItem] = []
if let (message, _, _) = strongSelf.contentInfo() {
let context = strongSelf.context
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self, let peer = peer else {
return
}
if let navigationController = strongSelf.baseNavigationController() {
strongSelf.beginCustomDismiss(true)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: true, timecode: nil)))
Queue.mainQueue().after(0.3) {
strongSelf.completeCustomDismiss()
}
}
f(.default)
})
})))
}
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
var speedIconText: String = "1x"
@ -2804,7 +2827,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode

View File

@ -13,6 +13,7 @@ public class ItemListActivityTextItem: ListViewItem, ItemListItem {
case generic
case constructive
case destructive
case warning
}
let displayActivity: Bool
@ -123,6 +124,8 @@ public class ItemListActivityTextItemNode: ListViewItemNode {
textColor = item.presentationData.theme.list.freeTextSuccessColor
case .destructive:
textColor = item.presentationData.theme.list.freeTextErrorColor
case .warning:
textColor = UIColor(rgb: 0xef8c00)
}
let attributedString = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.presentationData.theme.list.itemAccentColor), linkAttribute: { contents in

View File

@ -20,6 +20,8 @@ import ContextUI
import FileMediaResourceStatus
import ManagedAnimationNode
import ShimmerEffect
import ComponentFlow
import EmojiStatusComponent
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
@ -159,6 +161,167 @@ final class CachedChatListSearchResult {
}
public final class ListMessageFileItemNode: ListMessageNode {
public final class DescriptionNode: ASDisplayNode {
let descriptionNode: TextNode
var titleTopicArrowNode: ASImageNode?
var topicTitleNode: TextNode?
var titleTopicIconView: ComponentHostView<Empty>?
var titleTopicIconComponent: EmojiStatusComponent?
var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
if let titleTopicIconView = self.titleTopicIconView, let titleTopicIconComponent = self.titleTopicIconComponent {
let _ = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent.withVisibleForAnimations(self.visibilityStatus)),
environment: {},
containerSize: titleTopicIconView.bounds.size
)
}
}
}
}
override init() {
self.descriptionNode = TextNode()
self.descriptionNode.displaysAsynchronously = true
super.init()
self.addSubnode(self.descriptionNode)
}
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topic: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) {
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
let makeTopicTitleLayout = TextNode.asyncLayout(self.topicTitleNode)
return { [weak self] context, constrainedWidth, theme, authorTitle, topic in
var maxTitleWidth = constrainedWidth
if let _ = topic {
maxTitleWidth = floor(constrainedWidth * 0.7)
}
let descriptionLayout = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: authorTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
var remainingWidth = constrainedWidth - descriptionLayout.0.size.width
var topicTitleArguments: TextNodeLayoutArguments?
var arrowIconImage: UIImage?
if let topic = topic {
remainingWidth -= 22.0 + 2.0
if authorTitle != nil {
arrowIconImage = PresentationResourcesChatList.topicArrowIcon(theme)
if let arrowIconImage = arrowIconImage {
remainingWidth -= arrowIconImage.size.width + 6.0 * 2.0
}
}
topicTitleArguments = TextNodeLayoutArguments(attributedString: topic.title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: remainingWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0))
}
let topicTitleLayout = topicTitleArguments.flatMap(makeTopicTitleLayout)
var size = descriptionLayout.0.size
if let topicTitleLayout = topicTitleLayout {
size.height = max(size.height, topicTitleLayout.0.size.height)
size.width += 10.0 + topicTitleLayout.0.size.width
}
return (size, {
guard let self else {
return
}
let _ = descriptionLayout.1()
let authorFrame = CGRect(origin: CGPoint(), size: descriptionLayout.0.size)
self.descriptionNode.frame = authorFrame
var nextX = authorFrame.maxX - 1.0
if authorTitle == nil {
nextX = 0.0
}
if let arrowIconImage = arrowIconImage {
let titleTopicArrowNode: ASImageNode
if let current = self.titleTopicArrowNode {
titleTopicArrowNode = current
} else {
titleTopicArrowNode = ASImageNode()
self.titleTopicArrowNode = titleTopicArrowNode
self.addSubnode(titleTopicArrowNode)
}
titleTopicArrowNode.image = arrowIconImage
nextX += 6.0
titleTopicArrowNode.frame = CGRect(origin: CGPoint(x: nextX, y: 5.0), size: arrowIconImage.size)
nextX += arrowIconImage.size.width + 6.0
} else {
if let titleTopicArrowNode = self.titleTopicArrowNode {
self.titleTopicArrowNode = nil
titleTopicArrowNode.removeFromSupernode()
}
}
if let topic {
let titleTopicIconView: ComponentHostView<Empty>
if let current = self.titleTopicIconView {
titleTopicIconView = current
} else {
titleTopicIconView = ComponentHostView<Empty>()
self.titleTopicIconView = titleTopicIconView
self.view.addSubview(titleTopicIconView)
}
let titleTopicIconContent: EmojiStatusComponent.Content
if let fileId = topic.iconId, fileId != 0 {
titleTopicIconContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 36.0, height: 36.0), placeholderColor: theme.list.mediaPlaceholderColor, themeColor: theme.list.itemAccentColor, loopMode: .count(2))
} else {
titleTopicIconContent = .topic(title: String(topic.title.string.prefix(1)), color: topic.iconColor, size: CGSize(width: 22.0, height: 22.0))
}
let titleTopicIconComponent = EmojiStatusComponent(
context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
content: titleTopicIconContent,
isVisibleForAnimations: self.visibilityStatus,
action: nil
)
self.titleTopicIconComponent = titleTopicIconComponent
let iconSize = titleTopicIconView.update(
transition: .immediate,
component: AnyComponent(titleTopicIconComponent),
environment: {},
containerSize: CGSize(width: 22.0, height: 22.0)
)
titleTopicIconView.frame = CGRect(origin: CGPoint(x: nextX, y: UIScreenPixel), size: iconSize)
nextX += iconSize.width + 2.0
} else {
if let titleTopicIconView = self.titleTopicIconView {
self.titleTopicIconView = nil
titleTopicIconView.removeFromSuperview()
}
}
if let topicTitleLayout = topicTitleLayout {
let topicTitleNode = topicTitleLayout.1()
if topicTitleNode.supernode == nil {
self.addSubnode(topicTitleNode)
self.topicTitleNode = topicTitleNode
}
topicTitleNode.frame = CGRect(origin: CGPoint(x: nextX - 1.0, y: 0.0), size: topicTitleLayout.0.size)
} else if let topicTitleNode = self.topicTitleNode {
self.topicTitleNode = nil
topicTitleNode.removeFromSupernode()
}
})
}
}
}
private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode
@ -377,6 +540,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
// let newDescriptionNodeMakeLayout = self.descriptionNode.asyncLayout()
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
let iconImageLayout = self.iconImageNode.asyncLayout()
@ -754,6 +918,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
// let forumThreadTitle: (title: NSAttributedString, iconId: Int64?, iconColor: Int32)? = nil
// let (newDescriptionNodeLayout, newDescriptionNodeApply) = newDescriptionNodeMakeLayout(item.context, params.width - leftInset - rightInset - 30.0, item.presentationData.theme.theme, descriptionText, forumThreadTitle)
var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
if extensionTextLayout.truncated, let text = extensionText?.string {
extensionText = NSAttributedString(string: text, font: smallExtensionFont, textColor: .white, paragraphAlignment: .center)

View File

@ -42,8 +42,9 @@ private final class ChannelVisibilityControllerArguments {
let toggleApproveMembers: (Bool) -> Void
let activateLink: (String) -> Void
let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) {
init(context: AccountContext, updateCurrentType: @escaping (CurrentChannelType) -> Void, updatePublicLinkText: @escaping (String?, String) -> Void, scrollToPublicLinkText: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, revokePeerId: @escaping (PeerId) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, linkContextAction: @escaping (ASDisplayNode, ContextGesture?) -> Void, manageInviteLinks: @escaping () -> Void, openLink: @escaping (ExportedInvitation) -> Void, toggleForwarding: @escaping (Bool) -> Void, updateJoinToSend: @escaping (CurrentChannelJoinToSend) -> Void, toggleApproveMembers: @escaping (Bool) -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.context = context
self.updateCurrentType = updateCurrentType
self.updatePublicLinkText = updatePublicLinkText
@ -60,6 +61,7 @@ private final class ChannelVisibilityControllerArguments {
self.toggleApproveMembers = toggleApproveMembers
self.activateLink = activateLink
self.deactivateLink = deactivateLink
self.openAuction = openAuction
}
}
@ -109,7 +111,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case privateLinkManageInfo(PresentationTheme, String)
case publicLinkInfo(PresentationTheme, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case existingLinksInfo(PresentationTheme, String)
case existingLinkPeerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, ItemListPeerItemEditing, Bool)
@ -317,8 +319,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
} else {
return false
}
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus):
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus {
case let .publicLinkStatus(lhsTheme, lhsText, lhsStatus, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsText, rhsStatus, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsStatus == rhsStatus, lhsUsername == rhsUsername {
return true
} else {
return false
@ -671,7 +673,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .publicLinkStatus(_, text, status):
case let .publicLinkStatus(_, text, status, username):
var displayActivity = false
let textColor: ItemListActivityTextItem.TextColor
switch status {
@ -686,13 +688,15 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry {
case .taken:
textColor = .destructive
case .purchaseAvailable:
textColor = .generic
textColor = .warning
}
case .checking:
textColor = .generic
displayActivity = true
}
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section)
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
arguments.openAuction(username)
}, sectionId: self.section)
case let .existingLinksInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .existingLinkPeerItem(_, _, _, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
@ -1074,7 +1078,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
text = presentationData.strings.Channel_Username_CheckingUsername
}
entries.append(.publicLinkStatus(presentationData.theme, text, status))
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
}
if isGroup {
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.peerGeoLocation != nil {
@ -1275,7 +1279,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa
text = presentationData.strings.Channel_Username_CheckingUsername
}
entries.append(.publicLinkStatus(presentationData.theme, text, status))
entries.append(.publicLinkStatus(presentationData.theme, text, status, currentUsername))
}
entries.append(.publicLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePublicLinkHelp))
@ -1742,6 +1746,10 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: false).start()
})]), nil)
})
}, openAuction: { username in
dismissInputImpl?()
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
})
let peerView = context.account.viewTracker.peerView(peerId)

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

View File

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

View File

@ -15,19 +15,19 @@ import TextFormat
private final class UsernameSetupControllerArguments {
let account: Account
let updatePublicLinkText: (String?, String) -> Void
let shareLink: () -> Void
let activateLink: (String) -> Void
let deactivateLink: (String) -> Void
let openAuction: (String) -> Void
init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void) {
init(account: Account, updatePublicLinkText: @escaping (String?, String) -> Void, shareLink: @escaping () -> Void, activateLink: @escaping (String) -> Void, deactivateLink: @escaping (String) -> Void, openAuction: @escaping (String) -> Void) {
self.account = account
self.updatePublicLinkText = updatePublicLinkText
self.shareLink = shareLink
self.activateLink = activateLink
self.deactivateLink = deactivateLink
self.openAuction = openAuction
}
}
@ -56,7 +56,7 @@ private enum UsernameSetupEntryId: Hashable {
private enum UsernameSetupEntry: ItemListNodeEntry {
case publicLinkHeader(PresentationTheme, String)
case editablePublicLink(PresentationTheme, PresentationStrings, String, String?, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String)
case publicLinkStatus(PresentationTheme, String, AddressNameValidationStatus, String, String)
case publicLinkInfo(PresentationTheme, String)
case additionalLinkHeader(PresentationTheme, String)
@ -111,8 +111,8 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
} else {
return false
}
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText):
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText {
case let .publicLinkStatus(lhsTheme, lhsAddressName, lhsStatus, lhsText, lhsUsername):
if case let .publicLinkStatus(rhsTheme, rhsAddressName, rhsStatus, rhsText, rhsUsername) = rhs, lhsTheme === rhsTheme, lhsAddressName == rhsAddressName, lhsStatus == rhsStatus, lhsText == rhsText, lhsUsername == rhsUsername {
return true
} else {
return false
@ -208,7 +208,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
arguments.shareLink()
}
})
case let .publicLinkStatus(_, _, status, text):
case let .publicLinkStatus(_, _, status, text, username):
var displayActivity = false
let textColor: ItemListActivityTextItem.TextColor
switch status {
@ -219,7 +219,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
case .available:
textColor = .constructive
case .purchaseAvailable:
textColor = .generic
textColor = .warning
case .invalid, .taken:
textColor = .destructive
}
@ -227,7 +227,9 @@ private enum UsernameSetupEntry: ItemListNodeEntry {
textColor = .generic
displayActivity = true
}
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in }, sectionId: self.section)
return ItemListActivityTextItem(displayActivity: displayActivity, presentationData: presentationData, text: text, color: textColor, linkAction: { _ in
arguments.openAuction(username)
}, sectionId: self.section)
case let .additionalLinkHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .additionalLink(_, link, _):
@ -343,7 +345,7 @@ private func usernameSetupControllerEntries(presentationData: PresentationData,
case .checking:
statusText = presentationData.strings.Username_CheckingUsername
}
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText))
entries.append(.publicLinkStatus(presentationData.theme, currentUsername, status, statusText, currentUsername))
}
var infoText = presentationData.strings.Username_Help
@ -471,6 +473,10 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: {
let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: false).start()
})]), nil)
}, openAuction: { username in
dismissInputImpl?()
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/username/\(username)", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
})
let temporaryOrder = Promise<[String]?>(nil)
@ -680,6 +686,5 @@ public func usernameSetupController(context: AccountContext) -> ViewController {
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
return controller
}

View File

@ -112,6 +112,10 @@ public enum PresentationResourceKey: Int32 {
case chatBubbleVerticalLineIncomingImage
case chatBubbleVerticalLineOutgoingImage
case chatBubbleArrowFreeImage
case chatBubbleArrowIncomingImage
case chatBubbleArrowOutgoingImage
case chatBubbleCheckBubbleFullImage
case chatBubbleBubblePartialImage
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? {
return theme.image(PresentationResourceKey.chatBubbleConsumableContentIncomingIcon.rawValue, { theme in
return generateFilledCircleImage(diameter: 4.0, color: theme.chat.message.incoming.accentTextColor)

View File

@ -224,20 +224,7 @@ public final class EmojiStatusComponent: Component {
iconImage = nil
}
case let .topic(title, color, realSize):
func generateTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
return ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
let colors = topicColors[color] ?? generateTopicColors(color)
let colors = topicIconColors(for: color)
if let image = generateTopicIcon(title: title, backgroundColors: colors.0.map(UIColor.init(rgb:)), strokeColors: colors.1.map(UIColor.init(rgb:)), size: realSize) {
iconImage = image
} else {
@ -575,3 +562,16 @@ public final class EmojiStatusComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public func topicIconColors(for color: Int32) -> ([UInt32], [UInt32]) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}

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))
}
case let .topic(title, color):
let colors = self.getTopicColors(color)
let colors = topicIconColors(for: color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0))
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
@ -2993,19 +2993,6 @@ public final class EmojiPagerContentComponent: Component {
return nullAction
}
private func getTopicColors(_ color: Int32) -> ([UInt32], [UInt32]) {
let topicColors: [Int32: ([UInt32], [UInt32])] = [
0x6FB9F0: ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7]),
0xFFD67E: ([0xFFD67E, 0xFC8601], [0xDA9400, 0xFA5F00]),
0xCB86DB: ([0xCB86DB, 0x9338AF], [0x812E98, 0x6F2B87]),
0x8EEE98: ([0x8EEE98, 0x02B504], [0x02A01B, 0x009716]),
0xFF93B2: ([0xFF93B2, 0xE23264], [0xFC447A, 0xC80C46]),
0xFB6F5F: ([0xFB6F5F, 0xD72615], [0xDC1908, 0xB61506])
]
return topicColors[color] ?? ([0x6FB9F0, 0x0261E4], [0x026CB5, 0x064BB7])
}
func update(content: ItemContent) {
if self.content != content {
if case let .icon(icon) = content, case let .topic(title, color) = icon {
@ -3014,7 +3001,7 @@ public final class EmojiPagerContentComponent: Component {
UIGraphicsPushContext(context)
let colors = self.getTopicColors(color)
let colors = topicIconColors(for: color)
if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) {
let imageSize = image.size
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))

View File

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

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)
}, navigateToMessageStandalone: { [weak self] id in
self?.navigateToMessage(from: nil, to: .id(id, nil), forceInCurrentChat: false)
}, navigateToThreadMessage: { [weak self] peerId, threadId, messageId in
if let context = self?.context, let navigationController = self?.effectiveNavigationController {
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: messageId, navigationController: navigationController, activateInput: nil, keepStack: .always).start()
}
}, tapMessage: nil, clickThroughMessage: { [weak self] in
self?.chatDisplayNode.dismissInput()
}, toggleMessagesSelection: { [weak self] ids, value in

View File

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

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 dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?), NoError> = combineLatest(
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
loadLimits,
loadStickerSaveStatusSignal,
loadResourceStatusSignal,
@ -626,9 +626,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager),
context.engine.stickers.availableReactions(),
context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]),
context.engine.peers.notificationSoundList() |> take(1)
context.engine.peers.notificationSoundList() |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?) in
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false
if !isAction {
@ -652,12 +653,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
loggingSettings = LoggingSettings.defaultSettings
}
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList)
return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer)
}
return dataSignal
|> deliverOnMainQueue
|> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList -> ContextController.Items in
|> map { data, updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer -> ContextController.Items in
let isPremium = accountPeer?.isPremium ?? false
var actions: [ContextMenuItem] = []
var isPinnedMessages = false
@ -794,7 +797,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string))
} else {
let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file))
|> deliverOnMainQueue).start(completed: {
|> deliverOnMainQueue).start(completed: {
controllerInteraction.displayUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_SaveSuccess_Text, action: {
controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil))
}))
@ -937,6 +940,53 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
var isDownloading = false
let resourceAvailable: Bool
if let resourceStatus = data.resourceStatus {
if case .Local = resourceStatus {
resourceAvailable = true
} else {
resourceAvailable = false
}
if case .Fetching = resourceStatus {
isDownloading = true
}
} else {
resourceAvailable = false
}
if !isPremium && isDownloading {
var isLargeFile = false
for media in message.media {
if let file = media as? TelegramMediaFile {
if let size = file.size, size >= 300 * 1024 * 1024 {
isLargeFile = true
}
break
}
}
if isLargeFile {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_IncreaseSpeed, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Speed"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
let context = context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .fasterDownload, action: {
let controller = PremiumIntroScreen(context: context, source: .fasterDownload)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
controllerInteraction.navigationController()?.pushViewController(controller)
f(.dismissWithoutContent)
})))
actions.append(.separator)
}
}
var isReplyThreadHead = false
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
@ -970,12 +1020,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})))
}
let resourceAvailable: Bool
if let resourceStatus = data.resourceStatus, case .Local = resourceStatus {
resourceAvailable = true
} else {
resourceAvailable = false
}
var messageText: String = ""
for message in messages {

View File

@ -58,8 +58,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode {
class ChatMessageShareButton: HighlightableButtonNode {
private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundNode: NavigationBackgroundNode
private let iconNode: ASImageNode
private var iconOffset = CGPoint()
@ -242,7 +242,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -472,6 +474,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.updateVisibility()
self.haptic?.enabled = self.visibilityStatus == true
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.visibility = self.visibilityStatus == true
}
}
@ -789,6 +792,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
}
if let threadInfoNode = self.threadInfoNode {
var threadInfoNodeFrame = threadInfoNode.frame
threadInfoNodeFrame.origin.x += rect.minX
threadInfoNodeFrame.origin.y += rect.minY
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
}
if let shareButtonNode = self.shareButtonNode {
var shareButtonNodeFrame = shareButtonNode.frame
shareButtonNodeFrame.origin.x += rect.minX
shareButtonNodeFrame.origin.y += rect.minY
shareButtonNode.updateAbsoluteRect(shareButtonNodeFrame, within: containerSize)
}
if let actionButtonsNode = self.actionButtonsNode {
var actionButtonsNodeFrame = actionButtonsNode.frame
actionButtonsNodeFrame.origin.x += rect.minX
actionButtonsNodeFrame.origin.y += rect.minY
actionButtonsNode.updateAbsoluteRect(actionButtonsNodeFrame, within: containerSize)
}
if let reactionButtonsNode = self.reactionButtonsNode {
var reactionButtonsNodeFrame = reactionButtonsNode.frame
reactionButtonsNodeFrame.origin.x += rect.minX
@ -796,6 +823,14 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
if let replyBackgroundContent = self.replyBackgroundContent {
var replyBackgroundContentFrame = replyBackgroundContent.frame
replyBackgroundContentFrame.origin.x += rect.minX
replyBackgroundContentFrame.origin.y += rect.minY
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
}
}
}
@ -855,6 +890,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo
@ -1124,11 +1160,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var needsReplyBackground = false
var replyMarkup: ReplyMarkupMessageAttribute?
let availableContentWidth = min(120.0, max(60.0, params.width - params.leftInset - params.rightInset - max(imageSize.width, 160.0) - 20.0 - layoutConstants.bubble.edgeInset * 2.0 - avatarInset - layoutConstants.bubble.contentInsets.left))
var ignoreForward = false
@ -1166,8 +1202,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
var hasReply = true
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else {
hasReply = false
} else if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
hasReply = false
}
if case .peer = item.chatLocation, replyMessage.threadId != nil {
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .standalone,
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
}
if hasReply {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
@ -1425,9 +1483,41 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
}
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
strongSelf.replyBackgroundContent = backgroundContent
strongSelf.insertSubnode(backgroundContent, at: 0)
}
} else {
strongSelf.replyBackgroundContent?.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
strongSelf.replyBackgroundNode = nil
replyBackgroundNode.removeFromSupernode()
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
replyBackgroundContent.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
}
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
let threadInfoNode = threadInfoApply(synchronousLoads)
if strongSelf.threadInfoNode == nil {
strongSelf.threadInfoNode = threadInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
}
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
threadInfoNode.frame = threadInfoFrame
headersOffset += threadInfoSize.height + 10.0
} else if let replyInfoNode = strongSelf.replyInfoNode {
replyInfoNode.removeFromSupernode()
strongSelf.replyInfoNode = nil
}
var messageInfoSize = CGSize()
@ -1447,7 +1537,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
}
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size)
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
@ -1466,7 +1556,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize)
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -1490,7 +1580,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
}
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
@ -1500,13 +1590,30 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
if let backgroundContent = strongSelf.replyBackgroundContent {
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.isHidden = true
backgroundContent.cornerRadius = cornerRadius
backgroundContent.frame = replyBackgroundNode.frame
if let (rect, containerSize) = strongSelf.absoluteRect {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
} else {
replyBackgroundNode.isHidden = false
}
}
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha

View File

@ -510,6 +510,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var forwardInfoReferenceNode: ASDisplayNode? {
return self.forwardInfoNode
}
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode?
private var contentContainersWrapperNode: ASDisplayNode
@ -547,6 +549,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
contentNode.visibility = mapVisibility(self.visibility, boundsSize: self.bounds.size, insets: self.insets, to: contentNode)
}
if let threadInfoNode = self.threadInfoNode {
threadInfoNode.visibility = self.visibility != .none
}
if let replyInfoNode = self.replyInfoNode {
replyInfoNode.visibility = self.visibility != .none
}
@ -932,6 +938,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
}
if let threadInfoNode = strongSelf.threadInfoNode, threadInfoNode.frame.contains(point) {
if let _ = threadInfoNode.hitTest(strongSelf.view.convert(point, to: threadInfoNode.view), with: nil) {
return .fail
}
}
if let replyInfoNode = strongSelf.replyInfoNode, replyInfoNode.frame.contains(point) {
return .waitForSingleTap
}
@ -1054,6 +1065,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let authorNameLayout = TextNode.asyncLayout(self.nameNode)
let adminBadgeLayout = TextNode.asyncLayout(self.adminBadgeNode)
let threadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let forwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let replyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
@ -1076,6 +1088,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentContentClassesPropertiesAndLayouts: currentContentClassesPropertiesAndLayouts,
authorNameLayout: authorNameLayout,
adminBadgeLayout: adminBadgeLayout,
threadInfoLayout: threadInfoLayout,
forwardInfoLayout: forwardInfoLayout,
replyInfoLayout: replyInfoLayout,
actionButtonsLayout: actionButtonsLayout,
@ -1093,6 +1106,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode),
forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode),
replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode),
actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)),
@ -1834,6 +1848,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var nameNodeOriginY: CGFloat = 0.0
var nameNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var adminNodeSizeApply: (CGSize, () -> TextNode?) = (CGSize(), { nil })
var threadInfoOriginY: CGFloat = 0.0
var threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?) = (CGSize(), { _ in nil })
var replyInfoOriginY: CGFloat = 0.0
var replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?) = (CGSize(), { _ in nil })
@ -1945,7 +1962,37 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
headerSize.height += forwardInfoSizeApply.0.height
}
if !isInstantVideo, let replyMessage = replyMessage {
var hasReply = replyMessage != nil
if !isInstantVideo, let replyMessage = replyMessage, replyMessage.threadId != nil, case .peer = item.chatLocation {
if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
hasReply = false
}
if headerSize.height.isZero {
headerSize.height += 14.0
} else {
headerSize.height += 5.0
}
let sizeAndApply = threadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .bubble(incoming: incoming),
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
threadInfoSizeApply = (sizeAndApply.0, { synchronousLoads in sizeAndApply.1(synchronousLoads) })
threadInfoOriginY = headerSize.height
headerSize.width = max(headerSize.width, threadInfoSizeApply.0.width + layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right)
headerSize.height += threadInfoSizeApply.0.height + 5.0
}
if !isInstantVideo, let replyMessage = replyMessage, hasReply {
if headerSize.height.isZero {
headerSize.height += 6.0
} else {
@ -2401,6 +2448,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: currentCredibilityIcon,
adminNodeSizeApply: adminNodeSizeApply,
contentUpperRightCorner: contentUpperRightCorner,
threadInfoSizeApply: threadInfoSizeApply,
threadInfoOriginY: threadInfoOriginY,
forwardInfoSizeApply: forwardInfoSizeApply,
forwardInfoOriginY: forwardInfoOriginY,
replyInfoSizeApply: replyInfoSizeApply,
@ -2449,6 +2498,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
currentCredibilityIcon: EmojiStatusComponent.Content?,
adminNodeSizeApply: (CGSize, () -> TextNode?),
contentUpperRightCorner: CGPoint,
threadInfoSizeApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode?),
threadInfoOriginY: CGFloat,
forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode?),
forwardInfoOriginY: CGFloat,
replyInfoSizeApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode?),
@ -2721,6 +2772,40 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
if let threadInfoNode = threadInfoSizeApply.1(synchronousLoads) {
strongSelf.threadInfoNode = threadInfoNode
var animateFrame = true
if threadInfoNode.supernode == nil {
strongSelf.clippingNode.addSubnode(threadInfoNode)
animateFrame = false
threadInfoNode.visibility = strongSelf.visibility != .none
if animation.isAnimated {
threadInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
let previousThreadInfoNodeFrame = threadInfoNode.frame
threadInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + threadInfoOriginY), size: threadInfoSizeApply.0)
if case let .System(duration, _) = animation {
if animateFrame {
threadInfoNode.layer.animateFrame(from: previousThreadInfoNodeFrame, to: threadInfoNode.frame, duration: duration, timingFunction: timingFunction)
}
}
} else {
if animation.isAnimated {
if let threadInfoNode = strongSelf.threadInfoNode {
strongSelf.threadInfoNode = nil
threadInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak threadInfoNode] _ in
threadInfoNode?.removeFromSupernode()
})
}
} else {
strongSelf.threadInfoNode?.removeFromSupernode()
strongSelf.threadInfoNode = nil
}
}
if let replyInfoNode = replyInfoSizeApply.1(synchronousLoads) {
strongSelf.replyInfoNode = replyInfoNode
var animateFrame = true
@ -3498,6 +3583,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
}
} else if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
if let item = self.item {
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute, let threadId = attribute.threadMessageId {
return .optionalAction({
item.controllerInteraction.navigateToThreadMessage(item.message.id.peerId, Int64(clamping: threadId.id), item.message.id)
})
}
}
}
}
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
if let item = self.item, let forwardInfo = item.message.forwardInfo {
@ -3652,6 +3747,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let item = self.item, self.backgroundNode.frame.contains(location) {
let message = item.message
if let threadInfoNode = self.threadInfoNode, self.item?.controllerInteraction.tapMessage == nil, threadInfoNode.frame.contains(location) {
return .action({})
}
var tapMessage: Message? = item.content.firstMessage
var selectAll = true
var hasFiles = false
@ -3773,6 +3872,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return nil
}
if let threadInfoNode = self.threadInfoNode, let result = threadInfoNode.hitTest(self.view.convert(point, to: threadInfoNode.view), with: event) {
return result
}
if let shareButtonNode = self.shareButtonNode, shareButtonNode.frame.contains(point) {
return shareButtonNode.view
}
@ -3803,7 +3906,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
return result
}
}
return super.hitTest(point, with: event)
}

View File

@ -495,7 +495,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
return
}
var messageId: MessageId?
if let messageReference = messageReference, case let .message(_, id, _, _, _) = messageReference.content {
if let messageReference = messageReference, case let .message(_, _, id, _, _, _) = messageReference.content {
messageId = id
}
strongSelf.controllerInteraction.openPeerContextMenu(peer, messageId, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture)
@ -641,7 +641,7 @@ final class ChatMessageAvatarHeaderNode: ListViewItemHeaderNode {
@objc func tapGesture(_ recognizer: ListViewTapGestureRecognizer) {
if case .ended = recognizer.state {
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, id, _, _, _) = self.messageReference?.content {
if self.peerId.namespace == Namespaces.Peer.Empty, case let .message(_, _, id, _, _, _) = self.messageReference?.content {
self.controllerInteraction.displayMessageTooltip(id, self.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, self, self.avatarNode.frame)
} else if let peer = self.peer {
if let adMessageId = self.adMessageId {

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)
if let threadId = arguments.parentMessage.threadId, Int64(arguments.message.id.id) == threadId, let channel = arguments.parentMessage.peers[arguments.parentMessage.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum), let threadInfo = arguments.parentMessage.associatedThreadInfo {
titleString = "\(threadInfo.title)"
textString = NSAttributedString()
}
let (textString, isMedia, isText) = descriptionStringForMessage(contentSettings: arguments.context.currentContentSettings.with { $0 }, message: EngineMessage(arguments.message), strings: arguments.strings, nameDisplayOrder: arguments.presentationData.nameDisplayOrder, dateTimeFormat: arguments.presentationData.dateTimeFormat, accountPeerId: arguments.context.account.peerId)
let placeholderColor: UIColor = arguments.message.effectivelyIncoming(arguments.context.account.peerId) ? arguments.presentationData.theme.theme.chat.message.incoming.mediaPlaceholderColor : arguments.presentationData.theme.theme.chat.message.outgoing.mediaPlaceholderColor
let titleColor: UIColor

View File

@ -38,7 +38,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var viaBotNode: TextNode?
private let dateAndStatusNode: ChatMessageDateAndStatusNode
private var threadInfoNode: ChatMessageThreadInfoNode?
private var replyInfoNode: ChatMessageReplyInfoNode?
private var replyBackgroundContent: WallpaperBubbleBackgroundNode?
private var replyBackgroundNode: NavigationBackgroundNode?
private var forwardInfoNode: ChatMessageForwardInfoNode?
@ -71,6 +73,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private var visibilityStatus: Bool? {
didSet {
if self.visibilityStatus != oldValue {
self.threadInfoNode?.visibility = self.visibilityStatus == true
self.replyInfoNode?.visibility = self.visibilityStatus == true
}
}
@ -277,6 +280,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
backgroundNode.update(rect: CGRect(origin: CGPoint(x: rect.minX + self.placeholderNode.frame.minX, y: rect.minY + self.placeholderNode.frame.minY), size: self.placeholderNode.frame.size), within: containerSize, transition: .immediate)
}
if let threadInfoNode = self.threadInfoNode {
var threadInfoNodeFrame = threadInfoNode.frame
threadInfoNodeFrame.origin.x += rect.minX
threadInfoNodeFrame.origin.y += rect.minY
threadInfoNode.updateAbsoluteRect(threadInfoNodeFrame, within: containerSize)
}
if let shareButtonNode = self.shareButtonNode {
var shareButtonNodeFrame = shareButtonNode.frame
shareButtonNodeFrame.origin.x += rect.minX
@ -300,6 +311,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
reactionButtonsNode.update(rect: rect, within: containerSize, transition: .immediate)
}
if let replyBackgroundContent = self.replyBackgroundContent {
var replyBackgroundContentFrame = replyBackgroundContent.frame
replyBackgroundContentFrame.origin.x += rect.minX
replyBackgroundContentFrame.origin.y += rect.minY
replyBackgroundContent.update(rect: rect, within: containerSize, transition: .immediate)
}
}
}
@ -357,6 +376,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
let makeThreadInfoLayout = ChatMessageThreadInfoNode.asyncLayout(self.threadInfoNode)
let makeReplyInfoLayout = ChatMessageReplyInfoNode.asyncLayout(self.replyInfoNode)
let currentShareButtonNode = self.shareButtonNode
let currentForwardInfo = self.appliedForwardInfo
@ -567,6 +587,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let (dateAndStatusSize, dateAndStatusApply) = statusSuggestedWidthAndContinue.1(statusSuggestedWidthAndContinue.0)
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var threadInfoApply: (CGSize, (Bool) -> ChatMessageThreadInfoNode)?
var replyInfoApply: (CGSize, (Bool) -> ChatMessageReplyInfoNode)?
var replyMarkup: ReplyMarkupMessageAttribute?
@ -610,8 +631,30 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
if let replyAttribute = attribute as? ReplyMessageAttribute, let replyMessage = item.message.associatedMessages[replyAttribute.messageId] {
var hasReply = true
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.messageId == replyAttribute.messageId {
} else {
hasReply = false
} else if let threadId = item.message.threadId, Int64(replyMessage.id.id) == threadId, let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, channel.flags.contains(.isForum) {
hasReply = false
}
if case .peer = item.chatLocation, replyMessage.threadId != nil {
threadInfoApply = makeThreadInfoLayout(ChatMessageThreadInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
context: item.context,
controllerInteraction: item.controllerInteraction,
type: .standalone,
message: replyMessage,
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
))
}
if hasReply {
replyInfoApply = makeReplyInfoLayout(ChatMessageReplyInfoNode.Arguments(
presentationData: item.presentationData,
strings: item.presentationData.strings,
@ -764,9 +807,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0
}
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, _) = threadInfoApply {
headersOffset += threadInfoSize.height + 10.0
}
var viaBotFrame: CGRect?
if let (viaBotLayout, _) = viaBotApply {
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size)
viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
}
var replyInfoFrame: CGRect?
@ -775,7 +823,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let viaBotFrame = viaBotFrame {
viaBotSize = viaBotFrame.size
}
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize)
let replyInfoFrameValue = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: headersOffset + 8.0 + viaBotSize.height), size: replyInfoSize)
replyInfoFrame = replyInfoFrameValue
if let viaBotFrameValue = viaBotFrame {
if replyInfoFrameValue.minX < replyInfoFrameValue.minX {
@ -791,7 +839,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
viaBotSize = viaBotFrame.size
}
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: headersOffset + replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0))
}
if let replyBackgroundFrameValue = replyBackgroundFrame {
@ -875,9 +923,41 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyBackgroundNode = replyBackgroundNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyBackgroundNode)
}
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if strongSelf.replyBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
strongSelf.replyBackgroundContent = backgroundContent
strongSelf.insertSubnode(backgroundContent, at: 0)
}
} else {
strongSelf.replyBackgroundContent?.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
} else if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.removeFromSupernode()
strongSelf.replyBackgroundNode = nil
if let replyBackgroundContent = strongSelf.replyBackgroundContent {
replyBackgroundContent.removeFromSupernode()
strongSelf.replyBackgroundContent = nil
}
}
var headersOffset: CGFloat = 0.0
if let (threadInfoSize, threadInfoApply) = threadInfoApply {
let threadInfoNode = threadInfoApply(synchronousLoads)
if strongSelf.threadInfoNode == nil {
strongSelf.threadInfoNode = threadInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(threadInfoNode)
}
let threadInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 6.0) : (params.width - params.rightInset - threadInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0), size: threadInfoSize)
threadInfoNode.frame = threadInfoFrame
headersOffset += threadInfoSize.height + 10.0
} else if let replyInfoNode = strongSelf.replyInfoNode {
replyInfoNode.removeFromSupernode()
strongSelf.replyInfoNode = nil
}
var messageInfoSize = CGSize()
@ -897,7 +977,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.viaBotNode = viaBotNode
strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode)
}
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size)
let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0), size: viaBotLayout.size)
viaBotNode.frame = viaBotFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height)
@ -916,7 +996,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize)
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: forwardInfoSize)
forwardInfoNode.frame = forwardInfoFrame
messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0)
@ -940,7 +1020,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = replyInfoNode
strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode)
}
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize)
let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: headersOffset + 8.0 + messageInfoSize.height), size: replyInfoSize)
replyInfoNode.frame = replyInfoFrame
messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height)
@ -949,15 +1029,31 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.replyInfoNode = nil
}
if let replyBackgroundNode = strongSelf.replyBackgroundNode {
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: headersOffset + 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0))
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate)
if let backgroundContent = strongSelf.replyBackgroundContent {
let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0
replyBackgroundNode.isHidden = true
backgroundContent.cornerRadius = cornerRadius
backgroundContent.frame = replyBackgroundNode.frame
if let (rect, containerSize) = strongSelf.absoluteRect {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
} else {
replyBackgroundNode.isHidden = false
}
}
let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0
strongSelf.threadInfoNode?.alpha = panelsAlpha
strongSelf.replyInfoNode?.alpha = panelsAlpha
strongSelf.viaBotNode?.alpha = panelsAlpha
strongSelf.forwardInfoNode?.alpha = panelsAlpha

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
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in

View File

@ -112,6 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
return false }, openPeer: { _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in
}, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in
}, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in

View File

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

View File

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

View File

@ -173,16 +173,16 @@ private final class PrefetchManagerInnerImpl {
if case .full = automaticDownload {
if let image = media as? TelegramMediaImage {
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
context.fetchDisposable.set(messageMediaImageInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), image: image, resource: resource, userInitiated: false, priority: priority, storeToDownloadsPeerType: nil).start())
} else if let _ = media as? TelegramMediaWebFile {
//strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
} else if let file = media as? TelegramMediaFile {
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
let fetchSignal = messageMediaFileInteractiveFetched(fetchManager: self.fetchManager, messageId: mediaItem.media.index.id, messageReference: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), file: file, userInitiated: false, priority: priority)
context.fetchDisposable.set(fetchSignal.start())
}
} else if case .prefetch = automaticDownload, mediaItem.media.peer.id.namespace != Namespaces.Peer.SecretChat {
if let file = media as? TelegramMediaFile, let _ = file.size {
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
context.fetchDisposable.set(preloadVideoResource(postbox: self.account.postbox, resourceReference: FileMediaReference.message(message: MessageReference(peer: mediaItem.media.peer, author: nil, id: mediaItem.media.index.id, timestamp: mediaItem.media.index.timestamp, incoming: true, secret: false), media: file).resourceReference(file.resource), duration: 4.0).start())
}
}
}

View File

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