Merge commit '202274073f' into call-messages

This commit is contained in:
Ilya Laktyushin 2025-09-17 22:05:40 +04:00
commit 4b8d87b05f
154 changed files with 2981 additions and 3156 deletions

View file

@ -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\nIm 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.";

View file

@ -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)

View file

@ -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

View file

@ -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))
}
}

View file

@ -46,6 +46,7 @@ swift_library(
"//submodules/InAppPurchaseManager",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/Utils/DeviceModel",
],
visibility = [
"//visibility:public",

View file

@ -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<EnvironmentType>, 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

View file

@ -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])

View file

@ -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",

View file

@ -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) {

View file

@ -21,6 +21,7 @@ import ChatFolderLinkPreviewScreen
import ChatListHeaderComponent
import StoryPeerListComponent
import TelegramNotices
import EdgeEffect
public enum ChatListContainerNodeFilter: Equatable {
case all

View file

@ -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<ChildEnvironmentType: Equatable, TopPanelEnvir
public let externalTopPanelContainer: PagerExternalTopPanelContainer?
public let bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?
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<ChildEnvironmentType: Equatable, TopPanelEnvir
externalTopPanelContainer: PagerExternalTopPanelContainer?,
bottomPanel: AnyComponent<PagerComponentPanelEnvironment<TopPanelEnvironment>>?,
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<ChildEnvironmentType: Equatable, TopPanelEnvir
self.externalTopPanelContainer = externalTopPanelContainer
self.bottomPanel = bottomPanel
self.externalBottomPanelContainer = externalBottomPanelContainer
self.externalTintMaskContainer = externalTintMaskContainer
self.panelStateUpdated = panelStateUpdated
self.isTopPanelExpandedUpdated = isTopPanelExpandedUpdated
self.isTopPanelHiddenUpdated = isTopPanelHiddenUpdated
@ -292,6 +295,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
if lhs.externalBottomPanelContainer !== rhs.externalBottomPanelContainer {
return false
}
if lhs.externalTintMaskContainer !== rhs.externalTintMaskContainer {
return false
}
if lhs.panelHideBehavior != rhs.panelHideBehavior {
return false
}
@ -307,6 +313,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
public final class View: UIView, ComponentTaggedView {
private final class ContentView {
let view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)>
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<ChildEnvironmentType: Equatable, TopPanelEnvir
init(view: ComponentHostView<(ChildEnvironmentType, PagerComponentChildEnvironment)>) {
self.view = view
self.tintMaskContainer = UIView()
}
}
@ -847,6 +855,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
} else {
self.contentClippingView.insertSubview(contentView.view, at: 0)
}
if let externalTintMaskContainer = component.externalTintMaskContainer {
externalTintMaskContainer.addSubview(contentView.tintMaskContainer)
}
}
let childContentInsets = contentInsets
@ -890,6 +901,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
if wasAdded {
if case .none = transition.animation {
contentView.view.frame = contentFrame
contentView.tintMaskContainer.frame = contentFrame
} else {
var referenceDirectionIsRight: Bool?
for (previousId, previousFrame) in referenceFrames {
@ -906,6 +918,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
if let referenceDirectionIsRight = referenceDirectionIsRight {
contentView.view.frame = contentFrame.offsetBy(dx: referenceDirectionIsRight ? contentFrame.width : (-contentFrame.width), dy: 0.0)
contentView.tintMaskContainer.frame = contentView.view.frame
transition.setFrame(view: contentView.view, frame: contentFrame, completion: { [weak self] completed in
if completed && !isInBounds && isPartOfTransition {
DispatchQueue.main.async {
@ -913,6 +926,7 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
}
})
transition.setFrame(view: contentView.tintMaskContainer, frame: contentFrame)
} else {
contentView.view.frame = contentFrame
}
@ -925,10 +939,11 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
}
})
transition.setFrame(view: contentView.tintMaskContainer, frame: contentFrame)
}
if let contentViewWithBackground = contentView.view.componentView as? PagerContentViewWithBackground {
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: backgroundFrame, topPanelHeight: topPanelHeight, transition: contentTransition)
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: backgroundFrame, topPanelHeight: topPanelHeight, externalTintMaskContainer: component.externalTintMaskContainer == nil ? nil : contentView.tintMaskContainer, transition: contentTransition)
}
}
}
@ -941,7 +956,10 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
}
}
for id in removedIds {
self.contentViews.removeValue(forKey: id)?.view.removeFromSuperview()
if let contentView = self.contentViews.removeValue(forKey: id) {
contentView.view.removeFromSuperview()
contentView.tintMaskContainer.removeFromSuperview()
}
}
if let panelStateUpdated = component.panelStateUpdated {

View file

@ -707,6 +707,7 @@ final class ComposePollScreenComponent: Component {
hasStickers: false,
hasGifs: false,
hideBackground: true,
maskEdge: true,
sendGif: nil
)
)

View file

@ -46,6 +46,7 @@ swift_library(
"//submodules/UndoUI",
"//submodules/TelegramIntents",
"//submodules/ContextUI",
"//submodules/TelegramUI/Components/EdgeEffect",
],
visibility = [
"//visibility:public",

View file

@ -17,6 +17,7 @@ import ChatListTitleView
import ComponentFlow
import SwiftUI
import ContactsUI
import EdgeEffect
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
@ -48,6 +49,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let contactListNode: ContactListNode
private let edgeEffectView: EdgeEffectView
private let context: AccountContext
private(set) var searchDisplayController: SearchDisplayController?
@ -116,6 +118,8 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
contextAction?(peer, node, gesture, location, isStories)
})
self.edgeEffectView = EdgeEffectView()
super.init()
self.setViewBlock({
@ -125,6 +129,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.addSubnode(self.contactListNode)
self.view.addSubview(self.edgeEffectView)
self.presentationDataDisposable = (context.sharedContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
@ -442,6 +447,11 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
self.contactListNode.frame = CGRect(origin: CGPoint(), size: layout.size)
let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, 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 {

View file

@ -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: {

View file

@ -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:

View file

@ -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

View file

@ -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())
}

View file

@ -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
}
}

View file

@ -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 {

View file

@ -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)

View file

@ -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 t1)
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

View file

@ -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
}
}

View file

@ -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:

View file

@ -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

View file

@ -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)
)),

View file

@ -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)

View file

@ -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

View file

@ -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(

View file

@ -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),

View file

@ -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),

View file

@ -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;

View file

@ -16,7 +16,7 @@ UIColor *TGAccentColor()
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^
{
color = TGColorWithHex(0x007aff);
color = TGColorWithHex(0x0088ff);
});
return color;
}

View file

@ -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];

View file

@ -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;
}

View file

@ -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
}

View file

@ -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()

View file

@ -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)!

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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 {

View file

@ -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<Bool>(true)
override public var ready: Promise<Bool> {
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)
})
}
}

View file

@ -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
}

View file

@ -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))

View file

@ -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

View file

@ -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<LoadedStickerPack>()
private let stickerPackInstalledDisposable = MetaDisposable()
private let stickerPackInstalled = Promise<Bool>()
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<PresentationData, NoError>)? = 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<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> 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<Bool, NoError>] = []
let info = info._parse()
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { 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<Bool, NoError> { 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)
}
}

View file

@ -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<Bool>()
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?()
}
}

View file

@ -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

View file

@ -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",

View file

@ -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<Empty>()
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()
}
}

View file

@ -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<Bool, NoError> in
if let tabBarItem = tabBarItem, tabBarItem.image == nil {

View file

@ -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 {

View file

@ -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 {

View file

@ -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
}

View file

@ -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)

View file

@ -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),

View file

@ -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<Bool, NoError> { get }

View file

@ -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)

View file

@ -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)

View file

@ -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 {

View file

@ -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 ?? [])

View file

@ -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)

View file

@ -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))
}
}
}

View file

@ -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,

View file

@ -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
)

View file

@ -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)
})
}

View file

@ -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 {

View file

@ -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": [],

View file

@ -1219,6 +1219,7 @@ final class AvatarEditorScreenComponent: Component {
defaultToEmojiTab: true,
externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil,
externalTintMaskContainer: nil,
displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },

View file

@ -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
}
}

View file

@ -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,

View file

@ -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",

View file

@ -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
}

View file

@ -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",
],
)

View file

@ -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 }
}

View file

@ -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
}

View file

@ -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? {

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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))

View file

@ -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)

View file

@ -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)

View file

@ -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",

View file

@ -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)
}

View file

@ -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<ChatInputAccessoryPanelEnvironment>, view: ComponentView<ChatInputAccessoryPanelEnvironment>)?
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<ChatInputAccessoryPanelEnvironment>?)?
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<ChatInputAccessoryPanelEnvironment>?
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
}

View file

@ -41,6 +41,7 @@ swift_library(
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
"//submodules/AttachmentTextInputPanelNode",
"//submodules/TelegramUI/Components/BatchVideoRendering",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -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<InputData, NoError> {
@ -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<Empty>()
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 {

View file

@ -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 {

View file

@ -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))
}
}

View file

@ -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",
],
)

View file

@ -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
}
}
}
}

View file

@ -187,6 +187,7 @@ public final class EmojiStatusSelectionComponent: Component {
defaultToEmojiTab: true,
externalTopPanelContainer: self.panelHostView,
externalBottomPanelContainer: nil,
externalTintMaskContainer: nil,
displayTopPanelBackground: .blur,
topPanelExtensionUpdated: { _, _ in },
topPanelScrollingOffset: { _, _ in },

View file

@ -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)

View file

@ -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 {

View file

@ -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<EmojiPagerContentComponent, NoError> {
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<EmojiPagerContentComponent, NoError> {
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<EmojiPagerContentComponent, NoError> {
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,

View file

@ -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 },

View file

@ -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

View file

@ -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<EnvironmentType>, 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
}

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more