diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ceaad32f05..932ddc3e51 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,6 +15001,36 @@ 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."; "Gift.Upgrade.PriceWillDecrease" = "Price will decrease in {m}:{s}"; "Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift."; 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/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/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/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/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..360c326f12 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, 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/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/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 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, isInverted: false, 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/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/Display/Source/DeviceMetrics.swift b/submodules/Display/Source/DeviceMetrics.swift index dad4b4778d..04df421fac 100644 --- a/submodules/Display/Source/DeviceMetrics.swift +++ b/submodules/Display/Source/DeviceMetrics.swift @@ -237,7 +237,11 @@ public enum DeviceMetrics: CaseIterable, Equatable { public func onScreenNavigationHeight(inLandscape: Bool, systemOnScreenNavigationHeight: CGFloat?) -> 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/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 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/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/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/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/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/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index 8e88b058c8..10802fe6e7 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -237,7 +237,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0) glassBackgroundFrame.size.height -= 8.0 transition.updateFrame(view: glassBackgroundView, frame: glassBackgroundFrame, beginWithCurrentState: true) - glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: ComponentTransition(transition)) + glassBackgroundView.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x1b1d22)), transition: ComponentTransition(transition)) transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0)) } else { 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/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/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..064f74ffd3 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, 8.0) + } + let sideInset: CGFloat = 20.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/Components/MessageItemComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift index 6b125f6018..536551eef4 100644 --- a/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift +++ b/submodules/TelegramCallsUI/Sources/Components/MessageItemComponent.swift @@ -106,8 +106,8 @@ final class MessageItemComponent: Component { transition.setPosition(view: textSnapshotView, position: CGPoint(x: textSnapshotView.center.x + 71.0, y: textSnapshotView.center.y)) let initialSize = self.background.frame.size - self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: .immediate) - self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: UIColor(rgb: 0x1b1d22), transition: transition) + self.background.update(size: globalFrame.size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x1b1d22)), transition: .immediate) + self.background.update(size: initialSize, cornerRadius: 18.0, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x1b1d22)), transition: transition) let deltaX = (globalFrame.width - self.container.frame.width) / 2.0 let deltaY = (globalFrame.height - self.container.frame.height) / 2.0 @@ -137,9 +137,7 @@ final class MessageItemComponent: Component { self.component = component let theme = defaultDarkPresentationTheme - - let backgroundColor = UIColor(rgb: 0x1b1d22) - + let textFont = Font.regular(14.0) let boldTextFont = Font.semibold(14.0) let textColor: UIColor = .white @@ -211,7 +209,7 @@ final class MessageItemComponent: Component { transition.setFrame(view: self.container, frame: CGRect(origin: CGPoint(), size: size)) - self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: backgroundColor, transition: transition) + self.background.update(size: size, cornerRadius: cornerRadius, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x1b1d22)), transition: transition) transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) if isFirstTime, let availableReactions = component.availableReactions, let textView = self.text.view { diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index ba5f5cf411..1548f895e0 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -2981,6 +2981,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 } diff --git a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift index f7faf4c7d0..621b648255 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatActionButtonComponent.swift @@ -282,7 +282,7 @@ final class VideoChatActionButtonComponent: Component { } } - self.background.update(size: size, cornerRadius: size.width * 0.5, isDark: true, tintColor: backgroundColor, transition: tintTransition) + self.background.update(size: size, cornerRadius: size.width * 0.5, isDark: true, tintColor: .init(kind: .custom, color: backgroundColor), transition: tintTransition) transition.setFrame(view: self.background, frame: CGRect(origin: CGPoint(), size: size)) let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) * 0.5), y: size.height + 8.0), size: titleSize) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index 13bc645722..cfca87664f 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -2232,7 +2232,7 @@ final class VideoChatScreenComponent: Component { color: .white )), background: AnyComponent( - GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: UIColor(rgb: 0x101014)) + GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: .init(kind: .custom, color: UIColor(rgb: 0x101014))) ), effectAlignment: .center, minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), @@ -2254,7 +2254,7 @@ final class VideoChatScreenComponent: Component { image: closeButtonImage(dark: false) )), background: AnyComponent( - GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: UIColor(rgb: 0x101014)) + GlassBackgroundComponent(size: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), tintColor: .init(kind: .custom, color: UIColor(rgb: 0x101014))) ), effectAlignment: .center, minSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter), 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/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/TelegramEngine/AccountData/UpdateAccountPeerName.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift index 4aad4b509f..e1354a30ba 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/UpdateAccountPeerName.swift @@ -68,7 +68,6 @@ func _internal_updateNameColorAndEmoji(account: Account, nameColor: PeerNameColo if let _ = backgroundEmojiId { flagsReplies |= (1 << 1) } - var flagsProfile: Int32 = 0 if let _ = profileColor { flagsProfile |= (1 << 0) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 6b20b98b6c..0f3448db00 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -3488,9 +3488,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)) + } } } 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..a8848b51ae 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), @@ -941,20 +941,20 @@ 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), 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/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/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/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/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/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/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 37695469fb..ec45087b2f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -721,7 +721,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if self.visibility != oldValue { self.visibilityStatus = self.visibility != .none - self.updateVisibility() + self.updateVisibility(isScroll: true) } } } @@ -746,6 +746,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() @@ -3341,7 +3342,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) @@ -3367,6 +3370,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI params: params, applyInfo: applyInfo, layout: layout, + layoutInsets: layoutInsets, item: item, forwardSource: forwardSource, forwardAuthorSignature: forwardAuthorSignature, @@ -3380,12 +3384,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, @@ -3395,25 +3399,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, @@ -3431,6 +3437,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI params: ListViewItemLayoutParams, applyInfo: ListViewItemApply, layout: ListViewItemNodeLayout, + layoutInsets: UIEdgeInsets, item: ChatMessageItem, forwardSource: Peer?, forwardAuthorSignature: String?, @@ -3491,6 +3498,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 @@ -3573,7 +3581,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 @@ -5002,7 +5010,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI strongSelf.updateSearchTextHighlightState() - strongSelf.updateVisibility() + strongSelf.updateVisibility(isScroll: false) if let (_, f) = strongSelf.awaitingAppliedReaction { strongSelf.awaitingAppliedReaction = nil @@ -6335,10 +6343,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 { @@ -6564,7 +6572,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI contentNode.unreadMessageRangeUpdated() } - self.updateVisibility() + self.updateVisibility(isScroll: false) } public func animateQuizInvalidOptionSelected() { @@ -6764,10 +6772,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 } @@ -6844,6 +6852,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/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 326548d1ef..43e999f5ea 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)) @@ -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/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode/Sources/ChatRecordingViewOnceButtonNode.swift index b94b71e7c6..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: 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 7e87297a08..8f94a44ff8 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.7)), 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.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/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/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..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,12 +218,14 @@ 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 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? @@ -252,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? @@ -265,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 @@ -281,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 = { } @@ -569,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() @@ -725,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) } } } @@ -762,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) @@ -792,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(_:))) @@ -925,11 +944,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() } @@ -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) @@ -1865,7 +1895,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 { @@ -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 } @@ -2213,7 +2246,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 { @@ -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 @@ -2334,7 +2367,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.7)), transition: ComponentTransition(transition)) if let removedAccessoryPanelView { if let removedAccessoryPanelView = removedAccessoryPanelView as? ChatInputAccessoryPanelView { @@ -2352,17 +2385,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 { @@ -2372,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() @@ -2396,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 { @@ -2435,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) @@ -2463,19 +2525,25 @@ 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)) } - 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)) @@ -2604,18 +2672,21 @@ 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)) 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.7)), transition: ComponentTransition(transition)) transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame) transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame) @@ -2726,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 } @@ -3242,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 @@ -3601,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 { @@ -3611,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) } } @@ -3753,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) { @@ -4562,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/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/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..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) @@ -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..5b05658797 --- /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, 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)) + 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/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/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/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 3c7f5ec804..7e44a174bd 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 @@ -313,12 +233,27 @@ public 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 +264,18 @@ public class GlassBackgroundView: UIView { private let nativeView: UIVisualEffectView? private let foregroundView: UIImageView? + private let shadowView: UIImageView? + private let maskContainerView: UIView 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,22 +291,30 @@ public class GlassBackgroundView: UIView { nativeView.traitOverrides.userInterfaceStyle = .light //self.foregroundView = UIImageView() self.foregroundView = 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.maskContainerView = UIView() + self.maskContainerView.backgroundColor = .white + if let filter = CALayer.luminanceToAlpha() { + self.maskContainerView.layer.filters = [filter] } self.maskContentView = UIView() - self.maskContentView.backgroundColor = .white - if let filter = CALayer.luminanceToAlpha() { - self.maskContentView.layer.filters = [filter] - } + self.maskContainerView.addSubview(self.maskContentView) self.contentContainer = ContentContainer(maskContentView: self.maskContentView) super.init(frame: frame) + if let shadowView = self.shadowView { + self.addSubview(shadowView) + } if let nativeView = self.nativeView { self.addSubview(nativeView) } @@ -374,7 +323,7 @@ public class GlassBackgroundView: UIView { } if let foregroundView = self.foregroundView { self.addSubview(foregroundView) - foregroundView.mask = self.maskContentView + foregroundView.mask = self.maskContainerView } self.addSubview(self.contentContainer) } @@ -383,7 +332,7 @@ public 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,22 +348,44 @@ public 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)) } + 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 { + 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: 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) + 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 = 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, *) { - let glassEffect = UIGlassEffect(style: .regular) - glassEffect.tintColor = tintColor//.withMultipliedAlpha(0.1) + let glassEffect = UIGlassEffect(style: .clear) + switch tintColor.kind { + case .panel: + glassEffect.tintColor = tintColor.color + case .custom: + glassEffect.tintColor = tintColor.color + } glassEffect.isInteractive = false nativeView.effect = glassEffect @@ -423,9 +394,13 @@ public class GlassBackgroundView: UIView { } } - transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size)) - if let foregroundView { - transition.setFrame(view: foregroundView, 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).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)) } transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size)) } @@ -520,11 +495,297 @@ 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 { + 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)) + } +} + public final class GlassBackgroundComponent: Component { private let size: CGSize - private let tintColor: UIColor + private let tintColor: GlassBackgroundView.TintColor - public init(size: CGSize, tintColor: UIColor) { + public init(size: CGSize, tintColor: GlassBackgroundView.TintColor) { self.size = size self.tintColor = tintColor } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 5958ab79b4..c73f98a301 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( @@ -888,7 +889,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/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift index 09e5c26f09..6f598f29f5 100644 --- a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift @@ -534,7 +534,7 @@ public final class MessageInputActionButtonComponent: Component { tintColor = UIColor(rgb: 0x0187ee) } let buttonSize = CGSize(width: 40.0, height: 40.0) - backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: tintColor, transition: transition) + backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height / 2.0, isDark: true, tintColor: .init(kind: .custom, color: tintColor), transition: transition) backgroundView.frame = CGRect(origin: .zero, size: buttonSize) } 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/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/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index dcb80f0dea..978e0c6dc9 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -1076,8 +1076,7 @@ public final class MessageInputPanelComponent: Component { self.fieldBackgroundTint.isHidden = true } if let fieldGlassBackgroundView = self.fieldGlassBackgroundView { - let backgroundColor = UIColor(rgb: 0x1b1d22) - fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: backgroundColor, transition: transition) + fieldGlassBackgroundView.update(size: fieldBackgroundFrame.size, cornerRadius: baseFieldHeight * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0x1b1d22)), transition: transition) transition.setFrame(view: fieldGlassBackgroundView, frame: fieldBackgroundFrame) } default: 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/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index 46532e538c..7ced9eef64 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -166,6 +166,7 @@ swift_library( "//submodules/TelegramUI/Components/MarqueeComponent", "//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist", "//submodules/TelegramUI/Components/TextFieldComponent", + "//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 a560bf57da..a49d4b5efa 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 @@ -467,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))) @@ -2972,6 +2973,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] = [:] @@ -3109,6 +3111,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 @@ -3984,6 +3988,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 @@ -12311,15 +12317,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let self else { return } - //TODO:release - var text = "\(value.day)_\(value.month)" - if let year = value.year { - text += "_\(year)" - } - let _ = enqueueMessages(account: self.context.account, peerId: self.peerId, messages: [ - .message(text: "[birthdate]\(text)", attributes: [], inlineStickers: [:], mediaReference: nil, threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - ]).start() + let _ = self.context.engine.peers.suggestBirthday(peerId: self.peerId, birthday: value).startStandalone() + self.headerNode.navigationButtonContainer.performAction?(.cancel, nil, nil) self.openChat(peerId: self.peerId) } @@ -12421,6 +12421,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, isInverted: false, 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/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/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/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/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 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 773929470d..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 { @@ -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 @@ -2163,11 +2160,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 { @@ -2316,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 @@ -2700,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 { @@ -2831,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 @@ -3947,6 +3932,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) @@ -4240,15 +4226,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 } } @@ -5006,7 +4997,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let titleAccessoryPanelSnapshot: UIView? let navigationBarHeight: CGFloat let inputPanelNodeSnapshot: UIView? - let inputPanelOverscrollNodeSnapshot: UIView? fileprivate init( backgroundNode: WallpaperBackgroundNode, @@ -5016,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 @@ -5027,7 +5016,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot self.navigationBarHeight = navigationBarHeight self.inputPanelNodeSnapshot = inputPanelNodeSnapshot - self.inputPanelOverscrollNodeSnapshot = inputPanelOverscrollNodeSnapshot } } @@ -5045,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(), @@ -5058,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 ) } @@ -5115,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 e1608ea0b3..88c4bb2aad 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -916,7 +916,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } self.dynamicBounceEnabled = !self.currentPresentationData.disableAnimations - self.experimentalSnapScrollToItem = true + self.experimentalSnapScrollToItem = false //self.debugInfo = true @@ -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/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index 1262582940..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: 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: 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 { @@ -124,7 +124,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.imageView.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) } - self.badgeBackgroundView.update(size: self.badgeBackgroundView.bounds.size, cornerRadius: self.badgeBackgroundView.bounds.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: theme.chat.inputPanel.actionControlFillColor, transition: .immediate) + self.badgeBackgroundView.update(size: self.badgeBackgroundView.bounds.size, cornerRadius: self.badgeBackgroundView.bounds.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: theme.chat.inputPanel.actionControlFillColor), transition: .immediate) var segments: [AnimatedCountLabelNode.Segment] = [] if let value = Int(self.badge) { @@ -174,7 +174,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { } let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) - self.badgeBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: self.theme.chat.inputPanel.actionControlFillColor, transition: ComponentTransition(transition)) + self.badgeBackgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.height * 0.5, isDark: theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: self.theme.chat.inputPanel.actionControlFillColor), transition: ComponentTransition(transition)) self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 2.0), size: badgeSize) 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 40fdb504a3..b2302adb43 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -418,25 +418,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } - var isScheduledMessages = false - if case .scheduledMessages = chatPresentationInterfaceState.subject { - isScheduledMessages = true - } - var displayBotStartPanel = false - - if !isScheduledMessages { - if let _ = chatPresentationInterfaceState.botStartPayload { - if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil { - displayBotStartPanel = true - } - } else if let chatHistoryState = chatPresentationInterfaceState.chatHistoryState, case .loaded(true, _) = chatHistoryState { - if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil { - displayBotStartPanel = true - } - } - } - let _ = displayBotStartPanel - displayInputTextPanel = true } @@ -480,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/LegacyDataImportSplash.swift b/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift index 53b723f2bb..9ca35d36d3 100644 --- a/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift +++ b/submodules/TelegramUI/Sources/LegacyDataImportSplash.swift @@ -35,7 +35,7 @@ private final class LegacyDataImportSplashImpl: WindowCoveringView, LegacyDataIm self.updateLayout(size) } } - self.progressNode.transitionToState(.progress(color: self.theme?.list.itemAccentColor ?? UIColor(rgb: 0x007aff), lineWidth: 2.0, value: CGFloat(max(0.025, self.progress.1)), cancelEnabled: false, animateRotation: true), animated: false, completion: {}) + self.progressNode.transitionToState(.progress(color: self.theme?.list.itemAccentColor ?? UIColor(rgb: 0x0088ff), lineWidth: 2.0, value: CGFloat(max(0.025, self.progress.1)), cancelEnabled: false, animateRotation: true), animated: false, completion: {}) } } 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/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 8d062984b6..097135dc7d 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -114,7 +114,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon let previousTheme = strongSelf.presentationData.theme strongSelf.presentationData = presentationData if previousTheme !== presentationData.theme { - (strongSelf.rootTabController as? TabBarControllerImpl)?.updateTheme(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData), theme: TabBarControllerTheme(rootControllerTheme: presentationData.theme)) + (strongSelf.rootTabController as? TabBarControllerImpl)?.updateTheme(theme: presentationData.theme) strongSelf.rootTabController?.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style } } @@ -188,7 +188,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } public func addRootControllers(showCallsTab: Bool) { - let tabBarController = TabBarControllerImpl(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), theme: TabBarControllerTheme(rootControllerTheme: self.presentationData.theme)) + let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme) tabBarController.navigationPresentation = .master let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild) if let sharedContext = self.context.sharedContext as? SharedAccountContextImpl { 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/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift index e62f227ee2..3e7f2c5484 100644 --- a/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift +++ b/submodules/TelegramUIPreferences/Sources/PresentationThemeSettings.swift @@ -407,7 +407,7 @@ public enum PresentationThemeBaseColor: Int32, CaseIterable { let value: UInt32 switch self { case .blue: - value = 0x007aff + value = 0x0088ff case .cyan: value = 0x00c2ed case .green: 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) diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 2ae6088517..8c96748f85 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -123,6 +123,39 @@ public protocol WallpaperBackgroundNode: ASDisplayNode { } private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOverlayLayer { + final class CloneLayer: SimpleLayer { + private weak var parentLayer: EffectImageLayer? + private var index: SparseBag>.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) @@ -1720,31 +1787,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 @@ -1757,6 +1805,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 @@ -1771,6 +1822,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 @@ -1784,6 +1843,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 @@ -1798,6 +1862,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 @@ -1852,9 +1935,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)) } } diff --git a/versions.json b/versions.json index 6b7e5549eb..9f9254ee9f 100644 --- a/versions.json +++ b/versions.json @@ -1,6 +1,6 @@ { - "app": "12.0", - "xcode": "16.2", + "app": "12.1", + "xcode": "26.0", "bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217", - "macos": "15" + "macos": "26" }