From 8fdcb91bbc679ff8e660b211eac8bb4c8a94d93b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 10 Aug 2024 00:16:43 +0200 Subject: [PATCH] Various improvements --- .../ChatItemGalleryFooterContentNode.swift | 7 +- .../Sources/Items/ChatImageGalleryItem.swift | 124 ++++++++++++++++- .../Items/UniversalVideoGalleryItem.swift | 130 +++++++++++++++++- 3 files changed, 254 insertions(+), 7 deletions(-) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 8a980e6a89..7105ccdb9b 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -772,7 +772,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll func setup(origin: GalleryItemOriginData?, caption: NSAttributedString, isAd: Bool = false) { var titleText = origin?.title - let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0).string } + var dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0).string } let caption = caption.mutableCopy() as! NSMutableAttributedString if isAd { @@ -780,6 +780,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll caption.insert(NSAttributedString(string: titleText + "\n", font: Font.semibold(17.0), textColor: .white), at: 0) } titleText = nil + dateText = nil } if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText { @@ -998,7 +999,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } messageText = galleryCaptionStringWithAppliedEntities(context: self.context, text: text, entities: entities, message: message, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight).mutableCopy() as! NSMutableAttributedString - messageText.insert(NSAttributedString(string: (authorNameText ?? "") + "\n", font: Font.semibold(17.0), textColor: .white), at: 0) + if let _ = message.adAttribute { + messageText.insert(NSAttributedString(string: (authorNameText ?? "") + "\n", font: Font.semibold(17.0), textColor: .white), at: 0) + } } if !displayInfo { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index b1bdf5aba1..2fa3300c5f 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -21,6 +21,7 @@ import ShareController import UndoUI import ContextUI import SaveToCameraRoll +import Pasteboard enum ChatMediaGalleryThumbnail: Equatable { case image(ImageMediaReference) @@ -532,6 +533,121 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.moreBarButton.play() self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) } + + private func adMenuMainItems() -> Signal<[ContextMenuItem], NoError> { + guard let message = self.message, let adAttribute = message.adAttribute else { + return .single([]) + } + + let context = self.context + let presentationData = self.presentationData + var actions: [ContextMenuItem] = [] + if adAttribute.canReport { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AboutAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) + +// controllerInteraction.navigationController()?.pushViewController(AdsInfoScreen(context: context)) + }))) + + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.default) + + let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) + |> deliverOnMainQueue).start(next: { result in + if case let .options(title, options) = result { + let _ = title + let _ = options +// controllerInteraction.navigationController()?.pushViewController( +// AdsReportScreen( +// context: context, +// peerId: message.id.peerId, +// opaqueId: adAttribute.opaqueId, +// title: title, +// options: options, +// completed: { [weak interfaceInteraction] in +// guard let interfaceInteraction else { +// return +// } +// guard let chatController = interfaceInteraction.chatController() as? ChatControllerImpl else { +// return +// } +// chatController.removeAd(opaqueId: adAttribute.opaqueId) +// } +// ) +// ) + } + }) + }))) + + actions.append(.separator) + + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + c?.dismiss(completion: { +// controllerInteraction.openNoAdsDemo() + }) + }))) + } else { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) +// controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context)) + }))) + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !context.isPremium && !premiumConfiguration.isPremiumDisabled { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + c?.dismiss(completion: { + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } +// controllerInteraction.navigationController()?.pushViewController(controller) + }) + }))) + } + + if !message.text.isEmpty { + + actions.append(.separator) + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak self] _, f in + var messageEntities: [MessageTextEntity]? + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + messageEntities = attribute.entities + } + } + + storeMessageTextInPasteboard(message.text, entities: messageEntities) + + Queue.mainQueue().after(0.2, { + guard let self, let controller = self.galleryController() else { + return + } + controller.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_MessageCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }) + + f(.default) + }))) + } + } + + return .single(actions) + } private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> { let peer: Signal @@ -609,11 +725,15 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { } private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) { - let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems() + let items: Signal<[ContextMenuItem], NoError> + if let message = self.message, let _ = message.adAttribute { + items = self.adMenuMainItems() + } else { + items = self.contextMenuMainItems() + } guard let controller = self.baseNavigationController()?.topViewController as? ViewController else { return } - let contextController = ContextController(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) } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index f9136ac677..a66dc812bd 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -23,6 +23,7 @@ import OpenInExternalAppUI import AVKit import TextFormat import SliderContextItem +import Pasteboard public enum UniversalVideoGalleryItemContentInfo { case message(Message, Int?) @@ -89,6 +90,8 @@ public class UniversalVideoGalleryItem: GalleryItem { if let indexData = self.indexData { node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string)) + } else if case let .message(message, _) = self.contentInfo, let _ = message.adAttribute { + node._title.set(.single(self.presentationData.strings.Gallery_Ad)) } node.setupItem(self) @@ -2480,9 +2483,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return } var dismissImpl: (() -> Void)? - let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems(dismiss: { - dismissImpl?() - }) + let items: Signal<[ContextMenuItem], NoError> + if case let .message(message, _) = self.item?.contentInfo, let _ = message.adAttribute { + items = self.adMenuMainItems() + } else { + items = self.contextMenuMainItems(dismiss: { + dismissImpl?() + }) + } + let contextController = ContextController(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) self.isShowingContextMenuPromise.set(true) controller.presentInGlobalOverlay(contextController) @@ -2506,6 +2515,121 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { return speedList } + + private func adMenuMainItems() -> Signal<[ContextMenuItem], NoError> { + guard case let .message(message, _) = self.item?.contentInfo, let adAttribute = message.adAttribute else { + return .single([]) + } + + let context = self.context + let presentationData = self.presentationData + var actions: [ContextMenuItem] = [] + if adAttribute.canReport { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_AboutAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) + +// controllerInteraction.navigationController()?.pushViewController(AdsInfoScreen(context: context)) + }))) + + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_ReportAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.default) + + let _ = (context.engine.messages.reportAdMessage(peerId: message.id.peerId, opaqueId: adAttribute.opaqueId, option: nil) + |> deliverOnMainQueue).start(next: { result in + if case let .options(title, options) = result { + let _ = title + let _ = options +// controllerInteraction.navigationController()?.pushViewController( +// AdsReportScreen( +// context: context, +// peerId: message.id.peerId, +// opaqueId: adAttribute.opaqueId, +// title: title, +// options: options, +// completed: { [weak interfaceInteraction] in +// guard let interfaceInteraction else { +// return +// } +// guard let chatController = interfaceInteraction.chatController() as? ChatControllerImpl else { +// return +// } +// chatController.removeAd(opaqueId: adAttribute.opaqueId) +// } +// ) +// ) + } + }) + }))) + + actions.append(.separator) + + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Chat_ContextMenu_RemoveAd, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + c?.dismiss(completion: { +// controllerInteraction.openNoAdsDemo() + }) + }))) + } else { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Info, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { _, f in + f(.dismissWithoutContent) +// controllerInteraction.navigationController()?.pushViewController(AdInfoScreen(context: context)) + }))) + + let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) + if !context.isPremium && !premiumConfiguration.isPremiumDisabled { + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.SponsoredMessageMenu_Hide, textColor: .primary, textLayout: .twoLinesMax, textFont: .custom(font: Font.regular(presentationData.listsFontSize.baseDisplaySize - 1.0), height: nil, verticalOffset: nil), badge: nil, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.primaryTextColor) + }, iconSource: nil, action: { c, _ in + c?.dismiss(completion: { + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .noAds, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .ads, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } +// controllerInteraction.navigationController()?.pushViewController(controller) + }) + }))) + } + + if !message.text.isEmpty { + actions.append(.separator) + actions.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuCopy, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) + }, action: { [weak self] _, f in + var messageEntities: [MessageTextEntity]? + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + messageEntities = attribute.entities + } + } + + storeMessageTextInPasteboard(message.text, entities: messageEntities) + + Queue.mainQueue().after(0.2, { + guard let self, let controller = self.galleryController() else { + return + } + controller.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_MessageCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }) + + f(.default) + }))) + } + } + + return .single(actions) + } + private func contextMenuMainItems(dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> { guard let videoNode = self.videoNode, let item = self.item else {