From 9a038722d318fa7880ab4263dc5ee304965e99ce Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 20 Nov 2024 22:09:16 +0400 Subject: [PATCH] Various improvements --- .../Sources/ChatListController.swift | 21 ++++ .../Sources/ChatListControllerNode.swift | 4 + .../Sources/ChatListSearchListPaneNode.swift | 2 + .../Sources/ChatListShimmerNode.swift | 1 + .../Sources/Node/ChatListItem.swift | 103 ++++++++++++++++-- .../Sources/Node/ChatListNode.swift | 11 +- .../GalleryData/Sources/GalleryData.swift | 6 +- .../TextSizeSelectionController.swift | 1 + .../Themes/ThemePreviewControllerNode.swift | 1 + submodules/StickerPackPreviewUI/BUILD | 1 + .../Sources/StickerPackScreen.swift | 100 ++++++++++++++++- ...pandedParticipantThumbnailsComponent.swift | 15 ++- .../VideoChatParticipantVideoComponent.swift | 8 +- .../VideoChatParticipantsComponent.swift | 36 +++++- .../Sources/VideoChatScreen.swift | 3 + .../Sources/VideoChatScreenMoreMenu.swift | 50 +++++++++ ...ChatInlineSearchResultsListComponent.swift | 2 + .../PeerInfoScreenPersonalChannelItem.swift | 3 + ...aticBusinessMessageListItemComponent.swift | 2 + .../Sources/QuickReplySetupScreen.swift | 2 + .../ThemeAccentColorControllerNode.swift | 1 + .../Chat/ChatControllerThemeManagement.swift | 4 +- .../TelegramUI/Sources/ChatController.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 1 + .../CommandChatInputContextPanelNode.swift | 2 + .../TelegramUI/Sources/OpenChatMessage.swift | 4 +- 26 files changed, 355 insertions(+), 31 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 349fbfa725..dbe316a148 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1182,6 +1182,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.openStarsTopup(amount: amount) } + self.chatListDisplayNode.mainContainerNode.openWebApp = { [weak self] user in + guard let self else { + return + } + //TODO:localize + self.context.sharedContext.openWebApp( + context: self.context, + parentController: self, + updatedPresentationData: self.updatedPresentationData, + botPeer: .user(user), + chatPeer: nil, + threadId: nil, + buttonText: "", + url: "", + simple: true, + source: .generic, + skipTermsOfService: true, + payload: nil + ) + } + self.chatListDisplayNode.mainContainerNode.openPremiumManagement = { [weak self] in guard let self else { return diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index f1ba822484..f6a06ce313 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -351,6 +351,9 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele itemNode.listNode.openStarsTopup = { [weak self] amount in self?.openStarsTopup?(amount) } + itemNode.listNode.openWebApp = { [weak self] amount in + self?.openWebApp?(amount) + } self.currentItemStateValue.set(itemNode.listNode.state |> map { state in let filterId: Int32? @@ -417,6 +420,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele var openPremiumManagement: (() -> Void)? var openStories: ((ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void)? var openStarsTopup: ((Int64?) -> Void)? + var openWebApp: ((TelegramUser) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var didBeginSelectingChats: (() -> Void)? var canExpandHiddenItems: (() -> Bool)? diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c052301330..531ebdc87c 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -3013,6 +3013,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) chatListInteraction.isSearchMode = true @@ -4905,6 +4906,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) var isInlineMode = false if case .topics = key { diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index bf3f6756d4..11d4fafc1d 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -159,6 +159,7 @@ public final class ChatListShimmerNode: ASDisplayNode { }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) interaction.isInlineMode = isInlineMode diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index a4879d5143..d91251f94f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -1234,6 +1234,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var credibilityIconComponent: EmojiStatusComponent? let mutedIconNode: ASImageNode var itemTagList: ComponentView? + var actionButtonTitleNode: TextNode? + var actionButtonBackgroundView: UIImageView? + var actionButtonNode: HighlightableButtonNode? private var hierarchyTrackingLayer: HierarchyTrackingLayer? private var cachedDataDisposable = MetaDisposable() @@ -1890,6 +1893,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let onlineLayout = self.onlineNode.asyncLayout() let selectableControlLayout = ItemListSelectableControlNode.asyncLayout(self.selectableControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) + let makeActionButtonTitleNodeLayout = TextNode.asyncLayout(self.actionButtonTitleNode) let currentItem = self.layoutParams?.0 let currentChatListText = self.cachedChatListText @@ -3061,6 +3065,12 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { let (mentionBadgeLayout, mentionBadgeApply) = mentionBadgeLayout(CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), badgeDiameter, badgeFont, currentMentionBadgeImage, mentionBadgeContent) + var actionButtonTitleNodeLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if case .none = badgeContent, case .none = mentionBadgeContent, case let .chat(itemPeer) = contentPeer, case let .user(user) = itemPeer.chatMainPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + //TODO:localize + actionButtonTitleNodeLayoutAndApply = makeActionButtonTitleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "OPEN", font: Font.semibold(15.0), textColor: theme.unreadBadgeActiveTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + } + var badgeSize: CGFloat = 0.0 if !badgeLayout.width.isZero { badgeSize += badgeLayout.width + 5.0 @@ -3081,6 +3091,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } badgeSize += currentPinnedIconImage.size.width } + if let (actionButtonTitleNodeLayout, _) = actionButtonTitleNodeLayoutAndApply { + if !badgeSize.isZero { + badgeSize += 4.0 + } else { + badgeSize += 5.0 + } + badgeSize += actionButtonTitleNodeLayout.size.width + 12.0 * 2.0 + } badgeSize = max(badgeSize, reorderInset) if !itemTags.isEmpty { @@ -3816,23 +3834,19 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent) + var nextBadgeX: CGFloat = contentRect.maxX if let _ = currentBadgeBackgroundImage { - let badgeFrame = CGRect(x: contentRect.maxX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0, width: badgeLayout.width, height: badgeLayout.height) + let badgeFrame = CGRect(x: nextBadgeX - badgeLayout.width, y: contentRect.maxY - badgeLayout.height - 2.0, width: badgeLayout.width, height: badgeLayout.height) transition.updateFrame(node: strongSelf.badgeNode, frame: badgeFrame) + nextBadgeX -= badgeLayout.width + 6.0 } if currentMentionBadgeImage != nil || currentBadgeBackgroundImage != nil { - let mentionBadgeOffset: CGFloat - if badgeLayout.width.isZero { - mentionBadgeOffset = contentRect.maxX - mentionBadgeLayout.width - } else { - mentionBadgeOffset = contentRect.maxX - badgeLayout.width - 6.0 - mentionBadgeLayout.width - } - - let badgeFrame = CGRect(x: mentionBadgeOffset, y: contentRect.maxY - mentionBadgeLayout.height - 2.0, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height) + let badgeFrame = CGRect(x: nextBadgeX - mentionBadgeLayout.width, y: contentRect.maxY - mentionBadgeLayout.height - 2.0, width: mentionBadgeLayout.width, height: mentionBadgeLayout.height) transition.updateFrame(node: strongSelf.mentionBadgeNode, frame: badgeFrame) + nextBadgeX -= mentionBadgeLayout.width + 6.0 } if let currentPinnedIconImage = currentPinnedIconImage { @@ -3840,14 +3854,71 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.pinnedIconNode.isHidden = false let pinnedIconSize = currentPinnedIconImage.size - let pinnedIconFrame = CGRect(x: contentRect.maxX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0, width: pinnedIconSize.width, height: pinnedIconSize.height) + let pinnedIconFrame = CGRect(x: nextBadgeX - pinnedIconSize.width, y: contentRect.maxY - pinnedIconSize.height - 2.0, width: pinnedIconSize.width, height: pinnedIconSize.height) strongSelf.pinnedIconNode.frame = pinnedIconFrame + nextBadgeX -= pinnedIconSize.width + 6.0 } else { strongSelf.pinnedIconNode.image = nil strongSelf.pinnedIconNode.isHidden = true } + if let (actionButtonTitleNodeLayout, apply) = actionButtonTitleNodeLayoutAndApply { + let actionButtonSize = CGSize(width: actionButtonTitleNodeLayout.size.width + 12.0 * 2.0, height: actionButtonTitleNodeLayout.size.height + 5.0 + 4.0) + let actionButtonFrame = CGRect(x: nextBadgeX - actionButtonSize.width, y: contentRect.maxY - actionButtonSize.height, width: actionButtonSize.width, height: actionButtonSize.height) + + let actionButtonNode: HighlightableButtonNode + if let current = strongSelf.actionButtonNode { + actionButtonNode = current + } else { + actionButtonNode = HighlightableButtonNode() + strongSelf.actionButtonNode = actionButtonNode + strongSelf.mainContentContainerNode.addSubnode(actionButtonNode) + actionButtonNode.addTarget(strongSelf, action: #selector(strongSelf.actionButtonPressed), forControlEvents: .touchUpInside) + } + + let actionButtonBackgroundView: UIImageView + if let current = strongSelf.actionButtonBackgroundView { + actionButtonBackgroundView = current + } else { + actionButtonBackgroundView = UIImageView() + strongSelf.actionButtonBackgroundView = actionButtonBackgroundView + actionButtonNode.view.addSubview(actionButtonBackgroundView) + + if actionButtonBackgroundView.image?.size.height != actionButtonSize.height { + actionButtonBackgroundView.image = generateStretchableFilledCircleImage(diameter: actionButtonSize.height, color: .white)?.withRenderingMode(.alwaysTemplate) + } + } + + actionButtonBackgroundView.tintColor = theme.unreadBadgeActiveBackgroundColor + + let actionButtonTitleNode = apply() + if strongSelf.actionButtonTitleNode !== actionButtonTitleNode { + strongSelf.actionButtonTitleNode?.removeFromSupernode() + strongSelf.actionButtonTitleNode = actionButtonTitleNode + actionButtonNode.addSubnode(actionButtonTitleNode) + } + + actionButtonNode.frame = actionButtonFrame + actionButtonBackgroundView.frame = CGRect(origin: CGPoint(), size: actionButtonFrame.size) + actionButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((actionButtonFrame.width - actionButtonTitleNodeLayout.size.width) * 0.5), y: 5.0), size: actionButtonTitleNodeLayout.size) + + nextBadgeX -= actionButtonSize.width + 6.0 + } else { + if let actionButtonTitleNode = strongSelf.actionButtonTitleNode { + actionButtonTitleNode.removeFromSupernode() + strongSelf.actionButtonTitleNode = nil + } + if let actionButtonBackgroundView = strongSelf.actionButtonBackgroundView { + actionButtonBackgroundView.removeFromSuperview() + strongSelf.actionButtonBackgroundView = nil + } + if let actionButtonNode = strongSelf.actionButtonNode { + actionButtonNode.removeFromSupernode() + strongSelf.actionButtonNode = nil + } + } + var titleOffset: CGFloat = 0.0 if let currentSecretIconImage = currentSecretIconImage { let iconNode: ASImageNode @@ -4495,6 +4566,18 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { item.interaction.openForumThread(index.messageIndex.id.peerId, topicItem.id) } + @objc private func actionButtonPressed() { + guard let item else { + return + } + guard case let .peer(peerData) = item.content else { + return + } + if case let .user(user) = peerData.peer.peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) { + item.interaction.openWebApp(user) + } + } + override public func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index c7ba701710..871de7f7e7 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -112,6 +112,7 @@ public final class ChatListNodeInteraction { let openStarsTopup: (Int64?) -> Void let dismissNotice: (ChatListNotice) -> Void let editPeer: (ChatListItem) -> Void + let openWebApp: (TelegramUser) -> Void public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? @@ -167,7 +168,8 @@ public final class ChatListNodeInteraction { openStories: @escaping (ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void, openStarsTopup: @escaping (Int64?) -> Void, dismissNotice: @escaping (ChatListNotice) -> Void, - editPeer: @escaping (ChatListItem) -> Void + editPeer: @escaping (ChatListItem) -> Void, + openWebApp: @escaping (TelegramUser) -> Void ) { self.activateSearch = activateSearch self.peerSelected = peerSelected @@ -211,6 +213,7 @@ public final class ChatListNodeInteraction { self.openStarsTopup = openStarsTopup self.dismissNotice = dismissNotice self.editPeer = editPeer + self.openWebApp = openWebApp } } @@ -1220,6 +1223,7 @@ public final class ChatListNode: ListView { public var openBirthdaySetup: (() -> Void)? public var openPremiumManagement: (() -> Void)? public var openStarsTopup: ((Int64?) -> Void)? + public var openWebApp: ((TelegramUser) -> Void)? private var theme: PresentationTheme @@ -1867,6 +1871,11 @@ public final class ChatListNode: ListView { break } }, editPeer: { _ in + }, openWebApp: { [weak self] user in + guard let self else { + return + } + self.openWebApp?(user) }) nodeInteraction.isInlineMode = isInlineMode diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 236db5f6a3..660a400c94 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -21,7 +21,7 @@ public enum ChatMessageGalleryControllerData { case pass(TelegramMediaFile) case instantPage(InstantPageGalleryController, Int, Media) case map(TelegramMediaMap) - case stickerPack(StickerPackReference) + case stickerPack(StickerPackReference, TelegramMediaFile?) case audio(TelegramMediaFile) case document(TelegramMediaFile, Bool) case gallery(Signal) @@ -104,7 +104,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati for attribute in file.attributes { if case let .CustomEmoji(_, _, _, reference) = attribute { if let reference = reference { - return .stickerPack(reference) + return .stickerPack(reference, file) } break } @@ -214,7 +214,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati for attribute in file.attributes { if case let .Sticker(_, reference, _) = attribute { if let reference = reference { - return .stickerPack(reference) + return .stickerPack(reference, file) } break } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index b5610e2dea..be013664c2 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -230,6 +230,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index 06edf2f4fe..2c7d812963 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -379,6 +379,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate { }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) func makeChatListItem( diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 276bd519f3..00654a3f56 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -42,6 +42,7 @@ swift_library( "//submodules/Pasteboard:Pasteboard", "//submodules/TelegramUI/Components/Stickers/StickerPackEditTitleController", "//submodules/TelegramUI/Components/CameraScreen", + "//submodules/TelegramUI/Components/EmojiStatusComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index c9935ec6d8..47fa692154 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -25,6 +25,8 @@ import Pasteboard import StickerPackEditTitleController import EntityKeyboard import CameraScreen +import ComponentFlow +import EmojiStatusComponent private let maxStickersCount = 120 @@ -134,6 +136,8 @@ private final class StickerPackContainer: ASDisplayNode { private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? private let backgroundNode: ASImageNode + private let previewIconFile: TelegramMediaFile? + private var mainPreviewIcon: ComponentView? private let gridNode: GridNode private let actionAreaBackgroundNode: NavigationBackgroundNode private let actionAreaSeparatorNode: ASDisplayNode @@ -190,6 +194,7 @@ private final class StickerPackContainer: ASDisplayNode { presentationData: PresentationData, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], + previewIconFile: TelegramMediaFile?, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, @@ -218,6 +223,11 @@ private final class StickerPackContainer: ASDisplayNode { self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor) + self.previewIconFile = previewIconFile + if self.previewIconFile != nil { + self.mainPreviewIcon = ComponentView() + } + self.gridNode = GridNode() self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.showsVerticalScrollIndicator = false @@ -292,6 +302,24 @@ private final class StickerPackContainer: ASDisplayNode { self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition) } + self.gridNode.scrollingInitiated = { [weak self] in + guard let self else { + return + } + if let mainPreviewIconView = self.mainPreviewIcon?.view { + mainPreviewIconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + guard let self else { + return + } + if let mainPreviewIconView = self.mainPreviewIcon?.view { + self.mainPreviewIcon = nil + mainPreviewIconView.removeFromSuperview() + } + }) + mainPreviewIconView.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2, removeOnCompletion: false) + } + } + self.gridNode.interactiveScrollingEnded = { [weak self] in guard let strongSelf = self, !strongSelf.isDismissed else { return @@ -1273,7 +1301,7 @@ private final class StickerPackContainer: ASDisplayNode { |> deliverOnMainQueue).start(completed: { commit() - let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) + let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], previewIconFile: nil, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root)) Queue.mainQueue().after(0.1) { @@ -1329,7 +1357,7 @@ private final class StickerPackContainer: ASDisplayNode { let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker) |> deliverOnMainQueue).start(completed: { - let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) + let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], previewIconFile: nil, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root)) Queue.mainQueue().after(0.1) { @@ -1378,7 +1406,7 @@ private final class StickerPackContainer: ASDisplayNode { |> deliverOnMainQueue).start(completed: { commit() - let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) + let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], previewIconFile: nil, expandIfNeeded: true, parentNavigationController: navigationController, sendSticker: sendSticker, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root)) Queue.mainQueue().after(0.1) { @@ -1956,6 +1984,20 @@ private final class StickerPackContainer: ASDisplayNode { self.modalProgress = modalProgress } + func animateIn() { + if let mainPreviewIconView = self.mainPreviewIcon?.view { + mainPreviewIconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + mainPreviewIconView.layer.animateScale(from: 0.5, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) + } + } + + func animateOut() { + if let mainPreviewIconView = self.mainPreviewIcon?.view { + mainPreviewIconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + mainPreviewIconView.layer.animateScale(from: 1.0, to: 0.5, duration: 0.2, removeOnCompletion: false) + } + } + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { var insets = layout.insets(options: [.statusBar]) if case .regular = layout.metrics.widthClass { @@ -2140,6 +2182,38 @@ private final class StickerPackContainer: ASDisplayNode { titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0)), size: CGSize()) } transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + + if let previewIconFile = self.previewIconFile, let mainPreviewIcon = self.mainPreviewIcon { + let iconFitSize = CGSize(width: 90.0, height: 90.0) + let iconSize = mainPreviewIcon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.context.animationCache, + animationRenderer: self.context.animationRenderer, + content: .animation( + content: .file(file: previewIconFile), + size: iconFitSize, + placeholderColor: .clear, + themeColor: self.presentationData.theme.list.itemPrimaryTextColor, + loopMode: .forever + ), + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: iconFitSize + ) + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width - iconSize.width) * 0.5), y: backgroundFrame.minY - 50.0 - iconSize.height), size: iconSize) + if let iconView = mainPreviewIcon.view { + if iconView.superview == nil { + self.backgroundNode.view.superview?.addSubview(iconView) + } + transition.updatePosition(layer: iconView.layer, position: iconFrame.center) + iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size) + } + } + transition.updateFrame(node: self.titleContainer, frame: titleContainerFrame) transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY + 56.0 - UIScreenPixel), size: CGSize(width: backgroundFrame.width, height: UIScreenPixel))) transition.updateFrame(node: self.titleBackgroundnode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: 56.0))) @@ -2218,6 +2292,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { private weak var controller: StickerPackScreenImpl? private var presentationData: PresentationData private let stickerPacks: [StickerPackReference] + private let previewIconFile: TelegramMediaFile? private let modalProgressUpdated: (CGFloat, ContainedViewLayoutTransition) -> Void private let dismissed: () -> Void private let presentInGlobalOverlay: (ViewController, Any?) -> Void @@ -2251,6 +2326,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { context: AccountContext, controller: StickerPackScreenImpl, stickerPacks: [StickerPackReference], + previewIconFile: TelegramMediaFile?, initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, @@ -2264,6 +2340,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { self.controller = controller self.presentationData = controller.presentationData self.stickerPacks = stickerPacks + self.previewIconFile = previewIconFile self.selectedStickerPackIndex = initialSelectedStickerPackIndex self.modalProgressUpdated = modalProgressUpdated self.dismissed = dismissed @@ -2398,7 +2475,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { wasAdded = true containerTransition = .immediate let index = i - container = StickerPackContainer(index: index, context: self.context, presentationData: self.presentationData, stickerPacks: self.stickerPacks, loadedStickerPacks: self.controller?.loadedStickerPacks ?? [], decideNextAction: { [weak self] container, action in + container = StickerPackContainer(index: index, context: self.context, presentationData: self.presentationData, stickerPacks: self.stickerPacks, loadedStickerPacks: self.controller?.loadedStickerPacks ?? [], previewIconFile: self.previewIconFile, decideNextAction: { [weak self] container, action in guard let strongSelf = self, let layout = strongSelf.validLayout else { return .dismiss } @@ -2565,6 +2642,10 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { let minInset: CGFloat = (self.containers.map { (_, container) -> CGFloat in container.topContentInset }).max() ?? 0.0 self.containerContainingNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerContainingNode.bounds.height - minInset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } + + for (_, container) in self.containers { + container.animateIn() + } } func animateOut(completion: @escaping () -> Void) { @@ -2587,6 +2668,10 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { self.modalProgressUpdated(0.0, .animated(duration: 0.2, curve: .easeInOut)) } + + for (_, container) in self.containers { + container.animateOut() + } } func dismiss() { @@ -2659,6 +2744,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { private let stickerPacks: [StickerPackReference] fileprivate let loadedStickerPacks: [LoadedStickerPack] + let previewIconFile: TelegramMediaFile? private let initialSelectedStickerPackIndex: Int fileprivate weak var parentNavigationController: NavigationController? @@ -2698,6 +2784,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], + previewIconFile: TelegramMediaFile?, selectedStickerPackIndex: Int = 0, mainActionTitle: String? = nil, actionTitle: String? = nil, @@ -2714,6 +2801,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { self.updatedPresentationData = updatedPresentationData self.stickerPacks = stickerPacks self.loadedStickerPacks = loadedStickerPacks + self.previewIconFile = previewIconFile self.initialSelectedStickerPackIndex = selectedStickerPackIndex self.mainActionTitle = mainActionTitle self.actionTitle = actionTitle @@ -2751,7 +2839,7 @@ public final class StickerPackScreenImpl: ViewController, StickerPackScreen { } override public func loadDisplayNode() { - self.displayNode = StickerPackScreenNode(context: self.context, controller: self, stickerPacks: self.stickerPacks, initialSelectedStickerPackIndex: self.initialSelectedStickerPackIndex, modalProgressUpdated: { [weak self] value, transition in + self.displayNode = StickerPackScreenNode(context: self.context, controller: self, stickerPacks: self.stickerPacks, previewIconFile: self.previewIconFile, initialSelectedStickerPackIndex: self.initialSelectedStickerPackIndex, modalProgressUpdated: { [weak self] value, transition in DispatchQueue.main.async { guard let strongSelf = self else { return @@ -2950,6 +3038,7 @@ public func StickerPackScreen( mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack] = [], + previewIconFile: TelegramMediaFile? = nil, mainActionTitle: String? = nil, actionTitle: String? = nil, isEditing: Bool = false, @@ -2967,6 +3056,7 @@ public func StickerPackScreen( updatedPresentationData: updatedPresentationData, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, + previewIconFile: previewIconFile, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, mainActionTitle: mainActionTitle, actionTitle: actionTitle, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift index ff93749b66..e54ce0cc77 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatExpandedParticipantThumbnailsComponent.swift @@ -20,6 +20,7 @@ final class VideoChatParticipantThumbnailComponent: Component { let isPresentation: Bool let isSelected: Bool let isSpeaking: Bool + let displayVideo: Bool let interfaceOrientation: UIInterfaceOrientation let action: (() -> Void)? let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? @@ -31,6 +32,7 @@ final class VideoChatParticipantThumbnailComponent: Component { isPresentation: Bool, isSelected: Bool, isSpeaking: Bool, + displayVideo: Bool, interfaceOrientation: UIInterfaceOrientation, action: (() -> Void)?, contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? @@ -41,6 +43,7 @@ final class VideoChatParticipantThumbnailComponent: Component { self.isPresentation = isPresentation self.isSelected = isSelected self.isSpeaking = isSpeaking + self.displayVideo = displayVideo self.interfaceOrientation = interfaceOrientation self.action = action self.contextAction = contextAction @@ -65,6 +68,9 @@ final class VideoChatParticipantThumbnailComponent: Component { if lhs.isSpeaking != rhs.isSpeaking { return false } + if lhs.displayVideo != rhs.displayVideo { + return false + } if lhs.interfaceOrientation != rhs.interfaceOrientation { return false } @@ -251,7 +257,7 @@ final class VideoChatParticipantThumbnailComponent: Component { titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) } - if let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription { + if component.displayVideo, let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription { let videoBackgroundLayer: SimpleLayer if let current = self.videoBackgroundLayer { videoBackgroundLayer = current @@ -470,6 +476,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { let call: PresentationGroupCall let theme: PresentationTheme + let displayVideo: Bool let participants: [Participant] let selectedParticipant: Participant.Key? let speakingParticipants: Set @@ -480,6 +487,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { init( call: PresentationGroupCall, theme: PresentationTheme, + displayVideo: Bool, participants: [Participant], selectedParticipant: Participant.Key?, speakingParticipants: Set, @@ -489,6 +497,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { ) { self.call = call self.theme = theme + self.displayVideo = displayVideo self.participants = participants self.selectedParticipant = selectedParticipant self.speakingParticipants = speakingParticipants @@ -504,6 +513,9 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { if lhs.theme !== rhs.theme { return false } + if lhs.displayVideo != rhs.displayVideo { + return false + } if lhs.participants != rhs.participants { return false } @@ -654,6 +666,7 @@ final class VideoChatExpandedParticipantThumbnailsComponent: Component { isPresentation: participant.isPresentation, isSelected: component.selectedParticipant == participant.key, isSpeaking: component.speakingParticipants.contains(participant.participant.peer.id), + displayVideo: component.displayVideo, interfaceOrientation: component.interfaceOrientation, action: { [weak self] in guard let self, let component = self.component else { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index fa08eaca1a..617d4df551 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -45,6 +45,7 @@ final class VideoChatParticipantVideoComponent: Component { let isMyPeer: Bool let isPresentation: Bool let isSpeaking: Bool + let maxVideoQuality: Int let isExpanded: Bool let isUIHidden: Bool let contentInsets: UIEdgeInsets @@ -63,6 +64,7 @@ final class VideoChatParticipantVideoComponent: Component { isMyPeer: Bool, isPresentation: Bool, isSpeaking: Bool, + maxVideoQuality: Int, isExpanded: Bool, isUIHidden: Bool, contentInsets: UIEdgeInsets, @@ -80,6 +82,7 @@ final class VideoChatParticipantVideoComponent: Component { self.isMyPeer = isMyPeer self.isPresentation = isPresentation self.isSpeaking = isSpeaking + self.maxVideoQuality = maxVideoQuality self.isExpanded = isExpanded self.isUIHidden = isUIHidden self.contentInsets = contentInsets @@ -104,6 +107,9 @@ final class VideoChatParticipantVideoComponent: Component { if lhs.isSpeaking != rhs.isSpeaking { return false } + if lhs.maxVideoQuality != rhs.maxVideoQuality { + return false + } if lhs.isExpanded != rhs.isExpanded { return false } @@ -413,7 +419,7 @@ final class VideoChatParticipantVideoComponent: Component { alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha) } - let videoDescription = component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription + let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription? = component.maxVideoQuality == 0 ? nil : (component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription) var isEffectivelyPaused = false if let videoDescription, videoDescription.isPaused { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift index 5d8605d1ef..7733798179 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantsComponent.swift @@ -129,6 +129,7 @@ final class VideoChatParticipantsComponent: Component { let participants: Participants? let speakingParticipants: Set let expandedVideoState: ExpandedVideoState? + let maxVideoQuality: Int let theme: PresentationTheme let strings: PresentationStrings let layout: Layout @@ -147,6 +148,7 @@ final class VideoChatParticipantsComponent: Component { participants: Participants?, speakingParticipants: Set, expandedVideoState: ExpandedVideoState?, + maxVideoQuality: Int, theme: PresentationTheme, strings: PresentationStrings, layout: Layout, @@ -164,6 +166,7 @@ final class VideoChatParticipantsComponent: Component { self.participants = participants self.speakingParticipants = speakingParticipants self.expandedVideoState = expandedVideoState + self.maxVideoQuality = maxVideoQuality self.theme = theme self.strings = strings self.layout = layout @@ -188,6 +191,9 @@ final class VideoChatParticipantsComponent: Component { if lhs.expandedVideoState != rhs.expandedVideoState { return false } + if lhs.maxVideoQuality != rhs.maxVideoQuality { + return false + } if lhs.theme !== rhs.theme { return false } @@ -1022,6 +1028,7 @@ final class VideoChatParticipantsComponent: Component { isMyPeer: videoParticipant.participant.peer.id == component.participants?.myPeerId, isPresentation: videoParticipant.isPresentation, isSpeaking: component.speakingParticipants.contains(videoParticipant.participant.peer.id), + maxVideoQuality: component.maxVideoQuality, isExpanded: isItemExpanded, isUIHidden: isItemUIHidden || self.isPinchToZoomActive, contentInsets: itemContentInsets, @@ -1376,6 +1383,7 @@ final class VideoChatParticipantsComponent: Component { component: AnyComponent(VideoChatExpandedParticipantThumbnailsComponent( call: component.call, theme: component.theme, + displayVideo: component.maxVideoQuality != 0, participants: thumbnailParticipants, selectedParticipant: component.expandedVideoState.flatMap { expandedVideoState in return VideoChatExpandedParticipantThumbnailsComponent.Participant.Key(id: expandedVideoState.mainParticipant.id, isPresentation: expandedVideoState.mainParticipant.isPresentation) @@ -1762,12 +1770,18 @@ final class VideoChatParticipantsComponent: Component { } var requestedVideo: [PresentationGroupCallRequestedVideo] = [] - if let participants = component.participants { + if let participants = component.participants, component.maxVideoQuality != 0 { for participant in participants.participants { var maxVideoQuality: PresentationGroupCallRequestedVideo.Quality = .medium if let expandedVideoState = component.expandedVideoState { if expandedVideoState.mainParticipant.id == participant.peer.id, !expandedVideoState.mainParticipant.isPresentation { - maxVideoQuality = .full + if component.maxVideoQuality == Int.max { + maxVideoQuality = .full + } else if component.maxVideoQuality == 360 { + maxVideoQuality = .medium + } else { + maxVideoQuality = .thumbnail + } } else { maxVideoQuality = .thumbnail } @@ -1776,15 +1790,27 @@ final class VideoChatParticipantsComponent: Component { var maxPresentationQuality: PresentationGroupCallRequestedVideo.Quality = .medium if let expandedVideoState = component.expandedVideoState { if expandedVideoState.mainParticipant.id == participant.peer.id, expandedVideoState.mainParticipant.isPresentation { - maxPresentationQuality = .full + if component.maxVideoQuality == Int.max { + maxVideoQuality = .full + } else if component.maxVideoQuality == 360 { + maxVideoQuality = .medium + } else { + maxVideoQuality = .thumbnail + } } else { maxPresentationQuality = .thumbnail } } if component.layout.videoColumn != nil && gridParticipants.count == 1 { - maxVideoQuality = .full - maxPresentationQuality = .full + if component.maxVideoQuality == Int.max { + maxVideoQuality = .full + } else if component.maxVideoQuality == 360 { + maxVideoQuality = .medium + } else { + maxVideoQuality = .thumbnail + } + maxPresentationQuality = maxVideoQuality } if let videoChannel = participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: maxVideoQuality) { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 751c4db8b9..5e6fc311e3 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -119,6 +119,8 @@ final class VideoChatScreenComponent: Component { let updateAvatarDisposable = MetaDisposable() var currentUpdatingAvatar: (TelegramMediaImageRepresentation, Float)? + var maxVideoQuality: Int = Int.max + override init(frame: CGRect) { self.containerView = UIView() self.containerView.clipsToBounds = true @@ -1506,6 +1508,7 @@ final class VideoChatScreenComponent: Component { participants: mappedParticipants, speakingParticipants: self.members?.speakingParticipants ?? Set(), expandedVideoState: self.expandedParticipantsVideoState, + maxVideoQuality: self.maxVideoQuality, theme: environment.theme, strings: environment.strings, layout: participantsLayout, diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index 16ecfecd1b..1e05a29420 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -152,6 +152,56 @@ extension VideoChatScreenComponent.View { } } + //TODO:localize + let qualityList: [(Int, String)] = [ + (0, "Audio Only"), + (180, "180p"), + (360, "360p"), + (Int.max, "720p") + ] + + let videoQualityTitle = qualityList.first(where: { $0.0 == self.maxVideoQuality })?.1 ?? "" + items.append(.action(ContextMenuActionItem(text: "Receive Video Quality", textColor: .primary, textLayout: .secondLineWithValue(videoQualityTitle), icon: { _ in + return nil + }, action: { [weak self] c, _ in + guard let self else { + c?.dismiss(completion: nil) + return + } + + var items: [ContextMenuItem] = [] + items.append(.action(ContextMenuActionItem(text: environment.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, iconPosition: .left, action: { (c, _) in + c?.popItems() + }))) + items.append(.separator) + + for (quality, title) in qualityList { + let isSelected = self.maxVideoQuality == quality + items.append(.action(ContextMenuActionItem(text: title, icon: { _ in + if isSelected { + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white) + } else { + return nil + } + }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + + if self.maxVideoQuality != quality { + self.maxVideoQuality = quality + self.state?.updated(transition: .immediate) + } + }))) + } + + c?.pushItems(items: .single(ContextController.Items(content: .list(items)))) + }))) + if callState.isVideoEnabled && (callState.muteState?.canUnmute ?? true) { if component.call.hasScreencast { items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_StopScreenSharing, icon: { theme in diff --git a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift index 9e7703d01b..b0d8a5351b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInlineSearchResultsListComponent/Sources/ChatInlineSearchResultsListComponent.swift @@ -669,6 +669,8 @@ public final class ChatInlineSearchResultsListComponent: Component { dismissNotice: { _ in }, editPeer: { _ in + }, + openWebApp: { _ in } ) self.chatListNodeInteraction = chatListNodeInteraction diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index bbf345b6d4..b6fbb079cc 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -185,6 +185,7 @@ public final class LoadingOverlayNode: ASDisplayNode { }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _, _ in }, openPremiumManagement: {}, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) let items = (0 ..< 1).map { _ -> ChatListItem in @@ -539,6 +540,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod dismissNotice: { _ in }, editPeer: { _ in + }, + openWebApp: { _ in } ) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 4704c3fbd3..75df903a85 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -207,6 +207,8 @@ final class GreetingMessageListItemComponent: Component { dismissNotice: { _ in }, editPeer: { _ in + }, + openWebApp: { _ in } ) self.chatListNodeInteraction = chatListNodeInteraction diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index aecb9b0677..37c5b4caa0 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -228,6 +228,8 @@ final class QuickReplySetupScreenComponent: Component { if let itemId = item.id { parentView.openEditShortcut(id: itemId, currentValue: item.shortcut) } + }, + openWebApp: { _ in } ) diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index 547bc03589..ec687b8b91 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -873,6 +873,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index d21126fadb..b070331813 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -294,14 +294,14 @@ extension ChatControllerImpl { }) } - func presentEmojiList(references: [StickerPackReference]) { + func presentEmojiList(references: [StickerPackReference], previewIconFile: TelegramMediaFile? = nil) { guard let packReference = references.first else { return } self.chatDisplayNode.dismissTextInput() let presentationData = self.presentationData - let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), parentNavigationController: self.effectiveNavigationController, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in + let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), previewIconFile: previewIconFile, parentNavigationController: self.effectiveNavigationController, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in if let strongSelf = self { strongSelf.controllerInteraction?.sendEmoji(text, attribute, false) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index b44df4b217..591dd7732d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9026,7 +9026,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let stickerPackReference = stickerPackReference { - self.presentEmojiList(references: [stickerPackReference]) + self.presentEmojiList(references: [stickerPackReference], previewIconFile: file) /*let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false) |> deliverOnMainQueue).startStandalone(next: { [weak self] stickerPack in diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 5931ca0819..9c6b8d4c23 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -293,6 +293,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe }, openStarsTopup: { _ in }, dismissNotice: { _ in }, editPeer: { _ in + }, openWebApp: { _ in }) interaction.searchTextHighightState = searchQuery self.interaction = interaction diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index af087f630f..16c6325453 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -177,6 +177,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { dismissNotice: { _ in }, editPeer: { _ in + }, + openWebApp: { _ in } ) diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 889f521c1d..daced6e649 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -181,8 +181,8 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { controller.navigationPresentation = .modal params.navigationController?.pushViewController(controller) return true - case let .stickerPack(reference): - let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, sendEmoji: params.sendEmoji, actionPerformed: { actions in + case let .stickerPack(reference, previewIconFile): + let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], previewIconFile: previewIconFile, parentNavigationController: params.navigationController, sendSticker: params.sendSticker, sendEmoji: params.sendEmoji, actionPerformed: { actions in let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } if actions.count > 1, let first = actions.first {