From 04765e0c36b1fa87920a6526778c521b1234e87f Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 17 Sep 2025 11:18:26 +0400 Subject: [PATCH] Update --- .../Sources/ChatContextQuery.swift | 9 - .../Sources/ChatListContainerItemNode.swift | 2 +- .../Sources/ContactsControllerNode.swift | 2 +- submodules/Display/Source/UIKitUtils.swift | 31 + .../StickerPackPreviewController.swift | 299 -------- .../StickerPackPreviewControllerNode.swift | 704 ------------------ .../Sources/StickerPackScreen.swift | 6 + submodules/TelegramApi/Sources/Api0.swift | 6 +- submodules/TelegramApi/Sources/Api19.swift | 80 ++ submodules/TelegramApi/Sources/Api25.swift | 20 +- submodules/TelegramApi/Sources/Api28.swift | 20 +- submodules/TelegramApi/Sources/Api39.swift | 32 +- .../Sources/ApiUtils/ApiGroupOrChannel.swift | 12 + .../Sources/ApiUtils/TelegramMediaFile.swift | 34 +- .../Sources/ApiUtils/TelegramUser.swift | 12 + .../AccountData/UpdateAccountPeerName.swift | 5 +- .../Contacts/ImportContact.swift | 2 +- .../Contacts/UpdateContactName.swift | 2 +- .../TelegramEngine/Messages/AdMessages.swift | 3 + .../TelegramEngine/Payments/StarGifts.swift | 2 +- .../Peers/ChannelAdminEventLogs.swift | 14 + .../Peers/UpdateCachedPeerData.swift | 4 +- .../Sources/DefaultDayPresentationTheme.swift | 6 +- .../Resources/PresentationResourcesChat.swift | 8 +- submodules/TelegramUI/BUILD | 1 + .../ChatChannelSubscriberInputPanelNode/BUILD | 3 + .../ChatChannelSubscriberInputPanelNode.swift | 283 +++---- .../Chat/ChatInputAutocompletePanel/BUILD | 20 + .../Sources/ChatInputAutocompletePanel.swift | 44 ++ .../Sources/ChatInputPanelNode.swift | 2 +- .../ChatMessageSelectionInputPanelNode.swift | 18 +- .../Sources/ChatOverscrollControl.swift | 61 +- .../ChatRecordingPreviewInputPanelNode.swift | 2 +- .../Chat/ChatTextInputPanelNode/BUILD | 2 + .../Sources/ChatTextInputPanelNode.swift | 192 ++++- .../EdgeEffect/Sources/EdgeEffect.swift | 2 +- .../Sources/GlassBackgroundComponent.swift | 225 +++++- .../Sources/InputContextQueries.swift | 3 - .../Sources/PeerInfoScreen.swift | 4 +- .../Chat/ChatControllerThemeManagement.swift | 5 + .../TelegramUI/Sources/ChatController.swift | 17 +- .../Sources/ChatControllerNode.swift | 98 +-- .../Sources/ChatHistoryListNode.swift | 62 -- .../ChatInterfaceInputContextPanels.swift | 208 ++++-- .../ChatInterfaceStateInputPanels.swift | 1 + .../ChatMessageReportInputPanelNode.swift | 4 +- .../ChatPremiumRequiredInputPanelNode.swift | 8 +- .../ChatRestrictedInputPanelNode.swift | 44 +- .../Sources/ChatSearchInputPanelNode.swift | 224 ------ .../Sources/ChatTagSearchInputPanelNode.swift | 8 +- .../Sources/ChatUnblockInputPanelNode.swift | 2 +- .../CommandChatInputContextPanelNode.swift | 79 +- .../Sources/CommandChatInputPanelItem.swift | 11 +- ...CommandMenuChatInputContextPanelNode.swift | 57 +- .../CommandMenuChatInputPanelItem.swift | 3 +- .../Sources/DeleteChatInputPanelNode.swift | 2 +- .../HashtagChatInputContextPanelNode.swift | 64 +- .../Sources/HashtagChatInputPanelItem.swift | 11 +- ...textResultsChatInputContextPanelNode.swift | 4 +- .../MentionChatInputContextPanelNode.swift | 67 +- .../Sources/MentionChatInputPanelItem.swift | 11 +- .../TelegramUI/Sources/OpenChatMessage.swift | 5 + ...retChatHandshakeStatusInputPanelNode.swift | 2 +- ...textResultsChatInputContextPanelNode.swift | 104 ++- ...ntextResultsChatInputPanelButtonItem.swift | 4 - ...ListContextResultsChatInputPanelItem.swift | 11 +- versions.json | 2 +- 67 files changed, 1405 insertions(+), 1890 deletions(-) delete mode 100644 submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift delete mode 100644 submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift create mode 100644 submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/BUILD create mode 100644 submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/Sources/ChatInputAutocompletePanel.swift delete mode 100644 submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift diff --git a/submodules/ChatContextQuery/Sources/ChatContextQuery.swift b/submodules/ChatContextQuery/Sources/ChatContextQuery.swift index 7de2cbbb01..d77fcdaad1 100644 --- a/submodules/ChatContextQuery/Sources/ChatContextQuery.swift +++ b/submodules/ChatContextQuery/Sources/ChatContextQuery.swift @@ -111,15 +111,6 @@ public func textInputStateContextQueryRangeAndType(inputText: NSAttributedString if inputText.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) == nil { return [(NSRange(location: 0, length: inputString.length - (string.count - trimmedString.count)), [.emoji], nil)] } - } else { - /*let activeString = inputText.attributedSubstring(from: NSRange(location: 0, length: inputState.selectionRange.upperBound)) - if let lastCharacter = activeString.string.last, String(lastCharacter).isSingleEmoji { - let matchLength = (String(lastCharacter) as NSString).length - - if activeString.attribute(ChatTextInputAttributes.customEmoji, at: activeString.length - matchLength, effectiveRange: nil) == nil { - return [(NSRange(location: inputState.selectionRange.upperBound - matchLength, length: matchLength), [.emojiSearch], nil)] - } - }*/ } var possibleTypes = PossibleContextQueryTypes([.command, .mention, .hashtag, .emojiSearch]) diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index 07d7532dbc..360c326f12 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -453,7 +453,7 @@ final class ChatListContainerItemNode: ASDisplayNode { let edgeEffectHeight: CGFloat = insets.bottom let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: size, transition: ComponentTransition(transition)) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: size, transition: ComponentTransition(transition)) } func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 0982b64bf1..60976a47c4 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -450,7 +450,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition)) + self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition)) self.updateNavigationScrolling(transition: transition) diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index fd6d47119d..0b109eb7d1 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -202,6 +202,37 @@ public extension UIColor { return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha) } + func adjustedPerceivedBrightness(_ factor: CGFloat) -> UIColor { + let f = max(0, factor) + let base = self + guard + let cs = CGColorSpace(name: CGColorSpace.extendedSRGB), + let cg = base.cgColor.converted(to: cs, intent: .defaultIntent, options: nil), + let c = cg.components, c.count >= 3 + else { return base } + + func toLin(_ x: CGFloat) -> CGFloat { x <= 0.04045 ? x/12.92 : pow((x+0.055)/1.055, 2.4) } + func toSRGB(_ x: CGFloat) -> CGFloat { x <= 0.0031308 ? 12.92*x : 1.055*pow(x, 1/2.4) - 0.055 } + func clamp(_ x: CGFloat) -> CGFloat { min(max(x, 0), 1) } + + var r = toLin(c[0]), g = toLin(c[1]), b = toLin(c[2]) + if f >= 1 { + // mix toward white: t = 1 - 1/f (so f=1 → t=0, f→∞ → t→1) + let t = 1 - 1/f + r = r + (1 - r) * t + g = g + (1 - g) * t + b = b + (1 - b) * t + } else { + // scale toward black + r *= f; g *= f; b *= f + } + + return UIColor(red: clamp(toSRGB(r)), + green: clamp(toSRGB(g)), + blue: clamp(toSRGB(b)), + alpha: cg.alpha) + } + func withMultiplied(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) -> UIColor { var hueValue: CGFloat = 0.0 var saturationValue: CGFloat = 0.0 diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift deleted file mode 100644 index a17ace08a1..0000000000 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewController.swift +++ /dev/null @@ -1,299 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramUIPreferences -import AccountContext -import StickerResources -import AlertUI -import PresentationDataUtils -import UndoUI -import TelegramPresentationData - -public enum StickerPackPreviewControllerMode { - case `default` - case settings -} - -public final class StickerPackPreviewController: ViewController, StandalonePresentableController { - private var controllerNode: StickerPackPreviewControllerNode { - return self.displayNode as! StickerPackPreviewControllerNode - } - - private var animatedIn = false - private var isDismissed = false - - public var dismissed: (() -> Void)? - - private let context: AccountContext - private let mode: StickerPackPreviewControllerMode - private weak var parentNavigationController: NavigationController? - - private let stickerPack: StickerPackReference - - private var stickerPackContentsValue: LoadedStickerPack? - - private let stickerPackDisposable = MetaDisposable() - private let stickerPackContents = Promise() - - private let stickerPackInstalledDisposable = MetaDisposable() - private let stickerPackInstalled = Promise() - - private let openMentionDisposable = MetaDisposable() - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? { - didSet { - if self.isNodeLoaded { - if let sendSticker = self.sendSticker { - self.controllerNode.sendSticker = { [weak self] file, sourceView, sourceRect in - if sendSticker(file, sourceView, sourceRect) { - self?.dismiss() - return true - } else { - return false - } - } - } else { - self.controllerNode.sendSticker = nil - } - } - } - } - - private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, stickerPack: StickerPackReference, mode: StickerPackPreviewControllerMode = .default, parentNavigationController: NavigationController?, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? = nil) { - self.context = context - self.mode = mode - self.parentNavigationController = parentNavigationController - self.actionPerformed = actionPerformed - - self.stickerPack = stickerPack - - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) - - self.blocksBackgroundWhenInOverlay = true - self.acceptsFocusWhenInOverlay = true - self.statusBar.statusBarStyle = .Ignore - - self.stickerPackContents.set(context.engine.stickers.loadedStickerPack(reference: stickerPack, forceActualized: true)) - - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.presentationData = presentationData - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.stickerPackDisposable.dispose() - self.stickerPackInstalledDisposable.dispose() - self.openMentionDisposable.dispose() - self.presentationDataDisposable?.dispose() - } - - override public func loadDisplayNode() { - var openShareImpl: (() -> Void)? - if self.mode == .settings { - openShareImpl = { [weak self] in - guard let strongSelf = self else { - return - } - - if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty { - let parentNavigationController = strongSelf.parentNavigationController - let shareController = strongSelf.context.sharedContext.makeShareController( - context: strongSelf.context, - subject: .url("https://t.me/addstickers/\(info.shortName)"), - forceExternal: true, - shareStory: nil, - enqueued: nil, - actionCompleted: { [weak parentNavigationController] in - if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) - } - } - ) - strongSelf.present(shareController, in: .window(.root)) - strongSelf.dismiss() - } - } - } - self.displayNode = StickerPackPreviewControllerNode(context: self.context, presentationData: self.presentationData, openShare: openShareImpl, openMention: { [weak self] mention in - guard let strongSelf = self else { - return - } - - strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention, referrer: nil) - |> mapToSignal { result -> Signal in - guard case let .result(result) = result else { - return .complete() - } - return .single(result) - } - |> mapToSignal { peer -> Signal in - if let peer = peer { - return .single(peer._asPeer()) - } else { - return .single(nil) - } - } - |> deliverOnMainQueue).start(next: { peer in - guard let strongSelf = self else { - return - } - if let peer = peer, let parentNavigationController = strongSelf.parentNavigationController { - strongSelf.dismiss() - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), animated: true)) - } - })) - }, actionPerformed: self.actionPerformed) - self.controllerNode.dismiss = { [weak self] in - self?.dismissed?() - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - self?.dismiss() - } - self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in - self?.presentInGlobalOverlay(controller, with: arguments) - } - if let sendSticker = self.sendSticker { - self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in - if sendSticker(file, sourceNode, sourceRect) { - self?.dismiss() - return true - } else { - return false - } - } - } - let account = self.context.account - self.displayNodeDidLoad() - self.stickerPackDisposable.set((combineLatest(self.stickerPackContents.get(), self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> take(1)) - |> mapToSignal { next, sharedData -> Signal<(LoadedStickerPack, StickerSettings), NoError> in - var stickerSettings = StickerSettings.defaultSettings - if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) { - stickerSettings = value - } - - switch next { - case let .result(info, items, _): - var preloadSignals: [Signal] = [] - - let info = info._parse() - if let thumbnail = info.thumbnail { - let signal = Signal { subscriber in - let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start() - let data = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in - if data.complete { - subscriber.putNext(true) - subscriber.putCompletion() - } else { - subscriber.putNext(false) - } - }) - return ActionDisposable { - fetched.dispose() - data.dispose() - } - } - preloadSignals.append(signal) - } - - let topItems = items.prefix(16) - for item in topItems { - if item.file.isAnimatedSticker { - let itemFile = item.file._parse() - let signal = Signal { subscriber in - let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: FileMediaReference.standalone(media: itemFile).resourceReference(itemFile.resource)).start() - let data = account.postbox.mediaBox.resourceData(itemFile.resource).start() - let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fetchedRepresentation = chatMessageAnimatedStickerDatas(postbox: account.postbox, userLocation: .other, file: itemFile, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)), fetched: true, onlyFullSize: false, synchronousLoad: false).start(next: { next in - let hasContent = next._0 != nil || next._1 != nil - subscriber.putNext(hasContent) - if hasContent { - subscriber.putCompletion() - } - }) - return ActionDisposable { - fetched.dispose() - data.dispose() - fetchedRepresentation.dispose() - } - } - preloadSignals.append(signal) - } - } - return combineLatest(preloadSignals) - |> map { values -> Bool in - return !values.contains(false) - } - |> distinctUntilChanged - |> mapToSignal { loaded -> Signal<(LoadedStickerPack, StickerSettings), NoError> in - if !loaded { - return .single((.fetching, stickerSettings)) - } else { - return .single((next, stickerSettings)) - } - } - default: - return .single((next, stickerSettings)) - } - } - |> deliverOnMainQueue).start(next: { [weak self] next in - if let strongSelf = self { - if case .none = next.0 { - let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) - strongSelf.dismiss() - } else { - strongSelf.controllerNode.updateStickerPack(next.0, stickerSettings: next.1) - strongSelf.stickerPackContentsValue = next.0 - } - } - })) - self.ready.set(self.controllerNode.ready.get()) - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() - } - } - - override public func dismiss(completion: (() -> Void)? = nil) { - if !self.isDismissed { - self.isDismissed = true - } else { - return - } - self.acceptsFocusWhenInOverlay = false - self.requestUpdateParameters() - self.controllerNode.animateOut(completion: completion) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } -} diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift deleted file mode 100644 index 8563133bb4..0000000000 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ /dev/null @@ -1,704 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import Postbox -import TelegramCore -import TelegramPresentationData -import TelegramUIPreferences -import MergeLists -import ActivityIndicator -import TextFormat -import AccountContext -import ContextUI -import StickerPeekUI -import AccountContext - -private struct StickerPackPreviewGridEntry: Comparable, Identifiable { - let index: Int - let stickerItem: StickerPackItem - - var stableId: MediaId { - return self.stickerItem.file.fileId - } - - static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool { - return lhs.index < rhs.index - } - - func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem { - return StickerPackPreviewGridItem(context: context, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false, isEditable: false, isEditing: false) - } -} - -private struct StickerPackPreviewGridTransaction { - let deletions: [Int] - let insertions: [GridNodeInsertItem] - let updates: [GridNodeUpdateItem] - - init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) { - let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list) - - self.deletions = deleteIndices - self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) } - self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) } - } -} - -final class StickerPackPreviewControllerNode: ViewControllerTracingNode, ASScrollViewDelegate { - private let context: AccountContext - private let openShare: (() -> Void)? - private var presentationData: PresentationData - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - private let dimNode: ASDisplayNode - - private let wrappingScrollNode: ASScrollNode - private let cancelButtonNode: ASButtonNode - - private let contentContainerNode: ASDisplayNode - private let contentBackgroundNode: ASImageNode - private let contentGridNode: GridNode - private let installActionButtonNode: ASButtonNode - private let installActionSeparatorNode: ASDisplayNode - private let shareActionButtonNode: ASButtonNode - private let shareActionSeparatorNode: ASDisplayNode - private let contentTitleNode: ImmediateTextNode - private let contentSeparatorNode: ASDisplayNode - - private var activityIndicator: ActivityIndicator? - - private var interaction: StickerPackPreviewInteraction! - - var presentInGlobalOverlay: ((ViewController, Any?) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? - private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? - - let ready = Promise() - private var didSetReady = false - - private var stickerPack: LoadedStickerPack? - private var stickerPackUpdated = false - private var stickerPackInitiallyInstalled : Bool? - private var stickerSettings: StickerSettings? - - private var currentItems: [StickerPackPreviewGridEntry] = [] - - private var hapticFeedback: HapticFeedback? - - private weak var peekController: PeekController? - - init(context: AccountContext, presentationData: PresentationData, openShare: (() -> Void)?, openMention: @escaping (String) -> Void, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?) { - self.context = context - self.openShare = openShare - self.presentationData = presentationData - self.actionPerformed = actionPerformed - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - - self.cancelButtonNode = ASButtonNode() - self.cancelButtonNode.displaysAsynchronously = false - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false - self.contentContainerNode.clipsToBounds = true - - self.contentBackgroundNode = ASImageNode() - self.contentBackgroundNode.displaysAsynchronously = false - self.contentBackgroundNode.displayWithoutProcessing = true - - self.contentGridNode = GridNode() - - self.installActionButtonNode = HighlightTrackingButtonNode() - self.installActionButtonNode.displaysAsynchronously = false - self.installActionButtonNode.titleNode.displaysAsynchronously = false - - self.shareActionButtonNode = HighlightTrackingButtonNode() - self.shareActionButtonNode.displaysAsynchronously = false - self.shareActionButtonNode.titleNode.displaysAsynchronously = false - - self.contentTitleNode = ImmediateTextNode() - self.contentTitleNode.displaysAsynchronously = false - self.contentTitleNode.maximumNumberOfLines = 1 - - self.contentSeparatorNode = ASDisplayNode() - self.contentSeparatorNode.isLayerBacked = true - - self.installActionSeparatorNode = ASDisplayNode() - self.installActionSeparatorNode.isLayerBacked = true - self.installActionSeparatorNode.displaysAsynchronously = false - - self.shareActionSeparatorNode = ASDisplayNode() - self.shareActionSeparatorNode.isLayerBacked = true - self.shareActionSeparatorNode.displaysAsynchronously = false - - super.init() - - self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }, addPressed: {}) - - self.backgroundColor = nil - self.isOpaque = false - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.cancelButtonNode) - self.cancelButtonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - - self.installActionButtonNode.addTarget(self, action: #selector(self.installActionButtonPressed), forControlEvents: .touchUpInside) - self.shareActionButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside) - - self.wrappingScrollNode.addSubnode(self.contentBackgroundNode) - - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - self.contentContainerNode.addSubnode(self.contentGridNode) - self.contentContainerNode.addSubnode(self.installActionSeparatorNode) - self.contentContainerNode.addSubnode(self.installActionButtonNode) - if openShare != nil { - self.contentContainerNode.addSubnode(self.shareActionSeparatorNode) - self.contentContainerNode.addSubnode(self.shareActionButtonNode) - } - self.wrappingScrollNode.addSubnode(self.contentTitleNode) - self.wrappingScrollNode.addSubnode(self.contentSeparatorNode) - - self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in - self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition) - } - - self.contentTitleNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention) - } else { - return nil - } - } - - self.contentTitleNode.tapAttributeAction = { attributes, _ in - if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String, mention.count > 1 { - openMention(String(mention[mention.index(after: mention.startIndex)...])) - } - } - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in - if let strongSelf = self { - if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { - let accountPeerId = strongSelf.context.account.peerId - return combineLatest( - strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId), - strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in - var hasPremium = false - if case let .user(user) = peer, user.isPremium { - hasPremium = true - } - return hasPremium - } - ) - |> deliverOnMainQueue - |> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in - if let strongSelf = self { - var menuItems: [ContextMenuItem] = [] - if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - if strongSelf.sendSticker != nil { - menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - if let strongSelf = self, let peekController = strongSelf.peekController { - if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode { - let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), animationNode.view, animationNode.bounds) - } else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode { - let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), imageNode.view, imageNode.bounds) - } - } - f(.default) - }))) - } - menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in - f(.default) - - if let strongSelf = self { - let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file._parse(), saved: !isStarred).start(next: { result in - - }) - } - }))) - } - return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file._parse()), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { - - })) - } else { - return nil - } - } - } - } - return nil - }, present: { [weak self] content, sourceView, sourceRect in - if let strongSelf = self { - let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: { - return (sourceView, sourceRect) - }) - controller.visibilityUpdated = { [weak self] visible in - if let strongSelf = self { - strongSelf.contentGridNode.forceHidden = visible - } - } - strongSelf.peekController = controller - strongSelf.presentInGlobalOverlay?(controller, nil) - return controller - } - return nil - }, updateContent: { [weak self] content in - if let strongSelf = self { - var item: StickerPreviewPeekItem? - if let content = content as? StickerPreviewPeekContent { - item = content.item - } - strongSelf.updatePreviewingItem(item: item, animated: true) - } - }, activateBySingleTap: true)) - - self.updatePresentationData(self.presentationData) - } - - func updatePresentationData(_ presentationData: PresentationData) { - self.presentationData = presentationData - - let theme = presentationData.theme - let solidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - - let highlightedSolidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - - let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) - })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - - let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0))) - })?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1) - - let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemBackgroundColor) - let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor) - - self.contentBackgroundNode.image = roundedBackground - - self.cancelButtonNode.setBackgroundImage(roundedBackground, for: .normal) - self.cancelButtonNode.setBackgroundImage(highlightedRoundedBackground, for: .highlighted) - - if self.shareActionButtonNode.supernode != nil { - self.installActionButtonNode.setBackgroundImage(solidBackground, for: .normal) - self.installActionButtonNode.setBackgroundImage(highlightedSolidBackground, for: .highlighted) - } else { - self.installActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal) - self.installActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted) - } - - self.shareActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal) - self.shareActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted) - self.shareActionButtonNode.setTitle(presentationData.strings.Conversation_ContextMenuShare, with: Font.regular(20.0), with: presentationData.theme.actionSheet.controlAccentColor, for: .normal) - - self.contentSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.installActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.shareActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - - self.cancelButtonNode.setTitle(presentationData.strings.Common_Cancel, with: Font.medium(20.0), with: presentationData.theme.actionSheet.standardActionTextColor, for: .normal) - - self.contentTitleNode.linkHighlightColor = presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5) - - if let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - var insets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - let cleanInsets = layout.insets(options: [.statusBar]) - let hasShareButton = self.shareActionButtonNode.supernode != nil - - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - var bottomInset: CGFloat = 10.0 + cleanInsets.bottom - if insets.bottom > 0 { - bottomInset -= 12.0 - } - - let buttonHeight: CGFloat = 57.0 - let sectionSpacing: CGFloat = 8.0 - let titleAreaHeight: CGFloat = 51.0 - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left) - - let sideInset = floor((layout.size.width - width) / 2.0) - - transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight))) - - let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing - - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight)) - let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0) - - var transaction: StickerPackPreviewGridTransaction? - - var itemCount = 0 - var animateIn = false - - if let stickerPack = self.stickerPack { - switch stickerPack { - case .fetching, .none: - if self.activityIndicator == nil { - let activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(self.presentationData.theme.actionSheet.controlAccentColor, 22.0, 2.0, false)) - self.activityIndicator = activityIndicator - self.addSubnode(activityIndicator) - } - case let .result(info, items, _): - if let activityIndicator = self.activityIndicator { - activityIndicator.removeFromSupernode() - self.activityIndicator = nil - } - itemCount = items.count - - var updatedItems: [StickerPackPreviewGridEntry] = [] - for item in items { - updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item)) - } - - if self.currentItems.isEmpty && !updatedItems.isEmpty { - let entities = generateTextEntities(info.title, enabledTypes: [.mention]) - let font = Font.medium(20.0) - self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: nil) - animateIn = true - } - transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, context: self.context, interaction: self.interaction, theme: self.presentationData.theme) - self.currentItems = updatedItems - } - } - - let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude)) - let titleFrame = CGRect(origin: CGPoint(x: contentContainerFrame.minX + floor((contentContainerFrame.size.width - titleSize.width) / 2.0), y: self.contentBackgroundNode.frame.minY + 15.0), size: titleSize) - let deltaTitlePosition = CGPoint(x: titleFrame.midX - self.contentTitleNode.frame.midX, y: titleFrame.midY - self.contentTitleNode.frame.midY) - self.contentTitleNode.frame = titleFrame - transition.animatePosition(node: self.contentTitleNode, from: CGPoint(x: titleFrame.midX + deltaTitlePosition.x, y: titleFrame.midY + deltaTitlePosition.y)) - - transition.updateFrame(node: self.contentTitleNode, frame: titleFrame) - transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentContainerFrame.minX, y: self.contentBackgroundNode.frame.minY + titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel))) - - let itemsPerRow = 4 - let itemWidth = floor(contentFrame.size.width / CGFloat(itemsPerRow)) - let rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0) - - let minimallyRevealedRowCount: CGFloat = 3.5 - let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount)) - - let bottomGridInset = hasShareButton ? buttonHeight * 2.0 : buttonHeight - let topInset = max(0.0, contentFrame.size.height - initiallyRevealedRowCount * itemWidth - titleAreaHeight - bottomGridInset) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - - if let activityIndicator = self.activityIndicator { - let indicatorSize = activityIndicator.calculateSizeThatFits(layout.size) - - transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.width - indicatorSize.width) / 2.0), y: contentFrame.maxY - indicatorSize.height - 30.0), size: indicatorSize)) - } - - let installButtonOffset = hasShareButton ? buttonHeight * 2.0 : buttonHeight - transition.updateFrame(node: self.installActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight))) - transition.updateFrame(node: self.installActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel))) - - transition.updateFrame(node: self.shareActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight))) - transition.updateFrame(node: self.shareActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel))) - - let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight)) - - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize)) - - if animateIn { - self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.shareActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.shareActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - - if let _ = self.stickerPack, self.stickerPackUpdated { - self.dequeueUpdateStickerPack() - } - } - - private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) { - if let (layout, _) = self.containerLayout { - var insets = layout.insets(options: [.statusBar]) - insets.top = max(10.0, insets.top) - let cleanInsets = layout.insets(options: [.statusBar]) - - var bottomInset: CGFloat = 10.0 + cleanInsets.bottom - if insets.bottom > 0 { - bottomInset -= 12.0 - } - - let buttonHeight: CGFloat = 57.0 - let sectionSpacing: CGFloat = 8.0 - let titleAreaHeight: CGFloat = 51.0 - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left) - - let sideInset = floor((layout.size.width - width) / 2.0) - - let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing - let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight)) - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY - presentationLayout.contentOffset.y), size: contentFrame.size) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - if backgroundFrame.maxY > contentFrame.maxY { - backgroundFrame.size.height += contentFrame.maxY - backgroundFrame.maxY - } - if backgroundFrame.size.height < buttonHeight + 32.0 { - backgroundFrame.origin.y -= buttonHeight + 32.0 - backgroundFrame.size.height - backgroundFrame.size.height = buttonHeight + 32.0 - } - var compactFrame = true - if let stickerPack = self.stickerPack, case .result = stickerPack { - compactFrame = false - } - if compactFrame { - backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.maxY - buttonHeight - 32.0), size: CGSize(width: contentFrame.size.width, height: buttonHeight + 32.0)) - } - let backgroundDeltaY = backgroundFrame.minY - self.contentBackgroundNode.frame.minY - transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame) - transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -backgroundDeltaY)) - - let titleSize = self.contentTitleNode.bounds.size - let titleFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.size.width - titleSize.width) / 2.0), y: backgroundFrame.minY + 15.0), size: titleSize) - transition.updateFrame(node: self.contentTitleNode, frame: titleFrame) - - transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: backgroundFrame.minY + titleAreaHeight), size: CGSize(width: contentFrame.size.width, height: UIScreenPixel))) - - if !compactFrame && CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) { - self.contentSeparatorNode.alpha = 1.0 - } else { - self.contentSeparatorNode.alpha = 0.0 - } - } - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancelButtonPressed() - } - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func installActionButtonPressed() { - if let stickerPack = self.stickerPack, let _ = self.stickerSettings { - switch stickerPack { - case let .result(info, items, installed): - if installed { - let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete) - |> deliverOnMainQueue).start(next: { [weak self] indexAndItems in - guard let strongSelf = self, let (positionInList, _) = indexAndItems else { - return - } - strongSelf.actionPerformed?(info._parse(), items, .remove(positionInList: positionInList)) - }) - } else { - let parsedInfo = info._parse() - let _ = self.context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items).start() - self.actionPerformed?(parsedInfo, items, .add) - } - self.cancelButtonPressed() - default: - break - } - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) - - let offset: CGFloat = 510.0 - let dimPosition = self.dimNode.layer.position - - let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) - let targetBounds = self.bounds - self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) - self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) - transition.animateView({ - self.bounds = targetBounds - self.dimNode.position = dimPosition - }) - } - - func animateOut(completion: (() -> Void)? = nil) { - var dimCompleted = false - var offsetCompleted = false - - let internalCompletion: () -> Void = { [weak self] in - if let strongSelf = self, dimCompleted && offsetCompleted { - strongSelf.dismiss?() - } - completion?() - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in - dimCompleted = true - internalCompletion() - }) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - offsetCompleted = true - internalCompletion() - }) - } - - func updateStickerPack(_ stickerPack: LoadedStickerPack, stickerSettings: StickerSettings) { - self.stickerPack = stickerPack - self.stickerSettings = stickerSettings - self.stickerPackUpdated = true - - self.interaction.playAnimatedStickers = self.context.sharedContext.energyUsageSettings.loopStickers - - if let _ = self.containerLayout { - self.dequeueUpdateStickerPack() - } - switch stickerPack { - case .none, .fetching: - self.installActionSeparatorNode.alpha = 0.0 - self.shareActionSeparatorNode.alpha = 0.0 - self.shareActionButtonNode.alpha = 0.0 - self.installActionButtonNode.alpha = 0.0 - self.installActionButtonNode.setTitle("", with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal) - case let .result(info, _, installed): - if self.stickerPackInitiallyInstalled == nil { - self.stickerPackInitiallyInstalled = installed - } - self.installActionSeparatorNode.alpha = 1.0 - self.shareActionSeparatorNode.alpha = 1.0 - self.shareActionButtonNode.alpha = 1.0 - self.installActionButtonNode.alpha = 1.0 - if installed { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_RemoveEmojiCount(info.count) - } else { - text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count) - } - self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal) - } else { - let text: String - if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { - text = self.presentationData.strings.StickerPack_AddStickerCount(info.count) - } else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - text = self.presentationData.strings.StickerPack_AddEmojiCount(info.count) - } else { - text = self.presentationData.strings.StickerPack_AddMaskCount(info.count) - } - self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) - } - } - } - - func dequeueUpdateStickerPack() { - if let (layout, navigationBarHeight) = self.containerLayout, let _ = self.stickerPack, self.stickerPackUpdated { - self.stickerPackUpdated = false - - let transition: ContainedViewLayoutTransition - if self.didSetReady { - transition = .animated(duration: 0.4, curve: .spring) - } else { - transition = .immediate - } - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) - - if !self.didSetReady { - self.didSetReady = true - self.ready.set(.single(true)) - } - } - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let result = self.installActionButtonNode.hitTest(self.installActionButtonNode.convert(point, from: self), with: event) { - return result - } - else if self.shareActionButtonNode.supernode != nil, let result = self.shareActionButtonNode.hitTest(self.shareActionButtonNode.convert(point, from: self), with: event) { - return result - } - if self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) && !self.cancelButtonNode.bounds.contains(self.convert(point, to: self.cancelButtonNode)) { - return self.dimNode.view - } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) { - if self.interaction.previewedItem != item { - self.interaction.previewedItem = item - - self.contentGridNode.forEachItemNode { itemNode in - if let itemNode = itemNode as? StickerPackPreviewGridItemNode { - itemNode.updatePreviewing(animated: animated) - } - } - } - } - - @objc private func sharePressed() { - self.openShare?() - } -} diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 0ae1c95212..1ed435d28c 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -29,6 +29,11 @@ import EmojiStatusComponent private let maxStickersCount = 120 +public enum StickerPackPreviewControllerMode { + case `default` + case settings +} + private enum StickerPackPreviewGridEntry: Comparable, Identifiable { case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool, isEditing: Bool, isAdd: Bool) case add @@ -2354,6 +2359,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 diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 3a94b801ff..9d0966c8d7 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -757,7 +757,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[918946202] = { return Api.Peer.parse_peerChat($0) } dict[1498486562] = { return Api.Peer.parse_peerUser($0) } dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) } + dict[-1192589655] = { return Api.PeerColor.parse_inputPeerColorCollectible($0) } dict[-1253352753] = { return Api.PeerColor.parse_peerColor($0) } + dict[-1178573926] = { return Api.PeerColor.parse_peerColorCollectible($0) } dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } @@ -962,7 +964,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } dict[-2136190013] = { return Api.StarGift.parse_starGift($0) } - dict[468707429] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[973640632] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } @@ -1201,7 +1203,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[34280482] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-982010451] = { return Api.UserFull.parse_userFull($0) } + dict[-1607745218] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index e49484163a..1512252096 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -750,10 +750,18 @@ public extension Api { } public extension Api { enum PeerColor: TypeConstructorDescription { + case inputPeerColorCollectible(collectibleId: Int64) case peerColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) + case peerColorCollectible(flags: Int32, collectibleId: Int64, giftEmojiId: Int64, backgroundEmojiId: Int64, accentColor: Int32, colors: [Int32], darkAccentColor: Int32?, darkColors: [Int32]?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .inputPeerColorCollectible(let collectibleId): + if boxed { + buffer.appendInt32(-1192589655) + } + serializeInt64(collectibleId, buffer: buffer, boxed: false) + break case .peerColor(let flags, let color, let backgroundEmojiId): if boxed { buffer.appendInt32(-1253352753) @@ -762,16 +770,52 @@ public extension Api { if Int(flags) & Int(1 << 0) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} break + case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors): + if boxed { + buffer.appendInt32(-1178573926) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(collectibleId, buffer: buffer, boxed: false) + serializeInt64(giftEmojiId, buffer: buffer, boxed: false) + serializeInt64(backgroundEmojiId, buffer: buffer, boxed: false) + serializeInt32(accentColor, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(colors.count)) + for item in colors { + serializeInt32(item, buffer: buffer, boxed: false) + } + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(darkAccentColor!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(darkColors!.count)) + for item in darkColors! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .inputPeerColorCollectible(let collectibleId): + return ("inputPeerColorCollectible", [("collectibleId", collectibleId as Any)]) case .peerColor(let flags, let color, let backgroundEmojiId): return ("peerColor", [("flags", flags as Any), ("color", color as Any), ("backgroundEmojiId", backgroundEmojiId as Any)]) + case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors): + return ("peerColorCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("giftEmojiId", giftEmojiId as Any), ("backgroundEmojiId", backgroundEmojiId as Any), ("accentColor", accentColor as Any), ("colors", colors as Any), ("darkAccentColor", darkAccentColor as Any), ("darkColors", darkColors as Any)]) } } + public static func parse_inputPeerColorCollectible(_ reader: BufferReader) -> PeerColor? { + var _1: Int64? + _1 = reader.readInt64() + let _c1 = _1 != nil + if _c1 { + return Api.PeerColor.inputPeerColorCollectible(collectibleId: _1!) + } + else { + return nil + } + } public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? { var _1: Int32? _1 = reader.readInt32() @@ -789,6 +833,42 @@ public extension Api { return nil } } + public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: Int32? + _5 = reader.readInt32() + var _6: [Int32]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _7: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_7 = reader.readInt32() } + var _8: [Int32]? + if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil + let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.PeerColor.peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8) + } + else { + return nil + } + } } } diff --git a/submodules/TelegramApi/Sources/Api25.swift b/submodules/TelegramApi/Sources/Api25.swift index 71a606a363..dc3eca3c04 100644 --- a/submodules/TelegramApi/Sources/Api25.swift +++ b/submodules/TelegramApi/Sources/Api25.swift @@ -289,7 +289,7 @@ public extension Api { public extension Api { enum StarGift: TypeConstructorDescription { case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?) - case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?) + case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?, peerColor: Api.PeerColor?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -315,9 +315,9 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)} break - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer): + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor): if boxed { - buffer.appendInt32(468707429) + buffer.appendInt32(973640632) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -345,6 +345,7 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {serializeInt64(valueAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(valueCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {themePeer!.serialize(buffer, true)} + if Int(flags) & Int(1 << 11) != 0 {peerColor!.serialize(buffer, true)} break } } @@ -353,8 +354,8 @@ public extension Api { switch self { case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate): return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)]) - case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer): - return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any)]) + case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any), ("peerColor", peerColor as Any)]) } } @@ -468,6 +469,10 @@ public extension Api { if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() { _18 = Api.parse(reader, signature: signature) as? Api.Peer } } + var _19: Api.PeerColor? + if Int(_1!) & Int(1 << 11) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.PeerColor + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -486,8 +491,9 @@ public extension Api { let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18) + let _c19 = (Int(_1!) & Int(1 << 11) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18, peerColor: _19) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 49b9046b8c..db0684250b 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -614,13 +614,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?) + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note): if boxed { - buffer.appendInt32(-982010451) + buffer.appendInt32(-1607745218) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -660,14 +660,15 @@ public extension Api { if Int(flags2) & Int(1 << 18) != 0 {serializeInt32(starsMyPendingRatingDate!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 20) != 0 {mainTab!.serialize(buffer, true)} if Int(flags2) & Int(1 << 21) != 0 {savedMusic!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 22) != 0 {note!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any), ("note", note as Any)]) } } @@ -796,6 +797,10 @@ public extension Api { if Int(_2!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() { _38 = Api.parse(reader, signature: signature) as? Api.Document } } + var _39: Api.TextWithEntities? + if Int(_2!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() { + _39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -834,8 +839,9 @@ public extension Api { let _c36 = (Int(_2!) & Int(1 << 18) == 0) || _36 != nil let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38) + let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index baca4c5d6f..b742f81fa1 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -1654,13 +1654,12 @@ public extension Api.functions.account { } } public extension Api.functions.account { - static func updateColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func updateColor(flags: Int32, color: Api.PeerColor?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(2096079197) + buffer.appendInt32(1749885262) serializeInt32(flags, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} - if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + if Int(flags) & Int(1 << 2) != 0 {color!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -4045,15 +4044,16 @@ public extension Api.functions.contacts { } } public extension Api.functions.contacts { - static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String, note: Api.TextWithEntities?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-386636848) + buffer.appendInt32(-642109868) serializeInt32(flags, buffer: buffer, boxed: false) id.serialize(buffer, true) serializeString(firstName, buffer: buffer, boxed: false) serializeString(lastName, buffer: buffer, boxed: false) serializeString(phone, buffer: buffer, boxed: false) - return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 1) != 0 {note!.serialize(buffer, true)} + return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -4474,6 +4474,22 @@ public extension Api.functions.contacts { }) } } +public extension Api.functions.contacts { + static func updateContactNote(id: Api.InputUser, note: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(329212923) + id.serialize(buffer, true) + note.serialize(buffer, true) + return (FunctionDescription(name: "contacts.updateContactNote", parameters: [("id", String(describing: id)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.folders { static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift index 87373e26a7..d6c9a972f7 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift @@ -175,6 +175,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -185,6 +188,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -254,6 +260,9 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -264,6 +273,9 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 965b629843..4dbcafd50b 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -46,6 +46,24 @@ public extension TelegramMediaFile { } } +public extension TelegramMediaFile { + func isValidForDisplay(chatPeerId: PeerId) -> Bool { + if chatPeerId.namespace == Namespaces.Peer.SecretChat { + if self.isAnimatedSticker { + if !self.attributes.contains(where: { attribute in + if case .hintIsValidated = attribute { + return true + } + return false + }) { + return false + } + } + } + return true + } +} + extension StickerPackReference { init?(apiInputSet: Api.InputStickerSet) { switch apiInputSet { @@ -163,7 +181,21 @@ func telegramMediaFileFromApiDocument(_ document: Api.Document, altDocuments: [A switch document { case let .document(_, id, accessHash, fileReference, _, mimeType, size, thumbs, videoThumbs, dcId, attributes): var parsedAttributes = telegramMediaFileAttributesFromApiAttributes(attributes) - parsedAttributes.append(.hintIsValidated) + var isSticker = false + var isAnimated = false + for attribute in parsedAttributes { + switch attribute { + case .Sticker: + isSticker = true + case .Animated: + isAnimated = true + default: + break + } + } + if isSticker && isAnimated { + parsedAttributes.append(.hintIsValidated) + } let (immediateThumbnail, previewRepresentations) = telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: dcId, documentId: id, accessHash: accessHash, fileReference: fileReference.makeData(), sizes: thumbs ?? []) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift index e95fc8896f..6eb50853ea 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift @@ -133,6 +133,9 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -143,6 +146,9 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -242,6 +248,9 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } @@ -252,6 +261,9 @@ extension TelegramUser { case let .peerColor(_, color, backgroundEmojiIdValue): profileColorIndex = color profileBackgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift index 48216dd0d1..79da3ef833 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift @@ -71,9 +71,10 @@ func _internal_updateNameColorAndEmoji(account: Account, nameColor: PeerNameColo flagsProfile |= (1 << 2) } + //TODO:release return combineLatest( - account.network.request(Api.functions.account.updateColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0)), - account.network.request(Api.functions.account.updateColor(flags: flagsProfile, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0)) + account.network.request(Api.functions.account.updateColor(flags: 0 << 0, color: .peerColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0))), + account.network.request(Api.functions.account.updateColor(flags: 0 << 1, color: .peerColor(flags: flagsProfile, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0))) ) |> mapError { _ -> UpdateNameColorAndEmojiError in return .generic diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift index ce9d96f5a7..1fe48b4db9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/ImportContact.swift @@ -60,7 +60,7 @@ func _internal_addContactInteractively(account: Account, peerId: PeerId, firstNa if addToPrivacyExceptions { flags |= (1 << 0) } - return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone)) + return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone, note: nil)) |> mapError { _ -> AddContactError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift index 6daaab4baa..478dcbf99d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Contacts/UpdateContactName.swift @@ -12,7 +12,7 @@ public enum UpdateContactNameError { func _internal_updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) { - return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: "")) + return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: "", note: nil)) |> mapError { _ -> UpdateContactNameError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index bdec7f6acc..5113a1b5f2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -534,6 +534,9 @@ private class AdMessagesHistoryContextImpl { case let .peerColor(_, color, backgroundEmojiIdValue): nameColorIndex = color backgroundEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index fdf02ea5f8..7793e4bcd9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -958,7 +958,7 @@ extension StarGift { return nil } self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate)) - case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer): + case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer, _): let owner: StarGift.UniqueGift.Owner if let ownerAddress { owner = .address(ownerAddress) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 6937e7b94a..aca4249239 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -390,6 +390,10 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .peerColor(_, color, backgroundEmojiIdValue): prevColorIndex = color ?? 0 prevEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + prevColorIndex = 0 + break } var newColorIndex: Int32 @@ -398,6 +402,10 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .peerColor(_, color, backgroundEmojiIdValue): newColorIndex = color ?? 0 newEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + newColorIndex = 0 + break } action = .changeNameColor(prevColor: PeerNameColor(rawValue: prevColorIndex), prevIcon: prevEmojiId, newColor: PeerNameColor(rawValue: newColorIndex), newIcon: newEmojiId) @@ -408,6 +416,9 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .peerColor(_, color, backgroundEmojiIdValue): prevColorIndex = color prevEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } var newColorIndex: Int32? @@ -416,6 +427,9 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .peerColor(_, color, backgroundEmojiIdValue): newColorIndex = color newEmojiId = backgroundEmojiIdValue + case .peerColorCollectible, .inputPeerColorCollectible: + //TODO:release + break } action = .changeProfileColor(prevColor: prevColorIndex.flatMap(PeerNameColor.init(rawValue:)), prevIcon: prevEmojiId, newColor: newColorIndex.flatMap(PeerNameColor.init(rawValue:)), newIcon: newEmojiId) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 4ecca44385..19f6a4f0c3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -265,7 +265,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)]) } @@ -277,7 +277,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic): + case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic, _): let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0 diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 35dff9586b..a8848b51ae 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -948,13 +948,13 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio panelControlDestructiveColor: UIColor(rgb: 0xff3b30), inputBackgroundColor: UIColor(rgb: 0xffffff), inputStrokeColor: UIColor(rgb: 0x000000, alpha: 0.1), - inputPlaceholderColor: UIColor(rgb: 0x909090, alpha: 0.7), + inputPlaceholderColor: UIColor(rgb: 0x202020, alpha: 0.4), inputTextColor: UIColor(rgb: 0x000000), inputControlColor: UIColor(rgb: 0x202020, alpha: 0.6), actionControlFillColor: defaultDayAccentColor, actionControlForegroundColor: UIColor(rgb: 0xffffff), - primaryTextColor: UIColor(rgb: 0x000000), - secondaryTextColor: UIColor(rgb: 0x8e8e93), + primaryTextColor: UIColor(rgb: 0x000000, alpha: 0.9), + secondaryTextColor: UIColor(rgb: 0x202020, alpha: 0.6), mediaRecordingDotColor: UIColor(rgb: 0xed2521), mediaRecordingControl: inputPanelMediaRecordingControl ) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 1f2724351e..4513c94f13 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -335,10 +335,12 @@ public struct PresentationResourcesChat { return theme.image(PresentationResourceKey.chatInputTextFieldClearImage.rawValue, { theme in return generateImage(CGSize(width: 14.0, height: 14.0), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(theme.chat.inputPanel.inputControlColor.cgColor) - context.setStrokeColor(theme.chat.inputPanel.inputBackgroundColor.cgColor) + context.setFillColor(UIColor.white.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + context.setLineWidth(1.5) context.setLineCap(.round) context.translateBy(x: size.width / 2.0, y: size.height / 2.0) @@ -356,7 +358,7 @@ public struct PresentationResourcesChat { context.move(to: CGPoint(x: (size.width - lineHeight) / 2.0, y: size.width / 2.0)) context.addLine(to: CGPoint(x: (size.width - lineHeight) / 2.0 + lineHeight, y: size.width / 2.0)) context.strokePath() - }) + })?.withRenderingMode(.alwaysTemplate) }) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index bedf351400..46b169144b 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -491,6 +491,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel", "//submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel", "//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode", + "//submodules/TelegramUI/Components/EdgeEffect", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD index 418c7fbfa7..2118c650c4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/BUILD @@ -24,6 +24,9 @@ swift_library( "//submodules/AccountContext", "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", "//submodules/TooltipUI", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index d75764b09e..a94bd33a69 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -15,6 +15,9 @@ import AccountContext import OldChannelsController import TooltipUI import TelegramNotices +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters private enum SubscriberAction: Equatable { case join @@ -140,16 +143,22 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState: private let badgeFont = Font.regular(14.0) public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { - private let button: HighlightableButtonNode - private let discussButton: HighlightableButtonNode - private let discussButtonText: ImmediateTextNode - private let badgeBackground: ASImageNode - private let badgeText: ImmediateTextNode - private let activityIndicator: UIActivityIndicatorView + private let buttonBackgroundView: GlassBackgroundView + private let button: HighlightableButton + private let buttonTitle: ImmediateTextNode + private let buttonTintTitle: ImmediateTextNode - private let helpButton: HighlightableButtonNode - private let giftButton: HighlightableButtonNode - private let suggestedPostButton: HighlightableButtonNode + private let helpButtonBackgroundView: GlassBackgroundView + private let helpButton: HighlightableButton + private let helpButtonIconView: UIImageView + + private let giftButtonBackgroundView: GlassBackgroundView + private let giftButton: HighlightableButton + private let giftButtonIconView: UIImageView + + private let suggestedPostButtonBackgroundView: GlassBackgroundView + private let suggestedPostButton: HighlightableButton + private let suggestedPostButtonIconView: UIImageView private var action: SubscriberAction? @@ -159,52 +168,53 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { private var presentationInterfaceState: ChatPresentationInterfaceState? - private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, Bool, LayoutMetrics)? + private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)? public override init() { - self.button = HighlightableButtonNode() - self.discussButton = HighlightableButtonNode() - self.activityIndicator = UIActivityIndicatorView(style: .medium) - self.activityIndicator.isHidden = true + self.button = HighlightableButton() + self.buttonBackgroundView = GlassBackgroundView() + self.buttonBackgroundView.isUserInteractionEnabled = false + self.button.addSubview(self.buttonBackgroundView) + self.buttonTitle = ImmediateTextNode() + self.buttonTitle.isUserInteractionEnabled = false + self.buttonTintTitle = ImmediateTextNode() + self.buttonBackgroundView.contentView.addSubview(self.buttonTitle.view) + self.buttonBackgroundView.maskContentView.addSubview(self.buttonTintTitle.view) - self.discussButtonText = ImmediateTextNode() - self.discussButtonText.displaysAsynchronously = false - - self.badgeBackground = ASImageNode() - self.badgeBackground.displaysAsynchronously = false - self.badgeBackground.displayWithoutProcessing = true - self.badgeBackground.isHidden = true - - self.badgeText = ImmediateTextNode() - self.badgeText.displaysAsynchronously = false - self.badgeText.isHidden = true - - self.helpButton = HighlightableButtonNode() + self.helpButton = HighlightableButton() self.helpButton.isHidden = true - self.giftButton = HighlightableButtonNode() - self.giftButton.isHidden = true - self.suggestedPostButton = HighlightableButtonNode() - self.suggestedPostButton.isHidden = true + self.helpButtonBackgroundView = GlassBackgroundView() + self.helpButtonBackgroundView.isUserInteractionEnabled = false + self.helpButton.addSubview(self.helpButtonBackgroundView) + self.helpButtonIconView = GlassBackgroundView.ContentImageView() + self.helpButtonBackgroundView.contentView.addSubview(self.helpButtonIconView) - self.discussButton.addSubnode(self.discussButtonText) - self.discussButton.addSubnode(self.badgeBackground) - self.discussButton.addSubnode(self.badgeText) + self.giftButton = HighlightableButton() + self.giftButton.isHidden = true + self.giftButtonBackgroundView = GlassBackgroundView() + self.giftButtonBackgroundView.isUserInteractionEnabled = false + self.giftButton.addSubview(self.giftButtonBackgroundView) + self.giftButtonIconView = GlassBackgroundView.ContentImageView() + self.giftButtonBackgroundView.contentView.addSubview(self.giftButtonIconView) + + self.suggestedPostButton = HighlightableButton() + self.suggestedPostButton.isHidden = true + self.suggestedPostButtonBackgroundView = GlassBackgroundView() + self.suggestedPostButtonBackgroundView.isUserInteractionEnabled = false + self.suggestedPostButton.addSubview(self.suggestedPostButtonBackgroundView) + self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView() + self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView) super.init() - self.clipsToBounds = true - - self.addSubnode(self.button) - self.addSubnode(self.discussButton) - self.view.addSubview(self.activityIndicator) - self.addSubnode(self.helpButton) - self.addSubnode(self.giftButton) - self.addSubnode(self.suggestedPostButton) - self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside) - self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside) - self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside) - self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), forControlEvents: .touchUpInside) + self.view.addSubview(self.button) + self.view.addSubview(self.helpButton) + self.view.addSubview(self.giftButton) + self.view.addSubview(self.suggestedPostButton) + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside) + self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside) + self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside) } deinit { @@ -238,33 +248,14 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { switch action { case .join, .joinGroup, .applyToJoin: - var delayActivity = false - if let peer = peer as? TelegramChannel, case .broadcast = peer.info { - delayActivity = true - } - - if delayActivity { - Queue.mainQueue().after(1.5) { - if self.isJoining { - self.activityIndicator.isHidden = false - self.activityIndicator.startAnimating() - } - } - } else { - self.activityIndicator.isHidden = false - self.activityIndicator.startAnimating() - } - self.isJoining = true - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true) } self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peer.id, hash: nil) |> afterDisposed { [weak self] in Queue.mainQueue().async { if let strongSelf = self { - strongSelf.activityIndicator.isHidden = true - strongSelf.activityIndicator.stopAnimating() strongSelf.isJoining = false } } @@ -311,14 +302,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } } - @objc private func discussPressed() { - if let presentationInterfaceState = self.presentationInterfaceState, let peerDiscussionId = presentationInterfaceState.peerDiscussionId { - self.interfaceInteraction?.navigateToChat(peerDiscussionId) - } - } - - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false) + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false) } private var displayedGiftOrSuggestTooltip = false @@ -349,7 +334,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start() Queue.mainQueue().after(0.4, { - let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view) + let absoluteFrame = self.giftButton.convert(self.giftButton.bounds, to: parentController.view) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize()) let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -376,7 +361,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start() Queue.mainQueue().after(0.4, { - let absoluteFrame = self.suggestedPostButton.view.convert(self.suggestedPostButton.bounds, to: parentController.view) + let absoluteFrame = self.suggestedPostButton.convert(self.suggestedPostButton.bounds, to: parentController.view) let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize()) let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -405,19 +390,23 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { }) } - private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat { + private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat { let isFirstTime = self.layoutData == nil - self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) + self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) if self.presentationInterfaceState != interfaceState || force { let previousState = self.presentationInterfaceState self.presentationInterfaceState = interfaceState if previousState?.theme !== interfaceState.theme { - self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0) - self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) - self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) - self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal) + self.helpButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.helpButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor + + self.suggestedPostButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.suggestedPostButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor + + self.giftButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.giftButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor } if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force { @@ -425,9 +414,11 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { if let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) { let previousAction = self.action self.action = action - let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) + let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings) - var offset: CGFloat = 30.0 + let _ = previousAction + + /*var offset: CGFloat = 30.0 if let previousAction = previousAction, [.join, .muteNotifications].contains(previousAction) && action == .unmuteNotifications || [.join, .unmuteNotifications].contains(previousAction) && action == .muteNotifications { if [.join, .muteNotifications].contains(previousAction) { @@ -444,81 +435,95 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { self.button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.button.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true) } - } + }*/ - self.button.setTitle(title, with: Font.regular(17.0), with: color, for: []) + let titleColor: UIColor + if case .join = self.action { + titleColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor + } else { + titleColor = interfaceState.theme.chat.inputPanel.inputControlColor + } + self.buttonTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: titleColor) + self.buttonTintTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: .black) self.button.accessibilityLabel = title } else { self.action = nil } - - self.discussButton.isHidden = true } } let panelHeight = defaultHeight(metrics: metrics) - if self.discussButton.isHidden { - if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel { - if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable { - if self.giftButton.isHidden && !isFirstTime { - self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) - } - - self.giftButton.isHidden = false - self.helpButton.isHidden = true - self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum) - self.presentGiftOrSuggestTooltip() - } else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) { - self.giftButton.isHidden = true - self.helpButton.isHidden = true - self.suggestedPostButton.isHidden = false - self.presentGiftOrSuggestTooltip() - } else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications { - self.giftButton.isHidden = true - self.helpButton.isHidden = false - self.suggestedPostButton.isHidden = true - } else { - self.giftButton.isHidden = true - self.helpButton.isHidden = true - self.suggestedPostButton.isHidden = true + if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel { + if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable { + if self.giftButton.isHidden && !isFirstTime { + self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2) } + + self.giftButton.isHidden = false + self.helpButton.isHidden = true + self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum) + self.presentGiftOrSuggestTooltip() + } else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) { + self.giftButton.isHidden = true + self.helpButton.isHidden = true + self.suggestedPostButton.isHidden = false + self.presentGiftOrSuggestTooltip() + } else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications { + self.giftButton.isHidden = true + self.helpButton.isHidden = false + self.suggestedPostButton.isHidden = true } else { self.giftButton.isHidden = true self.helpButton.isHidden = true self.suggestedPostButton.isHidden = true } - if let action = self.action, action == .muteNotifications || action == .unmuteNotifications { - let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0 - self.button.frame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight)) - } else { - self.button.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight)) - } - self.giftButton.frame = CGRect(x: width - rightInset - panelHeight - 5.0, y: 0.0, width: panelHeight, height: panelHeight) - self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight) - self.suggestedPostButton.frame = CGRect(x: leftInset + 5.0, y: 0.0, width: panelHeight, height: panelHeight) } else { self.giftButton.isHidden = true self.helpButton.isHidden = true self.suggestedPostButton.isHidden = true - - let availableWidth = min(600.0, width - leftInset - rightInset) - let leftOffset = floor((width - availableWidth) / 2.0) - self.button.frame = CGRect(origin: CGPoint(x: leftOffset, y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight)) - self.discussButton.frame = CGRect(origin: CGPoint(x: leftOffset + floor(availableWidth / 2.0), y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight)) - - let discussButtonSize = self.discussButton.bounds.size - let discussTextSize = self.discussButtonText.updateLayout(discussButtonSize) - self.discussButtonText.frame = CGRect(origin: CGPoint(x: floor((discussButtonSize.width - discussTextSize.width) / 2.0), y: floor((discussButtonSize.height - discussTextSize.height) / 2.0)), size: discussTextSize) - - let badgeOffset = self.discussButtonText.frame.maxX + 5.0 - self.badgeBackground.frame.minX - self.badgeBackground.frame = self.badgeBackground.frame.offsetBy(dx: badgeOffset, dy: 0.0) - self.badgeText.frame = self.badgeText.frame.offsetBy(dx: badgeOffset, dy: 0.0) } - let indicatorSize = self.activityIndicator.bounds.size - self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width - 12.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize) + let buttonTitleSize = self.buttonTitle.updateLayout(CGSize(width: width, height: panelHeight)) + let _ = self.buttonTintTitle.updateLayout(CGSize(width: width, height: panelHeight)) + let buttonSize = CGSize(width: buttonTitleSize.width + 16.0 * 2.0, height: 40.0) + let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) * 0.5)), size: buttonSize) + transition.updateFrame(view: self.button, frame: buttonFrame) + transition.updateFrame(view: self.buttonBackgroundView, frame: CGRect(origin: CGPoint(), size: buttonFrame.size)) + let buttonTintColor: GlassBackgroundView.TintColor + if case .join = self.action { + buttonTintColor = .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) + } else { + buttonTintColor = .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)) + } + self.buttonBackgroundView.update(size: buttonFrame.size, cornerRadius: buttonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: buttonTintColor, transition: ComponentTransition(transition)) + self.buttonTitle.frame = CGRect(origin: CGPoint(x: floor((buttonFrame.width - buttonTitleSize.width) * 0.5), y: floor((buttonFrame.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize) + self.buttonTintTitle.frame = self.buttonTitle.frame + + let giftButtonFrame = CGRect(x: width - rightInset - 40.0 - 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0) + transition.updateFrame(view: self.giftButton, frame: giftButtonFrame) + if let image = self.giftButtonIconView.image { + transition.updateFrame(view: self.giftButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: giftButtonFrame.size))) + } + transition.updateFrame(view: self.giftButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: giftButtonFrame.size)) + self.giftButtonBackgroundView.update(size: giftButtonFrame.size, cornerRadius: giftButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) + + let helpButtonFrame = CGRect(x: width - rightInset - 8.0 - 40.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0) + transition.updateFrame(view: self.helpButton, frame: helpButtonFrame) + if let image = self.helpButtonIconView.image { + transition.updateFrame(view: self.helpButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: helpButtonFrame.size))) + } + transition.updateFrame(view: self.helpButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: helpButtonFrame.size)) + self.helpButtonBackgroundView.update(size: helpButtonFrame.size, cornerRadius: helpButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) + + let suggestedPostButtonFrame = CGRect(x: leftInset + 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0) + transition.updateFrame(view: self.suggestedPostButton, frame: suggestedPostButtonFrame) + if let image = self.suggestedPostButtonIconView.image { + transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))) + } + transition.updateFrame(view: self.suggestedPostButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)) + self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) return panelHeight } diff --git a/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/BUILD b/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/BUILD new file mode 100644 index 0000000000..d6facc307f --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatInputAutocompletePanel", + module_name = "ChatInputAutocompletePanel", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/Sources/ChatInputAutocompletePanel.swift b/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/Sources/ChatInputAutocompletePanel.swift new file mode 100644 index 0000000000..e24036f95a --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel/Sources/ChatInputAutocompletePanel.swift @@ -0,0 +1,44 @@ +import Foundation +import UIKit +import TelegramPresentationData +import TelegramUIPreferences +import GlassBackgroundComponent + +public final class ChatInputAutocompletePanelEnvironment: Equatable { + public let theme: PresentationTheme + public let strings: PresentationStrings + public let nameDisplayOrder: PresentationPersonNameOrder + public let dateTimeFormat: PresentationDateTimeFormat + + public init( + theme: PresentationTheme, + strings: PresentationStrings, + nameDisplayOrder: PresentationPersonNameOrder, + dateTimeFormat: PresentationDateTimeFormat + ) { + self.theme = theme + self.strings = strings + self.nameDisplayOrder = nameDisplayOrder + self.dateTimeFormat = dateTimeFormat + } + + public static func ==(lhs: ChatInputAutocompletePanelEnvironment, rhs: ChatInputAutocompletePanelEnvironment) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.nameDisplayOrder != rhs.nameDisplayOrder { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + return true + } +} + +public protocol ChatInputAutocompletePanelView: UIView { + var contentTintView: UIView { get } +} diff --git a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift index 146451859d..42e195a818 100644 --- a/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatInputPanelNode/Sources/ChatInputPanelNode.swift @@ -23,7 +23,7 @@ open class ChatInputPanelNode: ASDisplayNode { open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) { } - open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { return 0.0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift index 3dc0a1b635..abcac90dc6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSelectionInputPanelNode/Sources/ChatMessageSelectionInputPanelNode.swift @@ -72,7 +72,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent - private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)? + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var actions: ChatAvailableMessageActions? @@ -167,8 +167,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { if self.selectedMessages.isEmpty { self.actions = nil - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } self.canDeleteMessagesDisposable.set(nil) } else if let context = self.context { @@ -176,8 +176,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { |> deliverOnMainQueue).startStrict(next: { [weak self] actions in if let strongSelf = self { strongSelf.actions = actions - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight: maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } } })) @@ -348,13 +348,13 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode { } private func update(transition: ContainedViewLayoutTransition) { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState { + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } } - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) let panelHeight = defaultHeight(metrics: metrics) diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift index 115eece0f0..d522989b4d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift @@ -981,10 +981,13 @@ final class OverscrollContentsComponent: Component { let titleSize = self.titleNode.updateLayout(CGSize(width: availableSize.width - 32.0, height: 100.0)) let titleBackgroundSize = CGSize(width: titleSize.width + 18.0, height: titleSize.height + 8.0) let titleBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleBackgroundSize.width) / 2.0), y: fullHeight - titleBackgroundSize.height - 8.0), size: titleBackgroundSize) - self.titleBackgroundNode.frame = titleBackgroundFrame + self.titleBackgroundNode.position = titleBackgroundFrame.center + self.titleBackgroundNode.bounds = CGRect(origin: CGPoint(), size: titleBackgroundFrame.size) self.titleBackgroundNode.update(rect: titleBackgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate) self.titleBackgroundNode.cornerRadius = min(titleBackgroundFrame.width, titleBackgroundFrame.height) / 2.0 - self.titleNode.frame = titleSize.centered(in: titleBackgroundFrame) + let titleFrame = titleSize.centered(in: titleBackgroundFrame) + self.titleNode.position = titleFrame.center + self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) let backgroundClippingFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: -fullHeight), size: CGSize(width: backgroundWidth, height: isFullyExpanded ? backgroundWidth : fullHeight)) self.backgroundClippingNode.cornerRadius = isFolderMask ? 10.0 : backgroundWidth / 2.0 @@ -1003,7 +1006,7 @@ final class OverscrollContentsComponent: Component { let transformTransition: ContainedViewLayoutTransition if self.isFullyExpanded != isFullyExpanded { self.isFullyExpanded = isFullyExpanded - transformTransition = .animated(duration: 0.12, curve: .easeInOut) + transformTransition = .animated(duration: 0.18, curve: .easeInOut) if isFullyExpanded { func animateBounce(layer: CALayer) { @@ -1067,8 +1070,11 @@ final class OverscrollContentsComponent: Component { transformTransition.updateSublayerTransformOffset(layer: self.avatarOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0)) transformTransition.updateSublayerTransformOffset(layer: self.arrowOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0)) + + transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : 20.0)) - transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : (titleBackgroundSize.height + 50.0))) + transformTransition.updateTransformScale(layer: self.titleBackgroundNode.layer, scale: isFullyExpanded ? 1.0 : 0.001) + transformTransition.updateTransformScale(layer: self.titleNode.layer, scale: isFullyExpanded ? 1.0 : 0.001) transformTransition.updateSublayerTransformScale(node: self.avatarExtraScalingContainer, scale: isFullyExpanded ? 1.0 : ((backgroundWidth - avatarInset * 2.0) / backgroundWidth)) @@ -1209,50 +1215,3 @@ public final class ChatOverscrollControl: CombinedComponent { } } } - -public final class ChatInputPanelOverscrollNode: ASDisplayNode { - public let text: NSAttributedString - public let priority: Int - private let titleNode: ImmediateTextNodeWithEntities - - public init(context: AccountContext, text: NSAttributedString, color: UIColor, priority: Int) { - self.text = text - self.priority = priority - self.titleNode = ImmediateTextNodeWithEntities() - - super.init() - - let attributedText = NSMutableAttributedString(string: text.string) - attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length)) - attributedText.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: text.length)) - text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in - for (key, value) in attributes { - if key == ChatTextInputAttributes.bold { - attributedText.addAttribute(.font, value: Font.bold(14.0), range: range) - } else if key == ChatTextInputAttributes.italic { - attributedText.addAttribute(.font, value: Font.italic(14.0), range: range) - } else if key == ChatTextInputAttributes.monospace { - attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range) - } else { - attributedText.addAttribute(key, value: value, range: range) - } - } - }) - self.titleNode.attributedText = attributedText - self.titleNode.visibility = true - self.titleNode.arguments = TextNodeWithEntities.Arguments( - context: context, - cache: context.animationCache, - renderer: context.animationRenderer, - placeholderColor: color.withMultipliedAlpha(0.1), - attemptSynchronous: true - ) - - self.addSubnode(self.titleNode) - } - - public func update(size: CGSize) { - let titleSize = self.titleNode.updateLayout(size) - self.titleNode.frame = titleSize.centered(in: CGRect(origin: CGPoint(), size: size)) - } -} diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift index e67e93a847..43e999f5ea 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -387,7 +387,7 @@ public final class ChatRecordingPreviewInputPanelNodeImpl: ChatInputPanelNode { })*/ } - override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { let innerSize = CGSize(width: 40.0, height: 40.0) let waveformBackgroundFrame = CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: width - 2.0 * 2.0, height: 40.0 - 2.0 * 2.0)) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD index 7d9fb24ceb..25a1e1b6ad 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/BUILD @@ -61,7 +61,9 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode", "//submodules/TelegramUI/Components/GlassBackgroundComponent", "//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel", + "//submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel", "//submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode", + "//submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 204e7a6c10..45247ccfdc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -50,12 +50,14 @@ import PhotoResources import GlassBackgroundComponent import ComponentDisplayAdapters import ChatInputAccessoryPanel +import ChatInputAutocompletePanel import ChatTextInputSlowmodePlaceholderNode import ChatTextInputActionButtonsNode import ChatTextInputAudioRecordingTimeNode import ChatTextInputAudioRecordingCancelIndicator import ChatRecordingViewOnceButtonNode import ChatRecordingPreviewInputPanelNode +import ChatInputContextPanelNode private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -216,6 +218,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public var textLockIconNode: ASImageNode? public var contextPlaceholderNode: TextNode? + public var tintContextPlaceholderNode: TextNode? public var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode? public let textInputContainerBackgroundView: GlassBackgroundView public let textInputContainer: ASDisplayNode @@ -253,8 +256,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public var attachmentImageNode: TransformImageNode? - public let searchLayoutClearButton: HighlightableButton - private let searchLayoutClearImageNode: ASImageNode + public let searchLayoutClearButton: HighlightTrackingButton + private let searchLayoutClearButtonIcon: GlassBackgroundView.ContentImageView private var searchActivityIndicator: ActivityIndicator? public var audioRecordingInfoContainerNode: ASDisplayNode? public var audioRecordingDotView: UIImageView? @@ -266,11 +269,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public let viewOnceButton: ChatRecordingViewOnceButtonNode private var accessoryPanel: (component: AnyComponentWithIdentity, view: ComponentView)? + private var contextPanel: (container: UIView, mask: UIImageView, panel: ChatInputContextPanelNode)? private var mediaPreviewPanelNode: ChatRecordingPreviewInputPanelNodeImpl? private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = [] - private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, LayoutMetrics, Bool, Bool)? + private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, LayoutMetrics, Bool, Bool)? private var leftMenuInset: CGFloat = 0.0 private var rightSlowModeInset: CGFloat = 0.0 private var currentTextInputBackgroundWidthOffset: CGFloat = 0.0 @@ -282,6 +286,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public var toggleExpandMediaInput: (() -> Void)? public var switchToTextInputIfNeeded: (() -> Void)? public var textInputAccessoryPanel: ((_ context: AccountContext, _ chatPresentationInterfaceState: ChatPresentationInterfaceState, _ chatControllerInteraction: ChatControllerInteraction?, _ interfaceInteraction: ChatPanelInterfaceInteraction?) -> AnyComponentWithIdentity?)? + public var textInputContextPanel: ((_ context: AccountContext, _ chatPresentationInterfaceState: ChatPresentationInterfaceState, _ chatControllerInteraction: ChatControllerInteraction?, _ interfaceInteraction: ChatPanelInterfaceInteraction?, _ current: ChatInputContextPanelNode?) -> ChatInputContextPanelNode?)? public var updateActivity: () -> Void = { } @@ -570,10 +575,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.attachmentButtonBackground.contentView.addSubview(self.attachmentButtonIcon) self.attachmentButtonDisabledNode = HighlightableButtonNode() - self.searchLayoutClearButton = HighlightableButton() - self.searchLayoutClearImageNode = ASImageNode() - self.searchLayoutClearImageNode.isUserInteractionEnabled = false - self.searchLayoutClearButton.addSubnode(self.searchLayoutClearImageNode) + self.searchLayoutClearButton = HighlightTrackingButton() + self.searchLayoutClearButtonIcon = GlassBackgroundView.ContentImageView() self.actionButtons = ChatTextInputActionButtonsNode(context: context, presentationInterfaceState: presentationInterfaceState, presentationContext: presentationContext, presentController: presentController) self.counterTextNode = ImmediateTextNode() @@ -726,15 +729,15 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } self.actionButtons.micButton.offsetRecordingControls = { [weak self] in if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } } } self.actionButtons.micButton.updateCancelTranslation = { [weak self] in if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState { - if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { + let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } } } @@ -763,8 +766,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.actionButtons.expandMediaInputButton.addTarget(self, action: #selector(self.expandButtonPressed), for: .touchUpInside) self.actionButtons.expandMediaInputButton.alpha = 0.0 + self.searchLayoutClearButton.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.searchLayoutClearButtonIcon.alpha = 0.6 + } else { + self.searchLayoutClearButtonIcon.alpha = 1.0 + let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(layer: self.searchLayoutClearButtonIcon.layer, alpha: 1.0) + } + } self.searchLayoutClearButton.addTarget(self, action: #selector(self.searchLayoutClearButtonPressed), for: .touchUpInside) self.searchLayoutClearButton.alpha = 0.0 + self.searchLayoutClearButtonIcon.alpha = 0.0 self.clippingNode.addSubnode(self.textInputContainer) self.clippingNode.addSubnode(self.textInputBackgroundNode) @@ -793,7 +809,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.clippingNode.addSubnode(self.slowModeButton) - self.clippingNode.view.addSubview(self.searchLayoutClearButton) + self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButton) + self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButtonIcon) + self.textInputContainerBackgroundView.maskContentView.addSubview(self.searchLayoutClearButtonIcon.tintMask) self.textInputBackgroundNode.clipsToBounds = true let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:))) @@ -1217,10 +1235,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } public func requestLayout(transition: ContainedViewLayoutTransition = .immediate) { - guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { + guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else { return } - let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) } override public func updateLayout( @@ -1230,6 +1248,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, + maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, @@ -1237,7 +1256,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg isMediaInputExpanded: Bool ) -> CGFloat { let previousAdditionalSideInsets = self.validLayout?.4 - self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) + self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) var transition = transition var additionalOffset: CGFloat = 0.0 @@ -1249,7 +1268,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } + let previousContextPanel = self.contextPanel + var accessoryPanel: AnyComponentWithIdentity? + var contextPanelNode: ChatInputContextPanelNode? if let context = self.context { accessoryPanel = self.textInputAccessoryPanel?( context, @@ -1257,6 +1279,13 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.chatControllerInteraction, self.interfaceInteraction ) + contextPanelNode = self.textInputContextPanel?( + context, + interfaceState, + self.chatControllerInteraction, + self.interfaceInteraction, + self.contextPanel?.panel + ) } var wasEditingMedia = false @@ -1593,7 +1622,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - self.searchLayoutClearImageNode.image = PresentationResourcesChat.chatInputTextFieldClearImage(interfaceState.theme) + self.searchLayoutClearButtonIcon.image = PresentationResourcesChat.chatInputTextFieldClearImage(interfaceState.theme) + self.searchLayoutClearButtonIcon.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor self.audioRecordingTimeNode?.updateTheme(theme: interfaceState.theme) self.audioRecordingCancelIndicator?.updateTheme(theme: interfaceState.theme) @@ -1930,6 +1960,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if additionalSideInsets.right > 0.0 { textFieldInsets.right += additionalSideInsets.right / 3.0 } + if self.extendedSearchLayout { + textFieldInsets.right = 8.0 + } if mediaRecordingState != nil { textFieldInsets.left = 8.0 } @@ -2311,7 +2344,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updateAlpha(layer: mediaPreviewPanelNode.tintMaskView.layer, alpha: 1.0) transition.updateFrame(view: mediaPreviewPanelNode.tintMaskView, frame: mediaPreviewPanelFrame) - let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: false) + let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, maxOverlayHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: false) } else if let mediaPreviewPanelNode = self.mediaPreviewPanelNode { self.mediaPreviewPanelNode = nil transition.updateAlpha(node: mediaPreviewPanelNode, alpha: 0.0, completion: { [weak mediaPreviewPanelNode] _ in @@ -2376,20 +2409,33 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if interfaceState.slowmodeState == nil || isScheduledMessages, let contextPlaceholder = interfaceState.inputTextPanelState.contextPlaceholder { let placeholderLayout = TextNode.asyncLayout(self.contextPlaceholderNode) + let tintPlaceholderLayout = TextNode.asyncLayout(self.tintContextPlaceholderNode) let (placeholderSize, placeholderApply) = placeholderLayout(TextNodeLayoutArguments(attributedString: contextPlaceholder, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let tintContextPlaceholder = NSMutableAttributedString(attributedString: contextPlaceholder) + tintContextPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: 0, length: tintContextPlaceholder.length)) + let (_, tintPlaceholderApply) = tintPlaceholderLayout(TextNodeLayoutArguments(attributedString: tintContextPlaceholder, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let contextPlaceholderNode = placeholderApply() + let tintContextPlaceholderNode = tintPlaceholderApply() if let currentContextPlaceholderNode = self.contextPlaceholderNode, currentContextPlaceholderNode !== contextPlaceholderNode { self.contextPlaceholderNode = nil currentContextPlaceholderNode.removeFromSupernode() } + if let currentTintContextPlaceholderNode = self.tintContextPlaceholderNode, currentTintContextPlaceholderNode !== tintContextPlaceholderNode { + self.tintContextPlaceholderNode = nil + currentTintContextPlaceholderNode.removeFromSupernode() + } if self.contextPlaceholderNode !== contextPlaceholderNode { contextPlaceholderNode.displaysAsynchronously = false contextPlaceholderNode.isUserInteractionEnabled = false self.contextPlaceholderNode = contextPlaceholderNode self.textInputContainerBackgroundView.contentView.insertSubview(contextPlaceholderNode.view, aboveSubview: self.textPlaceholderNode.view) - - self.textInputContainerBackgroundView.contentView.insertSubview(contextPlaceholderNode.view, aboveSubview: self.textPlaceholderNode.view) + } + if self.tintContextPlaceholderNode !== tintContextPlaceholderNode { + tintContextPlaceholderNode.displaysAsynchronously = false + tintContextPlaceholderNode.isUserInteractionEnabled = false + self.tintContextPlaceholderNode = tintContextPlaceholderNode + self.textInputContainerBackgroundView.maskContentView.insertSubview(tintContextPlaceholderNode.view, aboveSubview: self.tintMaskTextPlaceholderNode.view) } let _ = placeholderApply() @@ -2400,13 +2446,22 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } else { placeholderTransition = .immediate } - placeholderTransition.updateFrame(node: contextPlaceholderNode, frame: CGRect(origin: CGPoint(x: hideOffset.x + leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size)) + placeholderTransition.updateFrame(node: contextPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size)) contextPlaceholderNode.alpha = audioRecordingItemsAlpha - } else if let contextPlaceholderNode = self.contextPlaceholderNode { - self.contextPlaceholderNode = nil - contextPlaceholderNode.removeFromSupernode() - self.textPlaceholderNode.alpha = 1.0 - self.tintMaskTextPlaceholderNode.alpha = 1.0 + + placeholderTransition.updateFrame(node: tintContextPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size)) + tintContextPlaceholderNode.alpha = audioRecordingItemsAlpha + } else { + if let contextPlaceholderNode = self.contextPlaceholderNode { + self.contextPlaceholderNode = nil + contextPlaceholderNode.removeFromSupernode() + self.textPlaceholderNode.alpha = 1.0 + self.tintMaskTextPlaceholderNode.alpha = 1.0 + } + if let tintContextPlaceholderNode = self.tintContextPlaceholderNode { + self.tintContextPlaceholderNode = nil + tintContextPlaceholderNode.removeFromSupernode() + } } if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && rightSlowModeInset.isZero { @@ -2439,6 +2494,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } var nextButtonTopRight = CGPoint(x: textInputContainerBackgroundFrame.width - accessoryButtonInset - rightSlowModeInset, y: textInputContainerBackgroundFrame.height - minimalInputHeight) + if self.extendedSearchLayout { + nextButtonTopRight.x -= 26.0 + } for (item, button) in self.accessoryItemButtons.reversed() { let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight) button.updateLayout(item: item, size: buttonSize) @@ -2485,7 +2543,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string)) } - let attributedTintMaskPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: .black) + let attributedTintMaskPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: UIColor(white: 0.0, alpha: placeholderColor.alpha)) if placeholderHasStar, let range = attributedPlaceholder.string.range(of: "#") { attributedTintMaskPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: attributedPlaceholder.string)) attributedTintMaskPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(range, in: attributedPlaceholder.string)) @@ -2614,13 +2672,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } - let searchLayoutClearButtonSize = CGSize(width: 40.0, height: minimalHeight) + let searchLayoutClearButtonSize = CGSize(width: 40.0, height: 40.0) self.actionButtons.micButton.isHidden = additionalSideInsets.right > 0.0 self.actionButtons.micButtonBackgroundView.isHidden = self.actionButtons.micButton.isHidden - transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: CGRect(origin: CGPoint(x: width - rightInset - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset + 3.0, y: panelHeight - minimalHeight), size: searchLayoutClearButtonSize)) - if let image = self.searchLayoutClearImageNode.image { - self.searchLayoutClearImageNode.frame = CGRect(origin: CGPoint(x: floor((searchLayoutClearButtonSize.width - image.size.width) / 2.0), y: floor((searchLayoutClearButtonSize.height - image.size.height) / 2.0)), size: image.size) + let clearButtonFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.width - searchLayoutClearButtonSize.width, y: floor((textInputContainerBackgroundFrame.height - searchLayoutClearButtonSize.height) * 0.5)), size: searchLayoutClearButtonSize) + transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: clearButtonFrame) + if let image = self.searchLayoutClearButtonIcon.image { + let clearIconFrame = CGRect(origin: CGPoint(x: floor((searchLayoutClearButtonSize.width - image.size.width) / 2.0), y: floor((searchLayoutClearButtonSize.height - image.size.height) / 2.0)), size: image.size) + + transition.updateFrame(layer: self.searchLayoutClearButtonIcon.layer, frame: clearIconFrame.offsetBy(dx: clearButtonFrame.minX, dy: clearButtonFrame.minY)) } let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0)) @@ -2736,6 +2797,55 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.viewOnceButton.isHidden = true } + if contextPanelNode !== previousContextPanel?.panel, let previousContextPanel { + let panelContainer = previousContextPanel.container + previousContextPanel.panel.animateOut(completion: { [weak panelContainer] in + panelContainer?.removeFromSuperview() + }) + self.contextPanel = nil + } + if let contextPanelNode { + if self.contextPanel == nil { + self.contextPanel = (UIView(), UIImageView(), contextPanelNode) + } + } + + if let contextPanel = self.contextPanel { + let maskInset: CGFloat = 32.0 + var contextPanelTransition = transition + if contextPanel.container.superview == nil { + contextPanelTransition = .immediate + self.view.insertSubview(contextPanel.container, belowSubview: self.clippingNode.view) + contextPanel.container.addSubview(contextPanel.panel.view) + contextPanel.container.mask = contextPanel.mask + //contextPanel.container.addSubview(contextPanel.mask) + let maskSize = floor(minimalInputHeight) + contextPanel.mask.image = generateImage(CGSize(width: maskSize + maskInset * 2.0, height: maskSize + maskInset * 2.0), rotatedContext: { size, context in + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: maskInset, y: maskInset + maskSize * 0.5), size: CGSize(width: maskSize, height: maskSize))) + context.fill(CGRect(origin: CGPoint(x: 0.0, y: maskInset + maskSize), size: CGSize(width: maskSize + maskInset * 2.0, height: maskSize + maskInset))) + })?.stretchableImage(withLeftCapWidth: Int(maskInset) + Int(maskSize) / 2, topCapHeight: Int(maskInset) + 1) + } + let contextPanelBottomInset = floor(minimalInputHeight * 0.5) + let contextPanelFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.minX, y: contentHeight - maxOverlayHeight), size: CGSize(width: textInputContainerBackgroundFrame.width, height: max(0.0, maxOverlayHeight - contentHeight + contextPanelBottomInset))) + + contextPanelTransition.updateFrame(view: contextPanel.container, frame: contextPanelFrame) + contextPanelTransition.updateFrame(view: contextPanel.panel.view, frame: CGRect(origin: CGPoint(), size: contextPanelFrame.size)) + contextPanelTransition.updateFrame(view: contextPanel.mask, frame: CGRect(origin: CGPoint(), size: contextPanelFrame.size).insetBy(dx: -maskInset, dy: -maskInset)) + + contextPanel.panel.updateLayout( + size: contextPanelFrame.size, + leftInset: 0.0, + rightInset: 0.0, + bottomInset: contextPanelBottomInset, + transition: contextPanelTransition, + interfaceState: interfaceState + ) + } + return contentHeight } @@ -3252,7 +3362,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg self.counterTextNode.attributedText = NSAttributedString(string: "", font: counterFont, textColor: .black) } - if let (width, leftInset, rightInset, _, _, maxHeight, metrics, _, _) = self.validLayout { + if let (width, leftInset, rightInset, _, _, maxHeight, _, metrics, _, _) = self.validLayout { var composeButtonsOffset: CGFloat = 0.0 if self.extendedSearchLayout { composeButtonsOffset = 40.0 @@ -3611,9 +3721,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } if self.searchLayoutClearButton.alpha.isZero { self.searchLayoutClearButton.alpha = 1.0 + self.searchLayoutClearButtonIcon.alpha = 1.0 if animated { self.searchLayoutClearButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) self.searchLayoutClearButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2) + self.searchLayoutClearButtonIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.searchLayoutClearButtonIcon.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2) } } } else { @@ -3621,9 +3734,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg if !self.searchLayoutClearButton.alpha.isZero { animateWithBounce = false self.searchLayoutClearButton.alpha = 0.0 + self.searchLayoutClearButtonIcon.alpha = 0.0 if animated { self.searchLayoutClearButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) self.searchLayoutClearButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.2) + self.searchLayoutClearButtonIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + self.searchLayoutClearButtonIcon.layer.animateScale(from: 1.0, to: 0.8, duration: 0.2) } } @@ -3763,7 +3879,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } private func updateTextHeight(animated: Bool) { - if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _, _) = self.validLayout { + if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, _, metrics, _, _) = self.validLayout { let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, maxHeight: maxHeight, metrics: metrics) let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics) if !self.bounds.size.height.isEqual(to: panelHeight) { @@ -4572,6 +4688,18 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } + if !self.searchLayoutClearButton.alpha.isZero { + if let result = self.searchLayoutClearButton.hitTest(self.view.convert(point, to: self.searchLayoutClearButton), with: event) { + return result + } + } + + if !self.bounds.contains(point), let contextPanel = self.contextPanel { + if let result = contextPanel.panel.view.hitTest(self.view.convert(point, to: contextPanel.panel.view), with: event) { + return result + } + } + let result = super.hitTest(point, with: event) return result } diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift index 2462c77960..5b05658797 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -26,7 +26,7 @@ public final class EdgeEffectView: UIView { fatalError("init(coder:) has not been implemented") } - public func update(content: UIColor, rect: CGRect, edge: Edge, edgeSize: CGFloat, containerSize: CGSize, transition: ComponentTransition) { + public func update(content: UIColor, isInverted: Bool, rect: CGRect, edge: Edge, edgeSize: CGFloat, containerSize: CGSize, transition: ComponentTransition) { self.contentView.backgroundColor = content transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: rect.size)) diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index e0121387fc..826a4c3b95 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -266,6 +266,7 @@ public final class GlassBackgroundView: UIView { private let foregroundView: UIImageView? private let shadowView: UIImageView? + private let maskContainerView: UIView public let maskContentView: UIView private let contentContainer: ContentContainer @@ -298,12 +299,15 @@ public final class GlassBackgroundView: UIView { self.shadowView = UIImageView() } - self.maskContentView = UIView() - self.maskContentView.backgroundColor = .white + self.maskContainerView = UIView() + self.maskContainerView.backgroundColor = .white if let filter = CALayer.luminanceToAlpha() { - self.maskContentView.layer.filters = [filter] + self.maskContainerView.layer.filters = [filter] } + self.maskContentView = UIView() + self.maskContainerView.addSubview(self.maskContentView) + self.contentContainer = ContentContainer(maskContentView: self.maskContentView) super.init(frame: frame) @@ -319,7 +323,7 @@ public final class GlassBackgroundView: UIView { } if let foregroundView = self.foregroundView { self.addSubview(foregroundView) - foregroundView.mask = self.maskContentView + foregroundView.mask = self.maskContainerView } self.addSubview(self.contentContainer) } @@ -361,7 +365,7 @@ public final class GlassBackgroundView: UIView { context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(UIColor.black.cgColor) - context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 30.0, color: UIColor(white: 0.0, alpha: 0.08).cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.09).cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0))) context.setFillColor(UIColor.clear.cgColor) @@ -371,7 +375,7 @@ public final class GlassBackgroundView: UIView { } if let foregroundView = self.foregroundView { - foregroundView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color) + foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: tintColor.color) } else { if let nativeView { if #available(iOS 26.0, *) { @@ -390,9 +394,10 @@ public final class GlassBackgroundView: UIView { } } - transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: self.maskContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width + shadowInset * 2.0, height: size.height + shadowInset * 2.0))) + transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: size)) if let foregroundView = self.foregroundView { - transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size)) + transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -shadowInset, dy: -shadowInset)) } if let shadowView = self.shadowView { transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -shadowInset, dy: -shadowInset)) @@ -491,6 +496,210 @@ public final class VariableBlurView: UIVisualEffectView { } public extension GlassBackgroundView { + static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, isDark: Bool, fillColor: UIColor) -> UIImage { + var size = size + if size == .zero { + size = CGSize(width: 1.0, height: 1.0) + } + let innerSize = size + size.width += inset * 2.0 + size.height += inset * 2.0 + + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + func pathApplyingSpread(_ path: CGPath, spread: CGFloat) -> CGPath { + guard spread != 0 else { return path } + let result = CGMutablePath() + result.addPath(path) + + // Copy a stroked outline centered on the original path boundary. + // Filling it plus the original path approximates an outward "spread". + let outline = path.copy( + strokingWithWidth: abs(spread) * 2, + lineCap: .butt, + lineJoin: .miter, + miterLimit: 10, + transform: .identity + ) + result.addPath(outline) + + // For negative spread (tighten), use even-odd to carve inside: + if spread < 0 { + let carve = CGMutablePath() + carve.addPath(path) + carve.addPath(outline) + // even-odd: outline - original ≈ outer ring; union with original earlier keeps overall stable + // For "tightening" effect we rely on clipping in inner shadow branch below. + } + return result + } + + // Your requested closure: + let addShadow: (Bool, CGPoint, CGFloat, CGFloat, UIColor) -> Void = { isOuter, position, blur, spread, shadowColor in + var blur = blur + blur += abs(spread) + + if isOuter { + context.beginTransparencyLayer(auxiliaryInfo: nil) + context.saveGState() + defer { + context.restoreGState() + context.endTransparencyLayer() + } + + let spreadRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize).insetBy(dx: 0.25, dy: 0.25) + let spreadPath = UIBezierPath( + roundedRect: spreadRect, + cornerRadius: min(spreadRect.width, spreadRect.height) * 0.5 + ).cgPath + + context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor) + context.setFillColor(UIColor.black.withAlphaComponent(1.0).cgColor) + context.addPath(spreadPath) + context.fillPath() + + let cleanRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize) + let cleanPath = UIBezierPath( + roundedRect: cleanRect, + cornerRadius: min(cleanRect.width, cleanRect.height) * 0.5 + ).cgPath + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.addPath(cleanPath) + context.fillPath() + context.setBlendMode(.normal) + } else { + context.beginTransparencyLayer(auxiliaryInfo: nil) + context.saveGState() + defer { + context.restoreGState() + context.endTransparencyLayer() + } + + let spreadRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize).insetBy(dx: -0.25, dy: -0.25) + let spreadPath = UIBezierPath( + roundedRect: spreadRect, + cornerRadius: min(spreadRect.width, spreadRect.height) * 0.5 + ).cgPath + + context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor) + context.setFillColor(UIColor.black.withAlphaComponent(1.0).cgColor) + let enclosingRect = spreadRect.insetBy(dx: -10000.0, dy: -10000.0) + context.addPath(UIBezierPath(rect: enclosingRect).cgPath) + context.addPath(spreadPath) + context.fillPath(using: .evenOdd) + + let cleanRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize) + let cleanPath = UIBezierPath( + roundedRect: cleanRect, + cornerRadius: min(cleanRect.width, cleanRect.height) * 0.5 + ).cgPath + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.addPath(UIBezierPath(rect: enclosingRect).cgPath) + context.addPath(cleanPath) + context.fillPath(using: .evenOdd) + context.setBlendMode(.normal) + } + } + + if isDark { + addShadow(true, CGPoint(), 16.0, 0.0, UIColor(white: 0.0, alpha: 0.12)) + addShadow(true, CGPoint(), 8.0, 0.0, UIColor(white: 0.0, alpha: 0.1)) + + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset)) + + addShadow(false, CGPoint(x: 0.0, y: 0.0), 3.0, 0.0, UIColor(white: 1.0, alpha: 0.5)) + addShadow(false, CGPoint(x: 3.0, y: -3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25)) + addShadow(false, CGPoint(x: -3.0, y: 3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25)) + } else { + addShadow(true, CGPoint(), 16.0, 0.0, UIColor(white: 0.0, alpha: 0.08)) + addShadow(true, CGPoint(), 8.0, 0.0, UIColor(white: 0.0, alpha: 0.08)) + + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset)) + + addShadow(false, CGPoint(x: 3.0, y: -3.0), 0.5, 0.0, fillColor.withMultiplied(hue: 1.0, saturation: 2.0, brightness: 1.0).adjustedPerceivedBrightness(3.0).withMultipliedAlpha(1.0)) + addShadow(false, CGPoint(x: -2.0, y: 2.0), 0.5, 0.0, UIColor.black.withMultipliedAlpha(0.15)) + } + + if "".isEmpty { + return + } + + let maxColor = UIColor(white: 1.0, alpha: isDark ? 0.25 : 0.9) + let minColor = UIColor(white: 1.0, alpha: 0.0) + + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + let lineWidth: CGFloat = isDark ? 0.66 : 0.66 + + context.saveGState() + + let darkShadeColor = UIColor(white: isDark ? 1.0 : 0.0, alpha: 0.035) + let lightShadeColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.035) + let innerShadowBlur: CGFloat = 24.0 + + context.resetClip() + context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) + context.clip() + context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0)) + context.addEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.black.cgColor) + context.setShadow(offset: CGSize(width: 10.0, height: -10.0), blur: innerShadowBlur, color: darkShadeColor.cgColor) + context.fillPath(using: .evenOdd) + + context.resetClip() + context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) + context.clip() + context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0)) + context.addEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.setFillColor(UIColor.black.cgColor) + context.setShadow(offset: CGSize(width: -10.0, height: 10.0), blur: innerShadowBlur, color: lightShadeColor.cgColor) + context.fillPath(using: .evenOdd) + + context.restoreGState() + + context.setLineWidth(lineWidth) + + context.addRect(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))) + context.clip() + context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) + context.replacePathWithStrokedPath() + context.clip() + + do { + var locations: [CGFloat] = [0.0, 0.5, 0.5 + 0.2, 1.0 - 0.1, 1.0] + let colors: [CGColor] = [maxColor.cgColor, maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + } + + context.resetClip() + context.addRect(CGRect(origin: CGPoint(x: size.width - size.width * 0.5, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height))) + context.clip() + context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) + context.replacePathWithStrokedPath() + context.clip() + + do { + var locations: [CGFloat] = [0.0, 0.1, 0.5 - 0.2, 0.5, 1.0] + let colors: [CGColor] = [maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor, maxColor.cgColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + } + })!.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5)) + } + static func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage { var size = size if size == .zero { diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index 1671205bf3..a5c7b4d4b3 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -22,9 +22,6 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr result.append(.hashtag(query)) } else if possibleTypes == [.mention] { let types: ChatInputQueryMentionTypes = [.members] -// if possibleQueryRange.lowerBound == 1 { -// types.insert(.contextBots) -// } result.append(.mention(query: query, types: types)) } else if possibleTypes == [.command] { result.append(.command(query)) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 3ba88a4928..69ea91969b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -468,7 +468,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil) - let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false) + let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false) transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight))) @@ -12312,7 +12312,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight)) transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame) - self.edgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition)) + self.edgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition)) } let sectionSpacing: CGFloat = 24.0 diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift index 8006a42067..3005a84de5 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerThemeManagement.swift @@ -299,6 +299,11 @@ extension ChatControllerImpl { } self.chatDisplayNode.dismissTextInput() + var previewIconFile: TelegramMediaFile? = previewIconFile + if let file = previewIconFile, let peerId = self.chatLocation.peerId, !file.isValidForDisplay(chatPeerId: peerId) { + previewIconFile = nil + } + let presentationData = self.presentationData 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 { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 327d53695f..18447a45a1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -7968,19 +7968,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let stickerPackReference = stickerPackReference { - self.presentEmojiList(references: [stickerPackReference], previewIconFile: file) + var previewIconFile: TelegramMediaFile? = file + if !file.isValidForDisplay(chatPeerId: message.id.peerId) { + previewIconFile = nil + } - /*let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false) - |> deliverOnMainQueue).startStandalone(next: { [weak self] stickerPack in - if let strongSelf = self, case let .result(info, _, _) = stickerPack { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: nil, text: strongSelf.presentationData.strings.Stickers_EmojiPackInfoText(info.title).string, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in - if let strongSelf = self, action == .undo { - strongSelf.presentEmojiList(references: [stickerPackReference]) - } - return false - }), in: .current) - } - })*/ + self.presentEmojiList(references: [stickerPackReference], previewIconFile: previewIconFile) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index f458d29413..123c87a976 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -243,7 +243,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { private var inputPanelBackgroundBlurView: VariableBlurView? private(set) var inputPanelNode: ChatInputPanelNode? - private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode? private weak var currentDismissedInputPanelNode: ChatInputPanelNode? private(set) var secondaryInputPanelNode: ChatInputPanelNode? private(set) var accessoryPanelNode: AccessoryPanelNode? @@ -859,6 +858,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self?.interfaceInteraction?.presentController(controller, nil) }) self.textInputPanelNode?.textInputAccessoryPanel = textInputAccessoryPanel + self.textInputPanelNode?.textInputContextPanel = textInputContextPanel self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage self.textInputPanelNode?.updateHeight = { [weak self] animated in if let strongSelf = self, let _ = strongSelf.inputPanelNode as? ChatTextInputPanelNode, !strongSelf.ignoreUpdateHeight { @@ -1627,7 +1627,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if inputTextPanelNode.isFocused { self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring)) } - let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) } if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) { inputPanelNodeHandlesTransition = true @@ -1639,7 +1639,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } else { dismissedInputPanelNode = self.inputPanelNode } - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.inputPanelNode = inputPanelNode if inputPanelNode.supernode !== self { @@ -1650,7 +1650,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -1661,7 +1661,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing { if secondaryInputPanelNode !== self.secondaryInputPanelNode { dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) self.secondaryInputPanelNode = secondaryInputPanelNode if secondaryInputPanelNode.supernode == nil { @@ -1672,7 +1672,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent) } } else { - let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) + let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0) secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight) } } else { @@ -2309,7 +2309,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight, right: 0.0) + let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight + 8.0 + 8.0, right: 0.0) self.visibleAreaInset = visibleAreaInset var loadingNodeInsets = visibleAreaInset @@ -2693,19 +2693,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { transition.updateAlpha(node: accessoryPanelNode, alpha: 1.0) } - let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - inputPanelsHeight - insets.top))) + let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.top))) let inputContextPanelsOverMainPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - (inputPanelSize == nil ? CGFloat(0.0) : inputPanelSize!.height) - insets.top))) if let inputContextPanelNode = self.inputContextPanelNode { let panelFrame = inputContextPanelNode.placement == .overTextInput ? inputContextPanelsOverMainPanelFrame : inputContextPanelsFrame if immediatelyLayoutInputContextPanelAndAnimateAppearance { - /*var startPanelFrame = panelFrame - if let derivedLayoutState = self.derivedLayoutState { - let referenceFrame = inputContextPanelNode.placement == .overTextInput ? derivedLayoutState.inputContextPanelsOverMainPanelFrame : derivedLayoutState.inputContextPanelsFrame - startPanelFrame.origin.y = referenceFrame.maxY - panelFrame.height - }*/ inputContextPanelNode.frame = panelFrame - inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) + inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) } if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme { @@ -2824,9 +2819,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } if !transition.isAnimated { inputPanelNode.layer.removeAllAnimations() - if let currentDismissedInputPanelNode = self.currentDismissedInputPanelNode, inputPanelNode is ChatSearchInputPanelNode { - currentDismissedInputPanelNode.layer.removeAllAnimations() - } } if inputPanelNodeHandlesTransition { inputPanelNode.frame = apparentInputPanelFrame @@ -5005,7 +4997,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let titleAccessoryPanelSnapshot: UIView? let navigationBarHeight: CGFloat let inputPanelNodeSnapshot: UIView? - let inputPanelOverscrollNodeSnapshot: UIView? fileprivate init( backgroundNode: WallpaperBackgroundNode, @@ -5015,8 +5006,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState, titleAccessoryPanelSnapshot: UIView?, navigationBarHeight: CGFloat, - inputPanelNodeSnapshot: UIView?, - inputPanelOverscrollNodeSnapshot: UIView? + inputPanelNodeSnapshot: UIView? ) { self.backgroundNode = backgroundNode self.historySnapshotState = historySnapshotState @@ -5026,7 +5016,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot self.navigationBarHeight = navigationBarHeight self.inputPanelNodeSnapshot = inputPanelNodeSnapshot - self.inputPanelOverscrollNodeSnapshot = inputPanelOverscrollNodeSnapshot } } @@ -5044,11 +5033,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { snapshot.frame = inputPanelNode.frame inputPanelNodeSnapshot = snapshot } - var inputPanelOverscrollNodeSnapshot: UIView? - if let inputPanelOverscrollNode = self.inputPanelOverscrollNode, let snapshot = inputPanelOverscrollNode.view.snapshotView(afterScreenUpdates: false) { - snapshot.frame = inputPanelOverscrollNode.frame - inputPanelOverscrollNodeSnapshot = snapshot - } return SnapshotState( backgroundNode: self.backgroundNode, historySnapshotState: self.historyNode.prepareSnapshotState(), @@ -5057,8 +5041,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { navigationButtonsSnapshotState: self.navigateButtons.prepareSnapshotState(), titleAccessoryPanelSnapshot: titleAccessoryPanelSnapshot, navigationBarHeight: self.navigationBar?.backgroundNode.bounds.height ?? 0.0, - inputPanelNodeSnapshot: inputPanelNodeSnapshot, - inputPanelOverscrollNodeSnapshot: inputPanelOverscrollNodeSnapshot + inputPanelNodeSnapshot: inputPanelNodeSnapshot ) } @@ -5114,69 +5097,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { }) inputPanelNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - if let inputPanelOverscrollNodeSnapshot = snapshotState.inputPanelOverscrollNodeSnapshot { - inputPanelNode.view.superview?.insertSubview(inputPanelOverscrollNodeSnapshot, belowSubview: inputPanelNode.view) - - inputPanelOverscrollNodeSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNodeSnapshot] _ in - inputPanelOverscrollNodeSnapshot?.removeFromSuperview() - }) - inputPanelOverscrollNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - } - inputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) inputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 5.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } } - - private var preivousChatInputPanelOverscrollNodeTimestamp: Double = 0.0 - - func setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode?) { - let directionUp: Bool - if let overscrollNode = overscrollNode { - if let current = self.inputPanelOverscrollNode { - directionUp = current.priority > overscrollNode.priority - } else { - directionUp = true - } - } else { - directionUp = false - } - - let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut) - - let timestamp = CFAbsoluteTimeGetCurrent() - if self.preivousChatInputPanelOverscrollNodeTimestamp > timestamp - 0.05 { - if let inputPanelOverscrollNode = self.inputPanelOverscrollNode { - self.inputPanelOverscrollNode = nil - inputPanelOverscrollNode.removeFromSupernode() - } - } - self.preivousChatInputPanelOverscrollNodeTimestamp = timestamp - - if let inputPanelOverscrollNode = self.inputPanelOverscrollNode { - self.inputPanelOverscrollNode = nil - inputPanelOverscrollNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: directionUp ? -5.0 : 5.0), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - inputPanelOverscrollNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNode] _ in - inputPanelOverscrollNode?.removeFromSupernode() - }) - } - - if let inputPanelNode = self.inputPanelNode, let overscrollNode = overscrollNode { - self.inputPanelOverscrollNode = overscrollNode - inputPanelNode.supernode?.insertSubnode(overscrollNode, aboveSubnode: inputPanelNode) - - overscrollNode.frame = inputPanelNode.frame - overscrollNode.update(size: overscrollNode.bounds.size) - - overscrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: directionUp ? 5.0 : -5.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, additive: true) - overscrollNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) - } - - if let inputPanelNode = self.inputPanelNode { - transition.updateAlpha(node: inputPanelNode, alpha: overscrollNode == nil ? 1.0 : 0.0) - transition.updateSublayerTransformOffset(layer: inputPanelNode.layer, offset: CGPoint(x: 0.0, y: overscrollNode == nil ? 0.0 : -5.0)) - } - } private func setupHistoryNode() { var backgroundColors: [UInt32] = [] diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 096d206c0c..88c4bb2aad 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -2525,64 +2525,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto self.currentOverscrollExpandProgress = expandProgress - if let nextChannelToRead = self.nextChannelToRead { - let swipeText: NSAttributedString - let releaseText: NSAttributedString - switch nextChannelToRead.location { - case .same: - if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil { - swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress) - releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction) - } else if nextChannelToRead.threadData != nil { - swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress) - releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction) - } else { - swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress) - releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction) - } - case .archived: - swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress) - releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction) - case .unarchived: - swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress) - releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction) - case let .folder(_, title): - let swipeTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgressV2) - let swipeFolderRange = (swipeTextValue.string as NSString).range(of: "{folder}") - if swipeFolderRange.location != NSNotFound { - swipeTextValue.replaceCharacters(in: swipeFolderRange, with: "") - swipeTextValue.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: swipeFolderRange.location) - } - swipeText = swipeTextValue - - let releaseTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeActionV2) - let releaseTextFolderRange = (releaseTextValue.string as NSString).range(of: "{folder}") - if releaseTextFolderRange.location != NSNotFound { - releaseTextValue.replaceCharacters(in: releaseTextFolderRange, with: "") - releaseTextValue.insert(title.attributedString(attributes: [ - ChatTextInputAttributes.bold: true - ]), at: releaseTextFolderRange.location) - } - releaseText = releaseTextValue - } - - if expandProgress < 0.1 { - chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) - } else if expandProgress >= 1.0 { - if chatControllerNode.inputPanelOverscrollNode?.text.string != releaseText.string { - chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1)) - } - } else { - if chatControllerNode.inputPanelOverscrollNode?.text.string != swipeText.string { - chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2)) - } - } - } else { - chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) - } - var overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.bounds.width, height: 94.0)) if self.freezeOverscrollControlProgress { overscrollFrame.origin.y -= max(0.0, 94.0 - expandDistance) @@ -2618,10 +2560,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } else if let overscrollView = self.overscrollView { self.overscrollView = nil overscrollView.removeFromSuperview() - - if let chatControllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode { - chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) - } } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index 11bf2eabe9..e0a074b468 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -27,6 +27,164 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult } } +func textInputContextPanel(context: AccountContext, chatPresentationInterfaceState: ChatPresentationInterfaceState, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, currentPanel: ChatInputContextPanelNode?) -> ChatInputContextPanelNode? { + guard let controllerInteraction else { + return nil + } + guard let inputQueryResult = chatPresentationInterfaceState.inputQueryResults.values.sorted(by: { lhs, rhs in + let (lhsP, lhsHasItems) = inputQueryResultPriority(lhs) + let (rhsP, rhsHasItems) = inputQueryResultPriority(rhs) + if lhsHasItems != rhsHasItems { + if lhsHasItems { + return true + } else { + return false + } + } + return lhsP < rhsP + }).first else { + return nil + } + + var hasBannedInlineContent = false + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendInline) != nil { + hasBannedInlineContent = true + } else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendInline) { + hasBannedInlineContent = true + } + + if hasBannedInlineContent { + switch inputQueryResult { + case .stickers, .contextRequestResult: + if let currentPanel = currentPanel as? DisabledContextResultsChatInputContextPanelNode { + return currentPanel + } else { + let panel = DisabledContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + return panel + } + default: + break + } + } + + switch inputQueryResult { + case let .stickers(unfilteredResults): + let _ = unfilteredResults + return nil + /*if !unfilteredResults.isEmpty { + var results: [FoundStickerItem] = [] + for result in unfilteredResults { + if !results.contains(where: { $0.file.fileId == result.file.fileId }) { + results.append(result) + } + } + + let query = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.string + + if let currentPanel = currentPanel as? InlineReactionSearchPanel { + currentPanel.updateResults(results: results.map({ $0.file }), query: query) + return currentPanel + } else { + let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: chatPresentationInterfaceState.renderedPeer?.peerId, chatPresentationContext: chatPresentationContext) + panel.controllerInteraction = controllerInteraction + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(results: results.map({ $0.file }), query: query) + return panel + } + }*/ + case let .hashtags(results, query): + var peer: EnginePeer? + if let chatPeer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, chatPeer.addressName != nil { + peer = EnginePeer(chatPeer) + } + if !results.isEmpty || (peer != nil && query.count >= 4) { + if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode { + currentPanel.updateResults(results, query: query, peer: peer) + return currentPanel + } else { + let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(results, query: query, peer: peer) + return panel + } + } else { + return nil + } + case let .emojis(results, _): + let _ = results + return nil + /*if !results.isEmpty { + if let currentPanel = currentPanel as? EmojisChatInputContextPanelNode { + currentPanel.updateResults(results) + return currentPanel + } else { + let panel = EmojisChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: chatPresentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(results) + return panel + } + }*/ + case let .mentions(peers): + if !peers.isEmpty { + if let currentPanel = currentPanel as? MentionChatInputContextPanelNode, currentPanel.mode == .input { + currentPanel.updateResults(peers) + return currentPanel + } else { + let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(peers) + return panel + } + } else { + return nil + } + case let .commands(commands): + if !commands.commands.isEmpty || commands.hasShortcuts { + if let currentPanel = currentPanel as? CommandChatInputContextPanelNode { + currentPanel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query) + return currentPanel + } else { + let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query) + return panel + } + } else { + return nil + } + case let .contextRequestResult(_, results): + let _ = results + return nil + /*if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) { + switch results.presentation { + case .list: + if let currentPanel = currentPanel as? VerticalListContextResultsChatInputContextPanelNode { + currentPanel.updateResults(results) + return currentPanel + } else { + let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(results) + return panel + } + case .media: + if let currentPanel = currentPanel as? HorizontalListContextResultsChatInputContextPanelNode { + currentPanel.updateResults(results) + return currentPanel + } else { + let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) + panel.interfaceInteraction = interfaceInteraction + panel.updateResults(results) + return panel + } + } + } else { + return nil + }*/ + } +} + func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? { if chatPresentationInterfaceState.showCommands, let renderedPeer = chatPresentationInterfaceState.renderedPeer { if let currentPanel = currentPanel as? CommandMenuChatInputContextPanelNode { @@ -98,22 +256,8 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa return panel } } - case let .hashtags(results, query): - var peer: EnginePeer? - if let chatPeer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, chatPeer.addressName != nil { - peer = EnginePeer(chatPeer) - } - if !results.isEmpty || (peer != nil && query.count >= 4) { - if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode { - currentPanel.updateResults(results, query: query, peer: peer) - return currentPanel - } else { - let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) - panel.interfaceInteraction = interfaceInteraction - panel.updateResults(results, query: query, peer: peer) - return panel - } - } + case .hashtags: + return nil case let .emojis(results, _): if !results.isEmpty { if let currentPanel = currentPanel as? EmojisChatInputContextPanelNode { @@ -126,34 +270,10 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa return panel } } - case let .mentions(peers): - if !peers.isEmpty { - if let currentPanel = currentPanel as? MentionChatInputContextPanelNode, currentPanel.mode == .input { - currentPanel.updateResults(peers) - return currentPanel - } else { - let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input, chatPresentationContext: chatPresentationContext) - panel.interfaceInteraction = interfaceInteraction - panel.updateResults(peers) - return panel - } - } else { - return nil - } - case let .commands(commands): - if !commands.commands.isEmpty || commands.hasShortcuts { - if let currentPanel = currentPanel as? CommandChatInputContextPanelNode { - currentPanel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query) - return currentPanel - } else { - let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) - panel.interfaceInteraction = interfaceInteraction - panel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query) - return panel - } - } else { - return nil - } + case .mentions: + return nil + case .commands: + return nil case let .contextRequestResult(_, results): if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) { switch results.presentation { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 4b8f841836..b2302adb43 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -461,6 +461,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState interfaceInteraction?.presentController(controller, nil) }) panel.textInputAccessoryPanel = textInputAccessoryPanel + panel.textInputContextPanel = textInputContextPanel panel.chatControllerInteraction = chatControllerInteraction panel.interfaceInteraction = interfaceInteraction panel.context = context diff --git a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift index 0348668a84..48f96157ca 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReportInputPanelNode.swift @@ -15,7 +15,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode { private let reportButton: HighlightableButtonNode private let separatorNode: ASDisplayNode - private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)? + private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)? private var presentationInterfaceState: ChatPresentationInterfaceState? private var theme: PresentationTheme @@ -72,7 +72,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode { } } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift index 51571afe63..9cd52cba92 100644 --- a/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPremiumRequiredInputPanelNode.swift @@ -26,18 +26,20 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode { var bottomInset: CGFloat var additionalSideInsets: UIEdgeInsets var maxHeight: CGFloat + var maxOverlayHeight: CGFloat var isSecondary: Bool var interfaceState: ChatPresentationInterfaceState var metrics: LayoutMetrics var isMediaInputExpanded: Bool - init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { + init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { self.width = width self.leftInset = leftInset self.rightInset = rightInset self.bottomInset = bottomInset self.additionalSideInsets = additionalSideInsets self.maxHeight = maxHeight + self.maxOverlayHeight = maxOverlayHeight self.isSecondary = isSecondary self.interfaceState = interfaceState self.metrics = metrics @@ -72,8 +74,8 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode { deinit { } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) if let currentLayout = self.currentLayout, currentLayout.params == params { return currentLayout.height } diff --git a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift index c704f1c010..3018633377 100644 --- a/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatRestrictedInputPanelNode.swift @@ -10,31 +10,49 @@ import ChatPresentationInterfaceState import TelegramPresentationData import ChatInputPanelNode import AccountContext +import GlassBackgroundComponent +import ComponentFlow +import ComponentDisplayAdapters final class ChatRestrictedInputPanelNode: ChatInputPanelNode { + private let backgroundView: GlassBackgroundView private let buttonNode: HighlightTrackingButtonNode private let textNode: ImmediateTextNode + private let tintTextNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode + private let tintSubtitleNode: ImmediateTextNode private var iconView: UIImageView? private var presentationInterfaceState: ChatPresentationInterfaceState? override init() { + self.backgroundView = GlassBackgroundView() + self.textNode = ImmediateTextNode() self.textNode.maximumNumberOfLines = 2 self.textNode.textAlignment = .center + self.tintTextNode = ImmediateTextNode() + self.tintTextNode.maximumNumberOfLines = 2 + self.tintTextNode.textAlignment = .center self.subtitleNode = ImmediateTextNode() self.subtitleNode.maximumNumberOfLines = 1 self.subtitleNode.textAlignment = .center + self.tintSubtitleNode = ImmediateTextNode() + self.tintSubtitleNode.maximumNumberOfLines = 1 + self.tintSubtitleNode.textAlignment = .center self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.isUserInteractionEnabled = false super.init() - self.addSubnode(self.textNode) - self.addSubnode(self.subtitleNode) + self.backgroundView.contentView.addSubview(self.textNode.view) + self.backgroundView.maskContentView.addSubview(self.tintTextNode.view) + self.backgroundView.contentView.addSubview(self.subtitleNode.view) + self.backgroundView.maskContentView.addSubview(self.tintSubtitleNode.view) + + self.view.addSubview(self.backgroundView) self.addSubnode(self.buttonNode) self.buttonNode.highligthedChanged = { [weak self] highlighted in @@ -63,7 +81,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.openBoostToUnrestrict() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState } @@ -135,6 +153,9 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { } self.buttonNode.isUserInteractionEnabled = isUserInteractionEnabled + self.tintTextNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(15.0), textColor: .black) + self.tintSubtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: .black) + let panelHeight = defaultHeight(metrics: metrics) let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight)) @@ -166,13 +187,22 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode { combinedHeight += subtitleSize.height + 2.0 } let textFrame = CGRect(origin: CGPoint(x: originX, y: floor((panelHeight - combinedHeight) / 2.0)), size: textSize) - self.textNode.frame = textFrame let subtitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - subtitleSize.width) / 2.0), y: floor((panelHeight + combinedHeight) / 2.0) - subtitleSize.height), size: subtitleSize) - self.subtitleNode.frame = subtitleFrame - let combinedFrame = textFrame.union(subtitleFrame) - self.buttonNode.frame = combinedFrame.insetBy(dx: -8.0, dy: -12.0) + var combinedFrame = textFrame.union(subtitleFrame).insetBy(dx: -12.0, dy: -6.0) + combinedFrame.origin.y += 1.0 + + self.textNode.frame = textFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY) + self.tintTextNode.frame = self.textNode.frame + + self.subtitleNode.frame = subtitleFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY) + self.tintSubtitleNode.frame = self.subtitleNode.frame + + self.backgroundView.frame = combinedFrame + self.backgroundView.update(size: combinedFrame.size, cornerRadius: combinedFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) + + self.buttonNode.frame = combinedFrame return panelHeight } diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift deleted file mode 100644 index f66856418e..0000000000 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ /dev/null @@ -1,224 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import TelegramCore -import Postbox -import SwiftSignalKit -import TelegramNotices -import TelegramPresentationData -import ActivityIndicator -import ChatPresentationInterfaceState -import ChatInputPanelNode - -private let labelFont = Font.regular(15.0) - -final class ChatSearchInputPanelNode: ChatInputPanelNode { - private let upButton: HighlightableButtonNode - private let downButton: HighlightableButtonNode - private let calendarButton: HighlightableButtonNode - private let membersButton: HighlightableButtonNode - private let resultsButton: HighlightableButtonNode - private let measureResultsLabel: TextNode - private let activityIndicator: ActivityIndicator - - private var presentationInterfaceState: ChatPresentationInterfaceState? - - private let activityDisposable = MetaDisposable() - private var displayActivity = false - - private var needsSearchResultsTooltip = true - - private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)? - - override var interfaceInteraction: ChatPanelInterfaceInteraction? { - didSet { - if let statuses = self.interfaceInteraction?.statuses { - self.activityDisposable.set((combineLatest((statuses.searching |> deliverOnMainQueue), (statuses.loadingMessage |> deliverOnMainQueue))).startStrict(next: { [weak self] searching, loadingMessage in - let value = searching || loadingMessage == .generic - if let strongSelf = self, strongSelf.displayActivity != value { - strongSelf.displayActivity = value - strongSelf.activityIndicator.isHidden = !value - if let interfaceState = strongSelf.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout { - let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) - } - } - })) - } else { - self.activityDisposable.set(nil) - } - } - } - - init(theme: PresentationTheme) { - self.upButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) - self.upButton.isEnabled = false - self.downButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) - self.downButton.isEnabled = false - self.calendarButton = HighlightableButtonNode() - self.membersButton = HighlightableButtonNode(pointerStyle: .circle(36.0)) - self.measureResultsLabel = TextNode() - self.measureResultsLabel.displaysAsynchronously = false - self.resultsButton = HighlightableButtonNode(pointerStyle: .default) - self.activityIndicator = ActivityIndicator(type: .navigationAccent(theme.rootController.navigationBar.buttonColor)) - self.activityIndicator.isHidden = true - - super.init() - - //self.addSubnode(self.upButton) - //self.addSubnode(self.downButton) - self.addSubnode(self.calendarButton) - self.addSubnode(self.membersButton) - self.addSubnode(self.resultsButton) - self.resultsButton.addSubnode(self.measureResultsLabel) - self.addSubnode(self.activityIndicator) - - self.upButton.addTarget(self, action: #selector(self.upPressed), forControlEvents: [.touchUpInside]) - self.downButton.addTarget(self, action: #selector(self.downPressed), forControlEvents: [.touchUpInside]) - self.calendarButton.addTarget(self, action: #selector(self.calendarPressed), forControlEvents: [.touchUpInside]) - self.membersButton.addTarget(self, action: #selector(self.membersPressed), forControlEvents: [.touchUpInside]) - self.resultsButton.addTarget(self, action: #selector(self.resultsPressed), forControlEvents: [.touchUpInside]) - } - - deinit { - self.activityDisposable.dispose() - } - - @objc func upPressed() { - self.interfaceInteraction?.navigateMessageSearch(.earlier) - - guard self.needsSearchResultsTooltip, let context = self.context else { - return - } - - let _ = (ApplicationSpecificNotice.getChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager) - |> deliverOnMainQueue).startStandalone(next: { [weak self] counter in - guard let strongSelf = self else { - return - } - - if counter >= 3 { - strongSelf.needsSearchResultsTooltip = false - } else if arc4random_uniform(4) == 1 { - strongSelf.needsSearchResultsTooltip = false - - let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager).startStandalone() - strongSelf.interfaceInteraction?.displaySearchResultsTooltip(strongSelf.resultsButton, strongSelf.resultsButton.bounds) - } - }) - } - - @objc func downPressed() { - self.interfaceInteraction?.navigateMessageSearch(.later) - } - - @objc func calendarPressed() { - self.interfaceInteraction?.openCalendarSearch() - } - - @objc func membersPressed() { - self.interfaceInteraction?.toggleMembersSearch(true) - } - - @objc func resultsPressed() { - self.interfaceInteraction?.openSearchResults() - - if let context = self.context { - let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager, count: 4).startStandalone() - } - } - - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) - - if self.presentationInterfaceState != interfaceState { - let themeUpdated = self.presentationInterfaceState?.theme !== interfaceState.theme - - self.presentationInterfaceState = interfaceState - - if themeUpdated { - self.upButton.setImage(PresentationResourcesChat.chatInputSearchPanelUpImage(interfaceState.theme), for: [.normal]) - self.upButton.setImage(PresentationResourcesChat.chatInputSearchPanelUpDisabledImage(interfaceState.theme), for: [.disabled]) - self.downButton.setImage(PresentationResourcesChat.chatInputSearchPanelDownImage(interfaceState.theme), for: [.normal]) - self.downButton.setImage(PresentationResourcesChat.chatInputSearchPanelDownDisabledImage(interfaceState.theme), for: [.disabled]) - self.calendarButton.setImage(PresentationResourcesChat.chatInputSearchPanelCalendarImage(interfaceState.theme), for: []) - - self.membersButton.setImage(PresentationResourcesChat.chatInputSearchPanelMembersImage(interfaceState.theme), for: []) - } - } - - let panelHeight: CGFloat - if case .regular = metrics.widthClass { - panelHeight = 49.0 - } else { - panelHeight = 45.0 - } - - var width = width - if additionalSideInsets.right > 0.0 { - width -= additionalSideInsets.right - } - - self.downButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 48.0, y: 0.0), size: CGSize(width: 40.0, height: panelHeight)) - self.upButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 48.0 - 43.0, y: 0.0), size: CGSize(width: 40.0, height: panelHeight)) - self.calendarButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 60.0, height: panelHeight)) - self.membersButton.frame = CGRect(origin: CGPoint(x: leftInset + 43.0, y: 0.0), size: CGSize(width: 60.0, height: panelHeight)) - - var resultIndex: Int? - var resultCount: Int? - var resultsText: String? - if let results = interfaceState.search?.resultsState { - resultCount = results.messageIndices.count - let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount) - if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) { - let adjustedIndex = results.messageIndices.count - 1 - index - resultIndex = index - resultsText = interfaceState.strings.Items_NOfM("\(adjustedIndex + 1)", "\(displayTotalCount)").string - } else { - resultsText = interfaceState.strings.Conversation_SearchNoResults - } - } - - self.upButton.isEnabled = resultIndex != nil && resultIndex != 0 - self.downButton.isEnabled = resultIndex != nil && resultCount != nil && resultIndex != resultCount! - 1 - self.calendarButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity - - var canSearchMembers = false - if let search = interfaceState.search { - if case .everything = search.domain { - if let _ = interfaceState.renderedPeer?.peer as? TelegramGroup { - canSearchMembers = true - } else if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info, !peer.isMonoForum { - canSearchMembers = true - } - } else { - canSearchMembers = false - } - } - self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers - - let resultsEnabled = (resultCount ?? 0) > 0 - //self.resultsButton.setTitle(resultsText ?? "", with: labelFont, with: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, for: .normal) - self.resultsButton.isUserInteractionEnabled = resultsEnabled - - let makeLabelLayout = TextNode.asyncLayout(self.measureResultsLabel) - let (labelSize, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: resultsText ?? "", font: labelFont, textColor: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, paragraphAlignment: .left), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - 50.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets())) - let _ = labelApply() - - var resultsOffset: CGFloat = 16.0 - if !self.calendarButton.isHidden { - resultsOffset += 48.0 - } - self.resultsButton.frame = CGRect(origin: CGPoint(x: leftInset + resultsOffset, y: floor((panelHeight - labelSize.size.height) / 2.0)), size: labelSize.size) - self.measureResultsLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: labelSize.size) - - let indicatorSize = self.activityIndicator.measure(CGSize(width: 22.0, height: 22.0)) - self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - 41.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize) - - return panelHeight - } - - override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat { - return defaultHeight(metrics: metrics) - } -} diff --git a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift index dee8e42844..17eeec3fee 100644 --- a/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTagSearchInputPanelNode.swift @@ -27,18 +27,20 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { var bottomInset: CGFloat var additionalSideInsets: UIEdgeInsets var maxHeight: CGFloat + var maxOverlayHeight: CGFloat var isSecondary: Bool var interfaceState: ChatPresentationInterfaceState var metrics: LayoutMetrics var isMediaInputExpanded: Bool - init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { + init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) { self.width = width self.leftInset = leftInset self.rightInset = rightInset self.bottomInset = bottomInset self.additionalSideInsets = additionalSideInsets self.maxHeight = maxHeight + self.maxOverlayHeight = maxOverlayHeight self.isSecondary = isSecondary self.interfaceState = interfaceState self.metrics = metrics @@ -96,8 +98,8 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode { self.totalMessageCountDisposable?.dispose() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { - let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded) if let currentLayout = self.currentLayout, currentLayout.params == params { return currentLayout.height } diff --git a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift index 30823e6ee1..73a2cb7a2e 100644 --- a/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatUnblockInputPanelNode.swift @@ -83,7 +83,7 @@ final class ChatUnblockInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 8931af1240..465a52d442 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -16,6 +16,7 @@ import ChatInputContextPanelNode import ChatListUI import ComponentFlow import ComponentDisplayAdapters +import GlassBackgroundComponent private enum CommandChatInputContextPanelEntryStableId: Hashable { case editShortcuts @@ -284,8 +285,8 @@ private func preparedTransition(from fromEntries: [CommandChatInputContextPanelE } final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { + private let backgroundView: GlassBackgroundView private let listView: ListView - private let listBackgroundView: UIView private var currentEntries: [CommandChatInputContextPanelEntry]? private var contentOffsetChangeTransition: ComponentTransition? private var isAnimatingOut: Bool = false @@ -294,49 +295,43 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) { + self.backgroundView = GlassBackgroundView() + self.backgroundView.layer.anchorPoint = CGPoint() + self.listView = ListView() self.listView.isOpaque = false self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor self.listView.limitHitTestToNodes = true self.listView.view.disablesInteractiveTransitionGestureRecognizer = true self.listView.accessibilityPageScrolledString = { row, count in return strings.VoiceOver_ScrollStatus(row, count).string } - self.listBackgroundView = UIView() - self.listBackgroundView.backgroundColor = theme.list.plainBackgroundColor - self.listBackgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - self.listBackgroundView.layer.cornerRadius = 10.0 - super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext) self.isOpaque = false self.clipsToBounds = true - self.view.addSubview(self.listBackgroundView) + self.view.addSubview(self.backgroundView) self.addSubnode(self.listView) + self.backgroundView.isHidden = true self.listView.visibleContentOffsetChanged = { [weak self] offset in guard let self else { return } - - if self.isAnimatingOut { - return + var topOffset: CGFloat = 0.0 + switch offset { + case let .known(offset): + topOffset = max(0.0, -offset + self.listView.insets.top) + case .unknown: + break + case .none: + break } - var topItemOffset: CGFloat = self.listView.bounds.height - var isFirst = true - self.listView.forEachItemNode { itemNode in - if isFirst { - isFirst = false - topItemOffset = itemNode.frame.minY - } - } - - let transition: ComponentTransition = self.contentOffsetChangeTransition ?? .immediate - transition.setFrame(view: self.listBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: topItemOffset), size: CGSize(width: self.listView.bounds.width, height: self.listView.bounds.height + 1000.0))) + self.backgroundView.isHidden = false + self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset) } } @@ -431,8 +426,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { options.insert(.PreferSynchronousResourceLoading) if firstTime { self.contentOffsetChangeTransition = .immediate - - self.listBackgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: self.listView.bounds.height), size: CGSize(width: self.listView.bounds.width, height: self.listView.bounds.height + 1000.0)) } else { if transition.itemCountChanged { options.insert(.AnimateTopItemPosition) @@ -443,9 +436,10 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0) + insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3) insets.left = validLayout.1 insets.right = validLayout.2 + insets.bottom = validLayout.3 let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil)) @@ -458,11 +452,11 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { - let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - - transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: strongSelf.listView.bounds.size.height - topItemOffset)) - transition.animatePositionAdditive(layer: strongSelf.listBackgroundView.layer, offset: CGPoint(x: 0.0, y: strongSelf.listView.bounds.size.height - topItemOffset)) + if let topItemOffset { + let offset = strongSelf.listView.bounds.size.height - topItemOffset + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset)) + transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset)) } } }) @@ -471,7 +465,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { } } - private func topInsetForLayout(size: CGSize) -> CGFloat { + private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat { var minimumItemHeights: CGFloat = 0.0 if let currentEntries = self.currentEntries, !currentEntries.isEmpty { let indexLimit = min(4, currentEntries.count - 1) @@ -498,17 +492,27 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { minimumItemHeights = floor(MentionChatInputPanelItemNode.itemHeight * 3.5) } - return max(size.height - minimumItemHeights, 0.0) + return max(size.height - bottomInset - minimumItemHeights, 0.0) } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, leftInset, rightInset, bottomInset) + self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0)) + self.backgroundView.update( + size: self.backgroundView.bounds.size, + cornerRadius: 20.0, + isDark: interfaceState.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), + transition: ComponentTransition(transition) + ) + var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size) + insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset) insets.left = leftInset insets.right = rightInset + insets.bottom = bottomInset transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) @@ -529,7 +533,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { if self.theme !== interfaceState.theme { self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] prepareTransition(from: self.currentEntries, to: new) @@ -546,14 +549,14 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { - let position = self.listView.layer.position + if let topItemOffset { let offset = (self.listView.bounds.size.height - topItemOffset) + + let position = self.listView.layer.position self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) - self.listBackgroundView.layer.animatePosition(from: self.listBackgroundView.layer.position, to: CGPoint(x: self.listBackgroundView.layer.position.x, y: self.listBackgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - }) + self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } else { completion() } diff --git a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift index d92ac6b563..3b7fd42713 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputPanelItem.swift @@ -86,7 +86,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { private var item: CommandChatInputPanelItem? private let avatarNode: AvatarNode private let textNode: TextNode - private let topSeparatorNode: ASDisplayNode private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode private let arrowNode: ASButtonNode @@ -97,9 +96,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { self.avatarNode = AvatarNode(font: avatarFont) self.textNode = TextNode() - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -113,7 +109,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.topSeparatorNode) self.addSubnode(self.separatorNode) self.addSubnode(self.avatarNode) @@ -167,8 +162,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { strongSelf.item = item strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor strongSelf.arrowNode.setImage(iconImage, for: []) @@ -183,10 +176,8 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { let arrowSize = CGSize(width: 42.0, height: nodeLayout.contentSize.height) strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: nodeLayout.size.width - arrowSize.width - params.rightInset, y: 0.0), size: arrowSize) - strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom - strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) @@ -205,7 +196,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + //self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) } } else { if self.highlightedBackgroundNode.supernode != nil { diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift index a41da1c749..c06faaa585 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputContextPanelNode.swift @@ -13,6 +13,9 @@ import ChatPresentationInterfaceState import ChatControllerInteraction import ChatContextQuery import ChatInputContextPanelNode +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent private struct CommandMenuChatInputContextPanelEntryStableId: Hashable { let command: PeerCommand @@ -61,6 +64,7 @@ private func preparedTransition(from fromEntries: [CommandMenuChatInputContextPa } final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { + private let backgroundView: GlassBackgroundView private let listView: ListView private var currentEntries: [CommandMenuChatInputContextPanelEntry]? @@ -70,11 +74,13 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { private let disposable = MetaDisposable() init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId, chatPresentationContext: ChatPresentationContext) { + self.backgroundView = GlassBackgroundView() + self.backgroundView.layer.anchorPoint = CGPoint() + self.listView = ListView() self.listView.clipsToBounds = false self.listView.isOpaque = false self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor self.listView.limitHitTestToNodes = true self.listView.view.disablesInteractiveTransitionGestureRecognizer = true self.listView.accessibilityPageScrolledString = { row, count in @@ -86,8 +92,28 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { self.isOpaque = false self.clipsToBounds = true + self.view.addSubview(self.backgroundView) self.addSubnode(self.listView) + self.backgroundView.isHidden = true + self.listView.visibleContentOffsetChanged = { [weak self] offset in + guard let self else { + return + } + var topOffset: CGFloat = 0.0 + switch offset { + case let .known(offset): + topOffset = max(0.0, -offset + self.listView.insets.top) + case .unknown: + break + case .none: + break + } + + self.backgroundView.isHidden = false + self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset) + } + self.disposable.set((context.engine.peers.peerCommands(id: peerId) |> deliverOnMainQueue).startStrict(next: { [weak self] results in if let strongSelf = self { @@ -178,9 +204,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0) + insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3) insets.left = validLayout.1 insets.right = validLayout.2 + insets.bottom = validLayout.3 let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil)) @@ -193,21 +220,20 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { - let position = strongSelf.listView.layer.position - strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)) - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView { - strongSelf.listView.position = position - } + if let topItemOffset { + let offset = strongSelf.listView.bounds.size.height - topItemOffset + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) + transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset)) + transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset)) } } }) } } - private func topInsetForLayout(size: CGSize) -> CGFloat { + private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat { let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 4.7) - return max(size.height - minimumItemHeights, 0.0) + return max(size.height - bottomInset - minimumItemHeights, 0.0) } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { @@ -215,9 +241,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { self.validLayout = (size, leftInset, rightInset, bottomInset) var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size) + insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset) insets.left = leftInset insets.right = rightInset + insets.bottom = bottomInset transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) @@ -234,7 +261,6 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { if self.theme !== interfaceState.theme { self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] self.prepareTransition(from: self.currentEntries, to: new) @@ -249,11 +275,14 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { + if let topItemOffset { + let offset = (self.listView.bounds.size.height - topItemOffset) + let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) + self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } else { completion() } diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift index 5e03f5aa11..48db2d5460 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift @@ -219,7 +219,6 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { strongSelf.item = item strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor let _ = textApply() @@ -261,7 +260,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.backgroundNode.insertSubnode(self.highlightedBackgroundNode, at: 0) + //self.backgroundNode.insertSubnode(self.highlightedBackgroundNode, at: 0) } } else { if self.highlightedBackgroundNode.supernode != nil { diff --git a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift index 4a456d3ee6..f40a2c3a50 100644 --- a/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/DeleteChatInputPanelNode.swift @@ -36,7 +36,7 @@ final class DeleteChatInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.deleteChat() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index fe3ac7c4fe..29e0daafe3 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -14,6 +14,9 @@ import ChatPresentationInterfaceState import ChatControllerInteraction import ChatContextQuery import ChatInputContextPanelNode +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent private enum HashtagChatInputContextPanelEntryStableId: Hashable { case generic @@ -77,6 +80,7 @@ private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelE } final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { + private let backgroundView: GlassBackgroundView private let listView: ListView private var currentEntries: [HashtagChatInputContextPanelEntry]? @@ -89,10 +93,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) { + self.backgroundView = GlassBackgroundView() + self.backgroundView.layer.anchorPoint = CGPoint() + self.listView = ListView() self.listView.isOpaque = false self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor self.listView.limitHitTestToNodes = true self.listView.view.disablesInteractiveTransitionGestureRecognizer = true self.listView.accessibilityPageScrolledString = { row, count in @@ -104,7 +110,27 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { self.isOpaque = false self.clipsToBounds = true + self.view.addSubview(self.backgroundView) self.addSubnode(self.listView) + + self.backgroundView.isHidden = true + self.listView.visibleContentOffsetChanged = { [weak self] offset in + guard let self else { + return + } + var topOffset: CGFloat = 0.0 + switch offset { + case let .known(offset): + topOffset = max(0.0, -offset + self.listView.insets.top) + case .unknown: + break + case .none: + break + } + + self.backgroundView.isHidden = false + self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset) + } } func updateResults(_ results: [String], query: String, peer: EnginePeer?) { @@ -256,9 +282,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0) + insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3) insets.left = validLayout.1 insets.right = validLayout.2 + insets.bottom = validLayout.3 let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: insets, duration: 0.0, curve: .Default(duration: nil)) @@ -271,32 +298,41 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { - let position = strongSelf.listView.layer.position - strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)) - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView { - strongSelf.listView.position = position - } + if let topItemOffset { + let offset = strongSelf.listView.bounds.size.height - topItemOffset + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) + transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset)) + transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset)) } } }) } } - private func topInsetForLayout(size: CGSize) -> CGFloat { + private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat { let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 3.5) - return max(size.height - minimumItemHeights, 0.0) + return max(size.height - bottomInset - minimumItemHeights, 0.0) } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, leftInset, rightInset, bottomInset) + self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0)) + self.backgroundView.update( + size: self.backgroundView.bounds.size, + cornerRadius: 20.0, + isDark: interfaceState.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), + transition: ComponentTransition(transition) + ) + var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size) + insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset) insets.left = leftInset insets.right = rightInset + insets.bottom = bottomInset transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) @@ -313,7 +349,6 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { if self.theme !== interfaceState.theme { self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] self.prepareTransition(from: self.currentEntries, to: new) @@ -329,10 +364,13 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { } if let topItemOffset = topItemOffset { + let offset = (self.listView.bounds.size.height - topItemOffset) + let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) + self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } else { completion() } diff --git a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift index 899cbe278a..0ffafe066d 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift @@ -116,7 +116,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { private let titleNode: TextNode private let textNode: TextNode private let badgeNode: TextNode - private let topSeparatorNode: ASDisplayNode private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode @@ -141,9 +140,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { self.textNode = TextNode() self.badgeNode = TextNode() - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -155,7 +151,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.topSeparatorNode) self.addSubnode(self.separatorNode) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) @@ -226,8 +221,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { strongSelf.badgeBackgroundLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor let _ = titleApply() @@ -250,7 +243,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { strongSelf.badgeBackgroundLayer.frame = badgeBackgroundFrame } - strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom let iconSize = CGSize(width: 30.0, height: 30.0) @@ -273,7 +265,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { strongSelf.iconBackgroundLayer.isHidden = false } - strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset + textLeftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - textLeftInset, height: UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) @@ -305,7 +296,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + //self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) } } else { if self.highlightedBackgroundNode.supernode != nil { diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift index b05b3ca8eb..c8b33b8ff2 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputContextPanelNode.swift @@ -378,12 +378,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { let listHeight: CGFloat = 105.0 - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - listHeight), size: CGSize(width: size.width, height: UIScreenPixel))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - bottomInset - 8.0 - listHeight), size: CGSize(width: size.width, height: UIScreenPixel))) self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: listHeight, height: size.width) //transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) - transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - listHeight / 2.0)) + transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - bottomInset - 8.0 - listHeight / 2.0)) var insets = UIEdgeInsets() insets.top = leftInset diff --git a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift index f13e9ea36f..9f09c047d5 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputContextPanelNode.swift @@ -14,6 +14,9 @@ import ChatPresentationInterfaceState import ChatControllerInteraction import ChatContextQuery import ChatInputContextPanelNode +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent private struct MentionChatInputContextPanelEntry: Comparable, Identifiable { let index: Int @@ -61,6 +64,7 @@ enum MentionChatInputContextPanelMode { final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { let mode: MentionChatInputContextPanelMode + private let backgroundView: GlassBackgroundView private let listView: ListView private var currentEntries: [MentionChatInputContextPanelEntry]? @@ -73,10 +77,12 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, mode: MentionChatInputContextPanelMode, chatPresentationContext: ChatPresentationContext) { self.mode = mode + self.backgroundView = GlassBackgroundView() + self.backgroundView.layer.anchorPoint = CGPoint() + self.listView = ListView() self.listView.isOpaque = false self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor self.listView.limitHitTestToNodes = true self.listView.view.disablesInteractiveTransitionGestureRecognizer = true self.listView.accessibilityPageScrolledString = { row, count in @@ -86,13 +92,32 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext) self.isOpaque = false - self.clipsToBounds = true + self.view.addSubview(self.backgroundView) self.addSubnode(self.listView) if mode == .search { self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0) } + + self.backgroundView.isHidden = true + self.listView.visibleContentOffsetChanged = { [weak self] offset in + guard let self else { + return + } + var topOffset: CGFloat = 0.0 + switch offset { + case let .known(offset): + topOffset = max(0.0, -offset + self.listView.insets.top) + case .unknown: + break + case .none: + break + } + + self.backgroundView.isHidden = false + self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset) + } } func updateResults(_ results: [EnginePeer]) { @@ -202,9 +227,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0) + insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3) insets.left = validLayout.1 insets.right = validLayout.2 + insets.bottom = validLayout.3 let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: insets, duration: 0.0, curve: .Default(duration: nil)) @@ -217,22 +243,21 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { - let position = strongSelf.listView.layer.position - strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)) - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView { - strongSelf.listView.position = position - } + if let topItemOffset { + let offset = strongSelf.listView.bounds.size.height - topItemOffset + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) + transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset)) + transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset)) } } }) } } - private func topInsetForLayout(size: CGSize) -> CGFloat { + private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat { let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 3.5) - return max(size.height - minimumItemHeights, 0.0) + return max(size.height - bottomInset - minimumItemHeights, 0.0) } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { @@ -241,17 +266,26 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { if self.theme !== interfaceState.theme { self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor if let currentEntries = self.currentEntries { self.updateToEntries(entries: currentEntries, forceUpdate: true) } } + self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0)) + self.backgroundView.update( + size: self.backgroundView.bounds.size, + cornerRadius: 20.0, + isDark: interfaceState.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), + transition: ComponentTransition(transition) + ) + var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: size) + insets.top = topInsetForLayout(size: size, bottomInset: bottomInset) insets.left = leftInset insets.right = rightInset + insets.bottom = bottomInset transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) @@ -275,11 +309,14 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode { } } - if let topItemOffset = topItemOffset { + if let topItemOffset { + let offset = (self.listView.bounds.size.height - topItemOffset) + let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) + self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } else { completion() } diff --git a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift index 5c128d945e..e59b7c15d3 100644 --- a/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/MentionChatInputPanelItem.swift @@ -97,7 +97,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { private let avatarNode: AvatarNode private let textNode: TextNode - private let topSeparatorNode: ASDisplayNode private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode @@ -118,9 +117,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { self.avatarNode = AvatarNode(font: avatarFont) self.textNode = TextNode() - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -132,7 +128,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.topSeparatorNode) self.addSubnode(self.separatorNode) self.addSubnode(self.avatarNode) @@ -207,8 +202,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { } strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor - strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor) @@ -218,10 +211,8 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 12.0, y: floor((nodeLayout.contentSize.height - 30.0) / 2.0)), size: CGSize(width: 30.0, height: 30.0)) strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size) - strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom - strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: !item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) @@ -259,7 +250,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + //self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) } } else { if self.highlightedBackgroundNode.supernode != nil { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 8d1943adcc..de8074389f 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -183,6 +183,11 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.navigationController?.pushViewController(controller) return true case let .stickerPack(reference, previewIconFile): + var previewIconFile: TelegramMediaFile? = previewIconFile + if let file = previewIconFile, !file.isValidForDisplay(chatPeerId: params.message.id.peerId) { + previewIconFile = nil + } + 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 } diff --git a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift index 400e6bd8e3..ad862f9774 100644 --- a/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/SecretChatHandshakeStatusInputPanelNode.swift @@ -45,7 +45,7 @@ final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode { self.interfaceInteraction?.unblockPeer() } - override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat { if self.presentationInterfaceState != interfaceState { self.presentationInterfaceState = interfaceState diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift index e8dba3fc2f..78903e8874 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputContextPanelNode.swift @@ -11,6 +11,10 @@ import SwiftSignalKit import ChatPresentationInterfaceState import ChatControllerInteraction import ChatInputContextPanelNode +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent +import EdgeEffect private enum VerticalChatContextResultsEntryStableId: Hashable { case action @@ -123,7 +127,9 @@ private func preparedTransition(from fromEntries: [VerticalListContextResultsCha } final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode { + private let backgroundView: GlassBackgroundView private let listView: ListView + private let listMaskView: UIImageView private var currentExternalResults: ChatContextResultCollection? private var currentProcessedResults: ChatContextResultCollection? private var currentEntries: [VerticalListContextResultsChatInputContextPanelEntry]? @@ -135,10 +141,12 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex private var isLoadingMore: Bool = false override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) { + self.backgroundView = GlassBackgroundView() + self.backgroundView.layer.anchorPoint = CGPoint() + self.listView = ListView() self.listView.isOpaque = false self.listView.stackFromBottom = true - self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor self.listView.limitHitTestToNodes = true self.listView.isHidden = true self.listView.view.disablesInteractiveTransitionGestureRecognizer = true @@ -146,12 +154,17 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex return strings.VoiceOver_ScrollStatus(row, count).string } + self.listMaskView = UIImageView() + super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext) self.isOpaque = false self.clipsToBounds = true + self.view.addSubview(self.backgroundView) self.addSubnode(self.listView) + //self.view.addSubview(self.listMaskView) + self.listView.view.mask = self.listMaskView self.listView.visibleBottomContentOffsetChanged = { [weak self] offset in guard let strongSelf = self, !strongSelf.isLoadingMore, case let .known(value) = offset, value < 40.0 else { @@ -159,6 +172,25 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex } strongSelf.loadMore() } + + self.backgroundView.isHidden = true + self.listView.visibleContentOffsetChanged = { [weak self] offset in + guard let self else { + return + } + var topOffset: CGFloat = 0.0 + switch offset { + case let .known(offset): + topOffset = max(0.0, -offset + self.listView.insets.top) + case .unknown: + break + case .none: + break + } + + self.backgroundView.isHidden = false + self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset) + } } deinit { @@ -255,9 +287,10 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex } var insets = UIEdgeInsets() - insets.top = topInsetForLayout(size: validLayout.0, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil) + insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil) insets.left = validLayout.1 insets.right = validLayout.2 + insets.bottom = validLayout.3 let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil)) @@ -270,39 +303,78 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex } } - if let topItemOffset = topItemOffset { - let position = strongSelf.listView.layer.position - strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset)) - ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView { - strongSelf.listView.position = position - } + if let topItemOffset { + let offset = strongSelf.listView.bounds.size.height - topItemOffset + let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring) + transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset)) + transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset)) } - strongSelf.listView.isHidden = false } }) } } - private func topInsetForLayout(size: CGSize, hasSwitchPeer: Bool) -> CGFloat { + private func topInsetForLayout(size: CGSize, bottomInset: CGFloat, hasSwitchPeer: Bool) -> CGFloat { var minimumItemHeights: CGFloat = floor(VerticalListContextResultsChatInputPanelItemNode.itemHeight * 3.5) if hasSwitchPeer { minimumItemHeights += VerticalListContextResultsChatInputPanelButtonItemNode.itemHeight(style: .regular) } - return max(size.height - minimumItemHeights, 0.0) + return max(size.height - bottomInset - minimumItemHeights, 0.0) } override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) { let hadValidLayout = self.validLayout != nil self.validLayout = (size, leftInset, rightInset, bottomInset) + self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0)) + self.backgroundView.update( + size: self.backgroundView.bounds.size, + cornerRadius: 20.0, + isDark: interfaceState.theme.overallDarkAppearance, + tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), + transition: ComponentTransition(transition) + ) + var insets = UIEdgeInsets() - insets.top = self.topInsetForLayout(size: size, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil) + insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil) insets.left = leftInset insets.right = rightInset + insets.bottom = bottomInset transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) + let listMaskHeight: CGFloat = bottomInset + 1.0 + if self.listMaskView.image?.size.height != listMaskHeight { + let baseGradientAlpha: CGFloat = 0.65 + let numSteps = 8 + let firstStep = 1 + let firstLocation = 0.0 + let colors: [UIColor] = (0 ..< numSteps).map { i in + if i < firstStep { + return UIColor(white: 0.0, alpha: 0.0) + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 0.0, alpha: 1.0 - baseGradientAlpha * value) + } + } + let locations: [CGFloat] = (0 ..< numSteps).map { i in + if i < firstStep { + return 0.0 + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) + } + } + + self.listMaskView.image = generateGradientImage( + size: CGSize(width: 8.0, height: listMaskHeight), + colors: colors, + locations: locations + )?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1) + } + transition.updateFrame(view: self.listMaskView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) @@ -317,7 +389,6 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex if self.theme !== interfaceState.theme, let currentProcessedResults = self.currentProcessedResults { self.theme = interfaceState.theme - self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? [] prepareTransition(from: self.currentEntries, to: new, results: currentProcessedResults) @@ -332,11 +403,14 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex } } - if let topItemOffset = topItemOffset { + if let topItemOffset { + let offset = (self.listView.bounds.size.height - topItemOffset) + let position = self.listView.layer.position - self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in + self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in completion() }) + self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) } else { completion() } diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift index 51c63e48bb..4344efb867 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelButtonItem.swift @@ -161,13 +161,9 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem let titleOffsetY: CGFloat switch item.style { case .regular: - strongSelf.backgroundColor = item.theme.list.plainBackgroundColor - strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom titleOffsetY = 2.0 case .round: - strongSelf.backgroundColor = nil - strongSelf.topSeparatorNode.isHidden = true strongSelf.separatorNode.isHidden = !mergedBottom titleOffsetY = 1.0 } diff --git a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift index bdc3979960..09f9d3bf08 100644 --- a/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/VerticalListContextResultsChatInputPanelItem.swift @@ -85,7 +85,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { private let iconImageNode: TransformImageNode private let titleNode: TextNode private let textNode: TextNode - private let topSeparatorNode: ASDisplayNode private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode private var statusDisposable = MetaDisposable() @@ -100,9 +99,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { self.titleNode = TextNode() self.textNode = TextNode() - self.topSeparatorNode = ASDisplayNode() - self.topSeparatorNode.isLayerBacked = true - self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -124,7 +120,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.topSeparatorNode) self.addSubnode(self.separatorNode) self.addSubnode(self.iconImageNode) @@ -285,8 +280,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { strongSelf.item = item strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor - strongSelf.backgroundColor = item.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor let _ = titleApply() @@ -338,10 +331,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { } } - strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom - strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) @@ -388,7 +379,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode { if highlighted { self.highlightedBackgroundNode.alpha = 1.0 if self.highlightedBackgroundNode.supernode == nil { - self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) + //self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode) } } else { if self.highlightedBackgroundNode.supernode != nil { diff --git a/versions.json b/versions.json index 6b7e5549eb..63a8a13f63 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "12.0", + "app": "12.1", "xcode": "16.2", "bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217", "macos": "15"