mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '202274073f' into call-messages
This commit is contained in:
commit
4b8d87b05f
154 changed files with 2981 additions and 3156 deletions
|
|
@ -452,20 +452,6 @@
|
|||
"Login.PhoneGenericEmailBody" = "I'm trying to use my mobile phone number: %1$@\nBut Telegram shows an error. Please help.\n\nError: %2$@\nApp version: %3$@\nOS version: %4$@\nLocale: %5$@\nMNC: %6$@";
|
||||
|
||||
"Login.PhonePaidEmailSubject" = "Payment Issue";
|
||||
"Login.PhonePaidEmailBody" = "Hello Telegram Support,\n\nI’m experiencing an issue with an in-app purchase on iOS.\nMy phone number: %1$@\nApple ID (email used in the App Store): \nApp version: %2$@\nDevice model / iOS version: %3$@\nApp Store country/region: %4$@\nDate & time of attempt (with timezone): %5$@\nExact error message: \n\n#payment_failed";
|
||||
|
||||
"Login.PhoneTitle" = "Your Phone";
|
||||
"Login.PhonePlaceholder" = "Your phone number";
|
||||
"Login.CountryCode" = "Country Code";
|
||||
"Login.InvalidCountryCode" = "Invalid Country Code";
|
||||
|
||||
"Login.InfoTitle" = "Your Info";
|
||||
"Login.InfoAvatarAdd" = "add";
|
||||
"Login.InfoAvatarPhoto" = "photo";
|
||||
"Login.InfoFirstNamePlaceholder" = "First Name";
|
||||
"Login.InfoLastNamePlaceholder" = "Last Name";
|
||||
"Login.InfoDeletePhoto" = "Remove Photo";
|
||||
"Login.InfoHelp" = "Enter your name and add a profile photo.";
|
||||
|
||||
// Login.SelectCountry
|
||||
"Login.SelectCountry.Title" = "Country";
|
||||
|
|
@ -15015,6 +15001,36 @@ Sorry for the inconvenience.";
|
|||
"Gift.Options.Collectibles.Text" = "Collectible gifts are unique digital items you can exchange or sell.";
|
||||
|
||||
"Gift.Upgrade.UpgradeFor" = "Upgrade for %@";
|
||||
|
||||
"Login.PhonePaidEmailText" = "1. NAME OF YOUR PHONE CARRIER:
|
||||
|
||||
2. (OPTIONAL) WRITE YOUR COMMENT HERE:
|
||||
|
||||
=================================================
|
||||
Device: %1$@
|
||||
OS version: %2$@
|
||||
Locale: %3$@
|
||||
|
||||
Target phone: %4$@
|
||||
|
||||
App: %5$@
|
||||
App version: %6$@
|
||||
|
||||
Issue: %7$@
|
||||
Error: %8$@";
|
||||
|
||||
"Login.PhoneTitle" = "Your Phone";
|
||||
"Login.PhonePlaceholder" = "Your phone number";
|
||||
"Login.CountryCode" = "Country Code";
|
||||
"Login.InvalidCountryCode" = "Invalid Country Code";
|
||||
|
||||
"Login.InfoTitle" = "Your Info";
|
||||
"Login.InfoAvatarAdd" = "add";
|
||||
"Login.InfoAvatarPhoto" = "photo";
|
||||
"Login.InfoFirstNamePlaceholder" = "First Name";
|
||||
"Login.InfoLastNamePlaceholder" = "Last Name";
|
||||
"Login.InfoDeletePhoto" = "Remove Photo";
|
||||
"Login.InfoHelp" = "Enter your name and add a profile photo.";
|
||||
"Gift.Upgrade.PriceWillDecrease" = "Price will decrease in {m}:{s}";
|
||||
|
||||
"Stars.Purchase.RemoveOriginalDetailsStarGiftInfo" = "Buy Stars to remove original details of your gift.";
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ swift_library(
|
|||
"//submodules/InAppPurchaseManager",
|
||||
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/Utils/DeviceModel",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import ChatFolderLinkPreviewScreen
|
|||
import ChatListHeaderComponent
|
||||
import StoryPeerListComponent
|
||||
import TelegramNotices
|
||||
import EdgeEffect
|
||||
|
||||
public enum ChatListContainerNodeFilter: Equatable {
|
||||
case all
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -707,6 +707,7 @@ final class ComposePollScreenComponent: Component {
|
|||
hasStickers: false,
|
||||
hasGifs: false,
|
||||
hideBackground: true,
|
||||
maskEdge: true,
|
||||
sendGif: nil
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ swift_library(
|
|||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramIntents",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -202,6 +202,37 @@ public extension UIColor {
|
|||
return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha)
|
||||
}
|
||||
|
||||
func adjustedPerceivedBrightness(_ factor: CGFloat) -> UIColor {
|
||||
let f = max(0, factor)
|
||||
let base = self
|
||||
guard
|
||||
let cs = CGColorSpace(name: CGColorSpace.extendedSRGB),
|
||||
let cg = base.cgColor.converted(to: cs, intent: .defaultIntent, options: nil),
|
||||
let c = cg.components, c.count >= 3
|
||||
else { return base }
|
||||
|
||||
func toLin(_ x: CGFloat) -> CGFloat { x <= 0.04045 ? x/12.92 : pow((x+0.055)/1.055, 2.4) }
|
||||
func toSRGB(_ x: CGFloat) -> CGFloat { x <= 0.0031308 ? 12.92*x : 1.055*pow(x, 1/2.4) - 0.055 }
|
||||
func clamp(_ x: CGFloat) -> CGFloat { min(max(x, 0), 1) }
|
||||
|
||||
var r = toLin(c[0]), g = toLin(c[1]), b = toLin(c[2])
|
||||
if f >= 1 {
|
||||
// mix toward white: t = 1 - 1/f (so f=1 → t=0, f→∞ → t→1)
|
||||
let t = 1 - 1/f
|
||||
r = r + (1 - r) * t
|
||||
g = g + (1 - g) * t
|
||||
b = b + (1 - b) * t
|
||||
} else {
|
||||
// scale toward black
|
||||
r *= f; g *= f; b *= f
|
||||
}
|
||||
|
||||
return UIColor(red: clamp(toSRGB(r)),
|
||||
green: clamp(toSRGB(g)),
|
||||
blue: clamp(toSRGB(b)),
|
||||
alpha: cg.alpha)
|
||||
}
|
||||
|
||||
func withMultiplied(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) -> UIColor {
|
||||
var hueValue: CGFloat = 0.0
|
||||
var saturationValue: CGFloat = 0.0
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ UIColor *TGAccentColor()
|
|||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^
|
||||
{
|
||||
color = TGColorWithHex(0x007aff);
|
||||
color = TGColorWithHex(0x0088ff);
|
||||
});
|
||||
return color;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)!
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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?()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 ?? [])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -1219,6 +1219,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||
defaultToEmojiTab: true,
|
||||
externalTopPanelContainer: self.panelHostView,
|
||||
externalBottomPanelContainer: nil,
|
||||
externalTintMaskContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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? {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView:LegacyMessageInputPanelInputView",
|
||||
"//submodules/AttachmentTextInputPanelNode",
|
||||
"//submodules/TelegramUI/Components/BatchVideoRendering",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
submodules/TelegramUI/Components/EdgeEffect/BUILD
Normal file
19
submodules/TelegramUI/Components/EdgeEffect/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,6 +187,7 @@ public final class EmojiStatusSelectionComponent: Component {
|
|||
defaultToEmojiTab: true,
|
||||
externalTopPanelContainer: self.panelHostView,
|
||||
externalBottomPanelContainer: nil,
|
||||
externalTintMaskContainer: nil,
|
||||
displayTopPanelBackground: .blur,
|
||||
topPanelExtensionUpdated: { _, _ in },
|
||||
topPanelScrollingOffset: { _, _ in },
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue