From ebcd0557e5f16d4cb28694c16efe9e2b7b50d241 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 10:54:22 +0200 Subject: [PATCH 1/8] Temp --- .../Sources/ActivityIndicator.swift | 2 +- submodules/AudioBlob/Sources/BlobView.swift | 2 +- submodules/ChatListUI/BUILD | 1 + .../Sources/ChatListContainerItemNode.swift | 12 + .../Sources/ChatListControllerNode.swift | 1 + submodules/ContactListUI/BUILD | 1 + .../Sources/ContactsControllerNode.swift | 10 + .../Source/NavigationBackButtonNode.swift | 2 +- .../Display/Source/NavigationButtonNode.swift | 4 +- .../Display/Source/TabBarController.swift | 4 - submodules/Display/Source/WindowContent.swift | 6 +- .../Sources/DrawingTextEntityView.swift | 2 +- .../GalleryUI/Sources/GalleryController.swift | 2 +- .../Items/UniversalVideoGalleryItem.swift | 4 +- .../GraphUI/Sources/ChartStackSection.swift | 4 +- .../Sources/InstantPageControllerNode.swift | 2 +- .../InstantPageSettingsItemTheme.swift | 2 +- .../InstantPageSettingsThemeItemNode.swift | 2 +- .../Sources/InstantPageTheme.swift | 6 +- .../Sources/TGCheckButtonView.m | 2 +- submodules/LegacyComponents/Sources/TGColor.m | 2 +- .../TGMediaPickerGalleryInterfaceView.m | 2 +- .../Sources/LegacyPaintStickersContext.swift | 2 +- .../ChatMessageLiveLocationPositionNode.swift | 4 +- .../Sources/LocationAnnotation.swift | 2 +- .../LocationUI/Sources/LocationMapNode.swift | 2 +- .../Sources/BusinessPageComponent.swift | 2 +- .../Sources/PremiumIntroScreen.swift | 2 +- .../Sources/StoriesPageComponent.swift | 2 +- .../TabBarAccountSwitchController.swift | 105 --- .../TabBarAccountSwitchControllerNode.swift | 568 ---------------- .../Sources/Themes/ThemeColorPresets.swift | 4 +- .../Themes/ThemeSettingsAccentColorItem.swift | 2 +- submodules/TabBarUI/BUILD | 15 +- .../Sources/TabBarContollerNode.swift | 210 +++++- .../TabBarUI/Sources/TabBarController.swift | 152 +---- submodules/TabBarUI/Sources/TabBarNode.swift | 79 +-- .../VoiceChatCameraPreviewController.swift | 2 +- .../VoiceChatFullscreenParticipantItem.swift | 2 +- .../Sources/VoiceChatParticipantItem.swift | 2 +- .../VoiceChatRecordingSetupController.swift | 2 +- .../DefaultDarkPresentationTheme.swift | 4 +- .../Sources/DefaultDayPresentationTheme.swift | 4 +- .../PresentationResourcesSettings.swift | 2 +- .../Sources/BadgeComponent.swift | 28 +- .../Sources/PlaceholderComponent.swift | 4 +- .../Sources/ChatMessageBubbleItemNode.swift | 57 +- .../ChatRecordingPreviewInputPanelNode.swift | 2 +- .../ChatRecordingViewOnceButtonNode.swift | 2 +- .../ChatTextInputActionButtonsNode.swift | 6 +- .../Sources/AccessoryItemIconButton.swift | 1 + .../Sources/ChatTextInputPanelNode.swift | 48 +- ...TextInputAudioRecordingOverlayButton.swift | 4 +- .../ChatTextInputMediaRecordingButton.swift | 6 +- .../TelegramUI/Components/EdgeEffect/BUILD | 19 + .../EdgeEffect/Sources/EdgeEffect.swift | 69 ++ .../Sources/EmojiTextAttachmentView.swift | 2 +- .../Sources/GlassBackgroundComponent.swift | 82 ++- .../Sources/MediaEditorScreen.swift | 2 +- .../Sources/MediaScrubberComponent.swift | 2 +- .../Sources/HashtagListItemComponent.swift | 2 +- .../Components/PeerInfo/PeerInfoScreen/BUILD | 1 + .../Sources/PeerInfoScreen.swift | 13 + .../Sources/ThemeAccentColorController.swift | 2 +- .../Sources/ShareWithPeersScreen.swift | 2 +- .../Sources/DataUsageScreen.swift | 2 +- .../Components/TabBarComponent/BUILD | 27 + .../Sources/TabBarComponent.swift | 623 ++++++++++++++++++ .../TelegramUI/Sources/AppDelegate.swift | 18 - .../Sources/ChatHistoryListNode.swift | 2 +- .../ChatHistoryNavigationButtonNode.swift | 8 +- .../ChatInterfaceStateInputPanels.swift | 18 - .../Sources/LegacyDataImportSplash.swift | 2 +- .../Sources/TelegramRootController.swift | 4 +- .../Sources/PresentationThemeSettings.swift | 2 +- .../Sources/WallpaperBackgroundNode.swift | 138 +++- 76 files changed, 1352 insertions(+), 1090 deletions(-) delete mode 100644 submodules/SettingsUI/Sources/TabBarAccountSwitchController.swift delete mode 100644 submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift create mode 100644 submodules/TelegramUI/Components/EdgeEffect/BUILD create mode 100644 submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift create mode 100644 submodules/TelegramUI/Components/TabBarComponent/BUILD create mode 100644 submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift diff --git a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift index 00a4397777..b740ff674b 100644 --- a/submodules/ActivityIndicator/Sources/ActivityIndicator.swift +++ b/submodules/ActivityIndicator/Sources/ActivityIndicator.swift @@ -16,7 +16,7 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: } private func convertIndicatorColor(_ color: UIColor) -> UIColor { - if color.isEqual(UIColor(rgb: 0x007aff)) { + if color.isEqual(UIColor(rgb: 0x007aff)) || color.isEqual(UIColor(rgb: 0x0088ff)) { return .gray } else if color.isEqual(UIColor(rgb: 0x2ea6ff)) { return .white diff --git a/submodules/AudioBlob/Sources/BlobView.swift b/submodules/AudioBlob/Sources/BlobView.swift index 34d7c1c8f1..bfb43da5ee 100644 --- a/submodules/AudioBlob/Sources/BlobView.swift +++ b/submodules/AudioBlob/Sources/BlobView.swift @@ -461,7 +461,7 @@ final class BlobNode: ASDisplayNode { if let backgroundView = self.backgroundView, let color = self.color { let halfWidth = floor(self.bounds.width * self.minScale) - backgroundView.update(size: CGSize(width: halfWidth, height: halfWidth), cornerRadius: halfWidth * 0.5, isDark: false, tintColor: color, transition: .immediate) + backgroundView.update(size: CGSize(width: halfWidth, height: halfWidth), cornerRadius: halfWidth * 0.5, isDark: false, tintColor: .init(kind: .custom, color: color), transition: .immediate) backgroundView.frame = CGRect(origin: CGPoint(x: (self.bounds.width - halfWidth) * 0.5, y: (self.bounds.height - halfWidth) * 0.5), size: CGSize(width: halfWidth, height: halfWidth)) } } diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 29031671ee..a28100bbf7 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -117,6 +117,7 @@ swift_library( "//submodules/TelegramUI/Components/Ads/AdsReportScreen", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift index c28d06df04..07d7532dbc 100644 --- a/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift +++ b/submodules/ChatListUI/Sources/ChatListContainerItemNode.swift @@ -13,6 +13,8 @@ import Postbox import ChatListHeaderComponent import ActionPanelComponent import ChatFolderLinkPreviewScreen +import EdgeEffect +import ComponentDisplayAdapters final class ChatListContainerItemNode: ASDisplayNode { private final class TopPanelItem { @@ -37,6 +39,8 @@ final class ChatListContainerItemNode: ASDisplayNode { private var floatingHeaderOffset: CGFloat? + private let edgeEffectView: EdgeEffectView + private(set) var emptyNode: ChatListEmptyNode? var emptyShimmerEffectNode: ChatListShimmerNode? private var shimmerNodeOffset: CGFloat = 0.0 @@ -74,9 +78,12 @@ final class ChatListContainerItemNode: ASDisplayNode { self.listNode.scrollHeightTopInset = ChatListNavigationBar.searchScrollHeight + ChatListNavigationBar.storiesScrollHeight } + self.edgeEffectView = EdgeEffectView() + super.init() self.addSubnode(self.listNode) + self.view.addSubview(self.edgeEffectView) self.listNode.isEmptyUpdated = { [weak self] isEmptyState, _, transition in guard let strongSelf = self else { @@ -442,6 +449,11 @@ final class ChatListContainerItemNode: ASDisplayNode { } self.layoutAdditionalPanels(transition: transition) + + 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)) } func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index c413dc8a53..8fc934d5a1 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -21,6 +21,7 @@ import ChatFolderLinkPreviewScreen import ChatListHeaderComponent import StoryPeerListComponent import TelegramNotices +import EdgeEffect public enum ChatListContainerNodeFilter: Equatable { case all diff --git a/submodules/ContactListUI/BUILD b/submodules/ContactListUI/BUILD index ef6c5d49a2..0fb6ab5bde 100644 --- a/submodules/ContactListUI/BUILD +++ b/submodules/ContactListUI/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/UndoUI", "//submodules/TelegramIntents", "//submodules/ContextUI", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 6a9ad102a4..0982b64bf1 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -17,6 +17,7 @@ import ChatListTitleView import ComponentFlow import SwiftUI import ContactsUI +import EdgeEffect private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController @@ -48,6 +49,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { let contactListNode: ContactListNode + private let edgeEffectView: EdgeEffectView private let context: AccountContext private(set) var searchDisplayController: SearchDisplayController? @@ -116,6 +118,8 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { contextAction?(peer, node, gesture, location, isStories) }) + self.edgeEffectView = EdgeEffectView() + super.init() self.setViewBlock({ @@ -125,6 +129,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.addSubnode(self.contactListNode) + self.view.addSubview(self.edgeEffectView) self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in @@ -442,6 +447,11 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate { self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size) + 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.updateNavigationScrolling(transition: transition) if let navigationBarComponentView = self.navigationBarView.view as? ChatListNavigationBar.View { diff --git a/submodules/Display/Source/NavigationBackButtonNode.swift b/submodules/Display/Source/NavigationBackButtonNode.swift index b1164ebd78..12f55c986b 100644 --- a/submodules/Display/Source/NavigationBackButtonNode.swift +++ b/submodules/Display/Source/NavigationBackButtonNode.swift @@ -31,7 +31,7 @@ public class NavigationBackButtonNode: ASControlNode { } } - public var color: UIColor = UIColor(rgb: 0x007aff) { + public var color: UIColor = UIColor(rgb: 0x0088ff) { didSet { self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift index 73d49fc855..1ebc26af8a 100644 --- a/submodules/Display/Source/NavigationButtonNode.swift +++ b/submodules/Display/Source/NavigationButtonNode.swift @@ -107,7 +107,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode { } } - public var color: UIColor = UIColor(rgb: 0x007aff) { + public var color: UIColor = UIColor(rgb: 0x0088ff) { didSet { if let text = self._text { self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) @@ -347,7 +347,7 @@ public final class NavigationButtonNode: ContextControllerSourceNode { public var pressed: (Int) -> () = { _ in } public var highlightChanged: (Int, Bool) -> () = { _, _ in } - public var color: UIColor = UIColor(rgb: 0x007aff) { + public var color: UIColor = UIColor(rgb: 0x0088ff) { didSet { if !self.color.isEqual(oldValue) { for node in self.nodes { diff --git a/submodules/Display/Source/TabBarController.swift b/submodules/Display/Source/TabBarController.swift index e552c5fafe..0113526a86 100644 --- a/submodules/Display/Source/TabBarController.swift +++ b/submodules/Display/Source/TabBarController.swift @@ -13,16 +13,12 @@ public protocol TabBarController: ViewController { var controllers: [ViewController] { get } var selectedIndex: Int { get set } - var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)? { get set } - func setControllers(_ controllers: [ViewController], selectedIndex: Int?) func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) - func viewForCameraItem() -> UIView? func frameForControllerTab(controller: ViewController) -> CGRect? func isPointInsideContentArea(point: CGPoint) -> Bool - func sourceNodesForController(at index: Int) -> [ASDisplayNode]? func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) diff --git a/submodules/Display/Source/WindowContent.swift b/submodules/Display/Source/WindowContent.swift index 62de4a8e04..e5270bdae2 100644 --- a/submodules/Display/Source/WindowContent.swift +++ b/submodules/Display/Source/WindowContent.swift @@ -193,11 +193,7 @@ public final class WindowHostView { } fileprivate var onScreenNavigationHeight: CGFloat? { - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom - } else { - return nil - } + return self.eventView.safeAreaInsets.bottom.isLessThanOrEqualTo(0.0) ? nil : self.eventView.safeAreaInsets.bottom } } diff --git a/submodules/DrawingUI/Sources/DrawingTextEntityView.swift b/submodules/DrawingUI/Sources/DrawingTextEntityView.swift index ef85267ba2..8d40dc69db 100644 --- a/submodules/DrawingUI/Sources/DrawingTextEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingTextEntityView.swift @@ -544,7 +544,7 @@ public final class DrawingTextEntityView: DrawingEntityView, UITextViewDelegate self.textView.frameColor = nil case .filled: self.textView.textColor = color.lightness > 0.99 ? UIColor.black : UIColor.white - cursorColor = color.lightness > 0.99 ? UIColor(rgb: 0x007aff) : UIColor.white + cursorColor = color.lightness > 0.99 ? UIColor(rgb: 0x0088ff) : UIColor.white self.textView.strokeColor = nil self.textView.frameColor = color case .semi: diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 242e26bca4..2b09ceb02a 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -571,7 +571,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol { public static let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) - public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x007aff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) + public static let lightNavigationTheme = NavigationBarTheme(buttonColor: UIColor(rgb: 0x0088ff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index f949c7328c..216ad40183 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -776,7 +776,7 @@ final class SettingsHeaderButton: HighlightableButtonNode { component: AnyComponent(BadgeComponent( text: badgeText, font: self.badgeFont, - cornerRadius: 3.0, + cornerRadius: .custom(3.0), insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66), outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) )), @@ -818,7 +818,7 @@ final class SettingsHeaderButton: HighlightableButtonNode { component: AnyComponent(BadgeComponent( text: badgeText, font: self.badgeFont, - cornerRadius: 3.0, + cornerRadius: .custom(3.0), insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66), outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) )), diff --git a/submodules/GraphUI/Sources/ChartStackSection.swift b/submodules/GraphUI/Sources/ChartStackSection.swift index d0c5f41cc3..c20fbf861a 100644 --- a/submodules/GraphUI/Sources/ChartStackSection.swift +++ b/submodules/GraphUI/Sources/ChartStackSection.swift @@ -71,10 +71,10 @@ class ChartStackSection: UIView, ChartThemeContainer { backButton.addTarget(self, action: #selector(self.didTapBackButton), for: .touchUpInside) backButton.setTitle("Zoom Out", for: .normal) backButton.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .regular) - backButton.setTitleColor(UIColor(rgb: 0x007aff), for: .normal) + backButton.setTitleColor(UIColor(rgb: 0x0088ff), for: .normal) backButton.setImage(UIImage(bundleImageName: "Chart/arrow_left"), for: .normal) backButton.imageEdgeInsets = UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 3.0) - backButton.imageView?.tintColor = UIColor(rgb: 0x007aff) + backButton.imageView?.tintColor = UIColor(rgb: 0x0088ff) backButton.adjustsImageWhenHighlighted = false backButton.setVisible(false, animated: false) diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 52108faac6..c289b0b52d 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -911,7 +911,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate { if let current = self.linkHighlightingNode { linkHighlightingNode = current } else { - let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x007aff).withAlphaComponent(0.4) + let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x0088ff).withAlphaComponent(0.4) linkHighlightingNode = LinkHighlightingNode(color: highlightColor) linkHighlightingNode.isUserInteractionEnabled = false self.linkHighlightingNode = linkHighlightingNode diff --git a/submodules/InstantPageUI/Sources/InstantPageSettingsItemTheme.swift b/submodules/InstantPageUI/Sources/InstantPageSettingsItemTheme.swift index 21c766bdd8..017e85fb97 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSettingsItemTheme.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSettingsItemTheme.swift @@ -69,7 +69,7 @@ private let lightTheme = InstantPageSettingsItemTheme( separatorColor: UIColor(rgb: 0xc8c7cc), primaryColor: .black, secondaryColor: UIColor(rgb: 0xa8a8a8), - accentColor: UIColor(rgb: 0x007aff) + accentColor: UIColor(rgb: 0x0088ff) ) private let sepiaTheme = InstantPageSettingsItemTheme( diff --git a/submodules/InstantPageUI/Sources/InstantPageSettingsThemeItemNode.swift b/submodules/InstantPageUI/Sources/InstantPageSettingsThemeItemNode.swift index b96c2126bd..ef6438ec7c 100644 --- a/submodules/InstantPageUI/Sources/InstantPageSettingsThemeItemNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageSettingsThemeItemNode.swift @@ -101,7 +101,7 @@ final class InstantPageSettingsThemeItemNode: InstantPageSettingsItemNode { selectedIndex = 3 } - let selectionColor = UIColor(rgb: 0x007aff) + let selectionColor = UIColor(rgb: 0x0088ff) self.themeNodes = [ InstantPageSettingsThemeSelectorNode(color: .white, edgeColor: (selectedIndex == 1 || selectedIndex == 2) ? UIColor.lightGray : UIColor.white, selectionColor: selectionColor), InstantPageSettingsThemeSelectorNode(color: UIColor(rgb: 0xcbb98e), edgeColor: UIColor(rgb: 0xcbb98e), selectionColor: selectionColor), diff --git a/submodules/InstantPageUI/Sources/InstantPageTheme.swift b/submodules/InstantPageUI/Sources/InstantPageTheme.swift index 3706d23bc5..d1305895e4 100644 --- a/submodules/InstantPageUI/Sources/InstantPageTheme.swift +++ b/submodules/InstantPageUI/Sources/InstantPageTheme.swift @@ -153,15 +153,15 @@ private let lightTheme = InstantPageTheme( ), serif: false, codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc), - linkColor: UIColor(rgb: 0x007aff), + linkColor: UIColor(rgb: 0x0088ff), textHighlightColor: UIColor(rgb: 0, alpha: 0.12), - linkHighlightColor: UIColor(rgb: 0x007aff, alpha: 0.07), + linkHighlightColor: UIColor(rgb: 0x0088ff, alpha: 0.07), markerColor: UIColor(rgb: 0xfef3bc), panelBackgroundColor: UIColor(rgb: 0xf3f4f5), panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7), panelPrimaryColor: .black, panelSecondaryColor: UIColor(rgb: 0x79828b), - panelAccentColor: UIColor(rgb: 0x007aff), + panelAccentColor: UIColor(rgb: 0x0088ff), tableBorderColor: UIColor(rgb: 0xe2e2e2), tableHeaderColor: UIColor(rgb: 0xf4f4f4), controlColor: UIColor(rgb: 0xc7c7cd), diff --git a/submodules/LegacyComponents/Sources/TGCheckButtonView.m b/submodules/LegacyComponents/Sources/TGCheckButtonView.m index 45ee58d9a2..010d2545b0 100644 --- a/submodules/LegacyComponents/Sources/TGCheckButtonView.m +++ b/submodules/LegacyComponents/Sources/TGCheckButtonView.m @@ -60,7 +60,7 @@ int32_t hex = 0x29c519; UIColor *greenColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f]; - hex = 0x007aff; + hex = 0x0088ff; UIColor *blueColor = [[UIColor alloc] initWithRed:(((hex >> 16) & 0xff) / 255.0f) green:(((hex >> 8) & 0xff) / 255.0f) blue:(((hex) & 0xff) / 255.0f) alpha:1.0f]; hex = 0xcacacf; diff --git a/submodules/LegacyComponents/Sources/TGColor.m b/submodules/LegacyComponents/Sources/TGColor.m index dd57eb0164..e9fb71f7c3 100644 --- a/submodules/LegacyComponents/Sources/TGColor.m +++ b/submodules/LegacyComponents/Sources/TGColor.m @@ -16,7 +16,7 @@ UIColor *TGAccentColor() static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { - color = TGColorWithHex(0x007aff); + color = TGColorWithHex(0x0088ff); }); return color; } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index d6e7f8039c..771dd32528 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -466,7 +466,7 @@ _saveCoverButton.clipsToBounds = true; _saveCoverButton.layer.cornerRadius = 10.0; _saveCoverButton.hidden = true; - [_saveCoverButton setBackgroundColor:UIColorRGB(0x007aff)]; + [_saveCoverButton setBackgroundColor:UIColorRGB(0x0088ff)]; _saveCoverButton.titleLabel.font = TGBoldSystemFontOfSize(17.0); [_saveCoverButton setTitle:TGLocalized(@"Media.SaveCover") forState:UIControlStateNormal]; [_saveCoverButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift index 3bad8cf5df..5a3aa9211d 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyPaintStickersContext.swift @@ -719,7 +719,7 @@ private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButt let backgroundSize = CGSize(width: width - 11.0, height: 33.0) transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((buttonSize.height - backgroundSize.height) / 2.0)), size: backgroundSize)) self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0 - self.backgroundView.backgroundColor = UIColor(rgb: 0x007aff) + self.backgroundView.backgroundColor = UIColor(rgb: 0x0088ff) return buttonSize; } diff --git a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift index 7a65acaba8..8a2809491b 100644 --- a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift +++ b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift @@ -62,7 +62,7 @@ private func generateHeadingArrowImage() -> UIImage? { context.clip() var locations: [CGFloat] = [0.0, 0.4, 1.0] - let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor] + let colors: [CGColor] = [UIColor(rgb: 0x0088ff, alpha: 0.5).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.3).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.0).cgColor] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! @@ -184,7 +184,7 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode { let pulseImage: UIImage? let arrowImage: UIImage? if hasPulse { - pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27)) + pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x0088ff, alpha: 0.27)) } else { pulseImage = nil } diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index 78f0375eb0..a9957db257 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -197,7 +197,7 @@ public class LocationPinAnnotationView: MKAnnotationView { self.pulseNode = ASImageNode() self.pulseNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 120.0, height: 120.0)) - self.pulseNode.image = generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27)) + self.pulseNode.image = generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x0088ff, alpha: 0.27)) self.pulseNode.isHidden = true self.arrowNode = ASImageNode() diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index d3c265dc9c..206ac3c899 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -101,7 +101,7 @@ func generateHeadingArrowImage() -> UIImage? { context.clip() var locations: [CGFloat] = [0.0, 0.4, 1.0] - let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor] + let colors: [CGColor] = [UIColor(rgb: 0x0088ff, alpha: 0.5).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.3).cgColor, UIColor(rgb: 0x0088ff, alpha: 0.0).cgColor] let colorSpace = CGColorSpaceCreateDeviceRGB() let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! diff --git a/submodules/PremiumUI/Sources/BusinessPageComponent.swift b/submodules/PremiumUI/Sources/BusinessPageComponent.swift index 46c469d7ae..667decd45c 100644 --- a/submodules/PremiumUI/Sources/BusinessPageComponent.swift +++ b/submodules/PremiumUI/Sources/BusinessPageComponent.swift @@ -310,7 +310,7 @@ private final class BusinessListComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), - UIColor(rgb: 0x007aff) + UIColor(rgb: 0x0088ff) ] let titleColor = theme.list.itemPrimaryTextColor diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 3eece91a02..b65f458d11 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -2306,7 +2306,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), - UIColor(rgb: 0x007aff) + UIColor(rgb: 0x0088ff) ] var i = 0 diff --git a/submodules/PremiumUI/Sources/StoriesPageComponent.swift b/submodules/PremiumUI/Sources/StoriesPageComponent.swift index 396da5f2b1..83e1f3cb01 100644 --- a/submodules/PremiumUI/Sources/StoriesPageComponent.swift +++ b/submodules/PremiumUI/Sources/StoriesPageComponent.swift @@ -313,7 +313,7 @@ private final class StoriesListComponent: CombinedComponent { let strings = context.component.context.sharedContext.currentPresentationData.with { $0 }.strings let colors = [ - UIColor(rgb: 0x007aff), + UIColor(rgb: 0x0088ff), UIColor(rgb: 0x798aff), UIColor(rgb: 0xac64f3), UIColor(rgb: 0xc456ae), diff --git a/submodules/SettingsUI/Sources/TabBarAccountSwitchController.swift b/submodules/SettingsUI/Sources/TabBarAccountSwitchController.swift deleted file mode 100644 index 6c3beaa573..0000000000 --- a/submodules/SettingsUI/Sources/TabBarAccountSwitchController.swift +++ /dev/null @@ -1,105 +0,0 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import Postbox -import TelegramCore -import SwiftSignalKit -import TelegramPresentationData -import AccountContext - -public final class TabBarAccountSwitchController: ViewController { - private var controllerNode: TabBarAccountSwitchControllerNode { - return self.displayNode as! TabBarAccountSwitchControllerNode - } - - private let _ready = Promise(true) - override public var ready: Promise { - return self._ready - } - - private let sharedContext: SharedAccountContext - private let accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)]) - private let canAddAccounts: Bool - private let switchToAccount: (AccountRecordId) -> Void - private let addAccount: () -> Void - private let sourceNodes: [ASDisplayNode] - - private var presentationData: PresentationData - private var didPlayPresentationAnimation = false - private var changedAccount = false - - private let hapticFeedback = HapticFeedback() - - public init(sharedContext: SharedAccountContext, accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)]), canAddAccounts: Bool, switchToAccount: @escaping (AccountRecordId) -> Void, addAccount: @escaping () -> Void, sourceNodes: [ASDisplayNode]) { - self.sharedContext = sharedContext - self.accounts = accounts - self.canAddAccounts = canAddAccounts - self.switchToAccount = switchToAccount - self.addAccount = addAccount - self.sourceNodes = sourceNodes - - self.presentationData = sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - self.statusBar.ignoreInCall = true - - self.lockOrientation = true - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - } - - override public func loadDisplayNode() { - self.displayNode = TabBarAccountSwitchControllerNode(sharedContext: self.sharedContext, accounts: self.accounts, presentationData: self.presentationData, canAddAccounts: self.canAddAccounts, switchToAccount: { [weak self] id in - guard let strongSelf = self, !strongSelf.changedAccount else { - return - } - strongSelf.changedAccount = true - strongSelf.switchToAccount(id) - }, addAccount: { [weak self] in - guard let strongSelf = self, !strongSelf.changedAccount else { - return - } - strongSelf.addAccount() - }, cancel: { [weak self] in - self?.dismiss() - }, sourceNodes: self.sourceNodes) - self.displayNodeDidLoad() - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.didPlayPresentationAnimation { - self.didPlayPresentationAnimation = true - - self.hapticFeedback.impact() - self.controllerNode.animateIn() - } - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, transition: transition) - } - - override public func dismiss(completion: (() -> Void)? = nil) { - self.changedAccount = false - self.dismiss(sourceNodes: []) - } - - public func dismiss(sourceNodes: [ASDisplayNode]) { - self.controllerNode.animateOut(sourceNodes: sourceNodes, changedAccount: self.changedAccount, completion: { [weak self] in - self?.didPlayPresentationAnimation = false - self?.presentingViewController?.dismiss(animated: false, completion: nil) - }) - } -} diff --git a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift b/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift deleted file mode 100644 index 72b96a4221..0000000000 --- a/submodules/SettingsUI/Sources/TabBarAccountSwitchControllerNode.swift +++ /dev/null @@ -1,568 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import Display -import Postbox -import TelegramCore -import TelegramPresentationData -import AvatarNode -import AccountContext -import LocalizedPeerData - -private let animationDurationFactor: Double = 1.0 -private let avatarFont = avatarPlaceholderFont(size: 16.0) - -private protocol AbstractSwitchAccountItemNode { - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) -} - -private final class AddAccountItemNode: ASDisplayNode, AbstractSwitchAccountItemNode { - private let action: () -> Void - - private let separatorNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let plusNode: ASImageNode - private let titleNode: ImmediateTextNode - - init(displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Void) { - self.action = action - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.separatorNode.isHidden = !displaySeparator - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.buttonNode = HighlightTrackingButtonNode() - - self.titleNode = ImmediateTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.Settings_AddAccount, font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - - self.plusNode = ASImageNode() - self.plusNode.image = generateItemListPlusIcon(presentationData.theme.actionSheet.primaryTextColor) - - super.init() - - self.addSubnode(self.separatorNode) - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.titleNode) - self.addSubnode(self.plusNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.highlightedBackgroundNode.alpha = 1.0 - } else { - strongSelf.highlightedBackgroundNode.alpha = 0.0 - strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - } - - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { - let leftInset: CGFloat = 56.0 - let rightInset: CGFloat = 10.0 - let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - let height: CGFloat = 61.0 - - return (titleSize.width + leftInset + rightInset, height, { width in - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - if let image = self.plusNode.image { - self.plusNode.frame = CGRect(origin: CGPoint(x: floor((leftInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) - } - - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) - self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - }) - } - - @objc private func buttonPressed() { - self.action() - } -} - -private final class SwitchAccountItemNode: ASDisplayNode, AbstractSwitchAccountItemNode { - private let context: AccountContext - private let peer: Peer - private let isCurrent: Bool - private let unreadCount: Int32 - private let presentationData: PresentationData - private let action: () -> Void - - private let separatorNode: ASDisplayNode - private let highlightedBackgroundNode: ASDisplayNode - private let buttonNode: HighlightTrackingButtonNode - private let avatarNode: AvatarNode - private let titleNode: ImmediateTextNode - private let checkNode: ASImageNode - - private let badgeBackgroundNode: ASImageNode - private let badgeTitleNode: ImmediateTextNode - - init(context: AccountContext, peer: Peer, isCurrent: Bool, unreadCount: Int32, displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Void) { - self.context = context - self.peer = peer - self.isCurrent = isCurrent - self.unreadCount = unreadCount - self.presentationData = presentationData - self.action = action - - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor - self.separatorNode.isHidden = !displaySeparator - - self.highlightedBackgroundNode = ASDisplayNode() - self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor - self.highlightedBackgroundNode.alpha = 0.0 - - self.buttonNode = HighlightTrackingButtonNode() - - self.avatarNode = AvatarNode(font: avatarFont) - - self.titleNode = ImmediateTextNode() - self.titleNode.maximumNumberOfLines = 1 - self.titleNode.attributedText = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) - - self.checkNode = ASImageNode() - self.checkNode.image = generateItemListCheckIcon(color: presentationData.theme.actionSheet.primaryTextColor) - self.checkNode.isHidden = !isCurrent - - self.badgeBackgroundNode = ASImageNode() - self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: presentationData.theme.list.itemCheckColors.fillColor) - self.badgeTitleNode = ImmediateTextNode() - if unreadCount > 0 { - let countString = compactNumericCountString(Int(unreadCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) - self.badgeTitleNode.attributedText = NSAttributedString(string: countString, font: Font.regular(14.0), textColor: presentationData.theme.list.itemCheckColors.foregroundColor) - } else { - self.badgeBackgroundNode.isHidden = true - self.badgeTitleNode.isHidden = true - } - - super.init() - - self.addSubnode(self.separatorNode) - self.addSubnode(self.highlightedBackgroundNode) - self.addSubnode(self.avatarNode) - self.addSubnode(self.titleNode) - self.addSubnode(self.checkNode) - self.addSubnode(self.badgeBackgroundNode) - self.addSubnode(self.badgeTitleNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) - self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") - strongSelf.highlightedBackgroundNode.alpha = 1.0 - } else { - strongSelf.highlightedBackgroundNode.alpha = 0.0 - strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - } - } - - func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { - let leftInset: CGFloat = 56.0 - - let badgeTitleSize = self.badgeTitleNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude)) - let badgeMinSize = self.badgeBackgroundNode.image?.size.width ?? 20.0 - let badgeSize = CGSize(width: max(badgeMinSize, badgeTitleSize.width + 12.0), height: badgeMinSize) - - let rightInset: CGFloat = max(60.0, badgeSize.width + 40.0) - - let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) - - let height: CGFloat = 61.0 - - return (titleSize.width + leftInset + rightInset, height, { width in - let avatarSize = CGSize(width: 30.0, height: 30.0) - self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((leftInset - avatarSize.width) / 2.0), y: floor((height - avatarSize.height) / 2.0)), size: avatarSize) - self.avatarNode.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(self.peer)) - - self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) - - if let image = self.checkNode.image { - self.checkNode.frame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) - } - - let badgeBackgroundFrame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - badgeSize.width) / 2.0), y: floor((height - badgeSize.height) / 2.0)), size: badgeSize) - self.badgeBackgroundNode.frame = badgeBackgroundFrame - self.badgeTitleNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeTitleSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeTitleSize.height) / 2.0)), size: badgeTitleSize) - - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) - self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) - }) - } - - @objc private func buttonPressed() { - self.action() - } -} - -final class TabBarAccountSwitchControllerNode: ViewControllerTracingNode { - private let presentationData: PresentationData - private let cancel: () -> Void - - private let effectView: UIVisualEffectView - private var propertyAnimator: AnyObject? - private var displayLinkAnimator: DisplayLinkAnimator? - private let dimNode: ASDisplayNode - - private let contentContainerNode: ASDisplayNode - private let contentNodes: [ASDisplayNode & AbstractSwitchAccountItemNode] - - private var sourceNodes: [ASDisplayNode] - private var snapshotViews: [UIView] = [] - - private var validLayout: ContainerViewLayout? - - init(sharedContext: SharedAccountContext, accounts: (primary: (Account, Peer), other: [(Account, Peer, Int32)]), presentationData: PresentationData, canAddAccounts: Bool, switchToAccount: @escaping (AccountRecordId) -> Void, addAccount: @escaping () -> Void, cancel: @escaping () -> Void, sourceNodes: [ASDisplayNode]) { - self.presentationData = presentationData - self.cancel = cancel - self.sourceNodes = sourceNodes - - self.effectView = UIVisualEffectView() - if #available(iOS 9.0, *) { - } else { - if presentationData.theme.rootController.keyboardColor == .dark { - self.effectView.effect = UIBlurEffect(style: .dark) - } else { - self.effectView.effect = UIBlurEffect(style: .light) - } - self.effectView.alpha = 0.0 - } - - self.dimNode = ASDisplayNode() - self.dimNode.alpha = 1.0 - if presentationData.theme.rootController.keyboardColor == .light { - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.04) - } else { - self.dimNode.backgroundColor = presentationData.theme.chatList.backgroundColor.withAlphaComponent(0.2) - } - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - self.contentContainerNode.cornerRadius = 20.0 - self.contentContainerNode.clipsToBounds = true - - var contentNodes: [ASDisplayNode & AbstractSwitchAccountItemNode] = [] - if canAddAccounts { - contentNodes.append(AddAccountItemNode(displaySeparator: true, presentationData: presentationData, action: { - addAccount() - cancel() - })) - } - contentNodes.append(SwitchAccountItemNode(context: sharedContext.makeTempAccountContext(account: accounts.primary.0), peer: accounts.primary.1, isCurrent: true, unreadCount: 0, displaySeparator: !accounts.other.isEmpty, presentationData: presentationData, action: { - cancel() - })) - for i in 0 ..< accounts.other.count { - let (account, peer, count) = accounts.other[i] - let id = account.id - contentNodes.append(SwitchAccountItemNode(context: sharedContext.makeTempAccountContext(account: account), peer: peer, isCurrent: false, unreadCount: count, displaySeparator: i != accounts.other.count - 1, presentationData: presentationData, action: { - switchToAccount(id) - })) - } - self.contentNodes = contentNodes - - super.init() - - self.view.addSubview(self.effectView) - self.addSubnode(self.dimNode) - self.addSubnode(self.contentContainerNode) - self.contentNodes.forEach(self.contentContainerNode.addSubnode) - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - } - - deinit { - if let propertyAnimator = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in - guard let strongSelf = self else { - return - } - strongSelf.effectView.effect = makeCustomZoomBlurEffect(isLight: !strongSelf.presentationData.theme.overallDarkAppearance) - }) - } - - if let _ = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { - }) - } - } else { - UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { - self.effectView.effect = makeCustomZoomBlurEffect(isLight: !self.presentationData.theme.overallDarkAppearance) - }, completion: { _ in - }) - } - - self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { - let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - self.contentContainerNode.layer.animateFrame(from: sourceFrame, to: self.contentContainerNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - } - - for sourceNode in self.sourceNodes { - if let imageNode = sourceNode as? ASImageNode { - let snapshot = UIImageView() - snapshot.image = imageNode.image - snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - snapshot.isUserInteractionEnabled = false - self.view.addSubview(snapshot) - self.snapshotViews.append(snapshot) - } else if let snapshot = sourceNode.view.snapshotContentTree() { - snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - snapshot.isUserInteractionEnabled = false - self.view.addSubview(snapshot) - self.snapshotViews.append(snapshot) - } - sourceNode.alpha = 0.0 - } - } - - func animateOut(sourceNodes: [ASDisplayNode], changedAccount: Bool, completion: @escaping () -> Void) { - self.isUserInteractionEnabled = false - - var completedEffect = false - var completedSourceNodes = false - - let intermediateCompletion: () -> Void = { - if completedEffect && completedSourceNodes { - completion() - } - } - - if #available(iOS 10.0, *) { - if let propertyAnimator = self.propertyAnimator { - let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator - propertyAnimator?.stopAnimation(true) - } - self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { [weak self] in - self?.effectView.effect = nil - }) - } - - if let _ = self.propertyAnimator { - if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { - self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in - (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value - }, completion: { [weak self] in - if let strongSelf = self { - for sourceNode in strongSelf.sourceNodes { - sourceNode.alpha = 1.0 - } - } - - completedEffect = true - intermediateCompletion() - }) - } - self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) - } else { - UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: { - if #available(iOS 9.0, *) { - self.effectView.effect = nil - } else { - self.effectView.alpha = 0.0 - } - }, completion: { [weak self] _ in - if let strongSelf = self { - for sourceNode in strongSelf.sourceNodes { - sourceNode.alpha = 1.0 - } - } - - completedEffect = true - intermediateCompletion() - }) - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) - self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { _ in - }) - if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { - let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - self.contentContainerNode.layer.animateFrame(from: self.contentContainerNode.frame, to: sourceFrame, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false) - } - - if changedAccount { - for sourceNode in self.sourceNodes { - sourceNode.alpha = 1.0 - } - - var previousImage: UIImage? - for i in 0 ..< self.snapshotViews.count { - let view = self.snapshotViews[i] - if view.bounds.size.width.isEqual(to: 42.0) { - if i == 0, let imageView = view as? UIImageView { - previousImage = imageView.image - } - view.removeFromSuperview() - } else { - view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak view] _ in - view?.removeFromSuperview() - }) - view.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false) - } - } - let previousSnapshotViews = self.snapshotViews - self.snapshotViews = [] - - self.sourceNodes = sourceNodes - - var hadBounce = false - for i in 0 ..< self.sourceNodes.count { - let sourceNode = self.sourceNodes[i] - var snapshot: UIView? - if let imageNode = sourceNode as? ASImageNode { - let snapshotView = UIImageView() - snapshotView.image = imageNode.image - snapshotView.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - snapshotView.isUserInteractionEnabled = false - self.view.addSubview(snapshotView) - self.snapshotViews.append(snapshotView) - snapshot = snapshotView - } else if let genericSnapshot = sourceNode.view.snapshotContentTree() { - genericSnapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) - genericSnapshot.isUserInteractionEnabled = false - self.view.addSubview(genericSnapshot) - self.snapshotViews.append(genericSnapshot) - snapshot = genericSnapshot - } - - if let snapshot = snapshot { - if snapshot.bounds.size.width.isEqual(to: 42.0) { - if i == 0, let imageView = snapshot as? UIImageView { - hadBounce = true - let updatedImage = imageView.image - imageView.image = previousImage - setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.3), forView: imageView) - imageView.layer.animateScale(from: 1.0, to: 0.6, duration: 0.1, removeOnCompletion: false, completion: { [weak imageView] _ in - guard let imageView = imageView else { - return - } - imageView.image = updatedImage - if let previousContents = previousImage?.cgImage, let updatedContents = updatedImage?.cgImage { - imageView.layer.animate(from: previousContents as AnyObject, to: updatedContents as AnyObject, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.15) - } - imageView.layer.animateSpring(from: 0.6 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.6, completion: { _ in - completedSourceNodes = true - intermediateCompletion() - }) - }) - } - } else { - snapshot.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - snapshot.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2, removeOnCompletion: false) - } - } - sourceNode.alpha = 0.0 - } - - previousSnapshotViews.forEach { view in - self.view.bringSubviewToFront(view) - } - - if !hadBounce { - completedSourceNodes = true - } - } else { - completedSourceNodes = true - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let sideInset: CGFloat = 18.0 - - var contentSize = CGSize() - contentSize.width = min(layout.size.width - 40.0, 250.0) - var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = [] - for itemNode in self.contentNodes { - let (width, height, apply) = itemNode.updateLayout(maxWidth: layout.size.width - sideInset * 2.0) - applyNodes.append((itemNode, height, apply)) - contentSize.width = max(contentSize.width, width) - contentSize.height += height - } - - let insets = layout.insets(options: .input) - - let contentOrigin: CGPoint - if let sourceNode = self.sourceNodes.first, let screenFrame = sourceNode.supernode?.convert(sourceNode.frame, to: nil) { - contentOrigin = CGPoint(x: screenFrame.maxX - contentSize.width + 8.0, y: layout.size.height - 66.0 - insets.bottom - contentSize.height) - } else { - contentOrigin = CGPoint(x: layout.size.width - sideInset - contentSize.width, y: layout.size.height - 66.0 - layout.intrinsicInsets.bottom - contentSize.height) - } - - transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize)) - var nextY: CGFloat = 0.0 - for (itemNode, height, apply) in applyNodes { - transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height))) - apply(contentSize.width) - nextY += height - } - } - - @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancel() - } - } -} - -private func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) { - var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, - y: view.bounds.size.height * anchorPoint.y) - - - var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, - y: view.bounds.size.height * view.layer.anchorPoint.y) - - newPoint = newPoint.applying(view.transform) - oldPoint = oldPoint.applying(view.transform) - - var position = view.layer.position - position.x -= oldPoint.x - position.x += newPoint.x - - position.y -= oldPoint.y - position.y += newPoint.y - - view.layer.position = position - view.layer.anchorPoint = anchorPoint -} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift index 81ca956786..afd1531610 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorPresets.swift @@ -31,14 +31,14 @@ var dayClassicColorPresets: [PresentationThemeAccentColor] = [ ] var dayColorPresets: [PresentationThemeAccentColor] = [ - PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: nil), + PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x0088ff, bubbleColors: [0x0088ff, 0xff53f4], wallpaper: nil), PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: nil), PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: nil), PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: nil) ] var nightColorPresets: [PresentationThemeAccentColor] = [ -// PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x007aff, bubbleColors: [0x007aff, 0xff53f4], wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), +// PresentationThemeAccentColor(index: 101, baseColor: .preset, accentColor: 0x0088ff, bubbleColors: [0x0088ff, 0xff53f4], wallpaper: patternWallpaper(data: .variant4, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), PresentationThemeAccentColor(index: 102, baseColor: .preset, accentColor: 0x00b09b, bubbleColors: [0xaee946, 0x00b09b], wallpaper: patternWallpaper(data: .variant9, colors: [0xe4b2ea, 0x8376c2, 0xeab9d9, 0xb493e6], intensity: -35, rotation: nil)), PresentationThemeAccentColor(index: 103, baseColor: .preset, accentColor: 0xd33213, bubbleColors: [0xf9db00, 0xd33213], wallpaper: patternWallpaper(data: .variant2, colors: [0xfec496, 0xdd6cb9, 0x962fbf, 0x4f5bd5], intensity: -40, rotation: nil)), PresentationThemeAccentColor(index: 104, baseColor: .preset, accentColor: 0xea8ced, bubbleColors: [0xea8ced, 0x00c2ed], wallpaper: patternWallpaper(data: .variant6, colors: [0x8adbf2, 0x888dec, 0xe39fea, 0x679ced], intensity: -30, rotation: nil)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift index 9a18c3b9ae..2e5218a857 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAccentColorItem.swift @@ -385,7 +385,7 @@ private final class ThemeSettingsAccentColorIconItemNode : ListViewItemNode { topColor = bubbleColor bottomColor = bubbleColor } else { - fillColor = UIColor(rgb: 0x007aff) + fillColor = UIColor(rgb: 0x0088ff) strokeColor = fillColor topColor = UIColor(rgb: 0xe1ffc7) bottomColor = topColor diff --git a/submodules/TabBarUI/BUILD b/submodules/TabBarUI/BUILD index 1abbce2193..a2bed9e07b 100644 --- a/submodules/TabBarUI/BUILD +++ b/submodules/TabBarUI/BUILD @@ -10,12 +10,15 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/AsyncDisplayKit:AsyncDisplayKit", - "//submodules/Display:Display", - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/AnimatedStickerNode:AnimatedStickerNode", - "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", - "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/TabBarComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TabBarUI/Sources/TabBarContollerNode.swift b/submodules/TabBarUI/Sources/TabBarContollerNode.swift index d0b8d37680..6b6921fd92 100644 --- a/submodules/TabBarUI/Sources/TabBarContollerNode.swift +++ b/submodules/TabBarUI/Sources/TabBarContollerNode.swift @@ -2,24 +2,72 @@ import Foundation import UIKit import AsyncDisplayKit import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import TabBarComponent private extension ToolbarTheme { - convenience init(tabBarTheme theme: TabBarControllerTheme) { - self.init(barBackgroundColor: theme.tabBarBackgroundColor, barSeparatorColor: theme.tabBarSeparatorColor, barTextColor: theme.tabBarTextColor, barSelectedTextColor: theme.tabBarSelectedTextColor) + convenience init(theme: PresentationTheme) { + self.init(barBackgroundColor: theme.rootController.tabBar.backgroundColor, barSeparatorColor: .clear, barTextColor: theme.rootController.tabBar.textColor, barSelectedTextColor: theme.rootController.tabBar.selectedTextColor) } } final class TabBarControllerNode: ASDisplayNode { - private var navigationBarPresentationData: NavigationBarPresentationData - private var theme: TabBarControllerTheme - let tabBarNode: TabBarNode + private struct Params: Equatable { + let layout: ContainerViewLayout + let toolbar: Toolbar? + + init( + layout: ContainerViewLayout, + toolbar: Toolbar? + ) { + self.layout = layout + self.toolbar = toolbar + } + } + + private struct LayoutResult { + let params: Params + let bottomInset: CGFloat + + init(params: Params, bottomInset: CGFloat) { + self.params = params + self.bottomInset = bottomInset + } + } + + private final class View: UIView { + var onLayout: (() -> Void)? + + override func layoutSubviews() { + super.layoutSubviews() + + self.onLayout?() + } + } + + private var theme: PresentationTheme + private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void + private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void + + private let tabBarNode: TabBarNode + private let tabBarView = ComponentView() + private let disabledOverlayNode: ASDisplayNode private var toolbarNode: ToolbarNode? private let toolbarActionSelected: (ToolbarActionOption) -> Void private let disabledPressed: () -> Void + + private(set) var tabBarItems: [TabBarNodeItem] = [] + private(set) var selectedIndex: Int = 0 var currentControllerNode: ASDisplayNode? + private var layoutResult: LayoutResult? + private var isUpdateRequested: Bool = false + private var isChangingSelectedIndex: Bool = false + func setCurrentControllerNode(_ node: ASDisplayNode?) -> () -> Void { guard node !== self.currentControllerNode else { return {} @@ -42,12 +90,13 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) { + init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void, disabledPressed: @escaping () -> Void) { self.theme = theme - self.navigationBarPresentationData = navigationBarPresentationData + self.itemSelected = itemSelected + self.contextAction = contextAction self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected, contextAction: contextAction, swipeAction: swipeAction) self.disabledOverlayNode = ASDisplayNode() - self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5) + self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5) self.disabledOverlayNode.alpha = 0.0 self.toolbarActionSelected = toolbarActionSelected self.disabledPressed = disabledPressed @@ -55,13 +104,25 @@ final class TabBarControllerNode: ASDisplayNode { super.init() self.setViewBlock({ - return UITracingLayerView() + return View(frame: CGRect()) }) - self.backgroundColor = theme.backgroundColor + (self.view as? View)?.onLayout = { [weak self] in + guard let self else { + return + } + if self.isUpdateRequested { + self.isUpdateRequested = false + if let layoutResult = self.layoutResult { + let _ = self.updateImpl(params: layoutResult.params, transition: .immediate) + } + } + } - self.addSubnode(self.tabBarNode) - self.addSubnode(self.disabledOverlayNode) + self.backgroundColor = theme.list.plainBackgroundColor + + //self.addSubnode(self.tabBarNode) + //self.addSubnode(self.disabledOverlayNode) } override func didLoad() { @@ -76,14 +137,14 @@ final class TabBarControllerNode: ASDisplayNode { } } - func updateTheme(_ theme: TabBarControllerTheme, navigationBarPresentationData: NavigationBarPresentationData) { + func updateTheme(_ theme: PresentationTheme) { self.theme = theme - self.navigationBarPresentationData = navigationBarPresentationData - self.backgroundColor = theme.backgroundColor + self.backgroundColor = theme.list.plainBackgroundColor self.tabBarNode.updateTheme(theme) - self.disabledOverlayNode.backgroundColor = theme.backgroundColor.withAlphaComponent(0.5) - self.toolbarNode?.updateTheme(ToolbarTheme(tabBarTheme: theme)) + self.disabledOverlayNode.backgroundColor = theme.rootController.tabBar.backgroundColor.withAlphaComponent(0.5) + self.toolbarNode?.updateTheme(ToolbarTheme(theme: theme)) + self.requestUpdate() } func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) { @@ -92,32 +153,91 @@ final class TabBarControllerNode: ASDisplayNode { var tabBarHidden = false - func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { - var tabBarHeight: CGFloat + func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) -> CGFloat { + let params = Params(layout: layout, toolbar: toolbar) + if let layoutResult = self.layoutResult, layoutResult.params == params { + return layoutResult.bottomInset + } else { + let bottomInset = self.updateImpl(params: params, transition: transition) + self.layoutResult = LayoutResult(params: params, bottomInset: bottomInset) + return bottomInset + } + } + + private func requestUpdate() { + self.isUpdateRequested = true + self.view.setNeedsLayout() + } + + private func updateImpl(params: Params, transition: ContainedViewLayoutTransition) -> CGFloat { var options: ContainerViewLayoutInsetOptions = [] - if layout.metrics.widthClass == .regular { + if params.layout.metrics.widthClass == .regular { options.insert(.input) } - let bottomInset: CGFloat = layout.insets(options: options).bottom - if !layout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset + + var bottomInset: CGFloat = params.layout.insets(options: options).bottom + if bottomInset == 0.0 { + bottomInset = 8.0 } else { - tabBarHeight = 49.0 + bottomInset + bottomInset = max(bottomInset - 13.0, 8.0) + } + let sideInset: CGFloat = 21.0 + + var selectedId: AnyHashable? + if self.selectedIndex < self.tabBarItems.count { + selectedId = ObjectIdentifier(self.tabBarItems[self.selectedIndex].item) + } + var tabBarTransition = ComponentTransition(transition) + if self.isChangingSelectedIndex { + self.isChangingSelectedIndex = false + tabBarTransition = .spring(duration: 0.4) + } + if self.tabBarView.view == nil { + tabBarTransition = .immediate + } + let tabBarSize = self.tabBarView.update( + transition: tabBarTransition, + component: AnyComponent(TabBarComponent( + theme: self.theme, + items: self.tabBarItems.map { item in + let itemId = AnyHashable(ObjectIdentifier(item.item)) + return TabBarComponent.Item( + item: item.item, + action: { [weak self] isLongTap in + guard let self else { + return + } + if let index = self.tabBarItems.firstIndex(where: { AnyHashable(ObjectIdentifier($0.item)) == itemId }) { + self.itemSelected(index, isLongTap, []) + } + } + ) + }, + selectedId: selectedId + )), + environment: {}, + containerSize: CGSize(width: params.layout.size.width - sideInset * 2.0, height: 100.0) + ) + let tabBarFrame = CGRect(origin: CGPoint(x: floor((params.layout.size.width - tabBarSize.width) * 0.5), y: params.layout.size.height - (self.tabBarHidden ? 0.0 : (tabBarSize.height + bottomInset))), size: tabBarSize) + + if let tabBarComponentView = self.tabBarView.view { + if tabBarComponentView.superview == nil { + self.view.addSubview(tabBarComponentView) + } + transition.updateFrame(view: tabBarComponentView, frame: tabBarFrame) } - let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - (self.tabBarHidden ? 0.0 : tabBarHeight)), size: CGSize(width: layout.size.width, height: tabBarHeight)) - - transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame) - self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, transition: transition) + //transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame) + //self.tabBarNode.updateLayout(size: params.layout.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, transition: transition) transition.updateFrame(node: self.disabledOverlayNode, frame: tabBarFrame) - if let toolbar = toolbar { + if let toolbar = params.toolbar { if let toolbarNode = self.toolbarNode { transition.updateFrame(node: toolbarNode, frame: tabBarFrame) - toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition) + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: transition) } else { - let toolbarNode = ToolbarNode(theme: ToolbarTheme(tabBarTheme: self.theme), displaySeparator: true, left: { [weak self] in + let toolbarNode = ToolbarNode(theme: ToolbarTheme(theme: self.theme), displaySeparator: true, left: { [weak self] in self?.toolbarActionSelected(.left) }, right: { [weak self] in self?.toolbarActionSelected(.right) @@ -125,7 +245,7 @@ final class TabBarControllerNode: ASDisplayNode { self?.toolbarActionSelected(.middle) }) toolbarNode.frame = tabBarFrame - toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, additionalSideInsets: layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: params.layout.safeInsets.left, rightInset: params.layout.safeInsets.right, additionalSideInsets: params.layout.additionalInsets, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) self.addSubnode(toolbarNode) self.toolbarNode = toolbarNode if transition.isAnimated { @@ -138,5 +258,31 @@ final class TabBarControllerNode: ASDisplayNode { toolbarNode?.removeFromSupernode() }) } + + return params.layout.size.height - tabBarFrame.minY + } + + func frameForControllerTab(at index: Int) -> CGRect? { + return self.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarNode.view.convert($0, to: self.view) } + } + + func isPointInsideContentArea(point: CGPoint) -> Bool { + if point.y < self.tabBarNode.frame.minY { + return true + } + return false + } + + func updateTabBarItems(items: [TabBarNodeItem]) { + self.tabBarItems = items + self.tabBarNode.tabBarItems = items + self.requestUpdate() + } + + func updateSelectedIndex(index: Int) { + self.selectedIndex = index + self.tabBarNode.selectedIndex = index + self.isChangingSelectedIndex = true + self.requestUpdate() } } diff --git a/submodules/TabBarUI/Sources/TabBarController.swift b/submodules/TabBarUI/Sources/TabBarController.swift index 0d9b77d929..5523ab5171 100644 --- a/submodules/TabBarUI/Sources/TabBarController.swift +++ b/submodules/TabBarUI/Sources/TabBarController.swift @@ -5,41 +5,6 @@ import SwiftSignalKit import Display import TelegramPresentationData -public final class TabBarControllerTheme { - public let backgroundColor: UIColor - public let tabBarBackgroundColor: UIColor - public let tabBarSeparatorColor: UIColor - public let tabBarIconColor: UIColor - public let tabBarSelectedIconColor: UIColor - public let tabBarTextColor: UIColor - public let tabBarSelectedTextColor: UIColor - public let tabBarBadgeBackgroundColor: UIColor - public let tabBarBadgeStrokeColor: UIColor - public let tabBarBadgeTextColor: UIColor - public let tabBarExtractedIconColor: UIColor - public let tabBarExtractedTextColor: UIColor - - public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarIconColor: UIColor, tabBarSelectedIconColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor, tabBarExtractedIconColor: UIColor, tabBarExtractedTextColor: UIColor) { - self.backgroundColor = backgroundColor - self.tabBarBackgroundColor = tabBarBackgroundColor - self.tabBarSeparatorColor = tabBarSeparatorColor - self.tabBarIconColor = tabBarIconColor - self.tabBarSelectedIconColor = tabBarSelectedIconColor - self.tabBarTextColor = tabBarTextColor - self.tabBarSelectedTextColor = tabBarSelectedTextColor - self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor - self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor - self.tabBarBadgeTextColor = tabBarBadgeTextColor - self.tabBarExtractedIconColor = tabBarExtractedIconColor - self.tabBarExtractedTextColor = tabBarExtractedTextColor - } - - public convenience init(rootControllerTheme: PresentationTheme) { - let theme = rootControllerTheme.rootController.tabBar - self.init(backgroundColor: rootControllerTheme.list.plainBackgroundColor, tabBarBackgroundColor: theme.backgroundColor, tabBarSeparatorColor: theme.separatorColor, tabBarIconColor: theme.iconColor, tabBarSelectedIconColor: theme.selectedIconColor, tabBarTextColor: theme.textColor, tabBarSelectedTextColor: theme.selectedTextColor, tabBarBadgeBackgroundColor: theme.badgeBackgroundColor, tabBarBadgeStrokeColor: theme.badgeStrokeColor, tabBarBadgeTextColor: theme.badgeTextColor, tabBarExtractedIconColor: rootControllerTheme.contextMenu.extractedContentTintColor, tabBarExtractedTextColor: rootControllerTheme.contextMenu.extractedContentTintColor) - } -} - public final class TabBarItemInfo: NSObject { public let previewing: Bool @@ -127,13 +92,9 @@ open class TabBarControllerImpl: ViewController, TabBarController { private let pendingControllerDisposable = MetaDisposable() - private var navigationBarPresentationData: NavigationBarPresentationData - private var theme: TabBarControllerTheme + private var theme: PresentationTheme - public var cameraItemAndAction: (item: UITabBarItem, action: () -> Void)? - - public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { - self.navigationBarPresentationData = navigationBarPresentationData + public init(theme: PresentationTheme) { self.theme = theme super.init(navigationBarPresentationData: nil) @@ -156,54 +117,27 @@ open class TabBarControllerImpl: ViewController, TabBarController { self.pendingControllerDisposable.dispose() } - public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { + public func updateTheme(theme: PresentationTheme) { if self.theme !== theme { self.theme = theme - self.navigationBarPresentationData = navigationBarPresentationData if self.isNodeLoaded { - self.tabBarControllerNode.updateTheme(theme, navigationBarPresentationData: navigationBarPresentationData) + self.tabBarControllerNode.updateTheme(theme) } } } private var debugTapCounter: (Double, Int) = (0.0, 0) - public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? { - return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index) - } - - public func viewForCameraItem() -> UIView? { - if let (cameraItem, _) = self.cameraItemAndAction { - if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - return self.tabBarControllerNode.tabBarNode.viewForControllerTab(at: cameraItemIndex) - } - } - return nil - } - public func frameForControllerTab(controller: ViewController) -> CGRect? { if let index = self.controllers.firstIndex(of: controller) { - var index = index - if let (cameraItem, _) = self.cameraItemAndAction { - if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - if index == cameraItemIndex { - - } else if index > cameraItemIndex { - index -= 1 - } - } - } - return self.tabBarControllerNode.tabBarNode.frameForControllerTab(at: index).flatMap { self.tabBarControllerNode.tabBarNode.view.convert($0, to: self.view) } + return self.tabBarControllerNode.frameForControllerTab(at: index) } else { return nil } } public func isPointInsideContentArea(point: CGPoint) -> Bool { - if point.y < self.tabBarControllerNode.tabBarNode.frame.minY { - return true - } - return false + return self.tabBarControllerNode.isPointInsideContentArea(point: point) } public func updateIsTabBarEnabled(_ value: Bool, transition: ContainedViewLayoutTransition) { @@ -218,19 +152,8 @@ open class TabBarControllerImpl: ViewController, TabBarController { } override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(theme: self.theme, navigationBarPresentationData: self.navigationBarPresentationData, itemSelected: { [weak self] index, longTap, itemNodes in + self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap, itemNodes in if let strongSelf = self { - var index = index - if let (cameraItem, cameraAction) = strongSelf.cameraItemAndAction { - if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - if index == cameraItemIndex { - cameraAction() - return - } else if index > cameraItemIndex { - index -= 1 - } - } - } if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController { controller.presentTabBarPreviewingController(sourceNodes: itemNodes) return @@ -298,34 +221,14 @@ open class TabBarControllerImpl: ViewController, TabBarController { guard let strongSelf = self else { return } - if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.count { - var index = index - if let (cameraItem, _) = strongSelf.cameraItemAndAction { - if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - if index == cameraItemIndex { - return - } else if index > cameraItemIndex { - index -= 1 - } - } - } + if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count { strongSelf.controllers[index].tabBarItemContextAction(sourceNode: node, gesture: gesture) } }, swipeAction: { [weak self] index, direction in guard let strongSelf = self else { return } - if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.count { - var index = index - if let (cameraItem, _) = strongSelf.cameraItemAndAction { - if let cameraItemIndex = strongSelf.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - if index == cameraItemIndex { - return - } else if index > cameraItemIndex { - index -= 1 - } - } - } + if index >= 0 && index < strongSelf.tabBarControllerNode.tabBarItems.count { strongSelf.controllers[index].tabBarItemSwipeAction(direction: direction) } }, toolbarActionSelected: { [weak self] action in @@ -339,9 +242,6 @@ open class TabBarControllerImpl: ViewController, TabBarController { } public func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { - let alpha = max(0.0, min(1.0, alpha)) - transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.backgroundNode, alpha: alpha, delay: 0.1) - transition.updateAlpha(node: self.tabBarControllerNode.tabBarNode.separatorNode, alpha: alpha, delay: 0.1) } private func updateSelectedIndex(animated: Bool = false) { @@ -354,15 +254,8 @@ open class TabBarControllerImpl: ViewController, TabBarController { animated = false } - var tabBarSelectedIndex = self.selectedIndex - if let (cameraItem, _) = self.cameraItemAndAction { - if let cameraItemIndex = self.tabBarControllerNode.tabBarNode.tabBarItems.firstIndex(where: { $0.item === cameraItem }) { - if tabBarSelectedIndex >= cameraItemIndex { - tabBarSelectedIndex += 1 - } - } - } - self.tabBarControllerNode.tabBarNode.selectedIndex = tabBarSelectedIndex + let tabBarSelectedIndex = self.selectedIndex + self.tabBarControllerNode.updateSelectedIndex(index: tabBarSelectedIndex) var transitionScale: CGFloat = 0.998 if let currentView = self.currentController?.view { @@ -428,26 +321,14 @@ open class TabBarControllerImpl: ViewController, TabBarController { self.validLayout = layout - self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition) + let bottomInset = self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition) if let currentController = self.currentController { currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) var updatedLayout = layout - - var tabBarHeight: CGFloat - var options: ContainerViewLayoutInsetOptions = [] - if updatedLayout.metrics.widthClass == .regular { - options.insert(.input) - } - let bottomInset: CGFloat = updatedLayout.insets(options: options).bottom - if !updatedLayout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset - } else { - tabBarHeight = 49.0 + bottomInset - } if !self.tabBarControllerNode.tabBarHidden { - updatedLayout.intrinsicInsets.bottom = tabBarHeight + updatedLayout.intrinsicInsets.bottom = bottomInset } currentController.containerLayoutUpdated(updatedLayout, transition: transition) @@ -525,12 +406,9 @@ open class TabBarControllerImpl: ViewController, TabBarController { } self.controllers = controllers - var tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) }) - if let (cameraItem, _) = self.cameraItemAndAction { - tabBarItems.insert(TabBarNodeItem(item: cameraItem, contextActionType: .none), at: Int(floor(CGFloat(controllers.count) / 2))) - } + let tabBarItems = self.controllers.map({ TabBarNodeItem(item: $0.tabBarItem, contextActionType: $0.tabBarItemContextActionType) }) - self.tabBarControllerNode.tabBarNode.tabBarItems = tabBarItems + self.tabBarControllerNode.updateTabBarItems(items: tabBarItems) let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal in if let tabBarItem = tabBarItem, tabBarItem.image == nil { diff --git a/submodules/TabBarUI/Sources/TabBarNode.swift b/submodules/TabBarUI/Sources/TabBarNode.swift index b5d14b5460..5b08f3eb81 100644 --- a/submodules/TabBarUI/Sources/TabBarNode.swift +++ b/submodules/TabBarUI/Sources/TabBarNode.swift @@ -6,6 +6,7 @@ import Display import UIKitRuntimeUtils import AnimatedStickerNode import TelegramAnimatedStickerNode +import TelegramPresentationData private extension CGRect { var center: CGPoint { @@ -343,7 +344,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { private let contextAction: (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void private let swipeAction: (Int, TabBarItemSwipeDirection) -> Void - private var theme: TabBarControllerTheme + private var theme: PresentationTheme private var validLayout: (CGSize, CGFloat, CGFloat, UIEdgeInsets, CGFloat)? private var horizontal: Bool = false private var centered: Bool = false @@ -351,25 +352,19 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { private var badgeImage: UIImage let backgroundNode: NavigationBackgroundNode - let separatorNode: ASDisplayNode private var tabBarNodeContainers: [TabBarNodeContainer] = [] private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) { + init(theme: PresentationTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, contextAction: @escaping (Int, ContextExtractedContentContainingNode, ContextGesture) -> Void, swipeAction: @escaping (Int, TabBarItemSwipeDirection) -> Void) { self.itemSelected = itemSelected self.contextAction = contextAction self.swipeAction = swipeAction self.theme = theme - self.backgroundNode = NavigationBackgroundNode(color: theme.tabBarBackgroundColor) + self.backgroundNode = NavigationBackgroundNode(color: theme.rootController.tabBar.backgroundColor) - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = theme.tabBarSeparatorColor - self.separatorNode.isOpaque = true - self.separatorNode.isLayerBacked = true - - self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.rootController.tabBar.badgeBackgroundColor, strokeColor: theme.rootController.tabBar.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! super.init() @@ -382,7 +377,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { self.isExclusiveTouch = true self.addSubnode(self.backgroundNode) - self.addSubnode(self.separatorNode) } override func didLoad() { @@ -417,17 +411,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { } } - func updateTheme(_ theme: TabBarControllerTheme) { + func updateTheme(_ theme: PresentationTheme) { if self.theme !== theme { self.theme = theme - self.separatorNode.backgroundColor = theme.tabBarSeparatorColor - self.backgroundNode.updateColor(color: theme.tabBarBackgroundColor, transition: .immediate) + self.backgroundNode.updateColor(color: theme.rootController.tabBar.backgroundColor, transition: .immediate) - self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.rootController.tabBar.badgeBackgroundColor, strokeColor: theme.rootController.tabBar.badgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! for container in self.tabBarNodeContainers { if let attributedText = container.badgeTextNode.attributedText, !attributedText.string.isEmpty { - container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) + container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: theme.rootController.tabBar.badgeTextColor) } } @@ -443,11 +436,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { } } - func sourceNodesForController(at index: Int) -> [ASDisplayNode]? { - let container = self.tabBarNodeContainers[index] - return [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode] - } - func frameForControllerTab(at index: Int) -> CGRect? { let container = self.tabBarNodeContainers[index] return container.imageNode.frame @@ -463,7 +451,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { node.imageNode.removeFromSupernode() } - self.centered = self.theme.tabBarTextColor == .clear + self.centered = self.theme.rootController.tabBar.textColor == .clear var tabBarNodeContainers: [TabBarNodeContainer] = [] for i in 0 ..< self.tabBarItems.count { @@ -484,13 +472,13 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { self?.swipeAction(i, direction) }) if item.item.ringSelection { - node.ringColor = self.theme.tabBarSelectedIconColor + node.ringColor = self.theme.rootController.tabBar.selectedIconColor } else { node.ringColor = nil } if let selectedIndex = self.selectedIndex, selectedIndex == i { - let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) let (image, imageContentWidth): (UIImage, CGFloat) if let _ = item.item.animationName { @@ -502,17 +490,17 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { if !node.isSelected { node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) } - node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false) + node.animationNode.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false) node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0)) } else { - (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.animationNode.isHidden = true node.animationNode.visibility = false } - let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage node.imageNode.image = image node.contextTextImageNode.image = contextTextImage @@ -522,10 +510,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { node.contentWidth = max(contentWidth, imageContentWidth) node.isSelected = true } else { - let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) - let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.textColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.iconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.animationNode.isHidden = true node.animationNode.visibility = false @@ -555,10 +543,10 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] - self.centered = self.theme.tabBarTextColor == .clear + self.centered = self.theme.rootController.tabBar.textColor == .clear if item.item.ringSelection { - node.ringColor = self.theme.tabBarSelectedIconColor + node.ringColor = self.theme.rootController.tabBar.selectedIconColor } else { node.ringColor = nil } @@ -566,7 +554,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { let previousImageSize = node.imageNode.image?.size ?? CGSize() let previousTextImageSize = node.textImageNode.image?.size ?? CGSize() if let selectedIndex = self.selectedIndex, selectedIndex == index { - let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) let (image, imageContentWidth): (UIImage, CGFloat) if let _ = item.item.animationName { (image, imageContentWidth) = (UIImage(), 0.0) @@ -577,21 +565,21 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { if !node.isSelected { node.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: item.item.animationName ?? ""), width: animationSize, height: animationSize, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) } - node.animationNode.setOverlayColor(self.theme.tabBarSelectedIconColor, replace: true, animated: false) + node.animationNode.setOverlayColor(self.theme.rootController.tabBar.selectedIconColor, replace: true, animated: false) node.animationNode.updateLayout(size: CGSize(width: 51.0, height: 51.0)) } else { if item.item.ringSelection { (image, imageContentWidth) = (item.item.selectedImage ?? UIImage(), item.item.selectedImage?.size.width ?? 0.0) } else { - (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + (image, imageContentWidth) = tabBarItemImage(item.item.selectedImage, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.selectedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) } node.animationNode.isHidden = true node.animationNode.visibility = false } - let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.textImageNode.image = textImage node.accessibilityLabel = item.item.title node.accessibilityTraits = [.button, .selected] @@ -610,16 +598,16 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { }) } } else { - let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (textImage, contentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.textColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) let (image, imageContentWidth): (UIImage, CGFloat) if item.item.ringSelection { (image, imageContentWidth) = (item.item.image ?? UIImage(), item.item.image?.size.width ?? 0.0) } else { - (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + (image, imageContentWidth) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.rootController.tabBar.iconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) } - let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedTextColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) - let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarExtractedIconColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) + let (contextTextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: false, centered: self.centered) + let (contextImage, _) = tabBarItemImage(item.item.image, title: item.item.title ?? "", backgroundColor: .clear, tintColor: self.theme.contextMenu.extractedContentTintColor, horizontal: self.horizontal, imageMode: true, centered: self.centered) node.animationNode.stop() node.animationNode.isHidden = true @@ -672,8 +660,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) self.backgroundNode.update(size: size, transition: transition) - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: separatorHeight))) - let horizontal = !leftInset.isZero if self.horizontal != horizontal { self.horizontal = horizontal @@ -759,7 +745,7 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue if let badgeValue = container.badgeValue, !badgeValue.isEmpty { - container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) + container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.rootController.tabBar.badgeTextColor) container.badgeContainerNode.isHidden = false } else { container.badgeContainerNode.isHidden = true @@ -797,7 +783,6 @@ class TabBarNode: ASDisplayNode, ASGestureRecognizerDelegate { return } var closestNode: (Int, CGFloat)? - for i in 0 ..< self.tabBarNodeContainers.count { let node = self.tabBarNodeContainers[i].imageNode if !node.isUserInteractionEnabled { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift index 959bf6cc82..76b7e7ab54 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift @@ -12,7 +12,7 @@ import PresentationDataUtils import UIKitRuntimeUtils import ReplayKit -private let accentColor: UIColor = UIColor(rgb: 0x007aff) +private let accentColor: UIColor = UIColor(rgb: 0x0088ff) protocol PreviewVideoNode: ASDisplayNode { var ready: Signal { get } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift index 3490109501..583671503a 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatFullscreenParticipantItem.swift @@ -26,7 +26,7 @@ private let videoCornerRadius: CGFloat = 23.0 private let avatarSize: CGFloat = 50.0 private let videoSize = CGSize(width: 180.0, height: 180.0) -private let accentColor: UIColor = UIColor(rgb: 0x007aff) +private let accentColor: UIColor = UIColor(rgb: 0x0088ff) private let constructiveColor: UIColor = UIColor(rgb: 0x34c759) private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift index da82ee3fea..81722b4d4f 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatParticipantItem.swift @@ -135,7 +135,7 @@ private let tileSize = CGSize(width: 84.0, height: 84.0) private let backgroundCornerRadius: CGFloat = 14.0 private let avatarSize: CGFloat = 40.0 -private let accentColor: UIColor = UIColor(rgb: 0x007aff) +private let accentColor: UIColor = UIColor(rgb: 0x0088ff) private let constructiveColor: UIColor = UIColor(rgb: 0x34c759) private let destructiveColor: UIColor = UIColor(rgb: 0xff3b30) diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift index e550fa3e71..d66762b760 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatRecordingSetupController.swift @@ -10,7 +10,7 @@ import SolidRoundedButtonNode import PresentationDataUtils import VoiceChatActionButton -private let accentColor: UIColor = UIColor(rgb: 0x007aff) +private let accentColor: UIColor = UIColor(rgb: 0x0088ff) final class VoiceChatRecordingSetupController: ViewController { private var controllerNode: VoiceChatRecordingSetupControllerNode { diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 30f45efffa..97f506b2a3 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -561,7 +561,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati outgoing: PresentationThemePartedColors( bubble: PresentationThemeBubbleColor( withWallpaper: PresentationThemeBubbleColorComponents( - fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)], + fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)], highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil, @@ -577,7 +577,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati reactionActiveMediaPlaceholder: UIColor(rgb: 0x000000, alpha: 0.1) ), withoutWallpaper: PresentationThemeBubbleColorComponents( - fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x007AFF)], + fill: [UIColor(rgb: 0x61BCF9), UIColor(rgb: 0x0088ff)], highlightedFill: UIColor(rgb: 0x61BCF9), stroke: .clear, shadow: nil, diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index 9e77b7e6cd..c7af0c0ed6 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -54,7 +54,7 @@ public func dateFillNeedsBlur(theme: PresentationTheme, wallpaper: TelegramWallp public let defaultServiceBackgroundColor = UIColor(rgb: 0x000000, alpha: 0.2) public let defaultPresentationTheme = makeDefaultDayPresentationTheme(serviceBackgroundColor: defaultServiceBackgroundColor, day: false, preview: false) -public let defaultDayAccentColor = UIColor(rgb: 0x007aff) +public let defaultDayAccentColor = UIColor(rgb: 0x0088ff) public func customizeDefaultDayTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, outgoingAccentColor: UIColor?, backgroundColors: [UInt32], bubbleColors: [UInt32], animateBubbleColors: Bool?, wallpaper forcedWallpaper: TelegramWallpaper? = nil, serviceBackgroundColor: UIColor?) -> PresentationTheme { if (theme.referenceTheme != .day && theme.referenceTheme != .dayClassic) { @@ -436,7 +436,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio separatorColor: UIColor(rgb: 0xb2b2b2), iconColor: UIColor(rgb: 0x959595), selectedIconColor: defaultDayAccentColor, - textColor: UIColor(rgb: 0x959595), + textColor: UIColor(rgb: 0x000000, alpha: 0.8), selectedTextColor: defaultDayAccentColor, badgeBackgroundColor: UIColor(rgb: 0xff3b30), badgeStrokeColor: UIColor(rgb: 0xff3b30), diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift index 1fac92579b..785aa906b8 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesSettings.swift @@ -164,7 +164,7 @@ public struct PresentationResourcesSettings { context.addPath(path.cgPath) context.clip() - context.setFillColor(UIColor(rgb: 0x007aff).cgColor) + context.setFillColor(UIColor(rgb: 0x0088ff).cgColor) context.fill(bounds) if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage { diff --git a/submodules/TelegramUI/Components/BadgeComponent/Sources/BadgeComponent.swift b/submodules/TelegramUI/Components/BadgeComponent/Sources/BadgeComponent.swift index be4b3b3042..15c504cd2c 100644 --- a/submodules/TelegramUI/Components/BadgeComponent/Sources/BadgeComponent.swift +++ b/submodules/TelegramUI/Components/BadgeComponent/Sources/BadgeComponent.swift @@ -5,16 +5,21 @@ import RasterizedCompositionComponent import ComponentFlow public final class BadgeComponent: Component { + public enum CornerRadius: Equatable { + case automatic + case custom(CGFloat) + } + public let text: String public let font: UIFont - public let cornerRadius: CGFloat + public let cornerRadius: CornerRadius public let insets: UIEdgeInsets public let outerInsets: UIEdgeInsets public init( text: String, font: UIFont, - cornerRadius: CGFloat, + cornerRadius: CornerRadius, insets: UIEdgeInsets, outerInsets: UIEdgeInsets ) { @@ -144,12 +149,6 @@ public final class BadgeComponent: Component { } } - if component.cornerRadius != previousComponent?.cornerRadius { - self.backgroundLayer.image = generateStretchableFilledCircleImage(diameter: component.cornerRadius * 2.0, color: .white) - - self.backgroundInsetLayer.image = generateStretchableFilledCircleImage(diameter: component.cornerRadius * 2.0, color: .black) - } - let textSize = self.textLayout?.size ?? CGSize(width: 1.0, height: 1.0) let size = CGSize(width: textSize.width + component.insets.left + component.insets.right, height: textSize.height + component.insets.top + component.insets.bottom) @@ -170,6 +169,19 @@ public final class BadgeComponent: Component { transition.setPosition(layer: self.textContentsLayer, position: textFrame.origin) self.textContentsLayer.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + if component.cornerRadius != previousComponent?.cornerRadius { + let cornerRadius: CGFloat + switch component.cornerRadius { + case let .custom(value): + cornerRadius = value + case .automatic: + cornerRadius = floor(min(size.width, size.height) * 0.5) + } + + self.backgroundLayer.image = generateStretchableFilledCircleImage(diameter: cornerRadius * 2.0, color: .white) + self.backgroundInsetLayer.image = generateStretchableFilledCircleImage(diameter: cornerRadius * 2.0, color: .black) + } + return size } } diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/PlaceholderComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/PlaceholderComponent.swift index 1fb49b88dc..9d566dd9d9 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/PlaceholderComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/PlaceholderComponent.swift @@ -113,9 +113,9 @@ final class PlaceholderComponent: Component { component: AnyComponent( ButtonComponent( background: ButtonComponent.Background( - color: UIColor(rgb: 0x007aff), + color: UIColor(rgb: 0x0088ff), foreground: .white, - pressedColor: UIColor(rgb: 0x007aff, alpha: 0.55) + pressedColor: UIColor(rgb: 0x0088ff, alpha: 0.55) ), content: AnyComponentWithIdentity( id: buttonTitle, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 79bc0e9406..8a97b497d5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -718,7 +718,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if self.visibility != oldValue { self.visibilityStatus = self.visibility != .none - self.updateVisibility() + self.updateVisibility(isScroll: true) } } } @@ -743,6 +743,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: ChatMessageHeaderSpec) private var currentInputParams: Params? private var currentApplyParams: ListViewItemApply? + private var contentLayoutInsets = UIEdgeInsets() required public init(rotated: Bool) { self.mainContextSourceNode = ContextExtractedContentContainingNode() @@ -3338,7 +3339,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI } } - let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: layoutInsets) + layoutSize.height += layoutInsets.top + layoutInsets.bottom + + let layout = ListViewItemNodeLayout(contentSize: layoutSize, insets: UIEdgeInsets()) let graphics = PresentationResourcesChat.principalGraphics(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper, bubbleCorners: item.presentationData.chatBubbleCorners) @@ -3364,6 +3367,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI params: params, applyInfo: applyInfo, layout: layout, + layoutInsets: layoutInsets, item: item, forwardSource: forwardSource, forwardAuthorSignature: forwardAuthorSignature, @@ -3377,12 +3381,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI graphics: graphics, presentationContext: item.controllerInteraction.presentationContext, bubbleContentWidth: bubbleContentWidth, - backgroundFrame: backgroundFrame, + backgroundFrame: backgroundFrame.offsetBy(dx: 0.0, dy: layoutInsets.top), deliveryFailedInset: deliveryFailedInset, nameNodeSizeApply: nameNodeSizeApply, viaWidth: viaWidth, - contentOrigin: contentOrigin, - nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight, + contentOrigin: contentOrigin.offsetBy(dx: 0.0, dy: layoutInsets.top), + nameNodeOriginY: layoutInsets.top + nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight, hasTitleAvatar: hasTitleAvatar, hasTitleTopicNavigation: hasTitleTopicNavigation, authorNameColor: authorNameColor, @@ -3392,25 +3396,27 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI boostNodeSizeApply: boostNodeSizeApply, contentUpperRightCorner: contentUpperRightCorner, threadInfoSizeApply: threadInfoSizeApply, - threadInfoOriginY: threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight, + threadInfoOriginY: layoutInsets.top + threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight, forwardInfoSizeApply: forwardInfoSizeApply, - forwardInfoOriginY: forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight, + forwardInfoOriginY: layoutInsets.top + forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight, replyInfoSizeApply: replyInfoSizeApply, - replyInfoOriginY: replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight, + replyInfoOriginY: layoutInsets.top + replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight, removedContentNodeIndices: removedContentNodeIndices, updatedContentNodeOrder: updatedContentNodeOrder, addedContentNodes: addedContentNodes, contentNodeMessagesAndClasses: contentNodeMessagesAndClasses, contentNodeFramesPropertiesAndApply: contentNodeFramesPropertiesAndApply, - contentContainerNodeFrames: contentContainerNodeFrames, - mosaicStatusOrigin: mosaicStatusOrigin, + contentContainerNodeFrames: contentContainerNodeFrames.map { containerGroupId, containerFrame, currentItemSelection, currentContainerGroupOverlap in + return (containerGroupId, containerFrame.offsetBy(dx: 0.0, dy: layoutInsets.top), currentItemSelection, currentContainerGroupOverlap) + }, + mosaicStatusOrigin: mosaicStatusOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top), mosaicStatusSizeAndApply: mosaicStatusSizeAndApply, - unlockButtonPosition: unlockButtonPosition, + unlockButtonPosition: unlockButtonPosition?.offsetBy(dx: 0.0, dy: layoutInsets.top), unlockButtonSizeAndApply: unlockButtonSizeApply, - mediaInfoOrigin: mediaInfoOrigin, + mediaInfoOrigin: mediaInfoOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top), mediaInfoSizeAndApply: mediaInfoSizeApply, needsShareButton: needsShareButton, - shareButtonOffset: shareButtonOffset, + shareButtonOffset: shareButtonOffset?.offsetBy(dx: 0.0, dy: layoutInsets.top), avatarOffset: avatarOffset, hidesHeaders: hidesHeaders, disablesComments: disablesComments, @@ -3428,6 +3434,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI params: ListViewItemLayoutParams, applyInfo: ListViewItemApply, layout: ListViewItemNodeLayout, + layoutInsets: UIEdgeInsets, item: ChatMessageItem, forwardSource: Peer?, forwardAuthorSignature: String?, @@ -3488,6 +3495,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.currentInputParams = inputParams strongSelf.currentApplyParams = applyInfo + strongSelf.contentLayoutInsets = layoutInsets if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal { strongSelf.wasPending = true @@ -3570,7 +3578,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.suggestedPostInfoNode = suggestedPostInfoNode strongSelf.addSubnode(suggestedPostInfoNode) } - let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize) + let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: layoutInsets.top + 4.0), size: suggestedPostInfoSize) suggestedPostInfoNode.frame = suggestedPostInfoFrame } else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode { strongSelf.suggestedPostInfoNode = nil @@ -4999,7 +5007,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.updateSearchTextHighlightState() - strongSelf.updateVisibility() + strongSelf.updateVisibility(isScroll: false) if let (_, f) = strongSelf.awaitingAppliedReaction { strongSelf.awaitingAppliedReaction = nil @@ -6332,10 +6340,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let swipeToReplyNode = self.swipeToReplyNode { if translation.x < 0.0 { swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)) - swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentSize.height / 2.0) + swipeToReplyNode.position = CGPoint(x: bounds.size.width + offset + 33.0 * 0.5, y: self.contentLayoutInsets.top + (self.contentSize.height - self.contentLayoutInsets.top - self.contentLayoutInsets.bottom) / 2.0) } else { swipeToReplyNode.bounds = CGRect(origin: .zero, size: CGSize(width: 33.0, height: 33.0)) - swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentSize.height / 2.0) + swipeToReplyNode.position = CGPoint(x: leftOffset - 33.0 * 0.5, y: self.contentLayoutInsets.top + (self.contentSize.height - self.contentLayoutInsets.top - self.contentLayoutInsets.bottom) / 2.0) } if let (rect, containerSize) = self.absoluteRect { @@ -6561,7 +6569,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI contentNode.unreadMessageRangeUpdated() } - self.updateVisibility() + self.updateVisibility(isScroll: false) } public func animateQuizInvalidOptionSelected() { @@ -6761,10 +6769,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI override public func updateStickerSettings(forceStopAnimations: Bool) { self.forceStopAnimations = forceStopAnimations - self.updateVisibility() + self.updateVisibility(isScroll: false) } - private func updateVisibility() { + private func updateVisibility(isScroll: Bool) { guard let item = self.item else { return } @@ -6841,6 +6849,15 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI self.playMessageEffect(force: false) } } + + if item.message.adAttribute != nil { + let transition: ContainedViewLayoutTransition = isScroll ? .animated(duration: 0.4, curve: .spring) : .immediate + if case let .visible(_, rect) = self.visibility, rect.height >= 1.0 { + transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 0.0)) + } else { + transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: 200.0)) + } + } } override public func messageEffectTargetView() -> UIView? { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift index 326548d1ef..e67e93a847 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode/Sources/ChatRecordingPreviewInputPanelNode.swift @@ -118,7 +118,7 @@ final class PlayButtonNode: ASDisplayNode { let backgroundFrame = buttonSize.centered(in: CGRect(origin: .zero, size: size)) transition.updateFrame(view: self.backgroundView, frame: backgroundFrame) - self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.4), transition: ComponentTransition(transition)) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.4)), transition: ComponentTransition(transition)) self.playPauseIconNode.frame = CGRect(origin: CGPoint(x: 3.0, y: 1.0 - UIScreenPixel), size: CGSize(width: 21.0, height: 21.0)) diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift index b94b71e7c6..d62b84c0c2 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift @@ -99,7 +99,7 @@ public final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - innerSize.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - innerSize.height / 2.0)), size: innerSize) self.backgroundView.frame = backgroundFrame - self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate) if let iconImage = self.iconNode.image { let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift index 7e87297a08..75380ef626 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift @@ -334,13 +334,13 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag } transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition)) + self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size)) self.micButton.layoutItems() transition.updateFrame(view: self.sendButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: innerSize)) - self.sendButtonBackgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition)) + self.sendButtonBackgroundView.update(size: innerSize, cornerRadius: innerSize.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.sendButton.layer, frame: CGRect(origin: CGPoint(), size: innerSize)) transition.updateFrame(node: self.sendContainerNode, frame: CGRect(origin: CGPoint(), size: innerSize)) @@ -349,7 +349,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition)) + self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) if let image = self.expandMediaInputButtonIcon.image { let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size) transition.updatePosition(layer: self.expandMediaInputButtonIcon.layer, position: expandIconFrame.center) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/AccessoryItemIconButton.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/AccessoryItemIconButton.swift index ce7409da94..84218bb179 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/AccessoryItemIconButton.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/AccessoryItemIconButton.swift @@ -66,6 +66,7 @@ final class AccessoryItemIconButton: HighlightTrackingButton, GlassBackgroundVie if let text { if self.textView == nil { let textView = ImmediateTextView() + textView.isUserInteractionEnabled = false self.textView = textView self.addSubview(textView) } diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 970825f001..42145a2428 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -222,6 +222,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public let textInputNodeClippingContainer: ASDisplayNode public let textInputSeparator: GlassBackgroundView.ContentColorView public var textInputNode: ChatInputTextNode? + private var textInputNodeLayout: (frame: CGRect, insets: UIEdgeInsets)? public var dustNode: InvisibleInkDustNode? public var customEmojiContainerView: CustomEmojiContainerView? @@ -925,11 +926,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg textInputNode.textContainerInset = calculateTextFieldRealInsets(presentationInterfaceState: presentationInterfaceState, accessoryButtonsWidth: accessoryButtonsWidth) } - if !self.textInputContainer.bounds.size.width.isZero { - let textInputFrame = self.textInputContainer.frame - - textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom)) - textInputNode.updateLayout(size: textInputNode.bounds.size) + if let textInputNodeLayout = self.textInputNodeLayout { + textInputNode.textContainerInset = textInputNodeLayout.insets + textInputNode.frame = textInputNodeLayout.frame + textInputNode.updateLayout(size: textInputNodeLayout.frame.size) textInputNode.view.layoutIfNeeded() self.updateSpoiler() } @@ -1865,7 +1865,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let menuButtonFrame = CGRect(x: leftInset + 8.0, y: menuButtonOriginY, width: menuButtonExpanded ? menuButtonWidth : menuCollapsedButtonWidth, height: menuButtonHeight) transition.updateFrameAsPositionAndBounds(node: self.menuButton, frame: menuButtonFrame) transition.updateFrame(view: self.menuButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: menuButtonFrame.size)) - self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition)) + self.menuButtonBackgroundView.update(size: menuButtonFrame.size, cornerRadius: menuButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition)) transition.updateFrame(node: self.menuButtonClippingNode, frame: CGRect(origin: CGPoint(x: 19.0, y: 0.0), size: CGSize(width: menuButtonWidth - 19.0, height: menuButtonFrame.height))) var menuButtonTitleTransition = transition if buttonTitleUpdated { @@ -2334,7 +2334,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame) transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size)) - self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition)) + self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) if let removedAccessoryPanelView { if let removedAccessoryPanelView = removedAccessoryPanelView as? ChatInputAccessoryPanelView { @@ -2352,17 +2352,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg }) } + let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top + textFieldTopContentOffset), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputHeight - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) + let textInputNodeClippingContainerFrame = CGRect(origin: CGPoint(x: textFieldFrame.minX - self.textInputViewInternalInsets.left, y: textFieldFrame.minY - self.textInputViewInternalInsets.top), size: CGSize(width: textFieldFrame.width + self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right, height: textFieldFrame.height + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom)) + let shouldUpdateLayout = textInputNodeClippingContainerFrame.size != self.textInputNodeClippingContainer.frame.size + transition.updateFrame(node: self.textInputNodeClippingContainer, frame: textInputNodeClippingContainerFrame) + + transition.updateFrame(view: self.textInputSeparator, frame: CGRect(origin: CGPoint(x: 15.0, y: textFieldTopContentOffset - UIScreenPixel), size: CGSize(width: textFieldFrame.width, height: UIScreenPixel))) + self.textInputSeparator.backgroundColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor + transition.updateAlpha(layer: self.textInputSeparator.layer, alpha: isTextFieldOverflow ? 1.0 : 0.0) + + let actualTextFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: textFieldFrame.size) + self.textInputNodeLayout = (actualTextFieldFrame, textInputViewRealInsets) + if let textInputNode = self.textInputNode { textInputNode.textContainerInset = textInputViewRealInsets - let textFieldFrame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top + textFieldTopContentOffset), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right), height: textInputHeight - self.textInputViewInternalInsets.top - textInputViewInternalInsets.bottom)) - let shouldUpdateLayout = textFieldFrame.size != textInputNode.frame.size - transition.updateFrame(node: self.textInputNodeClippingContainer, frame: CGRect(origin: CGPoint(x: textFieldFrame.minX - self.textInputViewInternalInsets.left, y: textFieldFrame.minY - self.textInputViewInternalInsets.top), size: CGSize(width: textFieldFrame.width + self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right, height: textFieldFrame.height + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom))) - - transition.updateFrame(view: self.textInputSeparator, frame: CGRect(origin: CGPoint(x: 15.0, y: textFieldTopContentOffset - UIScreenPixel), size: CGSize(width: textFieldFrame.width, height: UIScreenPixel))) - self.textInputSeparator.backgroundColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor - transition.updateAlpha(layer: self.textInputSeparator.layer, alpha: isTextFieldOverflow ? 1.0 : 0.0) - - textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: textFieldFrame.size) + textInputNode.frame = actualTextFieldFrame textInputNode.updateLayout(size: textFieldFrame.size) self.updateInputField(textInputFrame: textFieldFrame, transition: ComponentTransition(transition)) if shouldUpdateLayout { @@ -2463,15 +2467,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let textPlaceholderSize: CGSize let textPlaceholderMaxWidth: CGFloat = max(1.0, nextButtonTopRight.x - 12.0) + let placeholderColor: UIColor = interfaceState.theme.chat.inputPanel.inputPlaceholderColor + if #available(iOS 26.0, *) { + //placeholderColor = placeholderColor.withProminence(.tertiary) + } + //self.textPlaceholderNode.view.tintColor = .red + if (updatedPlaceholder != nil && self.currentPlaceholder != updatedPlaceholder) || themeUpdated { let currentPlaceholder = updatedPlaceholder ?? self.currentPlaceholder ?? "" self.currentPlaceholder = currentPlaceholder let baseFontSize = max(minInputFontSize, interfaceState.fontSize.baseDisplaySize) - let attributedPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: interfaceState.theme.chat.inputPanel.inputPlaceholderColor) + let attributedPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: placeholderColor) if placeholderHasStar, let range = attributedPlaceholder.string.range(of: "#") { attributedPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: attributedPlaceholder.string)) - attributedPlaceholder.addAttribute(.foregroundColor, value: interfaceState.theme.chat.inputPanel.inputPlaceholderColor, range: NSRange(range, in: attributedPlaceholder.string)) + attributedPlaceholder.addAttribute(.foregroundColor, value: placeholderColor, range: NSRange(range, in: attributedPlaceholder.string)) attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string)) } @@ -2615,7 +2625,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0)) self.attachmentButtonBackground.frame = CGRect(origin: CGPoint(), size: attachmentButtonFrame.size) - self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? interfaceState.theme.chat.inputPanel.actionControlFillColor : interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65), transition: ComponentTransition(transition)) + self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) : .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame) transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame) diff --git a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputAudioRecordingOverlayButton.swift b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputAudioRecordingOverlayButton.swift index 9842645bc5..4e137ed031 100644 --- a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputAudioRecordingOverlayButton.swift +++ b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputAudioRecordingOverlayButton.swift @@ -8,8 +8,8 @@ import ObjCRuntimeUtils private let innerCircleDiameter: CGFloat = 110.0 private let outerCircleDiameter = innerCircleDiameter + 50.0 private let outerCircleMinScale = innerCircleDiameter / outerCircleDiameter -private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x007aff)) -private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x007aff, alpha: 0.2)) +private let innerCircleImage = generateFilledCircleImage(diameter: innerCircleDiameter, color: UIColor(rgb: 0x0088ff)) +private let outerCircleImage = generateFilledCircleImage(diameter: outerCircleDiameter, color: UIColor(rgb: 0x0088ff, alpha: 0.2)) private let micIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/IconMicrophone"), color: .white)! private final class ChatTextInputAudioRecordingOverlayDisplayLinkTarget: NSObject { diff --git a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift index 0ca1dff8b9..e2d2c0b811 100644 --- a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift @@ -634,7 +634,7 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi let view = GlassBackgroundView() view.frame = CGRect(origin: CGPoint(), size: size) - view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: .immediate) + view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: .immediate) self.view = view super.init(frame: CGRect(origin: CGPoint(), size: size)) @@ -652,13 +652,13 @@ private class WrapperBlurrredBackgroundView: UIView, TGModernConversationInputMi } set { super.frame = newValue self.view.frame = CGRect(origin: CGPoint(), size: newValue.size) - self.view.update(size: newValue.size, cornerRadius: min(newValue.width, newValue.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: .immediate) + self.view.update(size: newValue.size, cornerRadius: min(newValue.width, newValue.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: .immediate) } } func update(_ size: CGSize) { let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) transition.updateFrame(view: self.view, frame: CGRect(origin: CGPoint(), size: size)) - self.view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: self.glassTintColor, transition: ComponentTransition(transition)) + self.view.update(size: size, cornerRadius: min(size.width, size.height) * 0.5, isDark: self.isDark, tintColor: .init(kind: .panel, color: self.glassTintColor), transition: ComponentTransition(transition)) } } diff --git a/submodules/TelegramUI/Components/EdgeEffect/BUILD b/submodules/TelegramUI/Components/EdgeEffect/BUILD new file mode 100644 index 0000000000..62ff9343ac --- /dev/null +++ b/submodules/TelegramUI/Components/EdgeEffect/BUILD @@ -0,0 +1,19 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "EdgeEffect", + module_name = "EdgeEffect", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift new file mode 100644 index 0000000000..2462c77960 --- /dev/null +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -0,0 +1,69 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +public final class EdgeEffectView: UIView { + public enum Edge { + case top + case bottom + } + + private let contentView: UIView + private let contentMaskView: UIImageView + + public override init(frame: CGRect) { + self.contentView = UIView() + self.contentMaskView = UIImageView() + self.contentView.mask = self.contentMaskView + + super.init(frame: frame) + + self.addSubview(self.contentView) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func update(content: UIColor, 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)) + transition.setFrame(view: self.contentMaskView, frame: CGRect(origin: CGPoint(), size: rect.size)) + + if self.contentMaskView.image?.size.height != edgeSize { + 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: 1.0, alpha: 1.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: 1.0, alpha: 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) + } + } + + if edgeSize > 0.0 { + self.contentMaskView.image = generateGradientImage( + size: CGSize(width: 8.0, height: edgeSize), + colors: colors, + locations: locations + )?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edgeSize)) + } else { + self.contentMaskView.image = nil + } + } + } +} diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index e86a2fef1c..8bcb4abbb8 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -1050,7 +1050,7 @@ private let tonImage: UIImage? = { generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in context.clear(CGRect(origin: .zero, size: size)) - if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: UIColor(rgb: 0x007aff)), let cgImage = image.cgImage { + if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: UIColor(rgb: 0x0088ff)), let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false) } })?.withRenderingMode(.alwaysTemplate) diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index e0437a48fe..7ac15dbe9b 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -313,12 +313,27 @@ public final class GlassBackgroundView: UIView { } } + public struct TintColor: Equatable { + public enum Kind { + case panel + case custom + } + + public let kind: Kind + public let color: UIColor + + public init(kind: Kind, color: UIColor) { + self.kind = kind + self.color = color + } + } + private struct Params: Equatable { let cornerRadius: CGFloat let isDark: Bool - let tintColor: UIColor + let tintColor: TintColor - init(cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor) { + init(cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor) { self.cornerRadius = cornerRadius self.isDark = isDark self.tintColor = tintColor @@ -329,12 +344,18 @@ public final class GlassBackgroundView: UIView { private let nativeView: UIVisualEffectView? private let foregroundView: UIImageView? + private let shadowView: UIImageView? + private let shadowMaskView: UIImageView? public let maskContentView: UIView private let contentContainer: ContentContainer public var contentView: UIView { - return self.contentContainer + if let nativeView = self.nativeView { + return nativeView.contentView + } else { + return self.contentContainer + } } private var params: Params? @@ -350,10 +371,14 @@ public final class GlassBackgroundView: UIView { nativeView.traitOverrides.userInterfaceStyle = .light //self.foregroundView = UIImageView() self.foregroundView = nil + self.shadowView = nil + self.shadowMaskView = nil } else { self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0) self.nativeView = nil self.foregroundView = UIImageView() + self.shadowView = UIImageView() + self.shadowMaskView = UIImageView() } self.maskContentView = UIView() @@ -366,6 +391,12 @@ public final class GlassBackgroundView: UIView { super.init(frame: frame) + if let shadowView = self.shadowView { + self.addSubview(shadowView) + if let shadowMaskView = self.shadowMaskView { + shadowView.mask = shadowMaskView + } + } if let nativeView = self.nativeView { self.addSubview(nativeView) } @@ -383,7 +414,7 @@ public final class GlassBackgroundView: UIView { fatalError("init(coder:) has not been implemented") } - public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: UIColor, transition: ComponentTransition) { + public func update(size: CGSize, cornerRadius: CGFloat, isDark: Bool, tintColor: TintColor, transition: ComponentTransition) { if let nativeView = self.nativeView { let previousFrame = nativeView.frame @@ -399,7 +430,7 @@ public final class GlassBackgroundView: UIView { } } if let backgroundNode = self.backgroundNode { - backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.alpha != 1.0, transition: transition.containedViewLayoutTransition) + backgroundNode.updateColor(color: .clear, forceKeepBlur: tintColor.color.alpha != 1.0, transition: transition.containedViewLayoutTransition) backgroundNode.update(size: size, cornerRadius: cornerRadius, transition: transition.containedViewLayoutTransition) transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) } @@ -408,13 +439,38 @@ public final class GlassBackgroundView: UIView { if self.params != params { self.params = params + if let shadowView = self.shadowView { + shadowView.layer.shadowRadius = 10.0 + shadowView.layer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor + shadowView.layer.shadowOpacity = 0.08 + shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0) + + if let shadowMaskView = self.shadowMaskView { + let shadowInset: CGFloat = 32.0 + let shadowInnerInset: CGFloat = 0.5 + shadowMaskView.image = generateImage(CGSize(width: shadowInset * 2.0 + cornerRadius * 2.0, height: shadowInset * 2.0 + cornerRadius * 2.0), rotatedContext: { size, context in + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + 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))) + })?.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius), topCapHeight: Int(shadowInset + cornerRadius)) + } + } + if let foregroundView = self.foregroundView { - foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor) + foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color) } else { if let nativeView { if #available(iOS 26.0, *) { let glassEffect = UIGlassEffect(style: .regular) - //glassEffect.tintColor = tintColor.withMultipliedAlpha(0.1) + switch tintColor.kind { + case .panel: + glassEffect.tintColor = nil + case .custom: + glassEffect.tintColor = tintColor.color + } glassEffect.isInteractive = false nativeView.effect = glassEffect @@ -424,9 +480,19 @@ public final class GlassBackgroundView: UIView { } transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size)) - if let foregroundView { + if let foregroundView = self.foregroundView { transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size)) } + if let shadowView = self.shadowView { + if shadowView.bounds.size != size { + shadowView.layer.shadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath + } + transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size)) + + if let shadowMaskView = self.shadowMaskView { + transition.setFrame(view: shadowMaskView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -32.0, dy: -32.0)) + } + } transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size)) } } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index de9497d768..608ae85e60 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -888,7 +888,7 @@ final class MediaEditorScreenComponent: Component { transition: transition, component: AnyComponent(PlainButtonComponent( content: AnyComponent(DoneButtonContentComponent( - backgroundColor: UIColor(rgb: 0x007aff), + backgroundColor: UIColor(rgb: 0x0088ff), icon: doneButtonIcon, title: doneButtonTitle)), effectAlignment: .center, diff --git a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift index 1c843521e7..95ac4f04d2 100644 --- a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift +++ b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift @@ -238,7 +238,7 @@ public final class MediaScrubberComponent: Component { self.coverDotWrapper.isUserInteractionEnabled = false self.coverDotWrapper.isHidden = true - self.coverDotView = UIImageView(image: generateFilledCircleImage(diameter: 7.0, color: UIColor(rgb: 0x007aff))) + self.coverDotView = UIImageView(image: generateFilledCircleImage(diameter: 7.0, color: UIColor(rgb: 0x0088ff))) self.coverImageView = UIImageView() self.coverImageView.clipsToBounds = true diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift index ed641defc1..e51ab6cfea 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/HashtagListItemComponent.swift @@ -442,7 +442,7 @@ public final class HashtagListItemComponent: Component { } if themeUpdated { - let accentColor = UIColor(rgb: 0x007aff) + let accentColor = UIColor(rgb: 0x0088ff) self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor self.iconBackgroundLayer.backgroundColor = accentColor.cgColor self.iconLayer.layerTintColor = UIColor.white.cgColor diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 9e56ec5e89..4fdd989546 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -165,6 +165,7 @@ swift_library( "//submodules/TelegramUI/Components/BottomButtonPanelComponent", "//submodules/TelegramUI/Components/MarqueeComponent", "//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist", + "//submodules/TelegramUI/Components/EdgeEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 4e7c74798f..32a96f27e8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -113,6 +113,7 @@ import UrlHandling import VerifyAlertController import GiftViewScreen import PeerMessagesMediaPlaylist +import EdgeEffect public enum PeerInfoAvatarEditingMode { case generic @@ -2927,6 +2928,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro fileprivate let cachedDataPromise = Promise() let scrollNode: ASScrollNode + private let edgeEffectView: EdgeEffectView let headerNode: PeerInfoHeaderNode private var regularSections: [AnyHashable: PeerInfoScreenItemSectionContainerNode] = [:] @@ -3063,6 +3065,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.scrollNode.view.delaysContentTouches = false self.scrollNode.canCancelAllTouchesInViews = true + self.edgeEffectView = EdgeEffectView() + var forumTopicThreadId: Int64? if case let .replyThread(message) = chatLocation { forumTopicThreadId = message.threadId @@ -3930,6 +3934,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.addSubnode(self.scrollNode) self.scrollNode.addSubnode(self.paneContainerNode) + self.view.addSubview(self.edgeEffectView) + self.addSubnode(self.headerNode) self.scrollNode.view.isScrollEnabled = !self.isMediaOnly @@ -12296,6 +12302,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + if self.isSettings { + 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)) + } + let sectionSpacing: CGFloat = 24.0 var contentHeight: CGFloat = 0.0 diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift index 89eecec894..f4c58a78c6 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift @@ -13,7 +13,7 @@ import MediaResources import WallpaperGalleryScreen import GenerateThemeName -private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e] +private let randomBackgroundColors: [Int32] = [0x0088ff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e] public extension TelegramThemeSettings { convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, outgoingAccentColor: UIColor?, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) { diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 438a20a320..b97533ceb3 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1521,7 +1521,7 @@ final class ShareWithPeersScreenComponent: Component { )), maximumNumberOfLines: 0, lineSpacing: 0.1, - highlightColor: UIColor(rgb: 0x007aff, alpha: 0.2), + highlightColor: UIColor(rgb: 0x0088ff, alpha: 0.2), highlightAction: { attributes in if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { return NSAttributedString.Key(rawValue: "URL") diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index fcc7febb34..e81a49e8d7 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -211,7 +211,7 @@ final class DataUsageScreenComponent: Component { case .photos: return UIColor(rgb: 0x5AC8FA) case .videos: - return UIColor(rgb: 0x007AFF) + return UIColor(rgb: 0x0088ff) case .files: return UIColor(rgb: 0x34C759) case .music: diff --git a/submodules/TelegramUI/Components/TabBarComponent/BUILD b/submodules/TelegramUI/Components/TabBarComponent/BUILD new file mode 100644 index 0000000000..2dc21d7382 --- /dev/null +++ b/submodules/TelegramUI/Components/TabBarComponent/BUILD @@ -0,0 +1,27 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "TabBarComponent", + module_name = "TabBarComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/MultilineTextComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/TextBadgeComponent", + "//submodules/UIKitRuntimeUtils", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift new file mode 100644 index 0000000000..e75ae06981 --- /dev/null +++ b/submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift @@ -0,0 +1,623 @@ +import Foundation +import UIKit +import Display +import TelegramPresentationData +import ComponentFlow +import ComponentDisplayAdapters +import GlassBackgroundComponent +import MultilineTextComponent +import LottieComponent +import UIKitRuntimeUtils +import BundleIconComponent +import TextBadgeComponent + +public final class TabBarComponent: Component { + public final class Item: Equatable { + public let item: UITabBarItem + public let action: (Bool) -> Void + + fileprivate var id: AnyHashable { + return AnyHashable(ObjectIdentifier(self.item)) + } + + public init(item: UITabBarItem, action: @escaping (Bool) -> Void) { + self.item = item + self.action = action + } + + public static func ==(lhs: Item, rhs: Item) -> Bool { + if lhs === rhs { + return true + } + if lhs.item !== rhs.item { + return false + } + return true + } + } + + public let theme: PresentationTheme + public let items: [Item] + public let selectedId: AnyHashable? + + public init( + theme: PresentationTheme, + items: [Item], + selectedId: AnyHashable? + ) { + self.theme = theme + self.items = items + self.selectedId = selectedId + } + + public static func ==(lhs: TabBarComponent, rhs: TabBarComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.items != rhs.items { + return false + } + if lhs.selectedId != rhs.selectedId { + return false + } + return true + } + + public final class View: UIView, UITabBarDelegate, UIGestureRecognizerDelegate { + private let backgroundView: GlassBackgroundView + private let selectionView: GlassBackgroundView.ContentImageView + private let nativeTabBar: UITabBar? + + private var itemViews: [AnyHashable: ComponentView] = [:] + private var selectedItemViews: [AnyHashable: ComponentView] = [:] + + private var component: TabBarComponent? + private weak var state: EmptyComponentState? + + public override init(frame: CGRect) { + self.backgroundView = GlassBackgroundView(frame: CGRect()) + self.selectionView = GlassBackgroundView.ContentImageView() + + if #available(iOS 26.0, *) { + self.nativeTabBar = UITabBar() + } else { + self.nativeTabBar = nil + } + + super.init(frame: frame) + + if let nativeTabBar = self.nativeTabBar { + self.addSubview(nativeTabBar) + nativeTabBar.delegate = self + let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.onLongPressGesture(_:))) + longPressGesture.delegate = self + self.addGestureRecognizer(longPressGesture) + } else { + self.addSubview(self.backgroundView) + self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:)))) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { + guard let component = self.component else { + return + } + if let index = tabBar.items?.firstIndex(where: { $0 === item }) { + if index < component.items.count { + component.items[index].action(false) + } + } + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + @objc private func onLongPressGesture(_ recognizer: UILongPressGestureRecognizer) { + if case .began = recognizer.state { + if let nativeTabBar = self.nativeTabBar { + func cancelGestures(view: UIView) { + for recognizer in view.gestureRecognizers ?? [] { + if NSStringFromClass(type(of: recognizer)).contains("sSelectionGestureRecognizer") { + recognizer.state = .cancelled + } + } + for subview in view.subviews { + cancelGestures(view: subview) + } + } + + cancelGestures(view: nativeTabBar) + } + } + } + + @objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + if case .ended = recognizer.state { + let point = recognizer.location(in: self) + var closestItemView: (AnyHashable, CGFloat)? + for (id, itemView) in self.itemViews { + guard let itemView = itemView.view else { + continue + } + let distance = abs(point.x - itemView.center.x) + if let previousClosestItemView = closestItemView { + if previousClosestItemView.1 > distance { + closestItemView = (id, distance) + } + } else { + closestItemView = (id, distance) + } + } + + if let (id, _) = closestItemView { + guard let item = component.items.first(where: { $0.id == id }) else { + return + } + item.action(false) + /*if previousSelectedIndex != closestNode.0 { + if let selectedIndex = self.selectedIndex, let _ = self.tabBarItems[selectedIndex].item.animationName { + container.imageNode.animationNode.play(firstFrame: false, fromIndex: nil) + } + }*/ + } + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + func update(component: TabBarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let innerInset: CGFloat = 3.0 + + let previousComponent = self.component + self.component = component + self.state = state + + if let nativeTabBar = self.nativeTabBar { + if nativeTabBar.items?.count != component.items.count { + nativeTabBar.items = (0 ..< component.items.count).map { i in + return UITabBarItem(title: " ", image: nil, tag: i) + } + for (_, itemView) in self.itemViews { + itemView.view?.removeFromSuperview() + } + for (_, selectedItemView) in self.selectedItemViews { + selectedItemView.view?.removeFromSuperview() + } + if let index = component.items.firstIndex(where: { $0.id == component.selectedId }) { + nativeTabBar.selectedItem = nativeTabBar.items?[index] + } + } + + let nativeSize = nativeTabBar.sizeThatFits(availableSize) + nativeTabBar.bounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: nativeSize.height)) + nativeTabBar.layoutSubviews() + } + + var nativeItemContainers: [Int: UIView] = [:] + var nativeSelectedItemContainers: [Int: UIView] = [:] + if let nativeTabBar = self.nativeTabBar { + for subview in nativeTabBar.subviews { + if NSStringFromClass(type(of: subview)).contains("PlatterView") { + for subview in subview.subviews { + if NSStringFromClass(type(of: subview)).hasSuffix("SelectedContentView") { + for subview in subview.subviews { + if NSStringFromClass(type(of: subview)).hasSuffix("TabButton") { + nativeSelectedItemContainers[nativeSelectedItemContainers.count] = subview + } + } + } else if NSStringFromClass(type(of: subview)).hasSuffix("ContentView") { + for subview in subview.subviews { + if NSStringFromClass(type(of: subview)).hasSuffix("TabButton") { + nativeItemContainers[nativeItemContainers.count] = subview + } + } + } + } + } + } + } + + var itemSize = CGSize(width: floor((availableSize.width - innerInset * 2.0) / CGFloat(component.items.count)), height: 56.0) + itemSize.width = min(94.0, itemSize.width) + + if let itemContainer = nativeItemContainers[0] { + itemSize = itemContainer.bounds.size + } + + let contentHeight = itemSize.height + innerInset * 2.0 + var contentWidth: CGFloat = innerInset + + if self.selectionView.image?.size.height != itemSize.height { + self.selectionView.image = generateStretchableFilledCircleImage(radius: itemSize.height * 0.5, color: .white)?.withRenderingMode(.alwaysTemplate) + } + self.selectionView.tintColor = component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05) + + var validIds: [AnyHashable] = [] + var selectionFrame: CGRect? + for index in 0 ..< component.items.count { + let item = component.items[index] + validIds.append(item.id) + + let itemView: ComponentView + var itemTransition = transition + + if let current = self.itemViews[item.id] { + itemView = current + } else { + itemTransition = itemTransition.withAnimation(.none) + itemView = ComponentView() + self.itemViews[item.id] = itemView + } + + let selectedItemView: ComponentView + if let current = self.selectedItemViews[item.id] { + selectedItemView = current + } else { + selectedItemView = ComponentView() + self.selectedItemViews[item.id] = selectedItemView + } + + let isItemSelected = component.selectedId == item.id + + let _ = itemView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + item: item, + theme: component.theme, + isSelected: self.nativeTabBar == nil ? isItemSelected : false + )), + environment: {}, + containerSize: itemSize + ) + let _ = selectedItemView.update( + transition: itemTransition, + component: AnyComponent(ItemComponent( + item: item, + theme: component.theme, + isSelected: true + )), + environment: {}, + containerSize: itemSize + ) + + let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: floor((contentHeight - itemSize.height) * 0.5)), size: itemSize) + if let itemComponentView = itemView.view as? ItemComponent.View, let selectedItemComponentView = selectedItemView.view as? ItemComponent.View { + if itemComponentView.superview == nil { + itemComponentView.isUserInteractionEnabled = false + selectedItemComponentView.isUserInteractionEnabled = false + + if self.nativeTabBar != nil { + if let itemContainer = nativeItemContainers[index] { + itemContainer.addSubview(itemComponentView) + } + if let itemContainer = nativeSelectedItemContainers[index] { + itemContainer.addSubview(selectedItemComponentView) + } + } else { + self.addSubview(itemComponentView) + } + } + if self.nativeTabBar != nil { + if let parentView = itemComponentView.superview { + let itemFrame = CGRect(origin: CGPoint(x: floor((parentView.bounds.width - itemSize.width) * 0.5), y: floor((parentView.bounds.height - itemSize.height) * 0.5)), size: itemSize) + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + itemTransition.setFrame(view: selectedItemComponentView, frame: itemFrame) + } + } else { + itemTransition.setFrame(view: itemComponentView, frame: itemFrame) + } + + if let previousComponent, previousComponent.selectedId != item.id, isItemSelected { + itemComponentView.playSelectionAnimation() + selectedItemComponentView.playSelectionAnimation() + } + } + if isItemSelected { + selectionFrame = itemFrame + } + + contentWidth += itemFrame.width + } + contentWidth += innerInset + + var removeIds: [AnyHashable] = [] + for (id, itemView) in self.itemViews { + if !validIds.contains(id) { + removeIds.append(id) + itemView.view?.removeFromSuperview() + self.selectedItemViews[id]?.view?.removeFromSuperview() + } + } + for id in removeIds { + self.itemViews.removeValue(forKey: id) + self.selectedItemViews.removeValue(forKey: id) + } + + if let selectionFrame, self.nativeTabBar == nil { + var selectionViewTransition = transition + if self.selectionView.superview == nil { + selectionViewTransition = selectionViewTransition.withAnimation(.none) + self.backgroundView.contentView.addSubview(self.selectionView) + } + selectionViewTransition.setFrame(view: self.selectionView, frame: selectionFrame) + } else if self.selectionView.superview != nil { + self.selectionView.removeFromSuperview() + } + + let size = CGSize(width: min(availableSize.width, contentWidth), height: contentHeight) + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size)) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: component.theme.list.plainBackgroundColor.withMultipliedAlpha(0.75)), transition: transition) + + if let nativeTabBar = self.nativeTabBar { + transition.setFrame(view: nativeTabBar, frame: CGRect(origin: CGPoint(x: floor((size.width - nativeTabBar.bounds.width) * 0.5), y: 0.0), size: nativeTabBar.bounds.size)) + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +private final class ItemComponent: Component { + let item: TabBarComponent.Item + let theme: PresentationTheme + let isSelected: Bool + + init(item: TabBarComponent.Item, theme: PresentationTheme, isSelected: Bool) { + self.item = item + self.theme = theme + self.isSelected = isSelected + } + + static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool { + if lhs.item != rhs.item { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.isSelected != rhs.isSelected { + return false + } + return true + } + + final class View: UIView { + private var imageIcon: ComponentView? + private var animationIcon: ComponentView? + private let title = ComponentView() + private var badge: ComponentView? + + private var component: ItemComponent? + private weak var state: EmptyComponentState? + + private var setImageListener: Int? + private var setSelectedImageListener: Int? + private var setBadgeListener: Int? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + if let component = self.component { + if let setImageListener = self.setImageListener { + component.item.item.removeSetImageListener(setImageListener) + } + if let setSelectedImageListener = self.setSelectedImageListener { + component.item.item.removeSetSelectedImageListener(setSelectedImageListener) + } + if let setBadgeListener = self.setBadgeListener { + component.item.item.removeSetBadgeListener(setBadgeListener) + } + } + } + + func playSelectionAnimation() { + if let animationIconView = self.animationIcon?.view as? LottieComponent.View { + animationIconView.playOnce() + } + } + + func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + + if previousComponent?.item.item !== component.item.item { + if let setImageListener = self.setImageListener { + self.component?.item.item.removeSetImageListener(setImageListener) + } + if let setSelectedImageListener = self.setSelectedImageListener { + self.component?.item.item.removeSetSelectedImageListener(setSelectedImageListener) + } + if let setBadgeListener = self.setBadgeListener { + self.component?.item.item.removeSetBadgeListener(setBadgeListener) + } + self.setImageListener = component.item.item.addSetImageListener { [weak self] _ in + guard let self else { + return + } + self.state?.updated(transition: .immediate, isLocal: true) + } + self.setSelectedImageListener = component.item.item.addSetSelectedImageListener { [weak self] _ in + guard let self else { + return + } + self.state?.updated(transition: .immediate, isLocal: true) + } + self.setBadgeListener = UITabBarItem_addSetBadgeListener(component.item.item) { [weak self] _ in + guard let self else { + return + } + self.state?.updated(transition: .immediate, isLocal: true) + } + } + + self.component = component + self.state = state + + if let animationName = component.item.item.animationName { + if let imageIcon = self.imageIcon { + self.imageIcon = nil + imageIcon.view?.removeFromSuperview() + } + + let animationIcon: ComponentView + var iconTransition = transition + if let current = self.animationIcon { + animationIcon = current + } else { + iconTransition = iconTransition.withAnimation(.none) + animationIcon = ComponentView() + self.animationIcon = animationIcon + } + + let iconSize = animationIcon.update( + transition: iconTransition, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent( + name: animationName + ), + color: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor, + placeholderColor: nil, + startingPosition: .end, + size: CGSize(width: 48.0, height: 48.0), + loop: false + )), + environment: {}, + containerSize: CGSize(width: 48.0, height: 48.0) + ) + let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: -4.0), size: iconSize).offsetBy(dx: component.item.item.animationOffset.x, dy: component.item.item.animationOffset.y) + if let animationIconView = animationIcon.view { + if animationIconView.superview == nil { + if let badgeView = self.badge?.view { + self.insertSubview(animationIconView, belowSubview: badgeView) + } else { + self.addSubview(animationIconView) + } + } + iconTransition.setFrame(view: animationIconView, frame: iconFrame) + } + } else { + if let animationIcon = self.animationIcon { + self.animationIcon = nil + animationIcon.view?.removeFromSuperview() + } + + let imageIcon: ComponentView + var iconTransition = transition + if let current = self.imageIcon { + imageIcon = current + } else { + iconTransition = iconTransition.withAnimation(.none) + imageIcon = ComponentView() + self.imageIcon = imageIcon + } + + let iconSize = imageIcon.update( + transition: iconTransition, + component: AnyComponent(Image( + image: component.isSelected ? component.item.item.selectedImage : component.item.item.image, + tintColor: nil, + contentMode: .center + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 3.0), size: iconSize) + if let imageIconView = imageIcon.view { + if imageIconView.superview == nil { + if let badgeView = self.badge?.view { + self.insertSubview(imageIconView, belowSubview: badgeView) + } else { + self.addSubview(imageIconView) + } + } + iconTransition.setFrame(view: imageIconView, frame: iconFrame) + } + } + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.item.item.title ?? " ", font: Font.semibold(10.0), textColor: component.isSelected ? component.theme.rootController.tabBar.selectedTextColor : component.theme.rootController.tabBar.textColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: availableSize.height - 9.0 - titleSize.height), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + } + + if let badgeText = component.item.item.badgeValue, !badgeText.isEmpty { + let badge: ComponentView + var badgeTransition = transition + if let current = self.badge { + badge = current + } else { + badgeTransition = badgeTransition.withAnimation(.none) + badge = ComponentView() + self.badge = badge + } + let badgeSize = badge.update( + transition: badgeTransition, + component: AnyComponent(TextBadgeComponent( + text: badgeText, + font: Font.regular(13.0), + background: component.theme.rootController.tabBar.badgeBackgroundColor, + foreground: component.theme.rootController.tabBar.badgeTextColor, + insets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 1.0, right: 6.0) + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + let contentWidth: CGFloat = 25.0 + let badgeFrame = CGRect(origin: CGPoint(x: floor(availableSize.width / 2.0) + contentWidth - badgeSize.width - 5.0, y: -1.0), size: badgeSize) + if let badgeView = badge.view { + if badgeView.superview == nil { + self.addSubview(badgeView) + } + badgeTransition.setFrame(view: badgeView, frame: badgeFrame) + } + } else if let badge = self.badge { + self.badge = nil + badge.view?.removeFromSuperview() + } + + return availableSize + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 1bee710d4c..528d0c5843 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -1232,24 +1232,6 @@ private func extractAccountManagerState(records: AccountRecordsView>.Index? + + init(parentLayer: EffectImageLayer) { + self.parentLayer = parentLayer + + super.init() + + self.index = parentLayer.cloneLayers.add(Weak(self)) + + self.backgroundColor = parentLayer.backgroundColor + self.contents = parentLayer.contents + self.compositingFilter = parentLayer.compositingFilter + self.opacity = parentLayer.opacity + self.isOpaque = parentLayer.isOpaque + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + if let parentLayer = self.parentLayer, let index = self.index { + parentLayer.cloneLayers.remove(index) + } + } + } + enum SoftlightMode { case whileAnimating case always @@ -141,6 +174,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver } else { self.backgroundColor = nil } + + for cloneLayer in self.cloneLayers { + if let value = cloneLayer.value { + value.backgroundColor = self.backgroundColor + } + } } } } @@ -184,6 +223,8 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver var suspendCompositionUpdates: Bool = false private var needsCompositionUpdate: Bool = false + fileprivate let cloneLayers = SparseBag>() + private func updateFilters() { let useSoftlight: Bool let useFilter: Bool @@ -208,6 +249,12 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver self.compositingFilter = nil } + for cloneLayer in self.cloneLayers { + if let value = cloneLayer.value { + value.compositingFilter = self.compositingFilter + } + } + self.updateContents() self.updateOpacity() } @@ -330,6 +377,13 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver self.allowSettingContents = false self.backgroundColor = nil + + for cloneLayer in self.cloneLayers { + if let value = cloneLayer.value { + value.contents = self.contents + value.backgroundColor = self.backgroundColor + } + } } } @@ -345,6 +399,13 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver self.allowSettingOpacity = false self.isOpaque = true } + + for cloneLayer in self.cloneLayers { + if let value = cloneLayer.value { + value.opacity = self.opacity + value.isOpaque = self.isOpaque + } + } } } @@ -726,6 +787,8 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou private let contentNode: ASDisplayNode + fileprivate let edgeEffectNodes = SparseBag>() + private var blurredBackgroundContents: UIImage? private var freeBackgroundPortalSourceView: PortalSourceView? @@ -773,9 +836,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } } - private var gradientBackgroundNode: GradientBackgroundNode? + fileprivate var gradientBackgroundNode: GradientBackgroundNode? private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode? - private let patternImageLayer: EffectImageLayer + fileprivate let patternImageLayer: EffectImageLayer private let dimLayer: SimpleLayer private var isGeneratingPatternImage: Bool = false @@ -786,8 +849,6 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou private var modelStickerNode: DefaultAnimatedStickerNodeImpl? - fileprivate let edgeEffectNodes = SparseBag>() - private var isSettingUpWallpaper: Bool = false private struct CachedValidPatternImage { @@ -1057,6 +1118,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou if self.isLooping { scheduleLoopingEvent = true } + + for edgeEffectNode in self.edgeEffectNodes { + if let edgeEffectNode = edgeEffectNode.value { + edgeEffectNode.updateGradientNode() + } + } } self.gradientBackgroundNode?.updateColors(colors: mappedColors) @@ -1712,31 +1779,12 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou } public func makeEdgeEffectNode() -> WallpaperEdgeEffectNode? { - if let gradientBackgroundNode = self.gradientBackgroundNode { - let node = WallpaperEdgeEffectNodeImpl(parentNode: self) - node.cloneNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) - return node - } else { - return nil - } + let node = WallpaperEdgeEffectNodeImpl(parentNode: self) + return node } } private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode { - var cloneNode: GradientBackgroundNode.CloneNode? { - didSet { - if self.cloneNode !== oldValue { - if let cloneNode = self.cloneNode { - self.containerNode.insertSubnode(cloneNode, at: 0) - - if let params = self.params { - self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: .immediate) - } - } - } - } - } - private struct Params: Equatable { let rect: CGRect let edge: WallpaperEdgeEffectEdge @@ -1749,6 +1797,9 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff } } + private var gradientNode: GradientBackgroundNode.CloneNode? + private let patternImageLayer: EffectImageLayer.CloneLayer + private let containerNode: ASDisplayNode private let containerMaskingNode: ASDisplayNode private let overlayNode: ASDisplayNode @@ -1763,6 +1814,14 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff init(parentNode: WallpaperBackgroundNodeImpl) { self.parentNode = parentNode + if let gradientBackgroundNode = parentNode.gradientBackgroundNode { + self.gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) + } else { + self.gradientNode = nil + } + + self.patternImageLayer = EffectImageLayer.CloneLayer(parentLayer: parentNode.patternImageLayer) + self.containerNode = ASDisplayNode() self.containerNode.anchorPoint = CGPoint() self.containerNode.clipsToBounds = true @@ -1776,6 +1835,11 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff super.init() + if let gradientNode = self.gradientNode { + self.containerNode.addSubnode(gradientNode) + } + //self.layer.addSublayer(self.patternImageLayer) + self.addSubnode(self.containerMaskingNode) self.containerMaskingNode.view.mask = self.maskView @@ -1790,6 +1854,25 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff } } + func updateGradientNode() { + if let gradientBackgroundNode = self.parentNode?.gradientBackgroundNode { + if self.gradientNode == nil { + let gradientNode = GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode, isDimmed: false) + self.gradientNode = gradientNode + self.containerNode.insertSubnode(gradientNode, at: 0) + + if let params = self.params { + self.updateImpl(rect: params.rect, edge: params.edge, containerSize: params.containerSize, transition: .immediate) + } + } + } else { + if let gradientNode = self.gradientNode { + self.gradientNode = nil + gradientNode.removeFromSupernode() + } + } + } + func updatePattern(isInverted: Bool) { if self.isInverted != isInverted { self.isInverted = isInverted @@ -1844,9 +1927,10 @@ private final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEff transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize)) - if let cloneNode = self.cloneNode { - transition.updateFrame(node: cloneNode, frame: CGRect(origin: CGPoint(), size: containerSize)) + if let gradientNode = self.gradientNode { + transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: containerSize)) } + transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: containerSize)) } } From 842711de04bd33cb780a100e549bea5b6df8ba2e Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 10:55:11 +0200 Subject: [PATCH 2/8] Auth debug email --- .../Telegram-iOS/en.lproj/Localizable.strings | 44 ++++++++---- .../GenerateStrings/GenerateStrings.py | 2 +- submodules/AuthorizationUI/BUILD | 1 + .../AuthorizationSequencePaymentScreen.swift | 67 ++++++++++--------- .../Sources/DebugAccountsController.swift | 4 ++ .../DeviceModel/Sources/DeviceModel.swift | 11 +++ 6 files changed, 82 insertions(+), 47 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 53acaef12c..69bac6dd6d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -452,20 +452,6 @@ "Login.PhoneGenericEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram shows an error. Please help.\n\nError: %2$@\nApp version: %3$@\nOS version: %4$@\nLocale: %5$@\nMNC: %6$@"; "Login.PhonePaidEmailSubject" = "Payment Issue"; -"Login.PhonePaidEmailBody" = "Hello Telegram Support,\n\nI’m experiencing an issue with an in-app purchase on iOS.\nMy phone number: %1$@\nApple ID (email used in the App Store): \nApp version: %2$@\nDevice model / iOS version: %3$@\nApp Store country/region: %4$@\nDate & time of attempt (with timezone): %5$@\nExact error message: \n\n#payment_failed"; - -"Login.PhoneTitle" = "Your Phone"; -"Login.PhonePlaceholder" = "Your phone number"; -"Login.CountryCode" = "Country Code"; -"Login.InvalidCountryCode" = "Invalid Country Code"; - -"Login.InfoTitle" = "Your Info"; -"Login.InfoAvatarAdd" = "add"; -"Login.InfoAvatarPhoto" = "photo"; -"Login.InfoFirstNamePlaceholder" = "First Name"; -"Login.InfoLastNamePlaceholder" = "Last Name"; -"Login.InfoDeletePhoto" = "Remove Photo"; -"Login.InfoHelp" = "Enter your name and add a profile photo."; // Login.SelectCountry "Login.SelectCountry.Title" = "Country"; @@ -15015,3 +15001,33 @@ Sorry for the inconvenience."; "Gift.Options.Collectibles.Text" = "Collectible gifts are unique digital items you can exchange or sell."; "Gift.Upgrade.UpgradeFor" = "Upgrade for %@"; + +"Login.PhonePaidEmailText" = "1. NAME OF YOUR PHONE CARRIER: + +2. (OPTIONAL) WRITE YOUR COMMENT HERE: + +================================================= +Device: %1$@ +OS version: %2$@ +Locale: %3$@ + +Target phone: %4$@ + +App: %5$@ +App version: %6$@ + +Issue: %7$@ +Error: %8$@"; + +"Login.PhoneTitle" = "Your Phone"; +"Login.PhonePlaceholder" = "Your phone number"; +"Login.CountryCode" = "Country Code"; +"Login.InvalidCountryCode" = "Invalid Country Code"; + +"Login.InfoTitle" = "Your Info"; +"Login.InfoAvatarAdd" = "add"; +"Login.InfoAvatarPhoto" = "photo"; +"Login.InfoFirstNamePlaceholder" = "First Name"; +"Login.InfoLastNamePlaceholder" = "Last Name"; +"Login.InfoDeletePhoto" = "Remove Photo"; +"Login.InfoHelp" = "Enter your name and add a profile photo."; diff --git a/build-system/GenerateStrings/GenerateStrings.py b/build-system/GenerateStrings/GenerateStrings.py index 31741133a1..e10ff2b8a7 100644 --- a/build-system/GenerateStrings/GenerateStrings.py +++ b/build-system/GenerateStrings/GenerateStrings.py @@ -88,7 +88,7 @@ def parse_positional_arguments(string: str) -> [PositionalArgument]: result = list() implicit_index = 0 - argument = re.compile(r'%((\d)\$)?([@d])', re.U) + argument = re.compile(r'%((\d+)\$)?([@d])', re.U) start_position = 0 while True: m = argument.search(string, start_position) diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index fc3eb3f659..5a48a9b381 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -46,6 +46,7 @@ swift_library( "//submodules/InAppPurchaseManager", "//submodules/TelegramUI/Components/Premium/PremiumCoinComponent", "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/Utils/DeviceModel", ], visibility = [ "//visibility:public", diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift index 31ae741d74..332f744dbb 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePaymentScreen.swift @@ -28,6 +28,7 @@ import CoreTelephony import PhoneNumberFormat import PlainButtonComponent import StoreKit +import DeviceModel final class AuthorizationSequencePaymentScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -169,22 +170,12 @@ final class AuthorizationSequencePaymentScreenComponent: Component { text: errorText, actions: [ TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}), - TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_PhoneNumberHelp, action: { [weak controller] in - guard let controller else { + TextAlertAction(type: .defaultAction, title: presentationData.strings.Login_PhoneNumberHelp, action: { [weak self] in + guard let self else { return } - let formattedNumber = formatPhoneNumber(component.phoneNumber) - let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - let systemVersion = UIDevice.current.systemVersion - let locale = Locale.current.identifier - let carrier = CTCarrier() - let mnc = carrier.mobileNetworkCode ?? "none" - let errorString: String = "\(errorCode): \(errorText)" - var body = presentationData.strings.Login_PhoneGenericEmailBody(formattedNumber, errorString, appVersion, systemVersion, locale, mnc).string - body.append("\n#paidauth") - - AuthorizationSequenceController.presentEmailComposeController(address: component.supportEmailAddress, subject: component.supportEmailSubject, body: body, from: controller, presentationData: presentationData) + self.displaySendEmail(error: errorText, errorCode: "\(errorCode)") }) ] ) @@ -198,6 +189,35 @@ final class AuthorizationSequencePaymentScreenComponent: Component { }) } + private func displaySendEmail(error: String?, errorCode: String?) { + guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { + return + } + + let formattedNumber = "\(component.phoneNumber)" + let device = DeviceModel.currentModelCode() + let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" + let systemVersion = UIDevice.current.systemVersion + let locale = Locale.current.identifier + + let issue = error ?? "unknown" + let errorCode = errorCode ?? "unknown" + + let body = environment.strings.Login_PhonePaidEmailText( + device, + systemVersion, + locale, + formattedNumber, + "1", + appVersion, + issue, + errorCode + ).string + + let presentationData = component.presentationData + AuthorizationSequenceController.presentEmailComposeController(address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData) + } + func update(component: AuthorizationSequencePaymentScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -230,7 +250,6 @@ final class AuthorizationSequencePaymentScreenComponent: Component { let sideInset: CGFloat = 16.0 + environment.safeInsets.left - let presentationData = component.presentationData let helpButtonSize = self.helpButton.update( transition: transition, component: AnyComponent(PlainButtonComponent( @@ -240,26 +259,10 @@ final class AuthorizationSequencePaymentScreenComponent: Component { minSize: CGSize(width: 0.0, height: 44.0), contentInsets: UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0), action: { [weak self] in - guard let self, let environment = self.environment, let controller = environment.controller() else { + guard let self else { return } - let formattedNumber = formatPhoneNumber(component.phoneNumber) - let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" - let systemVersion = UIDevice.current.systemVersion - let region = SKPaymentQueue.default().storefront?.countryCode ?? "" - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz" - let dateString = dateFormatter.string(from: Date()) - - let body = environment.strings.Login_PhonePaidEmailBody( - formattedNumber, - appVersion, - systemVersion, - region, - dateString - ).string - - AuthorizationSequenceController.presentEmailComposeController(address: component.supportEmailAddress, subject: environment.strings.Login_PhonePaidEmailSubject, body: body, from: controller, presentationData: presentationData) + self.displaySendEmail(error: nil, errorCode: nil) }, animateScale: false, animateContents: false diff --git a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift index a5e45f7e75..d90e35e19a 100644 --- a/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugAccountsController.swift @@ -123,6 +123,10 @@ public func debugAccountsController(context: AccountContext, accountManager: Acc if case .internal = context.sharedContext.applicationBindings.appBuildType { context.sharedContext.beginNewAuth(testingEnvironment: false) + } else { + #if DEBUG + context.sharedContext.beginNewAuth(testingEnvironment: false) + #endif } }), ActionSheetButtonItem(title: "Test", color: .accent, action: { diff --git a/submodules/Utils/DeviceModel/Sources/DeviceModel.swift b/submodules/Utils/DeviceModel/Sources/DeviceModel.swift index 572a05d9ae..25fd13fa55 100644 --- a/submodules/Utils/DeviceModel/Sources/DeviceModel.swift +++ b/submodules/Utils/DeviceModel/Sources/DeviceModel.swift @@ -372,6 +372,17 @@ public enum DeviceModel: CaseIterable, Equatable { public static let current = DeviceModel() + public static func currentModelCode() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let modelCode = withUnsafePointer(to: &systemInfo.machine) { + $0.withMemoryRebound(to: CChar.self, capacity: 1) { + ptr in String.init(validatingUTF8: ptr) + } + } + return modelCode ?? "unknown" + } + private init() { var systemInfo = utsname() uname(&systemInfo) From 18e40eafe3a0b358b719ca198c7b24dfdde9464f Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 10:55:44 +0200 Subject: [PATCH 3/8] Fix simultaneous list view scroll + insets update animation --- submodules/Display/Source/ListView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index a800abaed0..cdd8b31a34 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -3235,6 +3235,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel completeOffset += snapToBoundsOffset if !updateSizeAndInsets.duration.isZero && !isExperimentalSnapToScrollToItem { + for i in 0 ..< previousApparentFrames.count { + previousApparentFrames[i].1.frame.origin.y += completeOffset - offsetFix + } + let animation: CABasicAnimation let animationCurve: ContainedViewLayoutTransitionCurve let animationDuration: Double From a257009780accfdb474037bbf1ee14d58ae0c766 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 10:56:05 +0200 Subject: [PATCH 4/8] Update api and call messages --- submodules/TelegramApi/Sources/Api0.swift | 11 +- submodules/TelegramApi/Sources/Api10.swift | 22 ++ submodules/TelegramApi/Sources/Api15.swift | 18 +- submodules/TelegramApi/Sources/Api23.swift | 18 +- submodules/TelegramApi/Sources/Api27.swift | 194 +++++++++----- submodules/TelegramApi/Sources/Api39.swift | 108 +++++--- .../Sources/PresentationGroupCall.swift | 31 +++ .../Account/AccountIntermediateState.swift | 21 +- .../ApiUtils/TelegramMediaAction.swift | 2 +- .../TelegramCore/Sources/ForumChannels.swift | 12 +- .../State/AccountStateManagementUtils.swift | 52 ++-- .../Sources/State/AccountStateManager.swift | 14 + .../TelegramEngine/Calls/GroupCalls.swift | 245 ++++++++++++++++++ .../Messages/TelegramEngineMessages.swift | 4 + .../TelegramEngine/Payments/StarGifts.swift | 2 +- 15 files changed, 606 insertions(+), 148 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 118426ace9..3a94b801ff 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -394,6 +394,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-625298705] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftStars($0) } dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) } dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) } + dict[153344209] = { return Api.InputInvoice.parse_inputInvoiceStarGiftDropOriginalDetails($0) } dict[-1710536520] = { return Api.InputInvoice.parse_inputInvoiceStarGiftPrepaidUpgrade($0) } dict[-1012968668] = { return Api.InputInvoice.parse_inputInvoiceStarGiftResale($0) } dict[1247763417] = { return Api.InputInvoice.parse_inputInvoiceStarGiftTransfer($0) } @@ -610,7 +611,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[-229775366] = { return Api.MessageAction.parse_messageActionStarGift($0) } - dict[888627955] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } + dict[-1787656893] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[-293988970] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } dict[1777932024] = { return Api.MessageAction.parse_messageActionSuggestedPostRefund($0) } @@ -895,7 +896,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1681948327] = { return Api.SavedDialog.parse_monoForumDialog($0) } dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) } dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) } - dict[430552434] = { return Api.SavedStarGift.parse_savedStarGift($0) } + dict[-1987861422] = { return Api.SavedStarGift.parse_savedStarGift($0) } dict[1040931690] = { return Api.SearchPostsFlood.parse_searchPostsFlood($0) } dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) } dict[2137295719] = { return Api.SearchResultsPosition.parse_searchResultPosition($0) } @@ -1068,8 +1069,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-761649164] = { return Api.Update.parse_updateChannelMessageForwards($0) } dict[-232346616] = { return Api.Update.parse_updateChannelMessageViews($0) } dict[-1738720581] = { return Api.Update.parse_updateChannelParticipant($0) } - dict[422509539] = { return Api.Update.parse_updateChannelPinnedTopic($0) } - dict[-31881726] = { return Api.Update.parse_updateChannelPinnedTopics($0) } dict[636691703] = { return Api.Update.parse_updateChannelReadMessagesContents($0) } dict[277713951] = { return Api.Update.parse_updateChannelTooLong($0) } dict[-1937192669] = { return Api.Update.parse_updateChannelUserTyping($0) } @@ -1108,6 +1107,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1747565759] = { return Api.Update.parse_updateGroupCall($0) } dict[-1535694705] = { return Api.Update.parse_updateGroupCallChainBlocks($0) } dict[192428418] = { return Api.Update.parse_updateGroupCallConnection($0) } + dict[-917002394] = { return Api.Update.parse_updateGroupCallEncryptedMessage($0) } + dict[-1761933248] = { return Api.Update.parse_updateGroupCallMessage($0) } dict[-219423922] = { return Api.Update.parse_updateGroupCallParticipants($0) } dict[1763610706] = { return Api.Update.parse_updateInlineBotCallbackQuery($0) } dict[1442983757] = { return Api.Update.parse_updateLangPack($0) } @@ -1140,6 +1141,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[643940105] = { return Api.Update.parse_updatePhoneCallSignalingData($0) } dict[1538885128] = { return Api.Update.parse_updatePinnedChannelMessages($0) } dict[-99664734] = { return Api.Update.parse_updatePinnedDialogs($0) } + dict[1748708434] = { return Api.Update.parse_updatePinnedForumTopic($0) } + dict[-554613808] = { return Api.Update.parse_updatePinnedForumTopics($0) } dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) } dict[1751942566] = { return Api.Update.parse_updatePinnedSavedDialogs($0) } dict[-298113238] = { return Api.Update.parse_updatePrivacy($0) } diff --git a/submodules/TelegramApi/Sources/Api10.swift b/submodules/TelegramApi/Sources/Api10.swift index da1c5b0399..dc5d19a80f 100644 --- a/submodules/TelegramApi/Sources/Api10.swift +++ b/submodules/TelegramApi/Sources/Api10.swift @@ -256,6 +256,7 @@ public extension Api { case inputInvoicePremiumGiftStars(flags: Int32, userId: Api.InputUser, months: Int32, message: Api.TextWithEntities?) case inputInvoiceSlug(slug: String) case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?) + case inputInvoiceStarGiftDropOriginalDetails(stargift: Api.InputSavedStarGift) case inputInvoiceStarGiftPrepaidUpgrade(peer: Api.InputPeer, hash: String) case inputInvoiceStarGiftResale(flags: Int32, slug: String, toId: Api.InputPeer) case inputInvoiceStarGiftTransfer(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) @@ -321,6 +322,12 @@ public extension Api { serializeInt64(giftId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} break + case .inputInvoiceStarGiftDropOriginalDetails(let stargift): + if boxed { + buffer.appendInt32(153344209) + } + stargift.serialize(buffer, true) + break case .inputInvoiceStarGiftPrepaidUpgrade(let peer, let hash): if boxed { buffer.appendInt32(-1710536520) @@ -377,6 +384,8 @@ public extension Api { return ("inputInvoiceSlug", [("slug", slug as Any)]) case .inputInvoiceStarGift(let flags, let peer, let giftId, let message): return ("inputInvoiceStarGift", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("message", message as Any)]) + case .inputInvoiceStarGiftDropOriginalDetails(let stargift): + return ("inputInvoiceStarGiftDropOriginalDetails", [("stargift", stargift as Any)]) case .inputInvoiceStarGiftPrepaidUpgrade(let peer, let hash): return ("inputInvoiceStarGiftPrepaidUpgrade", [("peer", peer as Any), ("hash", hash as Any)]) case .inputInvoiceStarGiftResale(let flags, let slug, let toId): @@ -523,6 +532,19 @@ public extension Api { return nil } } + public static func parse_inputInvoiceStarGiftDropOriginalDetails(_ reader: BufferReader) -> InputInvoice? { + var _1: Api.InputSavedStarGift? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputSavedStarGift + } + let _c1 = _1 != nil + if _c1 { + return Api.InputInvoice.inputInvoiceStarGiftDropOriginalDetails(stargift: _1!) + } + else { + return nil + } + } public static func parse_inputInvoiceStarGiftPrepaidUpgrade(_ reader: BufferReader) -> InputInvoice? { var _1: Api.InputPeer? if let signature = reader.readInt32() { diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index ca9922c304..28616ad79f 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -394,7 +394,7 @@ public extension Api { case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, prepaidUpgradeHash: String?, giftMsgId: Int32?) - case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?) + case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleAmount: Api.StarsAmount?, canTransferAt: Int32?, canResellAt: Int32?, dropOriginalDetailsStars: Int64?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) case messageActionSuggestedPostRefund(flags: Int32) @@ -799,9 +799,9 @@ public extension Api { if Int(flags) & Int(1 << 14) != 0 {serializeString(prepaidUpgradeHash!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(giftMsgId!, buffer: buffer, boxed: false)} break - case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt): + case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): if boxed { - buffer.appendInt32(888627955) + buffer.appendInt32(-1787656893) } serializeInt32(flags, buffer: buffer, boxed: false) gift.serialize(buffer, true) @@ -813,6 +813,7 @@ public extension Api { if Int(flags) & Int(1 << 8) != 0 {resaleAmount!.serialize(buffer, true)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(canTransferAt!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 10) != 0 {serializeInt32(canResellAt!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 12) != 0 {serializeInt64(dropOriginalDetailsStars!, buffer: buffer, boxed: false)} break case .messageActionSuggestProfilePhoto(let photo): if boxed { @@ -995,8 +996,8 @@ public extension Api { return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars, let fromId, let peer, let savedId, let prepaidUpgradeHash, let giftMsgId): return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("giftMsgId", giftMsgId as Any)]) - case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt): - return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any)]) + case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleAmount, let canTransferAt, let canResellAt, let dropOriginalDetailsStars): + return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleAmount", resaleAmount as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) case .messageActionSuggestProfilePhoto(let photo): return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let price): @@ -1810,6 +1811,8 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {_9 = reader.readInt32() } var _10: Int32? if Int(_1!) & Int(1 << 10) != 0 {_10 = reader.readInt32() } + var _11: Int64? + if Int(_1!) & Int(1 << 12) != 0 {_11 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil @@ -1820,8 +1823,9 @@ public extension Api { let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil let _c9 = (Int(_1!) & Int(1 << 9) == 0) || _9 != nil let _c10 = (Int(_1!) & Int(1 << 10) == 0) || _10 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 { - return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10) + let _c11 = (Int(_1!) & Int(1 << 12) == 0) || _11 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { + return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleAmount: _8, canTransferAt: _9, canResellAt: _10, dropOriginalDetailsStars: _11) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 7a79ed07e0..1c602ab476 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -626,13 +626,13 @@ public extension Api { } public extension Api { enum SavedStarGift: TypeConstructorDescription { - case savedStarGift(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?) + case savedStarGift(flags: Int32, fromId: Api.Peer?, date: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, msgId: Int32?, savedId: Int64?, convertStars: Int64?, upgradeStars: Int64?, canExportAt: Int32?, transferStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?, collectionId: [Int32]?, prepaidUpgradeHash: String?, dropOriginalDetailsStars: Int64?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash): + case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash, let dropOriginalDetailsStars): if boxed { - buffer.appendInt32(430552434) + buffer.appendInt32(-1987861422) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 1) != 0 {fromId!.serialize(buffer, true)} @@ -653,14 +653,15 @@ public extension Api { serializeInt32(item, buffer: buffer, boxed: false) }} if Int(flags) & Int(1 << 16) != 0 {serializeString(prepaidUpgradeHash!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 18) != 0 {serializeInt64(dropOriginalDetailsStars!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash): - return ("savedStarGift", [("flags", flags as Any), ("fromId", fromId as Any), ("date", date as Any), ("gift", gift as Any), ("message", message as Any), ("msgId", msgId as Any), ("savedId", savedId as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("collectionId", collectionId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any)]) + case .savedStarGift(let flags, let fromId, let date, let gift, let message, let msgId, let savedId, let convertStars, let upgradeStars, let canExportAt, let transferStars, let canTransferAt, let canResellAt, let collectionId, let prepaidUpgradeHash, let dropOriginalDetailsStars): + return ("savedStarGift", [("flags", flags as Any), ("fromId", fromId as Any), ("date", date as Any), ("gift", gift as Any), ("message", message as Any), ("msgId", msgId as Any), ("savedId", savedId as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("canTransferAt", canTransferAt as Any), ("canResellAt", canResellAt as Any), ("collectionId", collectionId as Any), ("prepaidUpgradeHash", prepaidUpgradeHash as Any), ("dropOriginalDetailsStars", dropOriginalDetailsStars as Any)]) } } @@ -703,6 +704,8 @@ public extension Api { } } var _15: String? if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } + var _16: Int64? + if Int(_1!) & Int(1 << 18) != 0 {_16 = reader.readInt64() } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c3 = _3 != nil @@ -718,8 +721,9 @@ public extension Api { let _c13 = (Int(_1!) & Int(1 << 14) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15) + let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 { + return Api.SavedStarGift.savedStarGift(flags: _1!, fromId: _2, date: _3!, gift: _4!, message: _5, msgId: _6, savedId: _7, convertStars: _8, upgradeStars: _9, canExportAt: _10, transferStars: _11, canTransferAt: _12, canResellAt: _13, collectionId: _14, prepaidUpgradeHash: _15, dropOriginalDetailsStars: _16) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index 32ede26785..d0d6ab7e37 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -569,8 +569,6 @@ public extension Api { case updateChannelMessageForwards(channelId: Int64, id: Int32, forwards: Int32) case updateChannelMessageViews(channelId: Int64, id: Int32, views: Int32) case updateChannelParticipant(flags: Int32, channelId: Int64, date: Int32, actorId: Int64, userId: Int64, prevParticipant: Api.ChannelParticipant?, newParticipant: Api.ChannelParticipant?, invite: Api.ExportedChatInvite?, qts: Int32) - case updateChannelPinnedTopic(flags: Int32, channelId: Int64, topicId: Int32) - case updateChannelPinnedTopics(flags: Int32, channelId: Int64, order: [Int32]?) case updateChannelReadMessagesContents(flags: Int32, channelId: Int64, topMsgId: Int32?, savedPeerId: Api.Peer?, messages: [Int32]) case updateChannelTooLong(flags: Int32, channelId: Int64, pts: Int32?) case updateChannelUserTyping(flags: Int32, channelId: Int64, topMsgId: Int32?, fromId: Api.Peer, action: Api.SendMessageAction) @@ -609,6 +607,8 @@ public extension Api { case updateGroupCall(flags: Int32, chatId: Int64?, call: Api.GroupCall) case updateGroupCallChainBlocks(call: Api.InputGroupCall, subChainId: Int32, blocks: [Buffer], nextOffset: Int32) case updateGroupCallConnection(flags: Int32, params: Api.DataJSON) + case updateGroupCallEncryptedMessage(call: Api.InputGroupCall, fromId: Api.Peer, encryptedMessage: Buffer) + case updateGroupCallMessage(call: Api.InputGroupCall, fromId: Api.Peer, message: Api.TextWithEntities) case updateGroupCallParticipants(call: Api.InputGroupCall, participants: [Api.GroupCallParticipant], version: Int32) case updateInlineBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, msgId: Api.InputBotInlineMessageID, chatInstance: Int64, data: Buffer?, gameShortName: String?) case updateLangPack(difference: Api.LangPackDifference) @@ -641,6 +641,8 @@ public extension Api { case updatePhoneCallSignalingData(phoneCallId: Int64, data: Buffer) case updatePinnedChannelMessages(flags: Int32, channelId: Int64, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedDialogs(flags: Int32, folderId: Int32?, order: [Api.DialogPeer]?) + case updatePinnedForumTopic(flags: Int32, peer: Api.Peer, topicId: Int32) + case updatePinnedForumTopics(flags: Int32, peer: Api.Peer, order: [Int32]?) case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedSavedDialogs(flags: Int32, order: [Api.DialogPeer]?) case updatePrivacy(key: Api.PrivacyKey, rules: [Api.PrivacyRule]) @@ -959,26 +961,6 @@ public extension Api { if Int(flags) & Int(1 << 2) != 0 {invite!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - if boxed { - buffer.appendInt32(422509539) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - serializeInt32(topicId, buffer: buffer, boxed: false) - break - case .updateChannelPinnedTopics(let flags, let channelId, let order): - if boxed { - buffer.appendInt32(-31881726) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt64(channelId, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order!.count)) - for item in order! { - serializeInt32(item, buffer: buffer, boxed: false) - }} - break case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let savedPeerId, let messages): if boxed { buffer.appendInt32(636691703) @@ -1317,6 +1299,22 @@ public extension Api { serializeInt32(flags, buffer: buffer, boxed: false) params.serialize(buffer, true) break + case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): + if boxed { + buffer.appendInt32(-917002394) + } + call.serialize(buffer, true) + fromId.serialize(buffer, true) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + break + case .updateGroupCallMessage(let call, let fromId, let message): + if boxed { + buffer.appendInt32(-1761933248) + } + call.serialize(buffer, true) + fromId.serialize(buffer, true) + message.serialize(buffer, true) + break case .updateGroupCallParticipants(let call, let participants, let version): if boxed { buffer.appendInt32(-219423922) @@ -1589,6 +1587,26 @@ public extension Api { item.serialize(buffer, true) }} break + case .updatePinnedForumTopic(let flags, let peer, let topicId): + if boxed { + buffer.appendInt32(1748708434) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + break + case .updatePinnedForumTopics(let flags, let peer, let order): + if boxed { + buffer.appendInt32(-554613808) + } + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order!.count)) + for item in order! { + serializeInt32(item, buffer: buffer, boxed: false) + }} + break case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): if boxed { buffer.appendInt32(-309990731) @@ -2021,10 +2039,6 @@ public extension Api { return ("updateChannelMessageViews", [("channelId", channelId as Any), ("id", id as Any), ("views", views as Any)]) case .updateChannelParticipant(let flags, let channelId, let date, let actorId, let userId, let prevParticipant, let newParticipant, let invite, let qts): return ("updateChannelParticipant", [("flags", flags as Any), ("channelId", channelId as Any), ("date", date as Any), ("actorId", actorId as Any), ("userId", userId as Any), ("prevParticipant", prevParticipant as Any), ("newParticipant", newParticipant as Any), ("invite", invite as Any), ("qts", qts as Any)]) - case .updateChannelPinnedTopic(let flags, let channelId, let topicId): - return ("updateChannelPinnedTopic", [("flags", flags as Any), ("channelId", channelId as Any), ("topicId", topicId as Any)]) - case .updateChannelPinnedTopics(let flags, let channelId, let order): - return ("updateChannelPinnedTopics", [("flags", flags as Any), ("channelId", channelId as Any), ("order", order as Any)]) case .updateChannelReadMessagesContents(let flags, let channelId, let topMsgId, let savedPeerId, let messages): return ("updateChannelReadMessagesContents", [("flags", flags as Any), ("channelId", channelId as Any), ("topMsgId", topMsgId as Any), ("savedPeerId", savedPeerId as Any), ("messages", messages as Any)]) case .updateChannelTooLong(let flags, let channelId, let pts): @@ -2101,6 +2115,10 @@ public extension Api { return ("updateGroupCallChainBlocks", [("call", call as Any), ("subChainId", subChainId as Any), ("blocks", blocks as Any), ("nextOffset", nextOffset as Any)]) case .updateGroupCallConnection(let flags, let params): return ("updateGroupCallConnection", [("flags", flags as Any), ("params", params as Any)]) + case .updateGroupCallEncryptedMessage(let call, let fromId, let encryptedMessage): + return ("updateGroupCallEncryptedMessage", [("call", call as Any), ("fromId", fromId as Any), ("encryptedMessage", encryptedMessage as Any)]) + case .updateGroupCallMessage(let call, let fromId, let message): + return ("updateGroupCallMessage", [("call", call as Any), ("fromId", fromId as Any), ("message", message as Any)]) case .updateGroupCallParticipants(let call, let participants, let version): return ("updateGroupCallParticipants", [("call", call as Any), ("participants", participants as Any), ("version", version as Any)]) case .updateInlineBotCallbackQuery(let flags, let queryId, let userId, let msgId, let chatInstance, let data, let gameShortName): @@ -2165,6 +2183,10 @@ public extension Api { return ("updatePinnedChannelMessages", [("flags", flags as Any), ("channelId", channelId as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updatePinnedDialogs(let flags, let folderId, let order): return ("updatePinnedDialogs", [("flags", flags as Any), ("folderId", folderId as Any), ("order", order as Any)]) + case .updatePinnedForumTopic(let flags, let peer, let topicId): + return ("updatePinnedForumTopic", [("flags", flags as Any), ("peer", peer as Any), ("topicId", topicId as Any)]) + case .updatePinnedForumTopics(let flags, let peer, let order): + return ("updatePinnedForumTopics", [("flags", flags as Any), ("peer", peer as Any), ("order", order as Any)]) case .updatePinnedMessages(let flags, let peer, let messages, let pts, let ptsCount): return ("updatePinnedMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)]) case .updatePinnedSavedDialogs(let flags, let order): @@ -2872,42 +2894,6 @@ public extension Api { return nil } } - public static func parse_updateChannelPinnedTopic(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopic(flags: _1!, channelId: _2!, topicId: _3!) - } - else { - return nil - } - } - public static func parse_updateChannelPinnedTopics(_ reader: BufferReader) -> Update? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int64? - _2 = reader.readInt64() - var _3: [Int32]? - if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateChannelPinnedTopics(flags: _1!, channelId: _2!, order: _3) - } - else { - return nil - } - } public static func parse_updateChannelReadMessagesContents(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() @@ -3582,6 +3568,50 @@ public extension Api { return nil } } + public static func parse_updateGroupCallEncryptedMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallEncryptedMessage(call: _1!, fromId: _2!, encryptedMessage: _3!) + } + else { + return nil + } + } + public static func parse_updateGroupCallMessage(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Api.TextWithEntities? + if let signature = reader.readInt32() { + _3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updateGroupCallMessage(call: _1!, fromId: _2!, message: _3!) + } + else { + return nil + } + } public static func parse_updateGroupCallParticipants(_ reader: BufferReader) -> Update? { var _1: Api.InputGroupCall? if let signature = reader.readInt32() { @@ -4156,6 +4186,46 @@ public extension Api { return nil } } + public static func parse_updatePinnedForumTopic(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopic(flags: _1!, peer: _2!, topicId: _3!) + } + else { + return nil + } + } + public static func parse_updatePinnedForumTopics(_ reader: BufferReader) -> Update? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.Peer? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.Peer + } + var _3: [Int32]? + if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.Update.updatePinnedForumTopics(flags: _1!, peer: _2!, order: _3) + } + else { + return nil + } + } public static func parse_updatePinnedMessages(_ reader: BufferReader) -> Update? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api39.swift b/submodules/TelegramApi/Sources/Api39.swift index 20b2443b6a..baca4c5d6f 100644 --- a/submodules/TelegramApi/Sources/Api39.swift +++ b/submodules/TelegramApi/Sources/Api39.swift @@ -3414,27 +3414,6 @@ public extension Api.functions.channels { }) } } -public extension Api.functions.channels { - static func reorderPinnedForumTopics(flags: Int32, channel: Api.InputChannel, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(693150095) - serializeInt32(flags, buffer: buffer, boxed: false) - channel.serialize(buffer, true) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(order.count)) - for item in order { - serializeInt32(item, buffer: buffer, boxed: false) - } - return (FunctionDescription(name: "channels.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} public extension Api.functions.channels { static func reorderUsernames(channel: Api.InputChannel, order: [String]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -3839,23 +3818,6 @@ public extension Api.functions.channels { }) } } -public extension Api.functions.channels { - static func updatePinnedForumTopic(channel: Api.InputChannel, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1814925350) - channel.serialize(buffer, true) - serializeInt32(topicId, buffer: buffer, boxed: false) - pinned.serialize(buffer, true) - return (FunctionDescription(name: "channels.updatePinnedForumTopic", parameters: [("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in - let reader = BufferReader(buffer) - var result: Api.Updates? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Updates - } - return result - }) - } -} public extension Api.functions.channels { static func updateUsername(channel: Api.InputChannel, username: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -7724,6 +7686,27 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func reorderPinnedForumTopics(flags: Int32, peer: Api.InputPeer, order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(242762224) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(order.count)) + for item in order { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reorderPinnedForumTopics", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("order", String(describing: order))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func reorderPinnedSavedDialogs(flags: Int32, order: [Api.InputDialogPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9183,6 +9166,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func updatePinnedForumTopic(peer: Api.InputPeer, topicId: Int32, pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(392032849) + peer.serialize(buffer, true) + serializeInt32(topicId, buffer: buffer, boxed: false) + pinned.serialize(buffer, true) + return (FunctionDescription(name: "messages.updatePinnedForumTopic", parameters: [("peer", String(describing: peer)), ("topicId", String(describing: topicId)), ("pinned", String(describing: pinned))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func updatePinnedMessage(flags: Int32, peer: Api.InputPeer, id: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -10796,6 +10796,38 @@ public extension Api.functions.phone { }) } } +public extension Api.functions.phone { + static func sendGroupCallEncryptedMessage(call: Api.InputGroupCall, encryptedMessage: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-441473683) + call.serialize(buffer, true) + serializeBytes(encryptedMessage, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.sendGroupCallEncryptedMessage", parameters: [("call", String(describing: call)), ("encryptedMessage", String(describing: encryptedMessage))]), 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.phone { + static func sendGroupCallMessage(call: Api.InputGroupCall, message: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-614432696) + call.serialize(buffer, true) + message.serialize(buffer, true) + return (FunctionDescription(name: "phone.sendGroupCallMessage", parameters: [("call", String(describing: call)), ("message", String(describing: message))]), 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.phone { static func sendSignalingData(peer: Api.InputPhoneCall, data: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 5d79763057..b3b34302d0 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -822,6 +822,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private let e2eContext: ConferenceCallE2EContext? + private var messagesContext: GroupCallMessagesContext? { + didSet { + if let messagesContext = self.messagesContext { + self.messagesStatePromise.set(messagesContext.state) + } + } + } + private let messagesStatePromise = Promise(GroupCallMessagesContext.State(messages: [])) + public var messagesState: Signal { + return self.messagesStatePromise.get() + } + private var lastErrorAlertTimestamp: Double = 0.0 init( @@ -911,6 +923,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.e2eContext = nil } + if let initialCall { + self.messagesContext = accountContext.engine.messages.groupCallMessages( + callId: initialCall.description.id, + reference: .id(id: initialCall.description.id, accessHash: initialCall.description.accessHash), + e2eContext: self.e2eContext + ) + } + var sharedAudioContext = sharedAudioContext if sharedAudioContext == nil { var useSharedAudio = !isStream @@ -2959,6 +2979,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func setIsMuted(action: PresentationGroupCallMuteAction) { + if "".isEmpty { + self.messagesContext?.send(text: "test\(UInt32.random(in: 0 ... UInt32.max))", entities: []) + return + } + if self.isMutedValue == action { return } @@ -3959,6 +3984,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } }) } + + public func sendMessage(text: String, entities: [MessageTextEntity]) { + if let messagesContext = self.messagesContext { + messagesContext.send(text: text, entities: entities) + } + } } public final class TelegramE2EEncryptionProviderImpl: TelegramE2EEncryptionProvider { diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 756178bc4a..df16ffedc2 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -118,6 +118,8 @@ enum AccountStateMutationOperation { case UpdateGroupCallParticipants(id: Int64, accessHash: Int64, participants: [Api.GroupCallParticipant], version: Int32) case UpdateGroupCall(peerId: PeerId?, call: Api.GroupCall) case UpdateGroupCallChainBlocks(id: Int64, accessHash: Int64, subChainId: Int32, blocks: [Data], nextOffset: Int32) + case UpdateGroupCallMessage(id: Int64, authorId: PeerId, text: Api.TextWithEntities) + case UpdateGroupCallOpaqueMessage(id: Int64, authorId: PeerId, data: Data) case UpdateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) case UpdateAttachMenuBots case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String) @@ -409,6 +411,14 @@ struct AccountMutableState { self.addOperation(.UpdateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks, nextOffset: nextOffset)) } + mutating func updateGroupCallMessage(id: Int64, authorId: PeerId, text: Api.TextWithEntities) { + self.addOperation(.UpdateGroupCallMessage(id: id, authorId: authorId, text: text)) + } + + mutating func updateGroupCallOpaqueMessage(id: Int64, authorId: PeerId, data: Data) { + self.addOperation(.UpdateGroupCallOpaqueMessage(id: id, authorId: authorId, data: data)) + } + mutating func updateAutoremoveTimeout(peer: Api.Peer, value: CachedPeerAutoremoveTimeout.Value?) { self.addOperation(.UpdateAutoremoveTimeout(peer: peer, value: value)) } @@ -719,7 +729,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: break case let .AddMessages(messages, location): for message in messages { @@ -855,6 +865,7 @@ struct AccountReplayedFinalState { let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] + let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] @@ -882,6 +893,7 @@ struct AccountFinalStateEvents { let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] let updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] + let groupCallMessageUpdates: [GroupCallMessageUpdate] let storyUpdates: [InternalStoryUpdate] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] @@ -903,10 +915,10 @@ struct AccountFinalStateEvents { let addedConferenceInvitationMessagesIds: [MessageId] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty + return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { + init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set = Set(), reportMessageDelivery: Set = Set(), addedConferenceInvitationMessagesIds: [MessageId] = []) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -916,6 +928,7 @@ struct AccountFinalStateEvents { self.updatedCalls = updatedCalls self.addedCallSignalingData = addedCallSignalingData self.updatedGroupCallParticipants = updatedGroupCallParticipants + self.groupCallMessageUpdates = groupCallMessageUpdates self.storyUpdates = storyUpdates self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates @@ -948,6 +961,7 @@ struct AccountFinalStateEvents { self.updatedCalls = state.updatedCalls self.addedCallSignalingData = state.addedCallSignalingData self.updatedGroupCallParticipants = state.updatedGroupCallParticipants + self.groupCallMessageUpdates = state.groupCallMessageUpdates self.storyUpdates = state.storyUpdates self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates @@ -1016,6 +1030,7 @@ struct AccountFinalStateEvents { updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, + groupCallMessageUpdates: self.groupCallMessageUpdates + other.groupCallMessageUpdates, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index d90a71b734..7b7e8bf07f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -196,7 +196,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return nil } return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash, giftMessageId: giftMessageId, upgradeSeparate: (flags & (1 << 16)) != 0)) - case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate): + case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate, _): guard let gift = StarGift(apiStarGift: apiGift) else { return nil } diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 51f6ea64c1..7fc609da77 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -499,8 +499,8 @@ func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, } } } else { - return account.postbox.transaction { transaction -> Api.InputChannel? in - guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else { + return account.postbox.transaction { transaction -> Api.InputPeer? in + guard let inputChannel = transaction.getPeer(id).flatMap(apiInputPeer) else { return nil } @@ -509,14 +509,14 @@ func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, return inputChannel } |> castError(SetForumChannelTopicPinnedError.self) - |> mapToSignal { inputChannel -> Signal in - guard let inputChannel = inputChannel else { + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer else { return .fail(.generic) } - return account.network.request(Api.functions.channels.reorderPinnedForumTopics( + return account.network.request(Api.functions.messages.reorderPinnedForumTopics( flags: 1 << 0, - channel: inputChannel, + peer: inputPeer, order: threadIds.map(Int32.init(clamping:)) )) |> mapError { _ -> SetForumChannelTopicPinnedError in diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 41e635d980..4bd29bab42 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -115,10 +115,9 @@ private func peerIdsRequiringLocalChatStateFromUpdates(_ updates: [Api.Update]) case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } case let .updateFolderPeers(folderPeers, _, _): for peer in folderPeers { @@ -357,10 +356,9 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } default: break @@ -384,10 +382,9 @@ private func peerIdsRequiringLocalChatStateFromDifference(_ difference: Api.upda case let .updateChannelTooLong(_, channelId, _): let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) peerIds.insert(peerId) - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if order == nil { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - peerIds.insert(peerId) + peerIds.insert(peerId.peerId) } default: break @@ -767,15 +764,15 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] { } else { updatesByChannel[peerId]!.append(update) } - case let .updateChannelPinnedTopic(_, channelId, _): - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + case let .updatePinnedForumTopic(_, peerId, _): + let peerId = peerId.peerId if updatesByChannel[peerId] == nil { updatesByChannel[peerId] = [update] } else { updatesByChannel[peerId]!.append(update) } - case let .updateChannelPinnedTopics(_, channelId, _): - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + case let .updatePinnedForumTopics(_, peerId, _): + let peerId = peerId.peerId if updatesByChannel[peerId] == nil { updatesByChannel[peerId] = [update] } else { @@ -922,11 +919,11 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: channelsToPoll[peerId] = channelPts } } - case let .updateChannelPinnedTopics(_, channelId, order): + case let .updatePinnedForumTopics(_, peerId, order): if let order = order { - updatedState.addUpdatePinnedTopicOrder(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadIds: order.map(Int64.init)) + updatedState.addUpdatePinnedTopicOrder(peerId: peerId.peerId, threadIds: order.map(Int64.init)) } else { - let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + let peerId = peerId.peerId if case .none = channelsToPoll[peerId] { channelsToPoll[peerId] = nil } @@ -1555,9 +1552,9 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: } else { updatedState.addUpdatePinnedSavedItemIds(operation: .sync) } - case let .updateChannelPinnedTopic(flags, channelId, topicId): + case let .updatePinnedForumTopic(flags, peerId, topicId): let isPinned = (flags & (1 << 0)) != 0 - updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned) + updatedState.addUpdatePinnedTopic(peerId: peerId.peerId, threadId: Int64(topicId), isPinned: isPinned) case let .updateReadMessagesContents(_, messages, _, _, date): updatedState.addReadMessagesContents((nil, nil, messages), date: date) case let .updateChannelReadMessagesContents(_, channelId, topMsgId, savedPeerId, messages): @@ -1687,6 +1684,14 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox: if case let .inputGroupCall(id, accessHash) = call { updatedState.updateGroupCallChainBlocks(id: id, accessHash: accessHash, subChainId: subChainId, blocks: blocks.map { $0.makeData() }, nextOffset: nextOffset) } + case let .updateGroupCallMessage(call, fromId, message): + if case let .inputGroupCall(id, _) = call { + updatedState.updateGroupCallMessage(id: id, authorId: fromId.peerId, text: message) + } + case let .updateGroupCallEncryptedMessage(call, fromId, encryptedMessage): + if case let .inputGroupCall(id, _) = call { + updatedState.updateGroupCallOpaqueMessage(id: id, authorId: fromId.peerId, data: encryptedMessage.makeData()) + } case let .updatePeerHistoryTTL(_, peer, ttl): updatedState.updateAutoremoveTimeout(peer: peer, value: CachedPeerAutoremoveTimeout.Value(ttl)) case let .updateLangPackTooLong(langCode): @@ -3613,7 +3618,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3730,6 +3735,7 @@ func replayFinalState( var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] var updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [] + var groupCallMessageUpdates: [GroupCallMessageUpdate] = [] var storyUpdates: [InternalStoryUpdate] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] @@ -4871,6 +4877,13 @@ func replayFinalState( callId, .state(update: GroupCallParticipantsContext.Update.StateUpdate(participants: participants, version: version)) )) + case let .UpdateGroupCallMessage(callId, authorId, text): + switch text { + case let .textWithEntities(text, entities): + groupCallMessageUpdates.append(GroupCallMessageUpdate(callId: callId, update: .newPlaintextMessage(authorId: authorId, text: text, entities: messageTextEntitiesFromApiEntities(entities)))) + } + case let .UpdateGroupCallOpaqueMessage(callId, authorId, data): + groupCallMessageUpdates.append(GroupCallMessageUpdate(callId: callId, update: .newOpaqueMessage(authorId: authorId, data: data))) case let .UpdateGroupCall(peerId, call): switch call { case .groupCall: @@ -5834,6 +5847,7 @@ func replayFinalState( updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, + groupCallMessageUpdates: groupCallMessageUpdates, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 59a730582b..3f61deae1c 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -305,6 +305,11 @@ public final class AccountStateManager { return self.groupCallParticipantUpdatesPipe.signal() } + private let groupCallMessageUpdatesPipe = ValuePipe<[GroupCallMessageUpdate]>() + public var groupCallMessageUpdates: Signal<[GroupCallMessageUpdate], NoError> { + return self.groupCallMessageUpdatesPipe.signal() + } + private let deletedMessagesPipe = ValuePipe<[DeletedMessageId]>() public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.deletedMessagesPipe.signal() @@ -1129,6 +1134,9 @@ public final class AccountStateManager { if !events.updatedGroupCallParticipants.isEmpty { strongSelf.groupCallParticipantUpdatesPipe.putNext(events.updatedGroupCallParticipants) } + if !events.groupCallMessageUpdates.isEmpty { + strongSelf.groupCallMessageUpdatesPipe.putNext(events.groupCallMessageUpdates) + } if !events.storyUpdates.isEmpty { strongSelf.storyUpdatesPipe.putNext(events.storyUpdates) } @@ -1924,6 +1932,12 @@ public final class AccountStateManager { } } + var groupCallMessageUpdates: Signal<[GroupCallMessageUpdate], NoError> { + return self.impl.signalWith { impl, subscriber in + return impl.groupCallMessageUpdates.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) + } + } + public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.impl.signalWith { impl, subscriber in return impl.deletedMessages.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index b20cb92f1c..b6d444c2d9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -3373,3 +3373,248 @@ func _internal_refreshInlineGroupCall(account: Account, messageId: MessageId) -> |> ignoreValues } } + +struct GroupCallMessageUpdate { + enum Update { + case newPlaintextMessage(authorId: PeerId, text: String, entities: [MessageTextEntity]) + case newOpaqueMessage(authorId: PeerId, data: Data) + } + + var callId: Int64 + var update: Update + + init(callId: Int64, update: Update) { + self.callId = callId + self.update = update + } +} + +public final class GroupCallMessagesContext { + public final class Message: Equatable { + public let id: Int64 + public let author: EnginePeer? + public let text: String + public let entities: [MessageTextEntity] + + public init(id: Int64, author: EnginePeer?, text: String, entities: [MessageTextEntity]) { + self.id = id + self.author = author + self.text = text + self.entities = entities + } + + public static func ==(lhs: Message, rhs: Message) -> Bool { + if lhs.id != rhs.id { + return false + } + if lhs === rhs { + return true + } + if lhs.author != rhs.author { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.entities != rhs.entities { + return false + } + return true + } + } + + public struct State: Equatable { + public var messages: [Message] + + public init(messages: [Message]) { + self.messages = messages + } + } + + private final class Impl { + let queue: Queue + let account: Account + let callId: Int64 + let reference: InternalGroupCallReference + let e2eContext: ConferenceCallE2EContext? + + var nextId: Int64 = 1 + + var state: State { + didSet { + self.stateValue.set(self.state) + } + } + let stateValue = ValuePromise() + + var updatesDisposable: Disposable? + let sendMessageDisposables = DisposableSet() + + init(queue: Queue, account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) { + self.queue = queue + self.account = account + self.callId = callId + self.reference = reference + self.e2eContext = e2eContext + + self.state = State(messages: []) + self.stateValue.set(self.state) + + self.updatesDisposable = (account.stateManager.groupCallMessageUpdates + |> deliverOn(self.queue)).startStrict(next: { [weak self] updates in + guard let self else { + return + } + var addedMessages: [(authorId: PeerId, text: String, entities: [MessageTextEntity])] = [] + var addedOpaqueMessages: [(authorId: PeerId, data: Data)] = [] + for update in updates { + if update.callId != self.callId { + continue + } + switch update.update { + case let .newPlaintextMessage(authorId, text, entities): + addedMessages.append((authorId, text, entities)) + case let .newOpaqueMessage(authorId, data): + addedOpaqueMessages.append((authorId, data)) + } + } + + if !addedMessages.isEmpty || !addedOpaqueMessages.isEmpty { + let _ = (self.account.postbox.transaction { transaction -> [Message] in + var messages: [Message] = [] + if let e2eContext = self.e2eContext { + let decryptedMessages = e2eContext.state.with({ state -> [Data?] in + guard let state = state.state else { + return [] + } + var result: [Data?] = [] + for addedOpaqueMessage in addedOpaqueMessages { + result.append(state.decrypt(message: addedOpaqueMessage.data, userId: addedOpaqueMessage.authorId.id._internalGetInt64Value())) + } + return result + }) + for i in 0 ..< addedOpaqueMessages.count { + let addedOpaqueMessage = addedOpaqueMessages[i] + var decryptedMessage: Data? + if i < decryptedMessages.count { + decryptedMessage = decryptedMessages[i] + } + guard let decryptedMessage else { + continue + } + guard let text = String(data: decryptedMessage, encoding: .utf8) else { + continue + } + + let messageId = self.nextId + self.nextId += 1 + messages.append(Message( + id: messageId, + author: transaction.getPeer(addedOpaqueMessage.authorId).flatMap(EnginePeer.init), + text: text, + entities: [] + )) + } + } else { + for addedMessage in addedMessages { + let messageId = self.nextId + self.nextId += 1 + messages.append(Message( + id: messageId, + author: transaction.getPeer(addedMessage.authorId).flatMap(EnginePeer.init), + text: addedMessage.text, + entities: addedMessage.entities + )) + } + } + return messages + } + |> deliverOn(self.queue)).startStandalone(next: { [weak self] messages in + guard let self else { + return + } + var state = self.state + state.messages.append(contentsOf: messages) + self.state = state + }) + } + }) + } + + deinit { + self.updatesDisposable?.dispose() + self.sendMessageDisposables.dispose() + } + + func send(text: String, entities: [MessageTextEntity]) { + let accountPeerId = self.account.peerId + let _ = (self.account.postbox.transaction { transaction -> Peer? in + return transaction.getPeer(accountPeerId) + } + |> deliverOn(self.queue)).startStandalone(next: { [weak self] accountPeer in + guard let self else { + return + } + + let messageId = self.nextId + self.nextId += 1 + + var state = self.state + state.messages.append(Message( + id: messageId, + author: accountPeer.flatMap(EnginePeer.init), + text: text, + entities: entities + )) + self.state = state + + if let e2eContext = self.e2eContext { + let messageData = text.data(using: .utf8)! + let encryptedMessage = e2eContext.state.with({ state -> Data? in + guard let state = state.state else { + return nil + } + return state.encrypt(message: messageData, channelId: 2, plaintextPrefixLength: 0) + }) + if let encryptedMessage { + self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallEncryptedMessage( + call: self.reference.apiInputGroupCall, + encryptedMessage: Buffer(data: encryptedMessage) + )).startStrict()) + } + } else { + self.sendMessageDisposables.add(self.account.network.request(Api.functions.phone.sendGroupCallMessage( + call: self.reference.apiInputGroupCall, + message: .textWithEntities( + text: text, + entities: apiEntitiesFromMessageTextEntities(entities, associatedPeers: SimpleDictionary()) + ) + )).startStrict()) + } + }) + } + } + + private let queue: Queue + private let impl: QueueLocalObject + + public var state: Signal { + return self.impl.signalWith { impl, subscriber in + return impl.stateValue.get().startStandalone(next: subscriber.putNext) + } + } + + init(account: Account, callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) { + let queue = Queue(name: "GroupCallMessagesContext") + self.queue = queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, account: account, callId: callId, reference: reference, e2eContext: e2eContext) + }) + } + + public func send(text: String, entities: [MessageTextEntity]) { + self.impl.with { impl in + impl.send(text: text, entities: entities) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 204e060b55..b8e432e461 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -1631,6 +1631,10 @@ public extension TelegramEngine { public func refreshGlobalPostSearchState() -> Signal { return _internal_refreshGlobalPostSearchState(account: self.account) } + + public func groupCallMessages(callId: Int64, reference: InternalGroupCallReference, e2eContext: ConferenceCallE2EContext?) -> GroupCallMessagesContext { + return GroupCallMessagesContext(account: self.account, callId: callId, reference: reference, e2eContext: e2eContext) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index f127989c2e..303ee0eeb4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -2609,7 +2609,7 @@ public final class ProfileGiftsContext { extension ProfileGiftsContext.State.StarGift { init?(apiSavedStarGift: Api.SavedStarGift, peerId: EnginePeer.Id, transaction: Transaction) { switch apiSavedStarGift { - case let .savedStarGift(flags, fromId, date, apiGift, message, msgId, savedId, convertStars, upgradeStars, canExportDate, transferStars, canTransferAt, canResaleAt, collectionIds, prepaidUpgradeHash): + case let .savedStarGift(flags, fromId, date, apiGift, message, msgId, savedId, convertStars, upgradeStars, canExportDate, transferStars, canTransferAt, canResaleAt, collectionIds, prepaidUpgradeHash, _): guard let gift = StarGift(apiStarGift: apiGift) else { return nil } From e0d511165c46f730be6fba280d119563cb8d0a48 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 12 Sep 2025 11:02:05 +0200 Subject: [PATCH 5/8] Fix call messages --- .../TelegramCallsUI/Sources/PresentationGroupCall.swift | 4 ++-- .../Sources/TelegramEngine/Calls/GroupCalls.swift | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index b3b34302d0..1a3ebb8d5f 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -2979,10 +2979,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public func setIsMuted(action: PresentationGroupCallMuteAction) { - if "".isEmpty { + /*if "".isEmpty { self.messagesContext?.send(text: "test\(UInt32.random(in: 0 ... UInt32.max))", entities: []) return - } + }*/ if self.isMutedValue == action { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index b6d444c2d9..2630fe9ff7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -3473,9 +3473,13 @@ public final class GroupCallMessagesContext { } switch update.update { case let .newPlaintextMessage(authorId, text, entities): - addedMessages.append((authorId, text, entities)) + if authorId != self.account.peerId { + addedMessages.append((authorId, text, entities)) + } case let .newOpaqueMessage(authorId, data): - addedOpaqueMessages.append((authorId, data)) + if authorId != self.account.peerId { + addedOpaqueMessages.append((authorId, data)) + } } } From 5a8bd1d98dcc53ab04568736a8839eb6b62b1db9 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Tue, 16 Sep 2025 10:11:54 +0400 Subject: [PATCH 6/8] Temp --- .../Sources/PagerComponent.swift | 24 +- .../Sources/ComposePollScreen.swift | 1 + submodules/Display/Source/DeviceMetrics.swift | 6 +- submodules/Display/Source/NavigationBar.swift | 8 +- .../Sources/TabBarContollerNode.swift | 4 +- .../Sources/DefaultDayPresentationTheme.swift | 2 +- .../Sources/AvatarEditorScreen.swift | 1 + .../ChatRecordingViewOnceButtonNode.swift | 2 +- .../ChatTextInputActionButtonsNode.swift | 4 +- .../Sources/ChatTextInputPanelNode.swift | 6 +- .../ChatEntityKeyboardInputNode/BUILD | 1 + .../Sources/ChatEntityKeyboardInputNode.swift | 123 +++++++++- .../ChatTextInputMediaRecordingButton.swift | 2 +- .../EmojiStatusSelectionComponent.swift | 1 + .../Sources/EmojiPagerContentComponent.swift | 35 ++- .../Sources/EmojiPagerContentSignals.swift | 12 +- .../Sources/EmojiSearchContent.swift | 2 + .../Sources/EntityKeyboard.swift | 14 +- .../EntityKeyboardBottomPanelComponent.swift | 6 +- ...tyKeyboardTopContainerPanelComponent.swift | 11 +- .../Sources/GifPagerContentComponent.swift | 4 +- .../Sources/ForumCreateTopicScreen.swift | 1 + .../Sources/GlassBackgroundComponent.swift | 216 ++++++++---------- .../Sources/MediaEditorScreen.swift | 1 + .../Sources/EmojiSelectionComponent.swift | 1 + .../Sources/EmojiPickerItem.swift | 1 + .../Sources/StickerPickerScreen.swift | 1 + .../Sources/ChatControllerNode.swift | 27 ++- .../ChatHistoryNavigationButtonNode.swift | 4 +- 29 files changed, 342 insertions(+), 179 deletions(-) diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index de0f925063..b4c8c28192 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -14,7 +14,7 @@ open class PagerExternalTopPanelContainer: SparseContainerView { } public protocol PagerContentViewWithBackground: UIView { - func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, transition: ComponentTransition) + func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, externalTintMaskContainer: UIView?, transition: ComponentTransition) } public final class PagerComponentChildEnvironment: Equatable { @@ -206,6 +206,7 @@ public final class PagerComponent>? public let externalBottomPanelContainer: PagerExternalTopPanelContainer? + public let externalTintMaskContainer: UIView? public let panelStateUpdated: ((PagerComponentPanelState, ComponentTransition) -> Void)? public let isTopPanelExpandedUpdated: (Bool, ComponentTransition) -> Void public let isTopPanelHiddenUpdated: (Bool, ComponentTransition) -> Void @@ -228,6 +229,7 @@ public final class PagerComponent>?, externalBottomPanelContainer: PagerExternalTopPanelContainer?, + externalTintMaskContainer: UIView?, panelStateUpdated: ((PagerComponentPanelState, ComponentTransition) -> Void)?, isTopPanelExpandedUpdated: @escaping (Bool, ComponentTransition) -> Void, isTopPanelHiddenUpdated: @escaping (Bool, ComponentTransition) -> Void, @@ -249,6 +251,7 @@ public final class PagerComponent + let tintMaskContainer: UIView var scrollingPanelOffsetToTopEdge: CGFloat = 0.0 var scrollingPanelOffsetToBottomEdge: CGFloat = .greatestFiniteMagnitude var scrollingPanelOffsetFraction: CGFloat = 0.0 @@ -315,6 +322,7 @@ public final class PagerComponent) { self.view = view + self.tintMaskContainer = UIView() } } @@ -847,6 +855,9 @@ public final class PagerComponent CGFloat? { switch self { case .iPhoneX, .iPhoneXSMax, .iPhoneXr, .iPhone12Mini, .iPhone12, .iPhone12ProMax, .iPhone13Mini, .iPhone13, .iPhone13Pro, .iPhone13ProMax, .iPhone14Pro, .iPhone14ProMax, .iPhone16Pro, .iPhone16ProMax: - return inLandscape ? 21.0 : 34.0 + if #available(iOS 26.0, *) { + return 20.0 + } else { + return inLandscape ? 21.0 : 34.0 + } case .iPhone14ProZoomed: return inLandscape ? 21.0 : 28.0 case .iPhone14ProMaxZoomed: diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 3cb91fded3..45b6568209 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -312,6 +312,7 @@ open class BlurredBackgroundView: UIView { private var _color: UIColor? private var enableBlur: Bool + private var customBlurRadius: CGFloat? public private(set) var effectView: UIVisualEffectView? private let backgroundView: UIView @@ -326,9 +327,10 @@ open class BlurredBackgroundView: UIView { } } - public init(color: UIColor?, enableBlur: Bool = true) { + public init(color: UIColor?, enableBlur: Bool = true, customBlurRadius: CGFloat? = nil) { self._color = nil self.enableBlur = enableBlur + self.customBlurRadius = customBlurRadius self.backgroundView = UIView() @@ -349,7 +351,6 @@ open class BlurredBackgroundView: UIView { if let color = self._color, self.enableBlur && !sharedIsReduceTransparencyEnabled && ((color.alpha > .ulpOfOne && color.alpha < 0.95) || forceKeepBlur) { if self.effectView == nil { let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) - //effectView.isHidden = true for subview in effectView.subviews { if subview.description.contains("VisualEffectSubview") { @@ -373,6 +374,9 @@ open class BlurredBackgroundView: UIView { if !allowedKeys.contains(filterName) { return false } + if let customBlurRadius = self.customBlurRadius, filterName == "gaussianBlur" { + filter.setValue(customBlurRadius as NSNumber, forKey: "inputRadius") + } return true } } diff --git a/submodules/TabBarUI/Sources/TabBarContollerNode.swift b/submodules/TabBarUI/Sources/TabBarContollerNode.swift index 6b6921fd92..064f74ffd3 100644 --- a/submodules/TabBarUI/Sources/TabBarContollerNode.swift +++ b/submodules/TabBarUI/Sources/TabBarContollerNode.swift @@ -179,9 +179,9 @@ final class TabBarControllerNode: ASDisplayNode { if bottomInset == 0.0 { bottomInset = 8.0 } else { - bottomInset = max(bottomInset - 13.0, 8.0) + bottomInset = max(bottomInset, 8.0) } - let sideInset: CGFloat = 21.0 + let sideInset: CGFloat = 20.0 var selectedId: AnyHashable? if self.selectedIndex < self.tabBarItems.count { diff --git a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift index c7af0c0ed6..35dff9586b 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDayPresentationTheme.swift @@ -941,7 +941,7 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio let inputPanel = PresentationThemeChatInputPanel( panelBackgroundColor: rootNavigationBar.blurredBackgroundColor, panelBackgroundColorNoWallpaper: UIColor(rgb: 0xffffff), - panelSeparatorColor: UIColor(rgb: 0xb2b2b2), + panelSeparatorColor: UIColor(white: 1.0, alpha: 0.5), panelControlAccentColor: defaultDayAccentColor, panelControlColor: UIColor(rgb: 0x858e99), panelControlDisabledColor: UIColor(rgb: 0x727b87, alpha: 0.5), diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index bcce0831af..5e6b6f25fd 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -1219,6 +1219,7 @@ final class AvatarEditorScreenComponent: Component { defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift index d62b84c0c2..e61edcd038 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift @@ -99,7 +99,7 @@ public final class ChatRecordingViewOnceButtonNode: HighlightTrackingButtonNode let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - innerSize.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - innerSize.height / 2.0)), size: innerSize) self.backgroundView.frame = backgroundFrame - self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate) + self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) if let iconImage = self.iconNode.image { let iconFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width / 2.0 - iconImage.size.width / 2.0), y: floorToScreenPixels(size.height / 2.0 - iconImage.size.height / 2.0)), size: iconImage.size) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift index 75380ef626..8f94a44ff8 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputActionButtonsNode/Sources/ChatTextInputActionButtonsNode.swift @@ -334,7 +334,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag } transition.updateFrame(view: self.micButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) + self.micButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(), size: size)) self.micButton.layoutItems() @@ -349,7 +349,7 @@ public final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessag transition.updateFrame(view: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(view: self.expandMediaInputButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: size)) - self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) + self.expandMediaInputButtonBackgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) if let image = self.expandMediaInputButtonIcon.image { let expandIconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size) transition.updatePosition(layer: self.expandMediaInputButtonIcon.layer, position: expandIconFrame.center) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 42145a2428..204e7a6c10 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -2213,7 +2213,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let alphaTransitionIn: ContainedViewLayoutTransition = transition.isAnimated ? ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) : .immediate let alphaTransitionOut: ContainedViewLayoutTransition = transition.isAnimated ? ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) : .immediate - let accessoryPanelAnimationBlurRadius: CGFloat = 10.0 + let accessoryPanelAnimationBlurRadius: CGFloat = 20.0 var removedAccessoryPanelView: UIView? if let currentAccessoryPanel = self.accessoryPanel, currentAccessoryPanel.component.id != accessoryPanel?.id { @@ -2334,7 +2334,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg transition.updateFrame(node: self.textInputContainer, frame: textInputContainerBackgroundFrame) transition.updateFrame(view: self.textInputContainerBackgroundView, frame: CGRect(origin: CGPoint(), size: textInputContainerBackgroundFrame.size)) - self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) + self.textInputContainerBackgroundView.update(size: textInputContainerBackgroundFrame.size, cornerRadius: floor(minimalInputHeight * 0.5), isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) if let removedAccessoryPanelView { if let removedAccessoryPanelView = removedAccessoryPanelView as? ChatInputAccessoryPanelView { @@ -2625,7 +2625,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0)) self.attachmentButtonBackground.frame = CGRect(origin: CGPoint(), size: attachmentButtonFrame.size) - self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) : .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: ComponentTransition(transition)) + self.attachmentButtonBackground.update(size: attachmentButtonFrame.size, cornerRadius: attachmentButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: isEditingMedia ? .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) : .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame) transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame) diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD index b5b70dc871..6c8e0d451e 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/BUILD @@ -41,6 +41,7 @@ swift_library( "//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView", "//submodules/AttachmentTextInputPanelNode", "//submodules/TelegramUI/Components/BatchVideoRendering", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 72a31c25c4..0a98d73031 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -31,6 +31,7 @@ import Pasteboard import EntityKeyboardGifContent import LegacyMessageInputPanelInputView import AttachmentTextInputPanelNode +import GlassBackgroundComponent public final class EmptyInputView: UIView, UIInputViewAudioFeedback { public var enableInputClicksWhenVisible: Bool { @@ -168,6 +169,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { hasStickers: Bool = true, hasGifs: Bool = true, hideBackground: Bool = false, + maskEdge: Bool = false, forceHasPremium: Bool = false, sendGif: ((FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool)? ) -> Signal { @@ -187,7 +189,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId, hasSearch: hasSearch, forceHasPremium: forceHasPremium, - hideBackground: hideBackground + hideBackground: hideBackground, + maskEdge: maskEdge ) let stickerNamespaces: [ItemCollectionId.Namespace] = [Namespaces.ItemCollection.CloudStickerPacks] @@ -414,6 +417,14 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return self.externalTopPanelContainerImpl } + private let clippingView: UIView + private var backgroundView: BlurredBackgroundView? + private var backgroundTintView: UIImageView? + private var backgroundChromeView: UIImageView? + private var backgroundTintMaskView: UIView? + private var backgroundTintMaskContentView: UIView? + private var externalBackground: EmojiPagerContentComponent.ExternalBackground? + public var switchToTextInput: (() -> Void)? private var currentState: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool)? @@ -476,6 +487,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.interaction = interaction + self.clippingView = UIView() + self.clippingView.clipsToBounds = true + self.clippingView.layer.cornerRadius = 20.0 + self.entityKeyboardView = ComponentHostView() super.init() @@ -485,7 +500,41 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { self.topBackgroundExtension = 34.0 self.followsDefaultHeight = true - self.view.addSubview(self.entityKeyboardView) + if "".isEmpty { + let backgroundView = BlurredBackgroundView(color: .black, enableBlur: true) + self.backgroundView = backgroundView + self.view.addSubview(backgroundView) + + let backgroundTintView = UIImageView() + self.backgroundTintView = backgroundTintView + self.view.addSubview(backgroundTintView) + + let backgroundTintMaskView = UIView() + backgroundTintMaskView.backgroundColor = .white + self.backgroundTintMaskView = backgroundTintMaskView + if let filter = CALayer.luminanceToAlpha() { + backgroundTintMaskView.layer.filters = [filter] + } + backgroundTintView.mask = backgroundTintMaskView + + let backgroundTintMaskContentView = UIView() + backgroundTintMaskView.addSubview(backgroundTintMaskContentView) + self.backgroundTintMaskContentView = backgroundTintMaskContentView + + let backgroundChromeView = UIImageView() + self.backgroundChromeView = backgroundChromeView + + self.externalBackground = EmojiPagerContentComponent.ExternalBackground( + effectContainerView: backgroundTintMaskContentView + ) + } + + self.clippingView.addSubview(self.entityKeyboardView) + self.view.addSubview(self.clippingView) + + if let backgroundChromeView = self.backgroundChromeView { + self.view.addSubview(backgroundChromeView) + } self.externalTopPanelContainerImpl = PagerExternalTopPanelContainer() @@ -1162,7 +1211,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId, peekBehavior: stickerPeekBehavior, customLayout: nil, - externalBackground: nil, + externalBackground: self.externalBackground, externalExpansionView: nil, customContentView: nil, useOpaqueTheme: self.useOpaqueTheme, @@ -1509,7 +1558,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { chatPeerId: chatPeerId, peekBehavior: stickerPeekBehavior, customLayout: nil, - externalBackground: nil, + externalBackground: self.externalBackground, externalExpansionView: nil, customContentView: nil, useOpaqueTheme: self.useOpaqueTheme, @@ -1739,6 +1788,22 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, standardInputHeight: standardInputHeight, inputHeight: inputHeight, maximumHeight: maximumHeight, inputPanelHeight: inputPanelHeight, transition: .immediate, interfaceState: interfaceState, layoutMetrics: layoutMetrics, deviceMetrics: deviceMetrics, isVisible: isVisible, isExpanded: isExpanded) } + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = super.hitTest(point, with: event) { + return result + } + + if let backgroundView = self.backgroundView, backgroundView.frame.contains(point) { + for subview in self.view.subviews.reversed() { + if let result = subview.hitTest(self.view.convert(point, to: subview), with: event) { + return result + } + } + } + + return nil + } + public override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, layoutMetrics: LayoutMetrics, deviceMetrics: DeviceMetrics, isVisible: Bool, isExpanded: Bool) -> (CGFloat, CGFloat) { self.currentState = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, layoutMetrics, deviceMetrics, isVisible, isExpanded) @@ -1849,7 +1914,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { defaultToEmojiTab: self.defaultToEmojiTab, externalTopPanelContainer: self.externalTopPanelContainerImpl, externalBottomPanelContainer: nil, - displayTopPanelBackground: self.opaqueTopPanelBackground ? .opaque : .none, + externalTintMaskContainer: self.backgroundTintMaskContentView, + displayTopPanelBackground: self.opaqueTopPanelBackground ? .opaque : .blur, topPanelExtensionUpdated: { [weak self] topPanelExtension, transition in guard let strongSelf = self else { return @@ -1939,7 +2005,52 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { environment: {}, containerSize: CGSize(width: width, height: expandedHeight) ) - transition.updateFrame(view: self.entityKeyboardView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize)) + + var clippingFrame = CGRect(origin: CGPoint(), size: entityKeyboardSize) + clippingFrame.size.height += 32.0 + + var entityKeyboardSizeFrame = CGRect(origin: CGPoint(), size: entityKeyboardSize) + if self.hideInput { + clippingFrame.size.height += self.topBackgroundExtension + clippingFrame.origin.y -= self.topBackgroundExtension + entityKeyboardSizeFrame.origin.y += self.topBackgroundExtension + } + + transition.updateFrame(view: self.entityKeyboardView, frame: entityKeyboardSizeFrame) + + transition.updateFrame(view: self.clippingView, frame: clippingFrame) + + if let backgroundView = self.backgroundView, let backgroundTintView = self.backgroundTintView, let backgroundTintMaskView = self.backgroundTintMaskView, let backgroundTintMaskContentView = self.backgroundTintMaskContentView, let backgroundChromeView = self.backgroundChromeView { + var backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: entityKeyboardSize) + if self.hideInput { + backgroundFrame.size.height += self.topBackgroundExtension + backgroundFrame.origin.y -= self.topBackgroundExtension + } + backgroundFrame.size.height += 32.0 + + if backgroundChromeView.image == nil { + backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 20.0 * 2.0, height: 20.0 * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear) + } + if backgroundTintView.image == nil { + backgroundTintView.image = generateStretchableFilledCircleImage(diameter: 20.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate) + } + backgroundTintView.tintColor = interfaceState.theme.chat.inputMediaPanel.backgroundColor + + transition.updateFrame(view: backgroundView, frame: backgroundFrame) + backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate) + backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition) + + transition.updateFrame(view: backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0)) + + var backgroundTintMaskContentFrame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + if self.hideInput { + backgroundTintMaskContentFrame.origin.y += self.topBackgroundExtension + } + transition.updateFrame(view: backgroundTintView, frame: backgroundFrame) + + transition.updateFrame(view: backgroundTintMaskView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) + transition.updateFrame(view: backgroundTintMaskContentView, frame: backgroundTintMaskContentFrame) + } let layoutTime = CFAbsoluteTimeGetCurrent() - startTime if layoutTime > 0.1 { diff --git a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift index e2d2c0b811..7f1ab9ea04 100644 --- a/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift +++ b/submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton/Sources/ChatTextInputMediaRecordingButton.swift @@ -467,7 +467,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM tintColor = UIColor(white: 0.0, alpha: 0.5) } else { isDark = self.theme.overallDarkAppearance - tintColor = self.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65) + tintColor = self.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7) } let view = WrapperBlurrredBackgroundView(size: CGSize(width: 40.0, height: 72.0), isDark: isDark, tintColor: tintColor) diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 9728c603d0..01d0a629d1 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -187,6 +187,7 @@ public final class EmojiStatusSelectionComponent: Component { defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index d861a02f6a..73f5c6ea89 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -623,6 +623,7 @@ public final class EmojiPagerContentComponent: Component { public let searchState: SearchState public let warpContentsOnEdges: Bool public let hideBackground: Bool + public let maskEdge: Bool public let displaySearchWithPlaceholder: String? public let searchCategories: EmojiSearchCategories? public let searchInitiallyHidden: Bool @@ -648,6 +649,7 @@ public final class EmojiPagerContentComponent: Component { searchState: SearchState, warpContentsOnEdges: Bool, hideBackground: Bool, + maskEdge: Bool, displaySearchWithPlaceholder: String?, searchCategories: EmojiSearchCategories?, searchInitiallyHidden: Bool, @@ -672,6 +674,7 @@ public final class EmojiPagerContentComponent: Component { self.searchState = searchState self.warpContentsOnEdges = warpContentsOnEdges self.hideBackground = hideBackground + self.maskEdge = maskEdge self.displaySearchWithPlaceholder = displaySearchWithPlaceholder self.searchCategories = searchCategories self.searchInitiallyHidden = searchInitiallyHidden @@ -699,6 +702,7 @@ public final class EmojiPagerContentComponent: Component { searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, + maskEdge: self.maskEdge, displaySearchWithPlaceholder: self.displaySearchWithPlaceholder, searchCategories: self.searchCategories, searchInitiallyHidden: self.searchInitiallyHidden, @@ -727,6 +731,7 @@ public final class EmojiPagerContentComponent: Component { searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, + maskEdge: self.maskEdge, displaySearchWithPlaceholder: self.displaySearchWithPlaceholder, searchCategories: self.searchCategories, searchInitiallyHidden: self.searchInitiallyHidden, @@ -755,6 +760,7 @@ public final class EmojiPagerContentComponent: Component { searchState: searchState, warpContentsOnEdges: self.warpContentsOnEdges, hideBackground: self.hideBackground, + maskEdge: self.maskEdge, displaySearchWithPlaceholder: self.displaySearchWithPlaceholder, searchCategories: self.searchCategories, searchInitiallyHidden: self.searchInitiallyHidden, @@ -811,6 +817,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.hideBackground != rhs.hideBackground { return false } + if lhs.maskEdge != rhs.maskEdge { + return false + } if lhs.displaySearchWithPlaceholder != rhs.displaySearchWithPlaceholder { return false } @@ -4016,12 +4025,12 @@ public final class EmojiPagerContentComponent: Component { self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(ContentAnimation(type: .groupExpanded(id: groupId)))) } - public func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, transition: ComponentTransition) { + public func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, externalTintMaskContainer: UIView?, transition: ComponentTransition) { guard let component = self.component, let keyboardChildEnvironment = self.keyboardChildEnvironment, let pagerEnvironment = self.pagerEnvironment else { return } - if let externalBackground = component.inputInteractionHolder.inputInteraction?.externalBackground, let effectContainerView = externalBackground.effectContainerView { + if let effectContainerView = externalTintMaskContainer { let mirrorContentClippingView: UIView if let current = self.mirrorContentClippingView { mirrorContentClippingView = current @@ -4066,17 +4075,19 @@ public final class EmojiPagerContentComponent: Component { if component.hideBackground { self.backgroundView.isHidden = true - let maskLayer: FadingMaskLayer - if let current = self.fadingMaskLayer { - maskLayer = current - } else { - maskLayer = FadingMaskLayer() - self.fadingMaskLayer = maskLayer + if component.maskEdge { + let maskLayer: FadingMaskLayer + if let current = self.fadingMaskLayer { + maskLayer = current + } else { + maskLayer = FadingMaskLayer() + self.fadingMaskLayer = maskLayer + } + if self.layer.mask == nil { + self.layer.mask = maskLayer + } + maskLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((topPanelHeight - 34.0) * 0.75)), size: backgroundFrame.size) } - if self.layer.mask == nil { - self.layer.mask = maskLayer - } - maskLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((topPanelHeight - 34.0) * 0.75)), size: backgroundFrame.size) } else if component.warpContentsOnEdges { self.backgroundView.isHidden = true } else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 65d4422d7a..c16e3501b5 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -65,7 +65,8 @@ public extension EmojiPagerContentComponent { hasRecent: Bool = true, forceHasPremium: Bool = false, premiumIfSavedMessages: Bool = true, - hideBackground: Bool = false + hideBackground: Bool = false, + maskEdge: Bool = false ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -1596,6 +1597,7 @@ public extension EmojiPagerContentComponent { searchState: .empty(hasResults: false), warpContentsOnEdges: warpContentsOnEdges, hideBackground: hideBackground, + maskEdge: maskEdge, displaySearchWithPlaceholder: displaySearchWithPlaceholder, searchCategories: searchCategories, searchInitiallyHidden: searchInitiallyHidden, @@ -1632,7 +1634,8 @@ public extension EmojiPagerContentComponent { hasAdd: Bool = false, searchIsPlaceholderOnly: Bool = true, subject: StickersSubject = .chatStickers, - hideBackground: Bool = false + hideBackground: Bool = false, + maskEdge: Bool = false ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -2178,6 +2181,7 @@ public extension EmojiPagerContentComponent { searchState: .empty(hasResults: false), warpContentsOnEdges: warpContentsOnEdges, hideBackground: hideBackground, + maskEdge: maskEdge, displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, searchCategories: searchCategories, searchInitiallyHidden: true, @@ -2197,7 +2201,8 @@ public extension EmojiPagerContentComponent { animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, hasSearch: Bool, - hideBackground: Bool = false + hideBackground: Bool = false, + maskEdge: Bool = false ) -> Signal { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) let isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -2330,6 +2335,7 @@ public extension EmojiPagerContentComponent { searchState: .empty(hasResults: false), warpContentsOnEdges: warpContentsOnEdges, hideBackground: hideBackground, + maskEdge: maskEdge, displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil, searchCategories: searchCategories, searchInitiallyHidden: true, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 7e04ed8f6e..1366bfafd4 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -469,6 +469,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode searchState: .empty(hasResults: false), warpContentsOnEdges: false, hideBackground: false, + maskEdge: false, displaySearchWithPlaceholder: self.presentationData.strings.EmojiSearch_SearchEmojiPlaceholder, searchCategories: nil, searchInitiallyHidden: false, @@ -509,6 +510,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index 1935765c7e..5916d871fa 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -106,6 +106,7 @@ public final class EntityKeyboardComponent: Component { public let defaultToEmojiTab: Bool public let externalTopPanelContainer: PagerExternalTopPanelContainer? public let externalBottomPanelContainer: PagerExternalTopPanelContainer? + public let externalTintMaskContainer: UIView? public let displayTopPanelBackground: DisplayTopPanelBackground public let topPanelExtensionUpdated: (CGFloat, ComponentTransition) -> Void public let topPanelScrollingOffset: (CGFloat, ComponentTransition) -> Void @@ -141,6 +142,7 @@ public final class EntityKeyboardComponent: Component { defaultToEmojiTab: Bool, externalTopPanelContainer: PagerExternalTopPanelContainer?, externalBottomPanelContainer: PagerExternalTopPanelContainer?, + externalTintMaskContainer: UIView?, displayTopPanelBackground: DisplayTopPanelBackground, topPanelExtensionUpdated: @escaping (CGFloat, ComponentTransition) -> Void, topPanelScrollingOffset: @escaping (CGFloat, ComponentTransition) -> Void, @@ -175,6 +177,7 @@ public final class EntityKeyboardComponent: Component { self.defaultToEmojiTab = defaultToEmojiTab self.externalTopPanelContainer = externalTopPanelContainer self.externalBottomPanelContainer = externalBottomPanelContainer + self.externalTintMaskContainer = externalTintMaskContainer self.displayTopPanelBackground = displayTopPanelBackground self.topPanelExtensionUpdated = topPanelExtensionUpdated self.topPanelScrollingOffset = topPanelScrollingOffset @@ -716,6 +719,12 @@ public final class EntityKeyboardComponent: Component { forceUpdate = true } + var bottomPanelContainerInsets = component.containerInsets + if bottomPanelContainerInsets.left == 0.0 && bottomPanelContainerInsets.bottom != 0.0 { + bottomPanelContainerInsets.left += 16.0 + bottomPanelContainerInsets.right += 16.0 + } + let isContentInFocus = component.isContentInFocus && self.searchComponent == nil let pagerSize = self.pagerView.update( transition: transition, @@ -732,19 +741,20 @@ public final class EntityKeyboardComponent: Component { topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent( theme: component.theme, overflowHeight: component.hiddenInputHeight, - topInset: 12.0, + topInset: 6.0, displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground )), externalTopPanelContainer: component.externalTopPanelContainer, bottomPanel: component.displayBottomPanel ? AnyComponent(EntityKeyboardBottomPanelComponent( theme: component.theme, - containerInsets: component.containerInsets, + containerInsets: bottomPanelContainerInsets, deleteBackwards: { [weak self] in self?.component?.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards?() AudioServicesPlaySystemSound(0x451) } )) : nil, externalBottomPanelContainer: component.externalBottomPanelContainer, + externalTintMaskContainer: component.externalTintMaskContainer, panelStateUpdated: { [weak self] panelState, transition in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift index 73390b8127..d81a344101 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardBottomPanelComponent.swift @@ -163,7 +163,7 @@ final class EntityKeyboardBottomPanelComponent: Component { private var component: EntityKeyboardBottomPanelComponent? override init(frame: CGRect) { - self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 5.0) self.separatorView = UIView() self.separatorView.isUserInteractionEnabled = false @@ -186,8 +186,8 @@ final class EntityKeyboardBottomPanelComponent: Component { func update(component: EntityKeyboardBottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { if self.component?.theme !== component.theme { - self.separatorView.backgroundColor = component.theme.chat.inputMediaPanel.panelSeparatorColor - self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate) + self.separatorView.backgroundColor = component.theme.chat.inputPanel.panelSeparatorColor + self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate) self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift index c0f3538db9..85a1fc65ee 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopContainerPanelComponent.swift @@ -206,7 +206,8 @@ final class EntityKeyboardTopContainerPanelComponent: Component { if let current = self.backgroundView { backgroundView = current } else { - backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) + backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 5.0) + self.backgroundView = backgroundView self.insertSubview(backgroundView, at: 0) } @@ -218,12 +219,12 @@ final class EntityKeyboardTopContainerPanelComponent: Component { self.insertSubview(backgroundSeparatorView, aboveSubview: backgroundView) } - backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate) - backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition) - transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height))) + backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.65), transition: .immediate) + backgroundView.update(size: CGSize(width: availableSize.width, height: height + component.overflowHeight), transition: transition.containedViewLayoutTransition) + transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: -component.overflowHeight), size: CGSize(width: availableSize.width, height: height + component.overflowHeight))) backgroundSeparatorView.backgroundColor = component.theme.chat.inputPanel.panelSeparatorColor - transition.setFrame(view: backgroundSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: availableSize.width, height: UIScreenPixel))) + transition.setFrame(view: backgroundSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: availableSize.width, height: UIScreenPixel))) } else if case .none = component.displayBackground { self.backgroundColor = nil diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index b60b78d694..9fa0ec62b8 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -876,7 +876,7 @@ public final class GifPagerContentComponent: Component { } } - public func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, transition: ComponentTransition) { + public func pagerUpdateBackground(backgroundFrame: CGRect, topPanelHeight: CGFloat, externalTintMaskContainer: UIView?, transition: ComponentTransition) { guard let theme = self.theme else { return } @@ -912,6 +912,8 @@ public final class GifPagerContentComponent: Component { transition.setFrame(view: self.backgroundView, frame: backgroundFrame) self.backgroundView.update(size: backgroundFrame.size, transition: transition.containedViewLayoutTransition) + self.backgroundView.isHidden = hideBackground + if let vibrancyEffectView = self.vibrancyEffectView { transition.setFrame(view: vibrancyEffectView, frame: CGRect(origin: CGPoint(x: 0.0, y: -backgroundFrame.minY), size: CGSize(width: backgroundFrame.width, height: backgroundFrame.height + backgroundFrame.minY))) } diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 182c9ed0f9..08000283b8 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -350,6 +350,7 @@ private final class TopicIconSelectionComponent: Component { defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift index 7ac15dbe9b..e0121387fc 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/GlassBackgroundComponent.swift @@ -4,86 +4,6 @@ import Display import ComponentFlow import ComponentDisplayAdapters -private func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage { - var size = size - if size == .zero { - size = CGSize(width: 1.0, height: 1.0) - } - - return generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let maxColor = UIColor(white: 1.0, alpha: isDark ? 0.67 : 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)) -} - private final class ContentContainer: UIView { private let maskContentView: UIView @@ -345,7 +265,6 @@ public final class GlassBackgroundView: UIView { private let foregroundView: UIImageView? private let shadowView: UIImageView? - private let shadowMaskView: UIImageView? public let maskContentView: UIView private let contentContainer: ContentContainer @@ -371,14 +290,12 @@ public final class GlassBackgroundView: UIView { nativeView.traitOverrides.userInterfaceStyle = .light //self.foregroundView = UIImageView() self.foregroundView = nil - self.shadowView = nil - self.shadowMaskView = nil + self.shadowView = UIImageView() } else { self.backgroundNode = NavigationBackgroundNode(color: .black, enableBlur: true, customBlurRadius: 5.0) self.nativeView = nil self.foregroundView = UIImageView() self.shadowView = UIImageView() - self.shadowMaskView = UIImageView() } self.maskContentView = UIView() @@ -393,9 +310,6 @@ public final class GlassBackgroundView: UIView { if let shadowView = self.shadowView { self.addSubview(shadowView) - if let shadowMaskView = self.shadowMaskView { - shadowView.mask = shadowMaskView - } } if let nativeView = self.nativeView { self.addSubview(nativeView) @@ -435,39 +349,36 @@ public final class GlassBackgroundView: UIView { transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) } + let shadowInset: CGFloat = 32.0 + let params = Params(cornerRadius: cornerRadius, isDark: isDark, tintColor: tintColor) if self.params != params { self.params = params if let shadowView = self.shadowView { - shadowView.layer.shadowRadius = 10.0 - shadowView.layer.shadowColor = UIColor(white: 0.0, alpha: 1.0).cgColor - shadowView.layer.shadowOpacity = 0.08 - shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 1.0) - - if let shadowMaskView = self.shadowMaskView { - let shadowInset: CGFloat = 32.0 - let shadowInnerInset: CGFloat = 0.5 - shadowMaskView.image = generateImage(CGSize(width: shadowInset * 2.0 + cornerRadius * 2.0, height: shadowInset * 2.0 + cornerRadius * 2.0), rotatedContext: { size, context in - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) - 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))) - })?.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius), topCapHeight: Int(shadowInset + cornerRadius)) - } + let shadowInnerInset: CGFloat = 0.5 + shadowView.image = generateImage(CGSize(width: shadowInset * 2.0 + cornerRadius * 2.0, height: shadowInset * 2.0 + cornerRadius * 2.0), rotatedContext: { size, context in + 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.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) + context.setBlendMode(.copy) + 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))) + })?.stretchableImage(withLeftCapWidth: Int(shadowInset + cornerRadius), topCapHeight: Int(shadowInset + cornerRadius)) } if let foregroundView = self.foregroundView { - foregroundView.image = generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color) + foregroundView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color) } else { if let nativeView { if #available(iOS 26.0, *) { - let glassEffect = UIGlassEffect(style: .regular) + let glassEffect = UIGlassEffect(style: .clear) switch tintColor.kind { case .panel: - glassEffect.tintColor = nil + glassEffect.tintColor = tintColor.color case .custom: glassEffect.tintColor = tintColor.color } @@ -484,14 +395,7 @@ public final class GlassBackgroundView: UIView { transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size)) } if let shadowView = self.shadowView { - if shadowView.bounds.size != size { - shadowView.layer.shadowPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: size.height * 0.5).cgPath - } - transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size)) - - if let shadowMaskView = self.shadowMaskView { - transition.setFrame(view: shadowMaskView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -32.0, dy: -32.0)) - } + transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -shadowInset, dy: -shadowInset)) } transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size)) } @@ -585,3 +489,85 @@ public final class VariableBlurView: UIVisualEffectView { backdropLayer?.filters = [variableBlur] } } + +public extension GlassBackgroundView { + static func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage { + var size = size + if size == .zero { + size = CGSize(width: 1.0, height: 1.0) + } + + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + 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)) + } +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 608ae85e60..7412d9bc9c 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -407,6 +407,7 @@ final class MediaEditorScreenComponent: Component { areCustomEmojiEnabled: true, hasSearch: true, hideBackground: true, + maskEdge: true, sendGif: nil ) |> map { inputData -> ChatEntityKeyboardInputNode.InputData in return ChatEntityKeyboardInputNode.InputData( diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiSelectionComponent.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiSelectionComponent.swift index 31c385a7fc..8f94c92974 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiSelectionComponent.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiSelectionComponent.swift @@ -257,6 +257,7 @@ public final class EmojiSelectionComponent: Component { defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift index ed4f8b15d0..e4f962bbcb 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/EmojiPickerItem.swift @@ -335,6 +335,7 @@ private final class EmojiSelectionComponent: Component { defaultToEmojiTab: true, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, topPanelScrollingOffset: { _, _ in }, diff --git a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift index 6968c89459..c5631fee10 100644 --- a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift +++ b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift @@ -283,6 +283,7 @@ private final class StickerSelectionComponent: Component { defaultToEmojiTab: defaultToEmoji, externalTopPanelContainer: self.panelHostView, externalBottomPanelContainer: nil, + externalTintMaskContainer: nil, displayTopPanelBackground: .blur, topPanelExtensionUpdated: { _, _ in }, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 19b57b0205..b1ab2da1b9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1768,9 +1768,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if let inputNode = inputNode as? ChatEntityKeyboardInputNode { inputNode.externalTopPanelContainerImpl = nil } - inputNode.clipsToBounds = true - inputNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - inputNode.layer.cornerRadius = 30.0 dismissedInputNode = self.inputNode dismissedInputNodeExternalTopPanelContainer = self.inputNode?.externalTopPanelContainer @@ -2158,11 +2155,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { if self.dismissedAsOverlay { inputPanelFrame!.origin.y = layout.size.height } - if let inputNode = self.inputNode, inputNode.hideInput, !inputNode.adjustLayoutForHiddenInput { - inputPanelsHeight += inputPanelNodeBaseHeight - } else { - inputPanelsHeight += inputPanelSize!.height - } + inputPanelsHeight += inputPanelSize!.height } if self.secondaryInputPanelNode != nil { @@ -3942,6 +3935,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { chatPeerId: self.chatLocation.peerId, areCustomEmojiEnabled: self.chatPresentationInterfaceState.customEmojiAvailable, hasEdit: true, + hideBackground: true, sendGif: { [weak self] fileReference, sourceView, sourceRect, silentPosting, schedule in if let self { return self.controllerInteraction.sendGif(fileReference, sourceView, sourceRect, silentPosting, schedule) @@ -4235,15 +4229,20 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } var maybeDismissOverlayContent = true - if let inputNode = self.inputNode, inputNode.bounds.contains(self.view.convert(point, to: inputNode.view)) { - if let externalTopPanelContainer = inputNode.externalTopPanelContainer { - if externalTopPanelContainer.hitTest(self.view.convert(point, to: externalTopPanelContainer), with: nil) != nil { - maybeDismissOverlayContent = true + if let inputNode = self.inputNode { + if let result = inputNode.view.hitTest(self.view.convert(point, to: inputNode.view), with: event) { + return result + } + if inputNode.bounds.contains(self.view.convert(point, to: inputNode.view)) { + if let externalTopPanelContainer = inputNode.externalTopPanelContainer { + if externalTopPanelContainer.hitTest(self.view.convert(point, to: externalTopPanelContainer), with: nil) != nil { + maybeDismissOverlayContent = true + } else { + maybeDismissOverlayContent = false + } } else { maybeDismissOverlayContent = false } - } else { - maybeDismissOverlayContent = false } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index a4b577d383..e997379ea8 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -94,7 +94,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.buttonNode.view.addSubview(self.backgroundView) self.backgroundView.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate) + self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) self.imageView.tintColor = theme.chat.inputPanel.inputControlColor self.backgroundView.contentView.addSubview(self.imageView) @@ -110,7 +110,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { if self.theme !== theme { self.theme = theme - self.backgroundView.update(size: self.backgroundView.bounds.size, cornerRadius: self.backgroundView.bounds.size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.65)), transition: .immediate) + self.backgroundView.update(size: self.backgroundView.bounds.size, cornerRadius: self.backgroundView.bounds.size.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: .immediate) self.imageView.tintColor = theme.chat.inputPanel.inputControlColor switch self.type { From 04765e0c36b1fa87920a6526778c521b1234e87f Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 17 Sep 2025 11:18:26 +0400 Subject: [PATCH 7/8] 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" From 202274073fa5df7a5fbaa4621e3da054a1a0284b Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 17 Sep 2025 11:19:13 +0400 Subject: [PATCH 8/8] Bump version --- versions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versions.json b/versions.json index 63a8a13f63..9f9254ee9f 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,6 @@ { "app": "12.1", - "xcode": "16.2", + "xcode": "26.0", "bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217", - "macos": "15" + "macos": "26" }