This commit is contained in:
Isaac 2025-09-17 11:18:26 +04:00
parent 0678d0ded0
commit 04765e0c36
67 changed files with 1405 additions and 1890 deletions

View file

@ -111,15 +111,6 @@ public func textInputStateContextQueryRangeAndType(inputText: NSAttributedString
if inputText.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) == nil {
return [(NSRange(location: 0, length: inputString.length - (string.count - trimmedString.count)), [.emoji], nil)]
}
} else {
/*let activeString = inputText.attributedSubstring(from: NSRange(location: 0, length: inputState.selectionRange.upperBound))
if let lastCharacter = activeString.string.last, String(lastCharacter).isSingleEmoji {
let matchLength = (String(lastCharacter) as NSString).length
if activeString.attribute(ChatTextInputAttributes.customEmoji, at: activeString.length - matchLength, effectiveRange: nil) == nil {
return [(NSRange(location: inputState.selectionRange.upperBound - matchLength, length: matchLength), [.emojiSearch], nil)]
}
}*/
}
var possibleTypes = PossibleContextQueryTypes([.command, .mention, .hashtag, .emojiSearch])

View file

@ -453,7 +453,7 @@ final class ChatListContainerItemNode: ASDisplayNode {
let edgeEffectHeight: CGFloat = insets.bottom
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - edgeEffectHeight), size: CGSize(width: size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: size, transition: ComponentTransition(transition))
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: size, transition: ComponentTransition(transition))
}
func updateScrollingOffset(navigationHeight: CGFloat, offset: CGFloat, transition: ContainedViewLayoutTransition) {

View file

@ -450,7 +450,7 @@ final class ContactsControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
self.edgeEffectView.update(content: self.presentationData.theme.list.plainBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
self.updateNavigationScrolling(transition: transition)

View file

@ -202,6 +202,37 @@ public extension UIColor {
return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha)
}
func adjustedPerceivedBrightness(_ factor: CGFloat) -> UIColor {
let f = max(0, factor)
let base = self
guard
let cs = CGColorSpace(name: CGColorSpace.extendedSRGB),
let cg = base.cgColor.converted(to: cs, intent: .defaultIntent, options: nil),
let c = cg.components, c.count >= 3
else { return base }
func toLin(_ x: CGFloat) -> CGFloat { x <= 0.04045 ? x/12.92 : pow((x+0.055)/1.055, 2.4) }
func toSRGB(_ x: CGFloat) -> CGFloat { x <= 0.0031308 ? 12.92*x : 1.055*pow(x, 1/2.4) - 0.055 }
func clamp(_ x: CGFloat) -> CGFloat { min(max(x, 0), 1) }
var r = toLin(c[0]), g = toLin(c[1]), b = toLin(c[2])
if f >= 1 {
// mix toward white: t = 1 - 1/f (so f=1 t=0, f t1)
let t = 1 - 1/f
r = r + (1 - r) * t
g = g + (1 - g) * t
b = b + (1 - b) * t
} else {
// scale toward black
r *= f; g *= f; b *= f
}
return UIColor(red: clamp(toSRGB(r)),
green: clamp(toSRGB(g)),
blue: clamp(toSRGB(b)),
alpha: cg.alpha)
}
func withMultiplied(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) -> UIColor {
var hueValue: CGFloat = 0.0
var saturationValue: CGFloat = 0.0

View file

@ -1,299 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramUIPreferences
import AccountContext
import StickerResources
import AlertUI
import PresentationDataUtils
import UndoUI
import TelegramPresentationData
public enum StickerPackPreviewControllerMode {
case `default`
case settings
}
public final class StickerPackPreviewController: ViewController, StandalonePresentableController {
private var controllerNode: StickerPackPreviewControllerNode {
return self.displayNode as! StickerPackPreviewControllerNode
}
private var animatedIn = false
private var isDismissed = false
public var dismissed: (() -> Void)?
private let context: AccountContext
private let mode: StickerPackPreviewControllerMode
private weak var parentNavigationController: NavigationController?
private let stickerPack: StickerPackReference
private var stickerPackContentsValue: LoadedStickerPack?
private let stickerPackDisposable = MetaDisposable()
private let stickerPackContents = Promise<LoadedStickerPack>()
private let stickerPackInstalledDisposable = MetaDisposable()
private let stickerPackInstalled = Promise<Bool>()
private let openMentionDisposable = MetaDisposable()
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? {
didSet {
if self.isNodeLoaded {
if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceView, sourceRect in
if sendSticker(file, sourceView, sourceRect) {
self?.dismiss()
return true
} else {
return false
}
}
} else {
self.controllerNode.sendSticker = nil
}
}
}
}
private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, stickerPack: StickerPackReference, mode: StickerPackPreviewControllerMode = .default, parentNavigationController: NavigationController?, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? = nil) {
self.context = context
self.mode = mode
self.parentNavigationController = parentNavigationController
self.actionPerformed = actionPerformed
self.stickerPack = stickerPack
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: nil)
self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true
self.statusBar.statusBarStyle = .Ignore
self.stickerPackContents.set(context.engine.stickers.loadedStickerPack(reference: stickerPack, forceActualized: true))
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self, strongSelf.isNodeLoaded {
strongSelf.presentationData = presentationData
strongSelf.controllerNode.updatePresentationData(presentationData)
}
})
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.stickerPackDisposable.dispose()
self.stickerPackInstalledDisposable.dispose()
self.openMentionDisposable.dispose()
self.presentationDataDisposable?.dispose()
}
override public func loadDisplayNode() {
var openShareImpl: (() -> Void)?
if self.mode == .settings {
openShareImpl = { [weak self] in
guard let strongSelf = self else {
return
}
if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty {
let parentNavigationController = strongSelf.parentNavigationController
let shareController = strongSelf.context.sharedContext.makeShareController(
context: strongSelf.context,
subject: .url("https://t.me/addstickers/\(info.shortName)"),
forceExternal: true,
shareStory: nil,
enqueued: nil,
actionCompleted: { [weak parentNavigationController] in
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
}
}
)
strongSelf.present(shareController, in: .window(.root))
strongSelf.dismiss()
}
}
}
self.displayNode = StickerPackPreviewControllerNode(context: self.context, presentationData: self.presentationData, openShare: openShareImpl, openMention: { [weak self] mention in
guard let strongSelf = self else {
return
}
strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention, referrer: nil)
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
guard case let .result(result) = result else {
return .complete()
}
return .single(result)
}
|> mapToSignal { peer -> Signal<Peer?, NoError> in
if let peer = peer {
return .single(peer._asPeer())
} else {
return .single(nil)
}
}
|> deliverOnMainQueue).start(next: { peer in
guard let strongSelf = self else {
return
}
if let peer = peer, let parentNavigationController = strongSelf.parentNavigationController {
strongSelf.dismiss()
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), animated: true))
}
}))
}, actionPerformed: self.actionPerformed)
self.controllerNode.dismiss = { [weak self] in
self?.dismissed?()
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.controllerNode.cancel = { [weak self] in
self?.dismiss()
}
self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in
self?.presentInGlobalOverlay(controller, with: arguments)
}
if let sendSticker = self.sendSticker {
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
if sendSticker(file, sourceNode, sourceRect) {
self?.dismiss()
return true
} else {
return false
}
}
}
let account = self.context.account
self.displayNodeDidLoad()
self.stickerPackDisposable.set((combineLatest(self.stickerPackContents.get(), self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> take(1))
|> mapToSignal { next, sharedData -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
var stickerSettings = StickerSettings.defaultSettings
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
stickerSettings = value
}
switch next {
case let .result(info, items, _):
var preloadSignals: [Signal<Bool, NoError>] = []
let info = info._parse()
if let thumbnail = info.thumbnail {
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()
let data = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in
if data.complete {
subscriber.putNext(true)
subscriber.putCompletion()
} else {
subscriber.putNext(false)
}
})
return ActionDisposable {
fetched.dispose()
data.dispose()
}
}
preloadSignals.append(signal)
}
let topItems = items.prefix(16)
for item in topItems {
if item.file.isAnimatedSticker {
let itemFile = item.file._parse()
let signal = Signal<Bool, NoError> { subscriber in
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: FileMediaReference.standalone(media: itemFile).resourceReference(itemFile.resource)).start()
let data = account.postbox.mediaBox.resourceData(itemFile.resource).start()
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fetchedRepresentation = chatMessageAnimatedStickerDatas(postbox: account.postbox, userLocation: .other, file: itemFile, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)), fetched: true, onlyFullSize: false, synchronousLoad: false).start(next: { next in
let hasContent = next._0 != nil || next._1 != nil
subscriber.putNext(hasContent)
if hasContent {
subscriber.putCompletion()
}
})
return ActionDisposable {
fetched.dispose()
data.dispose()
fetchedRepresentation.dispose()
}
}
preloadSignals.append(signal)
}
}
return combineLatest(preloadSignals)
|> map { values -> Bool in
return !values.contains(false)
}
|> distinctUntilChanged
|> mapToSignal { loaded -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
if !loaded {
return .single((.fetching, stickerSettings))
} else {
return .single((next, stickerSettings))
}
}
default:
return .single((next, stickerSettings))
}
}
|> deliverOnMainQueue).start(next: { [weak self] next in
if let strongSelf = self {
if case .none = next.0 {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
strongSelf.dismiss()
} else {
strongSelf.controllerNode.updateStickerPack(next.0, stickerSettings: next.1)
strongSelf.stickerPackContentsValue = next.0
}
}
}))
self.ready.set(self.controllerNode.ready.get())
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !self.animatedIn {
self.animatedIn = true
self.controllerNode.animateIn()
}
}
override public func dismiss(completion: (() -> Void)? = nil) {
if !self.isDismissed {
self.isDismissed = true
} else {
return
}
self.acceptsFocusWhenInOverlay = false
self.requestUpdateParameters()
self.controllerNode.animateOut(completion: completion)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}
}

View file

@ -1,704 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import ActivityIndicator
import TextFormat
import AccountContext
import ContextUI
import StickerPeekUI
import AccountContext
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
let index: Int
let stickerItem: StickerPackItem
var stableId: MediaId {
return self.stickerItem.file.fileId
}
static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool {
return lhs.index < rhs.index
}
func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem {
return StickerPackPreviewGridItem(context: context, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false, isEditable: false, isEditing: false)
}
}
private struct StickerPackPreviewGridTransaction {
let deletions: [Int]
let insertions: [GridNodeInsertItem]
let updates: [GridNodeUpdateItem]
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
self.deletions = deleteIndices
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) }
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) }
}
}
final class StickerPackPreviewControllerNode: ViewControllerTracingNode, ASScrollViewDelegate {
private let context: AccountContext
private let openShare: (() -> Void)?
private var presentationData: PresentationData
private var containerLayout: (ContainerViewLayout, CGFloat)?
private let dimNode: ASDisplayNode
private let wrappingScrollNode: ASScrollNode
private let cancelButtonNode: ASButtonNode
private let contentContainerNode: ASDisplayNode
private let contentBackgroundNode: ASImageNode
private let contentGridNode: GridNode
private let installActionButtonNode: ASButtonNode
private let installActionSeparatorNode: ASDisplayNode
private let shareActionButtonNode: ASButtonNode
private let shareActionSeparatorNode: ASDisplayNode
private let contentTitleNode: ImmediateTextNode
private let contentSeparatorNode: ASDisplayNode
private var activityIndicator: ActivityIndicator?
private var interaction: StickerPackPreviewInteraction!
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?
let ready = Promise<Bool>()
private var didSetReady = false
private var stickerPack: LoadedStickerPack?
private var stickerPackUpdated = false
private var stickerPackInitiallyInstalled : Bool?
private var stickerSettings: StickerSettings?
private var currentItems: [StickerPackPreviewGridEntry] = []
private var hapticFeedback: HapticFeedback?
private weak var peekController: PeekController?
init(context: AccountContext, presentationData: PresentationData, openShare: (() -> Void)?, openMention: @escaping (String) -> Void, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?) {
self.context = context
self.openShare = openShare
self.presentationData = presentationData
self.actionPerformed = actionPerformed
self.wrappingScrollNode = ASScrollNode()
self.wrappingScrollNode.view.alwaysBounceVertical = true
self.wrappingScrollNode.view.delaysContentTouches = false
self.wrappingScrollNode.view.canCancelContentTouches = true
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
self.cancelButtonNode = ASButtonNode()
self.cancelButtonNode.displaysAsynchronously = false
self.contentContainerNode = ASDisplayNode()
self.contentContainerNode.isOpaque = false
self.contentContainerNode.clipsToBounds = true
self.contentBackgroundNode = ASImageNode()
self.contentBackgroundNode.displaysAsynchronously = false
self.contentBackgroundNode.displayWithoutProcessing = true
self.contentGridNode = GridNode()
self.installActionButtonNode = HighlightTrackingButtonNode()
self.installActionButtonNode.displaysAsynchronously = false
self.installActionButtonNode.titleNode.displaysAsynchronously = false
self.shareActionButtonNode = HighlightTrackingButtonNode()
self.shareActionButtonNode.displaysAsynchronously = false
self.shareActionButtonNode.titleNode.displaysAsynchronously = false
self.contentTitleNode = ImmediateTextNode()
self.contentTitleNode.displaysAsynchronously = false
self.contentTitleNode.maximumNumberOfLines = 1
self.contentSeparatorNode = ASDisplayNode()
self.contentSeparatorNode.isLayerBacked = true
self.installActionSeparatorNode = ASDisplayNode()
self.installActionSeparatorNode.isLayerBacked = true
self.installActionSeparatorNode.displaysAsynchronously = false
self.shareActionSeparatorNode = ASDisplayNode()
self.shareActionSeparatorNode.isLayerBacked = true
self.shareActionSeparatorNode.displaysAsynchronously = false
super.init()
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }, addPressed: {})
self.backgroundColor = nil
self.isOpaque = false
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
self.addSubnode(self.dimNode)
self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate
self.addSubnode(self.wrappingScrollNode)
self.wrappingScrollNode.addSubnode(self.cancelButtonNode)
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
self.installActionButtonNode.addTarget(self, action: #selector(self.installActionButtonPressed), forControlEvents: .touchUpInside)
self.shareActionButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside)
self.wrappingScrollNode.addSubnode(self.contentBackgroundNode)
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.contentGridNode)
self.contentContainerNode.addSubnode(self.installActionSeparatorNode)
self.contentContainerNode.addSubnode(self.installActionButtonNode)
if openShare != nil {
self.contentContainerNode.addSubnode(self.shareActionSeparatorNode)
self.contentContainerNode.addSubnode(self.shareActionButtonNode)
}
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
self.wrappingScrollNode.addSubnode(self.contentSeparatorNode)
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
}
self.contentTitleNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)
} else {
return nil
}
}
self.contentTitleNode.tapAttributeAction = { attributes, _ in
if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String, mention.count > 1 {
openMention(String(mention[mention.index(after: mention.startIndex)...]))
}
}
}
override func didLoad() {
super.didLoad()
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in
if let strongSelf = self {
if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
let accountPeerId = strongSelf.context.account.peerId
return combineLatest(
strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId),
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in
var hasPremium = false
if case let .user(user) = peer, user.isPremium {
hasPremium = true
}
return hasPremium
}
)
|> deliverOnMainQueue
|> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in
if let strongSelf = self {
var menuItems: [ContextMenuItem] = []
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil {
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
if let strongSelf = self, let peekController = strongSelf.peekController {
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), animationNode.view, animationNode.bounds)
} else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode {
let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), imageNode.view, imageNode.bounds)
}
}
f(.default)
})))
}
menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
if let strongSelf = self {
let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file._parse(), saved: !isStarred).start(next: { result in
})
}
})))
}
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file._parse()), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: {
}))
} else {
return nil
}
}
}
}
return nil
}, present: { [weak self] content, sourceView, sourceRect in
if let strongSelf = self {
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
return (sourceView, sourceRect)
})
controller.visibilityUpdated = { [weak self] visible in
if let strongSelf = self {
strongSelf.contentGridNode.forceHidden = visible
}
}
strongSelf.peekController = controller
strongSelf.presentInGlobalOverlay?(controller, nil)
return controller
}
return nil
}, updateContent: { [weak self] content in
if let strongSelf = self {
var item: StickerPreviewPeekItem?
if let content = content as? StickerPreviewPeekContent {
item = content.item
}
strongSelf.updatePreviewingItem(item: item, animated: true)
}
}, activateBySingleTap: true))
self.updatePresentationData(self.presentationData)
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
let theme = presentationData.theme
let solidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
let highlightedSolidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemBackgroundColor)
let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor)
self.contentBackgroundNode.image = roundedBackground
self.cancelButtonNode.setBackgroundImage(roundedBackground, for: .normal)
self.cancelButtonNode.setBackgroundImage(highlightedRoundedBackground, for: .highlighted)
if self.shareActionButtonNode.supernode != nil {
self.installActionButtonNode.setBackgroundImage(solidBackground, for: .normal)
self.installActionButtonNode.setBackgroundImage(highlightedSolidBackground, for: .highlighted)
} else {
self.installActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal)
self.installActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
}
self.shareActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal)
self.shareActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
self.shareActionButtonNode.setTitle(presentationData.strings.Conversation_ContextMenuShare, with: Font.regular(20.0), with: presentationData.theme.actionSheet.controlAccentColor, for: .normal)
self.contentSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
self.installActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
self.shareActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
self.cancelButtonNode.setTitle(presentationData.strings.Common_Cancel, with: Font.medium(20.0), with: presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
self.contentTitleNode.linkHighlightColor = presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
if let (layout, navigationBarHeight) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.containerLayout = (layout, navigationBarHeight)
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
var insets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
let cleanInsets = layout.insets(options: [.statusBar])
let hasShareButton = self.shareActionButtonNode.supernode != nil
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
if insets.bottom > 0 {
bottomInset -= 12.0
}
let buttonHeight: CGFloat = 57.0
let sectionSpacing: CGFloat = 8.0
let titleAreaHeight: CGFloat = 51.0
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
let sideInset = floor((layout.size.width - width) / 2.0)
transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight)))
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0)
var transaction: StickerPackPreviewGridTransaction?
var itemCount = 0
var animateIn = false
if let stickerPack = self.stickerPack {
switch stickerPack {
case .fetching, .none:
if self.activityIndicator == nil {
let activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(self.presentationData.theme.actionSheet.controlAccentColor, 22.0, 2.0, false))
self.activityIndicator = activityIndicator
self.addSubnode(activityIndicator)
}
case let .result(info, items, _):
if let activityIndicator = self.activityIndicator {
activityIndicator.removeFromSupernode()
self.activityIndicator = nil
}
itemCount = items.count
var updatedItems: [StickerPackPreviewGridEntry] = []
for item in items {
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
}
if self.currentItems.isEmpty && !updatedItems.isEmpty {
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
let font = Font.medium(20.0)
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: nil)
animateIn = true
}
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, context: self.context, interaction: self.interaction, theme: self.presentationData.theme)
self.currentItems = updatedItems
}
}
let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
let titleFrame = CGRect(origin: CGPoint(x: contentContainerFrame.minX + floor((contentContainerFrame.size.width - titleSize.width) / 2.0), y: self.contentBackgroundNode.frame.minY + 15.0), size: titleSize)
let deltaTitlePosition = CGPoint(x: titleFrame.midX - self.contentTitleNode.frame.midX, y: titleFrame.midY - self.contentTitleNode.frame.midY)
self.contentTitleNode.frame = titleFrame
transition.animatePosition(node: self.contentTitleNode, from: CGPoint(x: titleFrame.midX + deltaTitlePosition.x, y: titleFrame.midY + deltaTitlePosition.y))
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentContainerFrame.minX, y: self.contentBackgroundNode.frame.minY + titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
let itemsPerRow = 4
let itemWidth = floor(contentFrame.size.width / CGFloat(itemsPerRow))
let rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0)
let minimallyRevealedRowCount: CGFloat = 3.5
let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount))
let bottomGridInset = hasShareButton ? buttonHeight * 2.0 : buttonHeight
let topInset = max(0.0, contentFrame.size.height - initiallyRevealedRowCount * itemWidth - titleAreaHeight - bottomGridInset)
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
if let activityIndicator = self.activityIndicator {
let indicatorSize = activityIndicator.calculateSizeThatFits(layout.size)
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.width - indicatorSize.width) / 2.0), y: contentFrame.maxY - indicatorSize.height - 30.0), size: indicatorSize))
}
let installButtonOffset = hasShareButton ? buttonHeight * 2.0 : buttonHeight
transition.updateFrame(node: self.installActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
transition.updateFrame(node: self.installActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.shareActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
transition.updateFrame(node: self.shareActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight))
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
if animateIn {
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shareActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shareActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
if let _ = self.stickerPack, self.stickerPackUpdated {
self.dequeueUpdateStickerPack()
}
}
private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) {
if let (layout, _) = self.containerLayout {
var insets = layout.insets(options: [.statusBar])
insets.top = max(10.0, insets.top)
let cleanInsets = layout.insets(options: [.statusBar])
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
if insets.bottom > 0 {
bottomInset -= 12.0
}
let buttonHeight: CGFloat = 57.0
let sectionSpacing: CGFloat = 8.0
let titleAreaHeight: CGFloat = 51.0
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
let sideInset = floor((layout.size.width - width) / 2.0)
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY - presentationLayout.contentOffset.y), size: contentFrame.size)
if backgroundFrame.minY < contentFrame.minY {
backgroundFrame.origin.y = contentFrame.minY
}
if backgroundFrame.maxY > contentFrame.maxY {
backgroundFrame.size.height += contentFrame.maxY - backgroundFrame.maxY
}
if backgroundFrame.size.height < buttonHeight + 32.0 {
backgroundFrame.origin.y -= buttonHeight + 32.0 - backgroundFrame.size.height
backgroundFrame.size.height = buttonHeight + 32.0
}
var compactFrame = true
if let stickerPack = self.stickerPack, case .result = stickerPack {
compactFrame = false
}
if compactFrame {
backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.maxY - buttonHeight - 32.0), size: CGSize(width: contentFrame.size.width, height: buttonHeight + 32.0))
}
let backgroundDeltaY = backgroundFrame.minY - self.contentBackgroundNode.frame.minY
transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame)
transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -backgroundDeltaY))
let titleSize = self.contentTitleNode.bounds.size
let titleFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.size.width - titleSize.width) / 2.0), y: backgroundFrame.minY + 15.0), size: titleSize)
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: backgroundFrame.minY + titleAreaHeight), size: CGSize(width: contentFrame.size.width, height: UIScreenPixel)))
if !compactFrame && CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) {
self.contentSeparatorNode.alpha = 1.0
} else {
self.contentSeparatorNode.alpha = 0.0
}
}
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.cancelButtonPressed()
}
}
@objc func cancelButtonPressed() {
self.cancel?()
}
@objc func installActionButtonPressed() {
if let stickerPack = self.stickerPack, let _ = self.stickerSettings {
switch stickerPack {
case let .result(info, items, installed):
if installed {
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|> deliverOnMainQueue).start(next: { [weak self] indexAndItems in
guard let strongSelf = self, let (positionInList, _) = indexAndItems else {
return
}
strongSelf.actionPerformed?(info._parse(), items, .remove(positionInList: positionInList))
})
} else {
let parsedInfo = info._parse()
let _ = self.context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items).start()
self.actionPerformed?(parsedInfo, items, .add)
}
self.cancelButtonPressed()
default:
break
}
}
}
func animateIn() {
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
let offset: CGFloat = 510.0
let dimPosition = self.dimNode.layer.position
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
let targetBounds = self.bounds
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
transition.animateView({
self.bounds = targetBounds
self.dimNode.position = dimPosition
})
}
func animateOut(completion: (() -> Void)? = nil) {
var dimCompleted = false
var offsetCompleted = false
let internalCompletion: () -> Void = { [weak self] in
if let strongSelf = self, dimCompleted && offsetCompleted {
strongSelf.dismiss?()
}
completion?()
}
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
dimCompleted = true
internalCompletion()
})
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
let dimPosition = self.dimNode.layer.position
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
offsetCompleted = true
internalCompletion()
})
}
func updateStickerPack(_ stickerPack: LoadedStickerPack, stickerSettings: StickerSettings) {
self.stickerPack = stickerPack
self.stickerSettings = stickerSettings
self.stickerPackUpdated = true
self.interaction.playAnimatedStickers = self.context.sharedContext.energyUsageSettings.loopStickers
if let _ = self.containerLayout {
self.dequeueUpdateStickerPack()
}
switch stickerPack {
case .none, .fetching:
self.installActionSeparatorNode.alpha = 0.0
self.shareActionSeparatorNode.alpha = 0.0
self.shareActionButtonNode.alpha = 0.0
self.installActionButtonNode.alpha = 0.0
self.installActionButtonNode.setTitle("", with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
case let .result(info, _, installed):
if self.stickerPackInitiallyInstalled == nil {
self.stickerPackInitiallyInstalled = installed
}
self.installActionSeparatorNode.alpha = 1.0
self.shareActionSeparatorNode.alpha = 1.0
self.shareActionButtonNode.alpha = 1.0
self.installActionButtonNode.alpha = 1.0
if installed {
let text: String
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count)
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
text = self.presentationData.strings.StickerPack_RemoveEmojiCount(info.count)
} else {
text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count)
}
self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal)
} else {
let text: String
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
text = self.presentationData.strings.StickerPack_AddStickerCount(info.count)
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
text = self.presentationData.strings.StickerPack_AddEmojiCount(info.count)
} else {
text = self.presentationData.strings.StickerPack_AddMaskCount(info.count)
}
self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
}
}
}
func dequeueUpdateStickerPack() {
if let (layout, navigationBarHeight) = self.containerLayout, let _ = self.stickerPack, self.stickerPackUpdated {
self.stickerPackUpdated = false
let transition: ContainedViewLayoutTransition
if self.didSetReady {
transition = .animated(duration: 0.4, curve: .spring)
} else {
transition = .immediate
}
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
if !self.didSetReady {
self.didSetReady = true
self.ready.set(.single(true))
}
}
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.installActionButtonNode.hitTest(self.installActionButtonNode.convert(point, from: self), with: event) {
return result
}
else if self.shareActionButtonNode.supernode != nil, let result = self.shareActionButtonNode.hitTest(self.shareActionButtonNode.convert(point, from: self), with: event) {
return result
}
if self.bounds.contains(point) {
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) && !self.cancelButtonNode.bounds.contains(self.convert(point, to: self.cancelButtonNode)) {
return self.dimNode.view
}
}
return super.hitTest(point, with: event)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
let contentOffset = scrollView.contentOffset
let additionalTopHeight = max(0.0, -contentOffset.y)
if additionalTopHeight >= 30.0 {
self.cancelButtonPressed()
}
}
private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) {
if self.interaction.previewedItem != item {
self.interaction.previewedItem = item
self.contentGridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? StickerPackPreviewGridItemNode {
itemNode.updatePreviewing(animated: animated)
}
}
}
}
@objc private func sharePressed() {
self.openShare?()
}
}

View file

@ -29,6 +29,11 @@ import EmojiStatusComponent
private let maxStickersCount = 120
public enum StickerPackPreviewControllerMode {
case `default`
case settings
}
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool, isEditing: Bool, isAdd: Bool)
case add
@ -2354,6 +2359,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
self.controller = controller
self.presentationData = controller.presentationData
self.stickerPacks = stickerPacks
self.previewIconFile = previewIconFile
self.selectedStickerPackIndex = initialSelectedStickerPackIndex
self.modalProgressUpdated = modalProgressUpdated

View file

@ -757,7 +757,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[918946202] = { return Api.Peer.parse_peerChat($0) }
dict[1498486562] = { return Api.Peer.parse_peerUser($0) }
dict[-386039788] = { return Api.PeerBlocked.parse_peerBlocked($0) }
dict[-1192589655] = { return Api.PeerColor.parse_inputPeerColorCollectible($0) }
dict[-1253352753] = { return Api.PeerColor.parse_peerColor($0) }
dict[-1178573926] = { return Api.PeerColor.parse_peerColorCollectible($0) }
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
@ -962,7 +964,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
dict[-2136190013] = { return Api.StarGift.parse_starGift($0) }
dict[468707429] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[973640632] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) }
@ -1201,7 +1203,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
dict[34280482] = { return Api.User.parse_user($0) }
dict[-742634630] = { return Api.User.parse_userEmpty($0) }
dict[-982010451] = { return Api.UserFull.parse_userFull($0) }
dict[-1607745218] = { return Api.UserFull.parse_userFull($0) }
dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) }
dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) }
dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) }

View file

@ -750,10 +750,18 @@ public extension Api {
}
public extension Api {
enum PeerColor: TypeConstructorDescription {
case inputPeerColorCollectible(collectibleId: Int64)
case peerColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?)
case peerColorCollectible(flags: Int32, collectibleId: Int64, giftEmojiId: Int64, backgroundEmojiId: Int64, accentColor: Int32, colors: [Int32], darkAccentColor: Int32?, darkColors: [Int32]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPeerColorCollectible(let collectibleId):
if boxed {
buffer.appendInt32(-1192589655)
}
serializeInt64(collectibleId, buffer: buffer, boxed: false)
break
case .peerColor(let flags, let color, let backgroundEmojiId):
if boxed {
buffer.appendInt32(-1253352753)
@ -762,16 +770,52 @@ public extension Api {
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)}
break
case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors):
if boxed {
buffer.appendInt32(-1178573926)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(collectibleId, buffer: buffer, boxed: false)
serializeInt64(giftEmojiId, buffer: buffer, boxed: false)
serializeInt64(backgroundEmojiId, buffer: buffer, boxed: false)
serializeInt32(accentColor, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(colors.count))
for item in colors {
serializeInt32(item, buffer: buffer, boxed: false)
}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(darkAccentColor!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(darkColors!.count))
for item in darkColors! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPeerColorCollectible(let collectibleId):
return ("inputPeerColorCollectible", [("collectibleId", collectibleId as Any)])
case .peerColor(let flags, let color, let backgroundEmojiId):
return ("peerColor", [("flags", flags as Any), ("color", color as Any), ("backgroundEmojiId", backgroundEmojiId as Any)])
case .peerColorCollectible(let flags, let collectibleId, let giftEmojiId, let backgroundEmojiId, let accentColor, let colors, let darkAccentColor, let darkColors):
return ("peerColorCollectible", [("flags", flags as Any), ("collectibleId", collectibleId as Any), ("giftEmojiId", giftEmojiId as Any), ("backgroundEmojiId", backgroundEmojiId as Any), ("accentColor", accentColor as Any), ("colors", colors as Any), ("darkAccentColor", darkAccentColor as Any), ("darkColors", darkColors as Any)])
}
}
public static func parse_inputPeerColorCollectible(_ reader: BufferReader) -> PeerColor? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.PeerColor.inputPeerColorCollectible(collectibleId: _1!)
}
else {
return nil
}
}
public static func parse_peerColor(_ reader: BufferReader) -> PeerColor? {
var _1: Int32?
_1 = reader.readInt32()
@ -789,6 +833,42 @@ public extension Api {
return nil
}
}
public static func parse_peerColorCollectible(_ reader: BufferReader) -> PeerColor? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int64?
_4 = reader.readInt64()
var _5: Int32?
_5 = reader.readInt32()
var _6: [Int32]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
var _7: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_7 = reader.readInt32() }
var _8: [Int32]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_8 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.PeerColor.peerColorCollectible(flags: _1!, collectibleId: _2!, giftEmojiId: _3!, backgroundEmojiId: _4!, accentColor: _5!, colors: _6!, darkAccentColor: _7, darkColors: _8)
}
else {
return nil
}
}
}
}

View file

@ -289,7 +289,7 @@ public extension Api {
public extension Api {
enum StarGift: TypeConstructorDescription {
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?)
case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?)
case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?, peerColor: Api.PeerColor?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -315,9 +315,9 @@ public extension Api {
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)}
break
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer):
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor):
if boxed {
buffer.appendInt32(468707429)
buffer.appendInt32(973640632)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false)
@ -345,6 +345,7 @@ public extension Api {
if Int(flags) & Int(1 << 8) != 0 {serializeInt64(valueAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeString(valueCurrency!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 10) != 0 {themePeer!.serialize(buffer, true)}
if Int(flags) & Int(1 << 11) != 0 {peerColor!.serialize(buffer, true)}
break
}
}
@ -353,8 +354,8 @@ public extension Api {
switch self {
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate):
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)])
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer):
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any)])
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer, let peerColor):
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any), ("peerColor", peerColor as Any)])
}
}
@ -468,6 +469,10 @@ public extension Api {
if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() {
_18 = Api.parse(reader, signature: signature) as? Api.Peer
} }
var _19: Api.PeerColor?
if Int(_1!) & Int(1 << 11) != 0 {if let signature = reader.readInt32() {
_19 = Api.parse(reader, signature: signature) as? Api.PeerColor
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -486,8 +491,9 @@ public extension Api {
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18)
let _c19 = (Int(_1!) & Int(1 << 11) == 0) || _19 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18, peerColor: _19)
}
else {
return nil

View file

@ -614,13 +614,13 @@ public extension Api {
}
public extension Api {
enum UserFull: TypeConstructorDescription {
case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?)
case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, theme: Api.ChatTheme?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?, birthday: Api.Birthday?, personalChannelId: Int64?, personalChannelMessage: Int32?, stargiftsCount: Int32?, starrefProgram: Api.StarRefProgram?, botVerification: Api.BotVerification?, sendPaidMessagesStars: Int64?, disallowedGifts: Api.DisallowedGiftsSettings?, starsRating: Api.StarsRating?, starsMyPendingRating: Api.StarsRating?, starsMyPendingRatingDate: Int32?, mainTab: Api.ProfileTab?, savedMusic: Api.Document?, note: Api.TextWithEntities?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic):
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note):
if boxed {
buffer.appendInt32(-982010451)
buffer.appendInt32(-1607745218)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(flags2, buffer: buffer, boxed: false)
@ -660,14 +660,15 @@ public extension Api {
if Int(flags2) & Int(1 << 18) != 0 {serializeInt32(starsMyPendingRatingDate!, buffer: buffer, boxed: false)}
if Int(flags2) & Int(1 << 20) != 0 {mainTab!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 21) != 0 {savedMusic!.serialize(buffer, true)}
if Int(flags2) & Int(1 << 22) != 0 {note!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic):
return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any)])
case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let theme, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro, let birthday, let personalChannelId, let personalChannelMessage, let stargiftsCount, let starrefProgram, let botVerification, let sendPaidMessagesStars, let disallowedGifts, let starsRating, let starsMyPendingRating, let starsMyPendingRatingDate, let mainTab, let savedMusic, let note):
return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("theme", theme as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any), ("birthday", birthday as Any), ("personalChannelId", personalChannelId as Any), ("personalChannelMessage", personalChannelMessage as Any), ("stargiftsCount", stargiftsCount as Any), ("starrefProgram", starrefProgram as Any), ("botVerification", botVerification as Any), ("sendPaidMessagesStars", sendPaidMessagesStars as Any), ("disallowedGifts", disallowedGifts as Any), ("starsRating", starsRating as Any), ("starsMyPendingRating", starsMyPendingRating as Any), ("starsMyPendingRatingDate", starsMyPendingRatingDate as Any), ("mainTab", mainTab as Any), ("savedMusic", savedMusic as Any), ("note", note as Any)])
}
}
@ -796,6 +797,10 @@ public extension Api {
if Int(_2!) & Int(1 << 21) != 0 {if let signature = reader.readInt32() {
_38 = Api.parse(reader, signature: signature) as? Api.Document
} }
var _39: Api.TextWithEntities?
if Int(_2!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() {
_39 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -834,8 +839,9 @@ public extension Api {
let _c36 = (Int(_2!) & Int(1 << 18) == 0) || _36 != nil
let _c37 = (Int(_2!) & Int(1 << 20) == 0) || _37 != nil
let _c38 = (Int(_2!) & Int(1 << 21) == 0) || _38 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 {
return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38)
let _c39 = (Int(_2!) & Int(1 << 22) == 0) || _39 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 {
return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, theme: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, wallpaper: _19, stories: _20, businessWorkHours: _21, businessLocation: _22, businessGreetingMessage: _23, businessAwayMessage: _24, businessIntro: _25, birthday: _26, personalChannelId: _27, personalChannelMessage: _28, stargiftsCount: _29, starrefProgram: _30, botVerification: _31, sendPaidMessagesStars: _32, disallowedGifts: _33, starsRating: _34, starsMyPendingRating: _35, starsMyPendingRatingDate: _36, mainTab: _37, savedMusic: _38, note: _39)
}
else {
return nil

View file

@ -1654,13 +1654,12 @@ public extension Api.functions.account {
}
}
public extension Api.functions.account {
static func updateColor(flags: Int32, color: Int32?, backgroundEmojiId: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
static func updateColor(flags: Int32, color: Api.PeerColor?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(2096079197)
buffer.appendInt32(1749885262)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(backgroundEmojiId!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color)), ("backgroundEmojiId", String(describing: backgroundEmojiId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
if Int(flags) & Int(1 << 2) != 0 {color!.serialize(buffer, true)}
return (FunctionDescription(name: "account.updateColor", parameters: [("flags", String(describing: flags)), ("color", String(describing: color))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
@ -4045,15 +4044,16 @@ public extension Api.functions.contacts {
}
}
public extension Api.functions.contacts {
static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func addContact(flags: Int32, id: Api.InputUser, firstName: String, lastName: String, phone: String, note: Api.TextWithEntities?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-386636848)
buffer.appendInt32(-642109868)
serializeInt32(flags, buffer: buffer, boxed: false)
id.serialize(buffer, true)
serializeString(firstName, buffer: buffer, boxed: false)
serializeString(lastName, buffer: buffer, boxed: false)
serializeString(phone, buffer: buffer, boxed: false)
return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
if Int(flags) & Int(1 << 1) != 0 {note!.serialize(buffer, true)}
return (FunctionDescription(name: "contacts.addContact", parameters: [("flags", String(describing: flags)), ("id", String(describing: id)), ("firstName", String(describing: firstName)), ("lastName", String(describing: lastName)), ("phone", String(describing: phone)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
@ -4474,6 +4474,22 @@ public extension Api.functions.contacts {
})
}
}
public extension Api.functions.contacts {
static func updateContactNote(id: Api.InputUser, note: Api.TextWithEntities) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(329212923)
id.serialize(buffer, true)
note.serialize(buffer, true)
return (FunctionDescription(name: "contacts.updateContactNote", parameters: [("id", String(describing: id)), ("note", String(describing: note))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.folders {
static func editPeerFolders(folderPeers: [Api.InputFolderPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View file

@ -175,6 +175,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -185,6 +188,9 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -254,6 +260,9 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -264,6 +273,9 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}

View file

@ -46,6 +46,24 @@ public extension TelegramMediaFile {
}
}
public extension TelegramMediaFile {
func isValidForDisplay(chatPeerId: PeerId) -> Bool {
if chatPeerId.namespace == Namespaces.Peer.SecretChat {
if self.isAnimatedSticker {
if !self.attributes.contains(where: { attribute in
if case .hintIsValidated = attribute {
return true
}
return false
}) {
return false
}
}
}
return true
}
}
extension StickerPackReference {
init?(apiInputSet: Api.InputStickerSet) {
switch apiInputSet {
@ -163,7 +181,21 @@ func telegramMediaFileFromApiDocument(_ document: Api.Document, altDocuments: [A
switch document {
case let .document(_, id, accessHash, fileReference, _, mimeType, size, thumbs, videoThumbs, dcId, attributes):
var parsedAttributes = telegramMediaFileAttributesFromApiAttributes(attributes)
parsedAttributes.append(.hintIsValidated)
var isSticker = false
var isAnimated = false
for attribute in parsedAttributes {
switch attribute {
case .Sticker:
isSticker = true
case .Animated:
isAnimated = true
default:
break
}
}
if isSticker && isAnimated {
parsedAttributes.append(.hintIsValidated)
}
let (immediateThumbnail, previewRepresentations) = telegramMediaFileThumbnailRepresentationsFromApiSizes(datacenterId: dcId, documentId: id, accessHash: accessHash, fileReference: fileReference.makeData(), sizes: thumbs ?? [])

View file

@ -133,6 +133,9 @@ extension TelegramUser {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -143,6 +146,9 @@ extension TelegramUser {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -242,6 +248,9 @@ extension TelegramUser {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}
@ -252,6 +261,9 @@ extension TelegramUser {
case let .peerColor(_, color, backgroundEmojiIdValue):
profileColorIndex = color
profileBackgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}

View file

@ -71,9 +71,10 @@ func _internal_updateNameColorAndEmoji(account: Account, nameColor: PeerNameColo
flagsProfile |= (1 << 2)
}
//TODO:release
return combineLatest(
account.network.request(Api.functions.account.updateColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0)),
account.network.request(Api.functions.account.updateColor(flags: flagsProfile, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0))
account.network.request(Api.functions.account.updateColor(flags: 0 << 0, color: .peerColor(flags: flagsReplies, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0))),
account.network.request(Api.functions.account.updateColor(flags: 0 << 1, color: .peerColor(flags: flagsProfile, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0)))
)
|> mapError { _ -> UpdateNameColorAndEmojiError in
return .generic

View file

@ -60,7 +60,7 @@ func _internal_addContactInteractively(account: Account, peerId: PeerId, firstNa
if addToPrivacyExceptions {
flags |= (1 << 0)
}
return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone))
return account.network.request(Api.functions.contacts.addContact(flags: flags, id: inputUser, firstName: firstName, lastName: lastName, phone: phone, note: nil))
|> mapError { _ -> AddContactError in
return .generic
}

View file

@ -12,7 +12,7 @@ public enum UpdateContactNameError {
func _internal_updateContactName(account: Account, peerId: PeerId, firstName: String, lastName: String) -> Signal<Void, UpdateContactNameError> {
return account.postbox.transaction { transaction -> Signal<Void, UpdateContactNameError> in
if let peer = transaction.getPeer(peerId) as? TelegramUser, let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: ""))
return account.network.request(Api.functions.contacts.addContact(flags: 0, id: inputUser, firstName: firstName, lastName: lastName, phone: "", note: nil))
|> mapError { _ -> UpdateContactNameError in
return .generic
}

View file

@ -534,6 +534,9 @@ private class AdMessagesHistoryContextImpl {
case let .peerColor(_, color, backgroundEmojiIdValue):
nameColorIndex = color
backgroundEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
}

View file

@ -958,7 +958,7 @@ extension StarGift {
return nil
}
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate))
case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer):
case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer, _):
let owner: StarGift.UniqueGift.Owner
if let ownerAddress {
owner = .address(ownerAddress)

View file

@ -390,6 +390,10 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
case let .peerColor(_, color, backgroundEmojiIdValue):
prevColorIndex = color ?? 0
prevEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
prevColorIndex = 0
break
}
var newColorIndex: Int32
@ -398,6 +402,10 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
case let .peerColor(_, color, backgroundEmojiIdValue):
newColorIndex = color ?? 0
newEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
newColorIndex = 0
break
}
action = .changeNameColor(prevColor: PeerNameColor(rawValue: prevColorIndex), prevIcon: prevEmojiId, newColor: PeerNameColor(rawValue: newColorIndex), newIcon: newEmojiId)
@ -408,6 +416,9 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
case let .peerColor(_, color, backgroundEmojiIdValue):
prevColorIndex = color
prevEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
var newColorIndex: Int32?
@ -416,6 +427,9 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
case let .peerColor(_, color, backgroundEmojiIdValue):
newColorIndex = color
newEmojiId = backgroundEmojiIdValue
case .peerColorCollectible, .inputPeerColorCollectible:
//TODO:release
break
}
action = .changeProfileColor(prevColor: prevColorIndex.flatMap(PeerNameColor.init(rawValue:)), prevIcon: prevEmojiId, newColor: newColorIndex.flatMap(PeerNameColor.init(rawValue:)), newIcon: newEmojiId)

View file

@ -265,7 +265,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
}
switch fullUser {
case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)])
}
@ -277,7 +277,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
previous = CachedUserData()
}
switch fullUser {
case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic):
case let .userFull(userFullFlags, userFullFlags2, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullChatTheme, _, groupAdminRights, channelAdminRights, userWallpaper, _, businessWorkHours, businessLocation, greetingMessage, awayMessage, businessIntro, birthday, personalChannelId, personalChannelMessage, starGiftsCount, starRefProgram, verification, sendPaidMessageStars, disallowedStarGifts, starsRating, starsMyPendingRating, starsMyPendingRatingDate, mainTab, savedMusic, _):
let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:))
let isBlocked = (userFullFlags & (1 << 0)) != 0
let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0

View file

@ -948,13 +948,13 @@ public func makeDefaultDayPresentationTheme(extendingThemeReference: Presentatio
panelControlDestructiveColor: UIColor(rgb: 0xff3b30),
inputBackgroundColor: UIColor(rgb: 0xffffff),
inputStrokeColor: UIColor(rgb: 0x000000, alpha: 0.1),
inputPlaceholderColor: UIColor(rgb: 0x909090, alpha: 0.7),
inputPlaceholderColor: UIColor(rgb: 0x202020, alpha: 0.4),
inputTextColor: UIColor(rgb: 0x000000),
inputControlColor: UIColor(rgb: 0x202020, alpha: 0.6),
actionControlFillColor: defaultDayAccentColor,
actionControlForegroundColor: UIColor(rgb: 0xffffff),
primaryTextColor: UIColor(rgb: 0x000000),
secondaryTextColor: UIColor(rgb: 0x8e8e93),
primaryTextColor: UIColor(rgb: 0x000000, alpha: 0.9),
secondaryTextColor: UIColor(rgb: 0x202020, alpha: 0.6),
mediaRecordingDotColor: UIColor(rgb: 0xed2521),
mediaRecordingControl: inputPanelMediaRecordingControl
)

View file

@ -335,10 +335,12 @@ public struct PresentationResourcesChat {
return theme.image(PresentationResourceKey.chatInputTextFieldClearImage.rawValue, { theme in
return generateImage(CGSize(width: 14.0, height: 14.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chat.inputPanel.inputControlColor.cgColor)
context.setStrokeColor(theme.chat.inputPanel.inputBackgroundColor.cgColor)
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor.clear.cgColor)
context.setBlendMode(.copy)
context.setLineWidth(1.5)
context.setLineCap(.round)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
@ -356,7 +358,7 @@ public struct PresentationResourcesChat {
context.move(to: CGPoint(x: (size.width - lineHeight) / 2.0, y: size.width / 2.0))
context.addLine(to: CGPoint(x: (size.width - lineHeight) / 2.0 + lineHeight, y: size.width / 2.0))
context.strokePath()
})
})?.withRenderingMode(.alwaysTemplate)
})
}

View file

@ -491,6 +491,7 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel",
"//submodules/TelegramUI/Components/Chat/ChatInputMessageAccessoryPanel",
"//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode",
"//submodules/TelegramUI/Components/EdgeEffect",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View file

@ -24,6 +24,9 @@ swift_library(
"//submodules/AccountContext",
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
"//submodules/TooltipUI",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",

View file

@ -15,6 +15,9 @@ import AccountContext
import OldChannelsController
import TooltipUI
import TelegramNotices
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
private enum SubscriberAction: Equatable {
case join
@ -140,16 +143,22 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState:
private let badgeFont = Font.regular(14.0)
public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private let button: HighlightableButtonNode
private let discussButton: HighlightableButtonNode
private let discussButtonText: ImmediateTextNode
private let badgeBackground: ASImageNode
private let badgeText: ImmediateTextNode
private let activityIndicator: UIActivityIndicatorView
private let buttonBackgroundView: GlassBackgroundView
private let button: HighlightableButton
private let buttonTitle: ImmediateTextNode
private let buttonTintTitle: ImmediateTextNode
private let helpButton: HighlightableButtonNode
private let giftButton: HighlightableButtonNode
private let suggestedPostButton: HighlightableButtonNode
private let helpButtonBackgroundView: GlassBackgroundView
private let helpButton: HighlightableButton
private let helpButtonIconView: UIImageView
private let giftButtonBackgroundView: GlassBackgroundView
private let giftButton: HighlightableButton
private let giftButtonIconView: UIImageView
private let suggestedPostButtonBackgroundView: GlassBackgroundView
private let suggestedPostButton: HighlightableButton
private let suggestedPostButtonIconView: UIImageView
private var action: SubscriberAction?
@ -159,52 +168,53 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
private var presentationInterfaceState: ChatPresentationInterfaceState?
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, Bool, LayoutMetrics)?
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)?
public override init() {
self.button = HighlightableButtonNode()
self.discussButton = HighlightableButtonNode()
self.activityIndicator = UIActivityIndicatorView(style: .medium)
self.activityIndicator.isHidden = true
self.button = HighlightableButton()
self.buttonBackgroundView = GlassBackgroundView()
self.buttonBackgroundView.isUserInteractionEnabled = false
self.button.addSubview(self.buttonBackgroundView)
self.buttonTitle = ImmediateTextNode()
self.buttonTitle.isUserInteractionEnabled = false
self.buttonTintTitle = ImmediateTextNode()
self.buttonBackgroundView.contentView.addSubview(self.buttonTitle.view)
self.buttonBackgroundView.maskContentView.addSubview(self.buttonTintTitle.view)
self.discussButtonText = ImmediateTextNode()
self.discussButtonText.displaysAsynchronously = false
self.badgeBackground = ASImageNode()
self.badgeBackground.displaysAsynchronously = false
self.badgeBackground.displayWithoutProcessing = true
self.badgeBackground.isHidden = true
self.badgeText = ImmediateTextNode()
self.badgeText.displaysAsynchronously = false
self.badgeText.isHidden = true
self.helpButton = HighlightableButtonNode()
self.helpButton = HighlightableButton()
self.helpButton.isHidden = true
self.giftButton = HighlightableButtonNode()
self.giftButton.isHidden = true
self.suggestedPostButton = HighlightableButtonNode()
self.suggestedPostButton.isHidden = true
self.helpButtonBackgroundView = GlassBackgroundView()
self.helpButtonBackgroundView.isUserInteractionEnabled = false
self.helpButton.addSubview(self.helpButtonBackgroundView)
self.helpButtonIconView = GlassBackgroundView.ContentImageView()
self.helpButtonBackgroundView.contentView.addSubview(self.helpButtonIconView)
self.discussButton.addSubnode(self.discussButtonText)
self.discussButton.addSubnode(self.badgeBackground)
self.discussButton.addSubnode(self.badgeText)
self.giftButton = HighlightableButton()
self.giftButton.isHidden = true
self.giftButtonBackgroundView = GlassBackgroundView()
self.giftButtonBackgroundView.isUserInteractionEnabled = false
self.giftButton.addSubview(self.giftButtonBackgroundView)
self.giftButtonIconView = GlassBackgroundView.ContentImageView()
self.giftButtonBackgroundView.contentView.addSubview(self.giftButtonIconView)
self.suggestedPostButton = HighlightableButton()
self.suggestedPostButton.isHidden = true
self.suggestedPostButtonBackgroundView = GlassBackgroundView()
self.suggestedPostButtonBackgroundView.isUserInteractionEnabled = false
self.suggestedPostButton.addSubview(self.suggestedPostButtonBackgroundView)
self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView()
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView)
super.init()
self.clipsToBounds = true
self.addSubnode(self.button)
self.addSubnode(self.discussButton)
self.view.addSubview(self.activityIndicator)
self.addSubnode(self.helpButton)
self.addSubnode(self.giftButton)
self.addSubnode(self.suggestedPostButton)
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside)
self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside)
self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside)
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), forControlEvents: .touchUpInside)
self.view.addSubview(self.button)
self.view.addSubview(self.helpButton)
self.view.addSubview(self.giftButton)
self.view.addSubview(self.suggestedPostButton)
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside)
self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside)
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)
}
deinit {
@ -238,33 +248,14 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
switch action {
case .join, .joinGroup, .applyToJoin:
var delayActivity = false
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
delayActivity = true
}
if delayActivity {
Queue.mainQueue().after(1.5) {
if self.isJoining {
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
}
}
} else {
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimating()
}
self.isJoining = true
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true)
}
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peer.id, hash: nil)
|> afterDisposed { [weak self] in
Queue.mainQueue().async {
if let strongSelf = self {
strongSelf.activityIndicator.isHidden = true
strongSelf.activityIndicator.stopAnimating()
strongSelf.isJoining = false
}
}
@ -311,14 +302,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
}
}
@objc private func discussPressed() {
if let presentationInterfaceState = self.presentationInterfaceState, let peerDiscussionId = presentationInterfaceState.peerDiscussionId {
self.interfaceInteraction?.navigateToChat(peerDiscussionId)
}
}
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
}
private var displayedGiftOrSuggestTooltip = false
@ -349,7 +334,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view)
let absoluteFrame = self.giftButton.convert(self.giftButton.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -376,7 +361,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
Queue.mainQueue().after(0.4, {
let absoluteFrame = self.suggestedPostButton.view.convert(self.suggestedPostButton.bounds, to: parentController.view)
let absoluteFrame = self.suggestedPostButton.convert(self.suggestedPostButton.bounds, to: parentController.view)
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -405,19 +390,23 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
})
}
private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat {
private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat {
let isFirstTime = self.layoutData == nil
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics)
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics)
if self.presentationInterfaceState != interfaceState || force {
let previousState = self.presentationInterfaceState
self.presentationInterfaceState = interfaceState
if previousState?.theme !== interfaceState.theme {
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
self.helpButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.helpButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
self.suggestedPostButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.suggestedPostButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
self.giftButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: .white)?.withRenderingMode(.alwaysTemplate)
self.giftButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
}
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force {
@ -425,9 +414,11 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
if let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) {
let previousAction = self.action
self.action = action
let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
var offset: CGFloat = 30.0
let _ = previousAction
/*var offset: CGFloat = 30.0
if let previousAction = previousAction, [.join, .muteNotifications].contains(previousAction) && action == .unmuteNotifications || [.join, .unmuteNotifications].contains(previousAction) && action == .muteNotifications {
if [.join, .muteNotifications].contains(previousAction) {
@ -444,81 +435,95 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
self.button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.button.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true)
}
}
}*/
self.button.setTitle(title, with: Font.regular(17.0), with: color, for: [])
let titleColor: UIColor
if case .join = self.action {
titleColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor
} else {
titleColor = interfaceState.theme.chat.inputPanel.inputControlColor
}
self.buttonTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: titleColor)
self.buttonTintTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: .black)
self.button.accessibilityLabel = title
} else {
self.action = nil
}
self.discussButton.isHidden = true
}
}
let panelHeight = defaultHeight(metrics: metrics)
if self.discussButton.isHidden {
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable {
if self.giftButton.isHidden && !isFirstTime {
self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
self.giftButton.isHidden = false
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum)
self.presentGiftOrSuggestTooltip()
} else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
self.giftButton.isHidden = true
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = false
self.presentGiftOrSuggestTooltip()
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
self.giftButton.isHidden = true
self.helpButton.isHidden = false
self.suggestedPostButton.isHidden = true
} else {
self.giftButton.isHidden = true
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = true
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable {
if self.giftButton.isHidden && !isFirstTime {
self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
}
self.giftButton.isHidden = false
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum)
self.presentGiftOrSuggestTooltip()
} else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
self.giftButton.isHidden = true
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = false
self.presentGiftOrSuggestTooltip()
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
self.giftButton.isHidden = true
self.helpButton.isHidden = false
self.suggestedPostButton.isHidden = true
} else {
self.giftButton.isHidden = true
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = true
}
if let action = self.action, action == .muteNotifications || action == .unmuteNotifications {
let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0
self.button.frame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight))
} else {
self.button.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight))
}
self.giftButton.frame = CGRect(x: width - rightInset - panelHeight - 5.0, y: 0.0, width: panelHeight, height: panelHeight)
self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight)
self.suggestedPostButton.frame = CGRect(x: leftInset + 5.0, y: 0.0, width: panelHeight, height: panelHeight)
} else {
self.giftButton.isHidden = true
self.helpButton.isHidden = true
self.suggestedPostButton.isHidden = true
let availableWidth = min(600.0, width - leftInset - rightInset)
let leftOffset = floor((width - availableWidth) / 2.0)
self.button.frame = CGRect(origin: CGPoint(x: leftOffset, y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight))
self.discussButton.frame = CGRect(origin: CGPoint(x: leftOffset + floor(availableWidth / 2.0), y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight))
let discussButtonSize = self.discussButton.bounds.size
let discussTextSize = self.discussButtonText.updateLayout(discussButtonSize)
self.discussButtonText.frame = CGRect(origin: CGPoint(x: floor((discussButtonSize.width - discussTextSize.width) / 2.0), y: floor((discussButtonSize.height - discussTextSize.height) / 2.0)), size: discussTextSize)
let badgeOffset = self.discussButtonText.frame.maxX + 5.0 - self.badgeBackground.frame.minX
self.badgeBackground.frame = self.badgeBackground.frame.offsetBy(dx: badgeOffset, dy: 0.0)
self.badgeText.frame = self.badgeText.frame.offsetBy(dx: badgeOffset, dy: 0.0)
}
let indicatorSize = self.activityIndicator.bounds.size
self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width - 12.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
let buttonTitleSize = self.buttonTitle.updateLayout(CGSize(width: width, height: panelHeight))
let _ = self.buttonTintTitle.updateLayout(CGSize(width: width, height: panelHeight))
let buttonSize = CGSize(width: buttonTitleSize.width + 16.0 * 2.0, height: 40.0)
let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) * 0.5)), size: buttonSize)
transition.updateFrame(view: self.button, frame: buttonFrame)
transition.updateFrame(view: self.buttonBackgroundView, frame: CGRect(origin: CGPoint(), size: buttonFrame.size))
let buttonTintColor: GlassBackgroundView.TintColor
if case .join = self.action {
buttonTintColor = .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor)
} else {
buttonTintColor = .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
}
self.buttonBackgroundView.update(size: buttonFrame.size, cornerRadius: buttonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: buttonTintColor, transition: ComponentTransition(transition))
self.buttonTitle.frame = CGRect(origin: CGPoint(x: floor((buttonFrame.width - buttonTitleSize.width) * 0.5), y: floor((buttonFrame.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize)
self.buttonTintTitle.frame = self.buttonTitle.frame
let giftButtonFrame = CGRect(x: width - rightInset - 40.0 - 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.giftButton, frame: giftButtonFrame)
if let image = self.giftButtonIconView.image {
transition.updateFrame(view: self.giftButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: giftButtonFrame.size)))
}
transition.updateFrame(view: self.giftButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: giftButtonFrame.size))
self.giftButtonBackgroundView.update(size: giftButtonFrame.size, cornerRadius: giftButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
let helpButtonFrame = CGRect(x: width - rightInset - 8.0 - 40.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.helpButton, frame: helpButtonFrame)
if let image = self.helpButtonIconView.image {
transition.updateFrame(view: self.helpButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: helpButtonFrame.size)))
}
transition.updateFrame(view: self.helpButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: helpButtonFrame.size))
self.helpButtonBackgroundView.update(size: helpButtonFrame.size, cornerRadius: helpButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
let suggestedPostButtonFrame = CGRect(x: leftInset + 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
transition.updateFrame(view: self.suggestedPostButton, frame: suggestedPostButtonFrame)
if let image = self.suggestedPostButtonIconView.image {
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
}
transition.updateFrame(view: self.suggestedPostButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
return panelHeight
}

View file

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatInputAutocompletePanel",
module_name = "ChatInputAutocompletePanel",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/TelegramPresentationData",
"//submodules/TelegramUIPreferences",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,44 @@
import Foundation
import UIKit
import TelegramPresentationData
import TelegramUIPreferences
import GlassBackgroundComponent
public final class ChatInputAutocompletePanelEnvironment: Equatable {
public let theme: PresentationTheme
public let strings: PresentationStrings
public let nameDisplayOrder: PresentationPersonNameOrder
public let dateTimeFormat: PresentationDateTimeFormat
public init(
theme: PresentationTheme,
strings: PresentationStrings,
nameDisplayOrder: PresentationPersonNameOrder,
dateTimeFormat: PresentationDateTimeFormat
) {
self.theme = theme
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.dateTimeFormat = dateTimeFormat
}
public static func ==(lhs: ChatInputAutocompletePanelEnvironment, rhs: ChatInputAutocompletePanelEnvironment) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
return false
}
if lhs.dateTimeFormat != rhs.dateTimeFormat {
return false
}
return true
}
}
public protocol ChatInputAutocompletePanelView: UIView {
var contentTintView: UIView { get }
}

View file

@ -23,7 +23,7 @@ open class ChatInputPanelNode: ASDisplayNode {
open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
}
open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
return 0.0
}

View file

@ -72,7 +72,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
private var presentationInterfaceState: ChatPresentationInterfaceState?
private var actions: ChatAvailableMessageActions?
@ -167,8 +167,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
if self.selectedMessages.isEmpty {
self.actions = nil
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
self.canDeleteMessagesDisposable.set(nil)
} else if let context = self.context {
@ -176,8 +176,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|> deliverOnMainQueue).startStrict(next: { [weak self] actions in
if let strongSelf = self {
strongSelf.actions = actions
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight: maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
}
}))
@ -348,13 +348,13 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
}
private func update(transition: ContainedViewLayoutTransition) {
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
}
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
let panelHeight = defaultHeight(metrics: metrics)

View file

@ -981,10 +981,13 @@ final class OverscrollContentsComponent: Component {
let titleSize = self.titleNode.updateLayout(CGSize(width: availableSize.width - 32.0, height: 100.0))
let titleBackgroundSize = CGSize(width: titleSize.width + 18.0, height: titleSize.height + 8.0)
let titleBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleBackgroundSize.width) / 2.0), y: fullHeight - titleBackgroundSize.height - 8.0), size: titleBackgroundSize)
self.titleBackgroundNode.frame = titleBackgroundFrame
self.titleBackgroundNode.position = titleBackgroundFrame.center
self.titleBackgroundNode.bounds = CGRect(origin: CGPoint(), size: titleBackgroundFrame.size)
self.titleBackgroundNode.update(rect: titleBackgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate)
self.titleBackgroundNode.cornerRadius = min(titleBackgroundFrame.width, titleBackgroundFrame.height) / 2.0
self.titleNode.frame = titleSize.centered(in: titleBackgroundFrame)
let titleFrame = titleSize.centered(in: titleBackgroundFrame)
self.titleNode.position = titleFrame.center
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
let backgroundClippingFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: -fullHeight), size: CGSize(width: backgroundWidth, height: isFullyExpanded ? backgroundWidth : fullHeight))
self.backgroundClippingNode.cornerRadius = isFolderMask ? 10.0 : backgroundWidth / 2.0
@ -1003,7 +1006,7 @@ final class OverscrollContentsComponent: Component {
let transformTransition: ContainedViewLayoutTransition
if self.isFullyExpanded != isFullyExpanded {
self.isFullyExpanded = isFullyExpanded
transformTransition = .animated(duration: 0.12, curve: .easeInOut)
transformTransition = .animated(duration: 0.18, curve: .easeInOut)
if isFullyExpanded {
func animateBounce(layer: CALayer) {
@ -1067,8 +1070,11 @@ final class OverscrollContentsComponent: Component {
transformTransition.updateSublayerTransformOffset(layer: self.avatarOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))
transformTransition.updateSublayerTransformOffset(layer: self.arrowOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))
transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : 20.0))
transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : (titleBackgroundSize.height + 50.0)))
transformTransition.updateTransformScale(layer: self.titleBackgroundNode.layer, scale: isFullyExpanded ? 1.0 : 0.001)
transformTransition.updateTransformScale(layer: self.titleNode.layer, scale: isFullyExpanded ? 1.0 : 0.001)
transformTransition.updateSublayerTransformScale(node: self.avatarExtraScalingContainer, scale: isFullyExpanded ? 1.0 : ((backgroundWidth - avatarInset * 2.0) / backgroundWidth))
@ -1209,50 +1215,3 @@ public final class ChatOverscrollControl: CombinedComponent {
}
}
}
public final class ChatInputPanelOverscrollNode: ASDisplayNode {
public let text: NSAttributedString
public let priority: Int
private let titleNode: ImmediateTextNodeWithEntities
public init(context: AccountContext, text: NSAttributedString, color: UIColor, priority: Int) {
self.text = text
self.priority = priority
self.titleNode = ImmediateTextNodeWithEntities()
super.init()
let attributedText = NSMutableAttributedString(string: text.string)
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
attributedText.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: text.length))
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
attributedText.addAttribute(.font, value: Font.bold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
attributedText.addAttribute(key, value: value, range: range)
}
}
})
self.titleNode.attributedText = attributedText
self.titleNode.visibility = true
self.titleNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: color.withMultipliedAlpha(0.1),
attemptSynchronous: true
)
self.addSubnode(self.titleNode)
}
public func update(size: CGSize) {
let titleSize = self.titleNode.updateLayout(size)
self.titleNode.frame = titleSize.centered(in: CGRect(origin: CGPoint(), size: size))
}
}

View file

@ -387,7 +387,7 @@ public final class ChatRecordingPreviewInputPanelNodeImpl: ChatInputPanelNode {
})*/
}
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let innerSize = CGSize(width: 40.0, height: 40.0)
let waveformBackgroundFrame = CGRect(origin: CGPoint(x: 2.0, y: 2.0), size: CGSize(width: width - 2.0 * 2.0, height: 40.0 - 2.0 * 2.0))

View file

@ -61,7 +61,9 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatRecordingViewOnceButtonNode",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/Chat/ChatInputAccessoryPanel",
"//submodules/TelegramUI/Components/Chat/ChatInputAutocompletePanel",
"//submodules/TelegramUI/Components/Chat/ChatRecordingPreviewInputPanelNode",
"//submodules/TelegramUI/Components/Chat/ChatInputContextPanelNode",
],
visibility = [
"//visibility:public",

View file

@ -50,12 +50,14 @@ import PhotoResources
import GlassBackgroundComponent
import ComponentDisplayAdapters
import ChatInputAccessoryPanel
import ChatInputAutocompletePanel
import ChatTextInputSlowmodePlaceholderNode
import ChatTextInputActionButtonsNode
import ChatTextInputAudioRecordingTimeNode
import ChatTextInputAudioRecordingCancelIndicator
import ChatRecordingViewOnceButtonNode
import ChatRecordingPreviewInputPanelNode
import ChatInputContextPanelNode
private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers])
@ -216,6 +218,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public var textLockIconNode: ASImageNode?
public var contextPlaceholderNode: TextNode?
public var tintContextPlaceholderNode: TextNode?
public var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
public let textInputContainerBackgroundView: GlassBackgroundView
public let textInputContainer: ASDisplayNode
@ -253,8 +256,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public var attachmentImageNode: TransformImageNode?
public let searchLayoutClearButton: HighlightableButton
private let searchLayoutClearImageNode: ASImageNode
public let searchLayoutClearButton: HighlightTrackingButton
private let searchLayoutClearButtonIcon: GlassBackgroundView.ContentImageView
private var searchActivityIndicator: ActivityIndicator?
public var audioRecordingInfoContainerNode: ASDisplayNode?
public var audioRecordingDotView: UIImageView?
@ -266,11 +269,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public let viewOnceButton: ChatRecordingViewOnceButtonNode
private var accessoryPanel: (component: AnyComponentWithIdentity<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
@ -282,6 +286,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
public var toggleExpandMediaInput: (() -> Void)?
public var switchToTextInputIfNeeded: (() -> Void)?
public var textInputAccessoryPanel: ((_ context: AccountContext, _ chatPresentationInterfaceState: ChatPresentationInterfaceState, _ chatControllerInteraction: ChatControllerInteraction?, _ interfaceInteraction: ChatPanelInterfaceInteraction?) -> AnyComponentWithIdentity<ChatInputAccessoryPanelEnvironment>?)?
public var textInputContextPanel: ((_ context: AccountContext, _ chatPresentationInterfaceState: ChatPresentationInterfaceState, _ chatControllerInteraction: ChatControllerInteraction?, _ interfaceInteraction: ChatPanelInterfaceInteraction?, _ current: ChatInputContextPanelNode?) -> ChatInputContextPanelNode?)?
public var updateActivity: () -> Void = { }
@ -570,10 +575,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.attachmentButtonBackground.contentView.addSubview(self.attachmentButtonIcon)
self.attachmentButtonDisabledNode = HighlightableButtonNode()
self.searchLayoutClearButton = HighlightableButton()
self.searchLayoutClearImageNode = ASImageNode()
self.searchLayoutClearImageNode.isUserInteractionEnabled = false
self.searchLayoutClearButton.addSubnode(self.searchLayoutClearImageNode)
self.searchLayoutClearButton = HighlightTrackingButton()
self.searchLayoutClearButtonIcon = GlassBackgroundView.ContentImageView()
self.actionButtons = ChatTextInputActionButtonsNode(context: context, presentationInterfaceState: presentationInterfaceState, presentationContext: presentationContext, presentController: presentController)
self.counterTextNode = ImmediateTextNode()
@ -726,15 +729,15 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
self.actionButtons.micButton.offsetRecordingControls = { [weak self] in
if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState {
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
}
}
self.actionButtons.micButton.updateCancelTranslation = { [weak self] in
if let strongSelf = self, let presentationInterfaceState = strongSelf.presentationInterfaceState {
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
}
}
@ -763,8 +766,21 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.actionButtons.expandMediaInputButton.addTarget(self, action: #selector(self.expandButtonPressed), for: .touchUpInside)
self.actionButtons.expandMediaInputButton.alpha = 0.0
self.searchLayoutClearButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.searchLayoutClearButtonIcon.alpha = 0.6
} else {
self.searchLayoutClearButtonIcon.alpha = 1.0
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
transition.updateAlpha(layer: self.searchLayoutClearButtonIcon.layer, alpha: 1.0)
}
}
self.searchLayoutClearButton.addTarget(self, action: #selector(self.searchLayoutClearButtonPressed), for: .touchUpInside)
self.searchLayoutClearButton.alpha = 0.0
self.searchLayoutClearButtonIcon.alpha = 0.0
self.clippingNode.addSubnode(self.textInputContainer)
self.clippingNode.addSubnode(self.textInputBackgroundNode)
@ -793,7 +809,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.clippingNode.addSubnode(self.slowModeButton)
self.clippingNode.view.addSubview(self.searchLayoutClearButton)
self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButton)
self.textInputContainerBackgroundView.contentView.addSubview(self.searchLayoutClearButtonIcon)
self.textInputContainerBackgroundView.maskContentView.addSubview(self.searchLayoutClearButtonIcon.tintMask)
self.textInputBackgroundNode.clipsToBounds = true
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
@ -1217,10 +1235,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
public func requestLayout(transition: ContainedViewLayoutTransition = .immediate) {
guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else {
guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout else {
return
}
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: presentationInterfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
override public func updateLayout(
@ -1230,6 +1248,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
bottomInset: CGFloat,
additionalSideInsets: UIEdgeInsets,
maxHeight: CGFloat,
maxOverlayHeight: CGFloat,
isSecondary: Bool,
transition: ContainedViewLayoutTransition,
interfaceState: ChatPresentationInterfaceState,
@ -1237,7 +1256,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
isMediaInputExpanded: Bool
) -> CGFloat {
let previousAdditionalSideInsets = self.validLayout?.4
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
var transition = transition
var additionalOffset: CGFloat = 0.0
@ -1249,7 +1268,10 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
}
let previousContextPanel = self.contextPanel
var accessoryPanel: AnyComponentWithIdentity<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)
@ -1930,6 +1960,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if additionalSideInsets.right > 0.0 {
textFieldInsets.right += additionalSideInsets.right / 3.0
}
if self.extendedSearchLayout {
textFieldInsets.right = 8.0
}
if mediaRecordingState != nil {
textFieldInsets.left = 8.0
}
@ -2311,7 +2344,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
transition.updateAlpha(layer: mediaPreviewPanelNode.tintMaskView.layer, alpha: 1.0)
transition.updateFrame(view: mediaPreviewPanelNode.tintMaskView, frame: mediaPreviewPanelFrame)
let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: false)
let _ = mediaPreviewPanelNode.updateLayout(width: mediaPreviewPanelFrame.width, leftInset: 0.0, rightInset: 0.0, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: 40.0, maxOverlayHeight: 40.0, isSecondary: false, transition: mediaPreviewPanelTransition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: false)
} else if let mediaPreviewPanelNode = self.mediaPreviewPanelNode {
self.mediaPreviewPanelNode = nil
transition.updateAlpha(node: mediaPreviewPanelNode, alpha: 0.0, completion: { [weak mediaPreviewPanelNode] _ in
@ -2376,20 +2409,33 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if interfaceState.slowmodeState == nil || isScheduledMessages, let contextPlaceholder = interfaceState.inputTextPanelState.contextPlaceholder {
let placeholderLayout = TextNode.asyncLayout(self.contextPlaceholderNode)
let tintPlaceholderLayout = TextNode.asyncLayout(self.tintContextPlaceholderNode)
let (placeholderSize, placeholderApply) = placeholderLayout(TextNodeLayoutArguments(attributedString: contextPlaceholder, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let tintContextPlaceholder = NSMutableAttributedString(attributedString: contextPlaceholder)
tintContextPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(location: 0, length: tintContextPlaceholder.length))
let (_, tintPlaceholderApply) = tintPlaceholderLayout(TextNodeLayoutArguments(attributedString: tintContextPlaceholder, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contextPlaceholderNode = placeholderApply()
let tintContextPlaceholderNode = tintPlaceholderApply()
if let currentContextPlaceholderNode = self.contextPlaceholderNode, currentContextPlaceholderNode !== contextPlaceholderNode {
self.contextPlaceholderNode = nil
currentContextPlaceholderNode.removeFromSupernode()
}
if let currentTintContextPlaceholderNode = self.tintContextPlaceholderNode, currentTintContextPlaceholderNode !== tintContextPlaceholderNode {
self.tintContextPlaceholderNode = nil
currentTintContextPlaceholderNode.removeFromSupernode()
}
if self.contextPlaceholderNode !== contextPlaceholderNode {
contextPlaceholderNode.displaysAsynchronously = false
contextPlaceholderNode.isUserInteractionEnabled = false
self.contextPlaceholderNode = contextPlaceholderNode
self.textInputContainerBackgroundView.contentView.insertSubview(contextPlaceholderNode.view, aboveSubview: self.textPlaceholderNode.view)
self.textInputContainerBackgroundView.contentView.insertSubview(contextPlaceholderNode.view, aboveSubview: self.textPlaceholderNode.view)
}
if self.tintContextPlaceholderNode !== tintContextPlaceholderNode {
tintContextPlaceholderNode.displaysAsynchronously = false
tintContextPlaceholderNode.isUserInteractionEnabled = false
self.tintContextPlaceholderNode = tintContextPlaceholderNode
self.textInputContainerBackgroundView.maskContentView.insertSubview(tintContextPlaceholderNode.view, aboveSubview: self.tintMaskTextPlaceholderNode.view)
}
let _ = placeholderApply()
@ -2400,13 +2446,22 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
} else {
placeholderTransition = .immediate
}
placeholderTransition.updateFrame(node: contextPlaceholderNode, frame: CGRect(origin: CGPoint(x: hideOffset.x + leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size))
placeholderTransition.updateFrame(node: contextPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size))
contextPlaceholderNode.alpha = audioRecordingItemsAlpha
} else if let contextPlaceholderNode = self.contextPlaceholderNode {
self.contextPlaceholderNode = nil
contextPlaceholderNode.removeFromSupernode()
self.textPlaceholderNode.alpha = 1.0
self.tintMaskTextPlaceholderNode.alpha = 1.0
placeholderTransition.updateFrame(node: tintContextPlaceholderNode, frame: CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: hideOffset.y + textFieldInsets.top + self.textInputViewInternalInsets.top + textInputViewRealInsets.top + UIScreenPixel), size: placeholderSize.size))
tintContextPlaceholderNode.alpha = audioRecordingItemsAlpha
} else {
if let contextPlaceholderNode = self.contextPlaceholderNode {
self.contextPlaceholderNode = nil
contextPlaceholderNode.removeFromSupernode()
self.textPlaceholderNode.alpha = 1.0
self.tintMaskTextPlaceholderNode.alpha = 1.0
}
if let tintContextPlaceholderNode = self.tintContextPlaceholderNode {
self.tintContextPlaceholderNode = nil
tintContextPlaceholderNode.removeFromSupernode()
}
}
if let slowmodeState = interfaceState.slowmodeState, !isScheduledMessages && rightSlowModeInset.isZero {
@ -2439,6 +2494,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
var nextButtonTopRight = CGPoint(x: textInputContainerBackgroundFrame.width - accessoryButtonInset - rightSlowModeInset, y: textInputContainerBackgroundFrame.height - minimalInputHeight)
if self.extendedSearchLayout {
nextButtonTopRight.x -= 26.0
}
for (item, button) in self.accessoryItemButtons.reversed() {
let buttonSize = CGSize(width: button.buttonWidth, height: minimalInputHeight)
button.updateLayout(item: item, size: buttonSize)
@ -2485,7 +2543,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
attributedPlaceholder.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedPlaceholder.string))
}
let attributedTintMaskPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: .black)
let attributedTintMaskPlaceholder = NSMutableAttributedString(string: currentPlaceholder, font: Font.regular(baseFontSize), textColor: UIColor(white: 0.0, alpha: placeholderColor.alpha))
if placeholderHasStar, let range = attributedPlaceholder.string.range(of: "#") {
attributedTintMaskPlaceholder.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: attributedPlaceholder.string))
attributedTintMaskPlaceholder.addAttribute(.foregroundColor, value: UIColor.black, range: NSRange(range, in: attributedPlaceholder.string))
@ -2614,13 +2672,16 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
}
let searchLayoutClearButtonSize = CGSize(width: 40.0, height: minimalHeight)
let searchLayoutClearButtonSize = CGSize(width: 40.0, height: 40.0)
self.actionButtons.micButton.isHidden = additionalSideInsets.right > 0.0
self.actionButtons.micButtonBackgroundView.isHidden = self.actionButtons.micButton.isHidden
transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: CGRect(origin: CGPoint(x: width - rightInset - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset + 3.0, y: panelHeight - minimalHeight), size: searchLayoutClearButtonSize))
if let image = self.searchLayoutClearImageNode.image {
self.searchLayoutClearImageNode.frame = CGRect(origin: CGPoint(x: floor((searchLayoutClearButtonSize.width - image.size.width) / 2.0), y: floor((searchLayoutClearButtonSize.height - image.size.height) / 2.0)), size: image.size)
let clearButtonFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.width - searchLayoutClearButtonSize.width, y: floor((textInputContainerBackgroundFrame.height - searchLayoutClearButtonSize.height) * 0.5)), size: searchLayoutClearButtonSize)
transition.updateFrame(layer: self.searchLayoutClearButton.layer, frame: clearButtonFrame)
if let image = self.searchLayoutClearButtonIcon.image {
let clearIconFrame = CGRect(origin: CGPoint(x: floor((searchLayoutClearButtonSize.width - image.size.width) / 2.0), y: floor((searchLayoutClearButtonSize.height - image.size.height) / 2.0)), size: image.size)
transition.updateFrame(layer: self.searchLayoutClearButtonIcon.layer, frame: clearIconFrame.offsetBy(dx: clearButtonFrame.minX, dy: clearButtonFrame.minY))
}
let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: textInputFrame.maxY - 40.0), size: CGSize(width: 40.0, height: 40.0))
@ -2736,6 +2797,55 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.viewOnceButton.isHidden = true
}
if contextPanelNode !== previousContextPanel?.panel, let previousContextPanel {
let panelContainer = previousContextPanel.container
previousContextPanel.panel.animateOut(completion: { [weak panelContainer] in
panelContainer?.removeFromSuperview()
})
self.contextPanel = nil
}
if let contextPanelNode {
if self.contextPanel == nil {
self.contextPanel = (UIView(), UIImageView(), contextPanelNode)
}
}
if let contextPanel = self.contextPanel {
let maskInset: CGFloat = 32.0
var contextPanelTransition = transition
if contextPanel.container.superview == nil {
contextPanelTransition = .immediate
self.view.insertSubview(contextPanel.container, belowSubview: self.clippingNode.view)
contextPanel.container.addSubview(contextPanel.panel.view)
contextPanel.container.mask = contextPanel.mask
//contextPanel.container.addSubview(contextPanel.mask)
let maskSize = floor(minimalInputHeight)
contextPanel.mask.image = generateImage(CGSize(width: maskSize + maskInset * 2.0, height: maskSize + maskInset * 2.0), rotatedContext: { size, context in
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: maskInset, y: maskInset + maskSize * 0.5), size: CGSize(width: maskSize, height: maskSize)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: maskInset + maskSize), size: CGSize(width: maskSize + maskInset * 2.0, height: maskSize + maskInset)))
})?.stretchableImage(withLeftCapWidth: Int(maskInset) + Int(maskSize) / 2, topCapHeight: Int(maskInset) + 1)
}
let contextPanelBottomInset = floor(minimalInputHeight * 0.5)
let contextPanelFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.minX, y: contentHeight - maxOverlayHeight), size: CGSize(width: textInputContainerBackgroundFrame.width, height: max(0.0, maxOverlayHeight - contentHeight + contextPanelBottomInset)))
contextPanelTransition.updateFrame(view: contextPanel.container, frame: contextPanelFrame)
contextPanelTransition.updateFrame(view: contextPanel.panel.view, frame: CGRect(origin: CGPoint(), size: contextPanelFrame.size))
contextPanelTransition.updateFrame(view: contextPanel.mask, frame: CGRect(origin: CGPoint(), size: contextPanelFrame.size).insetBy(dx: -maskInset, dy: -maskInset))
contextPanel.panel.updateLayout(
size: contextPanelFrame.size,
leftInset: 0.0,
rightInset: 0.0,
bottomInset: contextPanelBottomInset,
transition: contextPanelTransition,
interfaceState: interfaceState
)
}
return contentHeight
}
@ -3252,7 +3362,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.counterTextNode.attributedText = NSAttributedString(string: "", font: counterFont, textColor: .black)
}
if let (width, leftInset, rightInset, _, _, maxHeight, metrics, _, _) = self.validLayout {
if let (width, leftInset, rightInset, _, _, maxHeight, _, metrics, _, _) = self.validLayout {
var composeButtonsOffset: CGFloat = 0.0
if self.extendedSearchLayout {
composeButtonsOffset = 40.0
@ -3611,9 +3721,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
if self.searchLayoutClearButton.alpha.isZero {
self.searchLayoutClearButton.alpha = 1.0
self.searchLayoutClearButtonIcon.alpha = 1.0
if animated {
self.searchLayoutClearButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.searchLayoutClearButton.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2)
self.searchLayoutClearButtonIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
self.searchLayoutClearButtonIcon.layer.animateScale(from: 0.8, to: 1.0, duration: 0.2)
}
}
} else {
@ -3621,9 +3734,12 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if !self.searchLayoutClearButton.alpha.isZero {
animateWithBounce = false
self.searchLayoutClearButton.alpha = 0.0
self.searchLayoutClearButtonIcon.alpha = 0.0
if animated {
self.searchLayoutClearButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.searchLayoutClearButton.layer.animateScale(from: 1.0, to: 0.8, duration: 0.2)
self.searchLayoutClearButtonIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.searchLayoutClearButtonIcon.layer.animateScale(from: 1.0, to: 0.8, duration: 0.2)
}
}
@ -3763,7 +3879,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
private func updateTextHeight(animated: Bool) {
if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, metrics, _, _) = self.validLayout {
if let (width, leftInset, rightInset, _, additionalSideInsets, maxHeight, _, metrics, _, _) = self.validLayout {
let (_, textFieldHeight, _) = self.calculateTextFieldMetrics(width: width - leftInset - rightInset - additionalSideInsets.right - self.leftMenuInset - self.rightSlowModeInset + self.currentTextInputBackgroundWidthOffset, maxHeight: maxHeight, metrics: metrics)
let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight, metrics: metrics)
if !self.bounds.size.height.isEqual(to: panelHeight) {
@ -4572,6 +4688,18 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
}
if !self.searchLayoutClearButton.alpha.isZero {
if let result = self.searchLayoutClearButton.hitTest(self.view.convert(point, to: self.searchLayoutClearButton), with: event) {
return result
}
}
if !self.bounds.contains(point), let contextPanel = self.contextPanel {
if let result = contextPanel.panel.view.hitTest(self.view.convert(point, to: contextPanel.panel.view), with: event) {
return result
}
}
let result = super.hitTest(point, with: event)
return result
}

View file

@ -26,7 +26,7 @@ public final class EdgeEffectView: UIView {
fatalError("init(coder:) has not been implemented")
}
public func update(content: UIColor, rect: CGRect, edge: Edge, edgeSize: CGFloat, containerSize: CGSize, transition: ComponentTransition) {
public func update(content: UIColor, isInverted: Bool, rect: CGRect, edge: Edge, edgeSize: CGFloat, containerSize: CGSize, transition: ComponentTransition) {
self.contentView.backgroundColor = content
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: rect.size))

View file

@ -266,6 +266,7 @@ public final class GlassBackgroundView: UIView {
private let foregroundView: UIImageView?
private let shadowView: UIImageView?
private let maskContainerView: UIView
public let maskContentView: UIView
private let contentContainer: ContentContainer
@ -298,12 +299,15 @@ public final class GlassBackgroundView: UIView {
self.shadowView = UIImageView()
}
self.maskContentView = UIView()
self.maskContentView.backgroundColor = .white
self.maskContainerView = UIView()
self.maskContainerView.backgroundColor = .white
if let filter = CALayer.luminanceToAlpha() {
self.maskContentView.layer.filters = [filter]
self.maskContainerView.layer.filters = [filter]
}
self.maskContentView = UIView()
self.maskContainerView.addSubview(self.maskContentView)
self.contentContainer = ContentContainer(maskContentView: self.maskContentView)
super.init(frame: frame)
@ -319,7 +323,7 @@ public final class GlassBackgroundView: UIView {
}
if let foregroundView = self.foregroundView {
self.addSubview(foregroundView)
foregroundView.mask = self.maskContentView
foregroundView.mask = self.maskContainerView
}
self.addSubview(self.contentContainer)
}
@ -361,7 +365,7 @@ public final class GlassBackgroundView: UIView {
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 30.0, color: UIColor(white: 0.0, alpha: 0.08).cgColor)
context.setShadow(offset: CGSize(width: 0.0, height: 1.0), blur: 40.0, color: UIColor(white: 0.0, alpha: 0.09).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset + shadowInnerInset, y: shadowInset + shadowInnerInset), size: CGSize(width: size.width - shadowInset * 2.0 - shadowInnerInset * 2.0, height: size.height - shadowInset * 2.0 - shadowInnerInset * 2.0)))
context.setFillColor(UIColor.clear.cgColor)
@ -371,7 +375,7 @@ public final class GlassBackgroundView: UIView {
}
if let foregroundView = self.foregroundView {
foregroundView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), isDark: isDark, fillColor: tintColor.color)
foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: tintColor.color)
} else {
if let nativeView {
if #available(iOS 26.0, *) {
@ -390,9 +394,10 @@ public final class GlassBackgroundView: UIView {
}
}
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.maskContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width + shadowInset * 2.0, height: size.height + shadowInset * 2.0)))
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: size))
if let foregroundView = self.foregroundView {
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: foregroundView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -shadowInset, dy: -shadowInset))
}
if let shadowView = self.shadowView {
transition.setFrame(view: shadowView, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -shadowInset, dy: -shadowInset))
@ -491,6 +496,210 @@ public final class VariableBlurView: UIVisualEffectView {
}
public extension GlassBackgroundView {
static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, isDark: Bool, fillColor: UIColor) -> UIImage {
var size = size
if size == .zero {
size = CGSize(width: 1.0, height: 1.0)
}
let innerSize = size
size.width += inset * 2.0
size.height += inset * 2.0
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
func pathApplyingSpread(_ path: CGPath, spread: CGFloat) -> CGPath {
guard spread != 0 else { return path }
let result = CGMutablePath()
result.addPath(path)
// Copy a stroked outline centered on the original path boundary.
// Filling it plus the original path approximates an outward "spread".
let outline = path.copy(
strokingWithWidth: abs(spread) * 2,
lineCap: .butt,
lineJoin: .miter,
miterLimit: 10,
transform: .identity
)
result.addPath(outline)
// For negative spread (tighten), use even-odd to carve inside:
if spread < 0 {
let carve = CGMutablePath()
carve.addPath(path)
carve.addPath(outline)
// even-odd: outline - original outer ring; union with original earlier keeps overall stable
// For "tightening" effect we rely on clipping in inner shadow branch below.
}
return result
}
// Your requested closure:
let addShadow: (Bool, CGPoint, CGFloat, CGFloat, UIColor) -> Void = { isOuter, position, blur, spread, shadowColor in
var blur = blur
blur += abs(spread)
if isOuter {
context.beginTransparencyLayer(auxiliaryInfo: nil)
context.saveGState()
defer {
context.restoreGState()
context.endTransparencyLayer()
}
let spreadRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize).insetBy(dx: 0.25, dy: 0.25)
let spreadPath = UIBezierPath(
roundedRect: spreadRect,
cornerRadius: min(spreadRect.width, spreadRect.height) * 0.5
).cgPath
context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor)
context.setFillColor(UIColor.black.withAlphaComponent(1.0).cgColor)
context.addPath(spreadPath)
context.fillPath()
let cleanRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize)
let cleanPath = UIBezierPath(
roundedRect: cleanRect,
cornerRadius: min(cleanRect.width, cleanRect.height) * 0.5
).cgPath
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.addPath(cleanPath)
context.fillPath()
context.setBlendMode(.normal)
} else {
context.beginTransparencyLayer(auxiliaryInfo: nil)
context.saveGState()
defer {
context.restoreGState()
context.endTransparencyLayer()
}
let spreadRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize).insetBy(dx: -0.25, dy: -0.25)
let spreadPath = UIBezierPath(
roundedRect: spreadRect,
cornerRadius: min(spreadRect.width, spreadRect.height) * 0.5
).cgPath
context.setShadow(offset: CGSize(width: position.x, height: position.y), blur: blur, color: shadowColor.cgColor)
context.setFillColor(UIColor.black.withAlphaComponent(1.0).cgColor)
let enclosingRect = spreadRect.insetBy(dx: -10000.0, dy: -10000.0)
context.addPath(UIBezierPath(rect: enclosingRect).cgPath)
context.addPath(spreadPath)
context.fillPath(using: .evenOdd)
let cleanRect = CGRect(origin: CGPoint(x: inset, y: inset), size: innerSize)
let cleanPath = UIBezierPath(
roundedRect: cleanRect,
cornerRadius: min(cleanRect.width, cleanRect.height) * 0.5
).cgPath
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.addPath(UIBezierPath(rect: enclosingRect).cgPath)
context.addPath(cleanPath)
context.fillPath(using: .evenOdd)
context.setBlendMode(.normal)
}
}
if isDark {
addShadow(true, CGPoint(), 16.0, 0.0, UIColor(white: 0.0, alpha: 0.12))
addShadow(true, CGPoint(), 8.0, 0.0, UIColor(white: 0.0, alpha: 0.1))
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset))
addShadow(false, CGPoint(x: 0.0, y: 0.0), 3.0, 0.0, UIColor(white: 1.0, alpha: 0.5))
addShadow(false, CGPoint(x: 3.0, y: -3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25))
addShadow(false, CGPoint(x: -3.0, y: 3.0), 2.0, 0.0, UIColor(white: 1.0, alpha: 0.25))
} else {
addShadow(true, CGPoint(), 16.0, 0.0, UIColor(white: 0.0, alpha: 0.08))
addShadow(true, CGPoint(), 8.0, 0.0, UIColor(white: 0.0, alpha: 0.08))
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset))
addShadow(false, CGPoint(x: 3.0, y: -3.0), 0.5, 0.0, fillColor.withMultiplied(hue: 1.0, saturation: 2.0, brightness: 1.0).adjustedPerceivedBrightness(3.0).withMultipliedAlpha(1.0))
addShadow(false, CGPoint(x: -2.0, y: 2.0), 0.5, 0.0, UIColor.black.withMultipliedAlpha(0.15))
}
if "".isEmpty {
return
}
let maxColor = UIColor(white: 1.0, alpha: isDark ? 0.25 : 0.9)
let minColor = UIColor(white: 1.0, alpha: 0.0)
context.setFillColor(fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
let lineWidth: CGFloat = isDark ? 0.66 : 0.66
context.saveGState()
let darkShadeColor = UIColor(white: isDark ? 1.0 : 0.0, alpha: 0.035)
let lightShadeColor = UIColor(white: isDark ? 0.0 : 1.0, alpha: 0.035)
let innerShadowBlur: CGFloat = 24.0
context.resetClip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.clip()
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.setShadow(offset: CGSize(width: 10.0, height: -10.0), blur: innerShadowBlur, color: darkShadeColor.cgColor)
context.fillPath(using: .evenOdd)
context.resetClip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.clip()
context.addRect(CGRect(origin: CGPoint(), size: size).insetBy(dx: -100.0, dy: -100.0))
context.addEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.black.cgColor)
context.setShadow(offset: CGSize(width: -10.0, height: 10.0), blur: innerShadowBlur, color: lightShadeColor.cgColor)
context.fillPath(using: .evenOdd)
context.restoreGState()
context.setLineWidth(lineWidth)
context.addRect(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
context.clip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.replacePathWithStrokedPath()
context.clip()
do {
var locations: [CGFloat] = [0.0, 0.5, 0.5 + 0.2, 1.0 - 0.1, 1.0]
let colors: [CGColor] = [maxColor.cgColor, maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
context.resetClip()
context.addRect(CGRect(origin: CGPoint(x: size.width - size.width * 0.5, y: 0.0), size: CGSize(width: size.width * 0.5, height: size.height)))
context.clip()
context.addEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
context.replacePathWithStrokedPath()
context.clip()
do {
var locations: [CGFloat] = [0.0, 0.1, 0.5 - 0.2, 0.5, 1.0]
let colors: [CGColor] = [maxColor.cgColor, minColor.cgColor, minColor.cgColor, maxColor.cgColor, maxColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
})!.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5))
}
static func generateForegroundImage(size: CGSize, isDark: Bool, fillColor: UIColor) -> UIImage {
var size = size
if size == .zero {

View file

@ -22,9 +22,6 @@ func inputContextQueries(_ inputState: TextFieldComponent.InputState) -> [ChatPr
result.append(.hashtag(query))
} else if possibleTypes == [.mention] {
let types: ChatInputQueryMentionTypes = [.members]
// if possibleQueryRange.lowerBound == 1 {
// types.insert(.contextBots)
// }
result.append(.mention(query: query, types: types))
} else if possibleTypes == [.command] {
result.append(.command(query))

View file

@ -468,7 +468,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: self.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height, maxOverlayHeight: layout.size.height, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight)))
@ -12312,7 +12312,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let edgeEffectHeight: CGFloat = layout.intrinsicInsets.bottom
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - edgeEffectHeight), size: CGSize(width: layout.size.width, height: edgeEffectHeight))
transition.updateFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
self.edgeEffectView.update(content: self.presentationData.theme.list.blocksBackgroundColor, isInverted: false, rect: edgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, containerSize: layout.size, transition: ComponentTransition(transition))
}
let sectionSpacing: CGFloat = 24.0

View file

@ -299,6 +299,11 @@ extension ChatControllerImpl {
}
self.chatDisplayNode.dismissTextInput()
var previewIconFile: TelegramMediaFile? = previewIconFile
if let file = previewIconFile, let peerId = self.chatLocation.peerId, !file.isValidForDisplay(chatPeerId: peerId) {
previewIconFile = nil
}
let presentationData = self.presentationData
let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), previewIconFile: previewIconFile, parentNavigationController: self.effectiveNavigationController, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in
if let strongSelf = self {

View file

@ -7968,19 +7968,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let stickerPackReference = stickerPackReference {
self.presentEmojiList(references: [stickerPackReference], previewIconFile: file)
var previewIconFile: TelegramMediaFile? = file
if !file.isValidForDisplay(chatPeerId: message.id.peerId) {
previewIconFile = nil
}
/*let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
|> deliverOnMainQueue).startStandalone(next: { [weak self] stickerPack in
if let strongSelf = self, case let .result(info, _, _) = stickerPack {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, loop: true, title: nil, text: strongSelf.presentationData.strings.Stickers_EmojiPackInfoText(info.title).string, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in
if let strongSelf = self, action == .undo {
strongSelf.presentEmojiList(references: [stickerPackReference])
}
return false
}), in: .current)
}
})*/
self.presentEmojiList(references: [stickerPackReference], previewIconFile: previewIconFile)
}
}

View file

@ -243,7 +243,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
private var inputPanelBackgroundBlurView: VariableBlurView?
private(set) var inputPanelNode: ChatInputPanelNode?
private(set) var inputPanelOverscrollNode: ChatInputPanelOverscrollNode?
private weak var currentDismissedInputPanelNode: ChatInputPanelNode?
private(set) var secondaryInputPanelNode: ChatInputPanelNode?
private(set) var accessoryPanelNode: AccessoryPanelNode?
@ -859,6 +858,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self?.interfaceInteraction?.presentController(controller, nil)
})
self.textInputPanelNode?.textInputAccessoryPanel = textInputAccessoryPanel
self.textInputPanelNode?.textInputContextPanel = textInputContextPanel
self.textInputPanelNode?.storedInputLanguage = chatPresentationInterfaceState.interfaceState.inputLanguage
self.textInputPanelNode?.updateHeight = { [weak self] animated in
if let strongSelf = self, let _ = strongSelf.inputPanelNode as? ChatTextInputPanelNode, !strongSelf.ignoreUpdateHeight {
@ -1627,7 +1627,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if inputTextPanelNode.isFocused {
self.context.sharedContext.mainWindow?.simulateKeyboardDismiss(transition: .animated(duration: 0.5, curve: .spring))
}
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
let _ = inputTextPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
}
if let prevInputPanelNode = self.inputPanelNode, inputPanelNode.canHandleTransition(from: prevInputPanelNode) {
inputPanelNodeHandlesTransition = true
@ -1639,7 +1639,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
} else {
dismissedInputPanelNode = self.inputPanelNode
}
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: inputPanelNode.supernode !== self ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
self.inputPanelNode = inputPanelNode
if inputPanelNode.supernode !== self {
@ -1650,7 +1650,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent)
}
} else {
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
let inputPanelHeight = inputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset - 120.0, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: false, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
inputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
}
} else {
@ -1661,7 +1661,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
if let secondaryInputPanelNode = inputPanelNodes.secondary, !previewing {
if secondaryInputPanelNode !== self.secondaryInputPanelNode {
dismissedSecondaryInputPanelNode = self.secondaryInputPanelNode
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: .immediate, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
self.secondaryInputPanelNode = secondaryInputPanelNode
if secondaryInputPanelNode.supernode == nil {
@ -1672,7 +1672,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent)
}
} else {
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, maxOverlayHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
}
} else {
@ -2309,7 +2309,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight, right: 0.0)
let visibleAreaInset = UIEdgeInsets(top: containerInsets.top, left: 0.0, bottom: containerInsets.bottom + inputPanelsHeight + 8.0 + 8.0, right: 0.0)
self.visibleAreaInset = visibleAreaInset
var loadingNodeInsets = visibleAreaInset
@ -2693,19 +2693,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateAlpha(node: accessoryPanelNode, alpha: 1.0)
}
let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - inputPanelsHeight - insets.top)))
let inputContextPanelsFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.top)))
let inputContextPanelsOverMainPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: max(0.0, layout.size.height - insets.bottom - (inputPanelSize == nil ? CGFloat(0.0) : inputPanelSize!.height) - insets.top)))
if let inputContextPanelNode = self.inputContextPanelNode {
let panelFrame = inputContextPanelNode.placement == .overTextInput ? inputContextPanelsOverMainPanelFrame : inputContextPanelsFrame
if immediatelyLayoutInputContextPanelAndAnimateAppearance {
/*var startPanelFrame = panelFrame
if let derivedLayoutState = self.derivedLayoutState {
let referenceFrame = inputContextPanelNode.placement == .overTextInput ? derivedLayoutState.inputContextPanelsOverMainPanelFrame : derivedLayoutState.inputContextPanelsFrame
startPanelFrame.origin.y = referenceFrame.maxY - panelFrame.height
}*/
inputContextPanelNode.frame = panelFrame
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: 0.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
inputContextPanelNode.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + inputPanelsHeight + 8.0, transition: .immediate, interfaceState: self.chatPresentationInterfaceState)
}
if !inputContextPanelNode.frame.equalTo(panelFrame) || inputContextPanelNode.theme !== self.chatPresentationInterfaceState.theme {
@ -2824,9 +2819,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if !transition.isAnimated {
inputPanelNode.layer.removeAllAnimations()
if let currentDismissedInputPanelNode = self.currentDismissedInputPanelNode, inputPanelNode is ChatSearchInputPanelNode {
currentDismissedInputPanelNode.layer.removeAllAnimations()
}
}
if inputPanelNodeHandlesTransition {
inputPanelNode.frame = apparentInputPanelFrame
@ -5005,7 +4997,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let titleAccessoryPanelSnapshot: UIView?
let navigationBarHeight: CGFloat
let inputPanelNodeSnapshot: UIView?
let inputPanelOverscrollNodeSnapshot: UIView?
fileprivate init(
backgroundNode: WallpaperBackgroundNode,
@ -5015,8 +5006,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
navigationButtonsSnapshotState: ChatHistoryNavigationButtons.SnapshotState,
titleAccessoryPanelSnapshot: UIView?,
navigationBarHeight: CGFloat,
inputPanelNodeSnapshot: UIView?,
inputPanelOverscrollNodeSnapshot: UIView?
inputPanelNodeSnapshot: UIView?
) {
self.backgroundNode = backgroundNode
self.historySnapshotState = historySnapshotState
@ -5026,7 +5016,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
self.titleAccessoryPanelSnapshot = titleAccessoryPanelSnapshot
self.navigationBarHeight = navigationBarHeight
self.inputPanelNodeSnapshot = inputPanelNodeSnapshot
self.inputPanelOverscrollNodeSnapshot = inputPanelOverscrollNodeSnapshot
}
}
@ -5044,11 +5033,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
snapshot.frame = inputPanelNode.frame
inputPanelNodeSnapshot = snapshot
}
var inputPanelOverscrollNodeSnapshot: UIView?
if let inputPanelOverscrollNode = self.inputPanelOverscrollNode, let snapshot = inputPanelOverscrollNode.view.snapshotView(afterScreenUpdates: false) {
snapshot.frame = inputPanelOverscrollNode.frame
inputPanelOverscrollNodeSnapshot = snapshot
}
return SnapshotState(
backgroundNode: self.backgroundNode,
historySnapshotState: self.historyNode.prepareSnapshotState(),
@ -5057,8 +5041,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
navigationButtonsSnapshotState: self.navigateButtons.prepareSnapshotState(),
titleAccessoryPanelSnapshot: titleAccessoryPanelSnapshot,
navigationBarHeight: self.navigationBar?.backgroundNode.bounds.height ?? 0.0,
inputPanelNodeSnapshot: inputPanelNodeSnapshot,
inputPanelOverscrollNodeSnapshot: inputPanelOverscrollNodeSnapshot
inputPanelNodeSnapshot: inputPanelNodeSnapshot
)
}
@ -5114,69 +5097,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
})
inputPanelNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
if let inputPanelOverscrollNodeSnapshot = snapshotState.inputPanelOverscrollNodeSnapshot {
inputPanelNode.view.superview?.insertSubview(inputPanelOverscrollNodeSnapshot, belowSubview: inputPanelNode.view)
inputPanelOverscrollNodeSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNodeSnapshot] _ in
inputPanelOverscrollNodeSnapshot?.removeFromSuperview()
})
inputPanelOverscrollNodeSnapshot.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -5.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
}
inputPanelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
inputPanelNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 5.0), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
private var preivousChatInputPanelOverscrollNodeTimestamp: Double = 0.0
func setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode?) {
let directionUp: Bool
if let overscrollNode = overscrollNode {
if let current = self.inputPanelOverscrollNode {
directionUp = current.priority > overscrollNode.priority
} else {
directionUp = true
}
} else {
directionUp = false
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .easeInOut)
let timestamp = CFAbsoluteTimeGetCurrent()
if self.preivousChatInputPanelOverscrollNodeTimestamp > timestamp - 0.05 {
if let inputPanelOverscrollNode = self.inputPanelOverscrollNode {
self.inputPanelOverscrollNode = nil
inputPanelOverscrollNode.removeFromSupernode()
}
}
self.preivousChatInputPanelOverscrollNodeTimestamp = timestamp
if let inputPanelOverscrollNode = self.inputPanelOverscrollNode {
self.inputPanelOverscrollNode = nil
inputPanelOverscrollNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: directionUp ? -5.0 : 5.0), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
inputPanelOverscrollNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak inputPanelOverscrollNode] _ in
inputPanelOverscrollNode?.removeFromSupernode()
})
}
if let inputPanelNode = self.inputPanelNode, let overscrollNode = overscrollNode {
self.inputPanelOverscrollNode = overscrollNode
inputPanelNode.supernode?.insertSubnode(overscrollNode, aboveSubnode: inputPanelNode)
overscrollNode.frame = inputPanelNode.frame
overscrollNode.update(size: overscrollNode.bounds.size)
overscrollNode.layer.animatePosition(from: CGPoint(x: 0.0, y: directionUp ? 5.0 : -5.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, additive: true)
overscrollNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
if let inputPanelNode = self.inputPanelNode {
transition.updateAlpha(node: inputPanelNode, alpha: overscrollNode == nil ? 1.0 : 0.0)
transition.updateSublayerTransformOffset(layer: inputPanelNode.layer, offset: CGPoint(x: 0.0, y: overscrollNode == nil ? 0.0 : -5.0))
}
}
private func setupHistoryNode() {
var backgroundColors: [UInt32] = []

View file

@ -2525,64 +2525,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.currentOverscrollExpandProgress = expandProgress
if let nextChannelToRead = self.nextChannelToRead {
let swipeText: NSAttributedString
let releaseText: NSAttributedString
switch nextChannelToRead.location {
case .same:
if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil {
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress)
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction)
} else if nextChannelToRead.threadData != nil {
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress)
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction)
} else {
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress)
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction)
}
case .archived:
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress)
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction)
case .unarchived:
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress)
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction)
case let .folder(_, title):
let swipeTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgressV2)
let swipeFolderRange = (swipeTextValue.string as NSString).range(of: "{folder}")
if swipeFolderRange.location != NSNotFound {
swipeTextValue.replaceCharacters(in: swipeFolderRange, with: "")
swipeTextValue.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: swipeFolderRange.location)
}
swipeText = swipeTextValue
let releaseTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeActionV2)
let releaseTextFolderRange = (releaseTextValue.string as NSString).range(of: "{folder}")
if releaseTextFolderRange.location != NSNotFound {
releaseTextValue.replaceCharacters(in: releaseTextFolderRange, with: "")
releaseTextValue.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: releaseTextFolderRange.location)
}
releaseText = releaseTextValue
}
if expandProgress < 0.1 {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
} else if expandProgress >= 1.0 {
if chatControllerNode.inputPanelOverscrollNode?.text.string != releaseText.string {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
}
} else {
if chatControllerNode.inputPanelOverscrollNode?.text.string != swipeText.string {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
}
}
} else {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
}
var overscrollFrame = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.bounds.width, height: 94.0))
if self.freezeOverscrollControlProgress {
overscrollFrame.origin.y -= max(0.0, 94.0 - expandDistance)
@ -2618,10 +2560,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
} else if let overscrollView = self.overscrollView {
self.overscrollView = nil
overscrollView.removeFromSuperview()
if let chatControllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
}
}
}

View file

@ -27,6 +27,164 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult
}
}
func textInputContextPanel(context: AccountContext, chatPresentationInterfaceState: ChatPresentationInterfaceState, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, currentPanel: ChatInputContextPanelNode?) -> ChatInputContextPanelNode? {
guard let controllerInteraction else {
return nil
}
guard let inputQueryResult = chatPresentationInterfaceState.inputQueryResults.values.sorted(by: { lhs, rhs in
let (lhsP, lhsHasItems) = inputQueryResultPriority(lhs)
let (rhsP, rhsHasItems) = inputQueryResultPriority(rhs)
if lhsHasItems != rhsHasItems {
if lhsHasItems {
return true
} else {
return false
}
}
return lhsP < rhsP
}).first else {
return nil
}
var hasBannedInlineContent = false
if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendInline) != nil {
hasBannedInlineContent = true
} else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendInline) {
hasBannedInlineContent = true
}
if hasBannedInlineContent {
switch inputQueryResult {
case .stickers, .contextRequestResult:
if let currentPanel = currentPanel as? DisabledContextResultsChatInputContextPanelNode {
return currentPanel
} else {
let panel = DisabledContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
return panel
}
default:
break
}
}
switch inputQueryResult {
case let .stickers(unfilteredResults):
let _ = unfilteredResults
return nil
/*if !unfilteredResults.isEmpty {
var results: [FoundStickerItem] = []
for result in unfilteredResults {
if !results.contains(where: { $0.file.fileId == result.file.fileId }) {
results.append(result)
}
}
let query = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.string
if let currentPanel = currentPanel as? InlineReactionSearchPanel {
currentPanel.updateResults(results: results.map({ $0.file }), query: query)
return currentPanel
} else {
let panel = InlineReactionSearchPanel(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, peerId: chatPresentationInterfaceState.renderedPeer?.peerId, chatPresentationContext: chatPresentationContext)
panel.controllerInteraction = controllerInteraction
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results: results.map({ $0.file }), query: query)
return panel
}
}*/
case let .hashtags(results, query):
var peer: EnginePeer?
if let chatPeer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, chatPeer.addressName != nil {
peer = EnginePeer(chatPeer)
}
if !results.isEmpty || (peer != nil && query.count >= 4) {
if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode {
currentPanel.updateResults(results, query: query, peer: peer)
return currentPanel
} else {
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results, query: query, peer: peer)
return panel
}
} else {
return nil
}
case let .emojis(results, _):
let _ = results
return nil
/*if !results.isEmpty {
if let currentPanel = currentPanel as? EmojisChatInputContextPanelNode {
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = EmojisChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
}
}*/
case let .mentions(peers):
if !peers.isEmpty {
if let currentPanel = currentPanel as? MentionChatInputContextPanelNode, currentPanel.mode == .input {
currentPanel.updateResults(peers)
return currentPanel
} else {
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(peers)
return panel
}
} else {
return nil
}
case let .commands(commands):
if !commands.commands.isEmpty || commands.hasShortcuts {
if let currentPanel = currentPanel as? CommandChatInputContextPanelNode {
currentPanel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query)
return currentPanel
} else {
let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query)
return panel
}
} else {
return nil
}
case let .contextRequestResult(_, results):
let _ = results
return nil
/*if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) {
switch results.presentation {
case .list:
if let currentPanel = currentPanel as? VerticalListContextResultsChatInputContextPanelNode {
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = VerticalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
}
case .media:
if let currentPanel = currentPanel as? HorizontalListContextResultsChatInputContextPanelNode {
currentPanel.updateResults(results)
return currentPanel
} else {
let panel = HorizontalListContextResultsChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results)
return panel
}
}
} else {
return nil
}*/
}
}
func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputContextPanelNode?, controllerInteraction: ChatControllerInteraction, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPresentationContext: ChatPresentationContext) -> ChatInputContextPanelNode? {
if chatPresentationInterfaceState.showCommands, let renderedPeer = chatPresentationInterfaceState.renderedPeer {
if let currentPanel = currentPanel as? CommandMenuChatInputContextPanelNode {
@ -98,22 +256,8 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
return panel
}
}
case let .hashtags(results, query):
var peer: EnginePeer?
if let chatPeer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, chatPeer.addressName != nil {
peer = EnginePeer(chatPeer)
}
if !results.isEmpty || (peer != nil && query.count >= 4) {
if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode {
currentPanel.updateResults(results, query: query, peer: peer)
return currentPanel
} else {
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(results, query: query, peer: peer)
return panel
}
}
case .hashtags:
return nil
case let .emojis(results, _):
if !results.isEmpty {
if let currentPanel = currentPanel as? EmojisChatInputContextPanelNode {
@ -126,34 +270,10 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
return panel
}
}
case let .mentions(peers):
if !peers.isEmpty {
if let currentPanel = currentPanel as? MentionChatInputContextPanelNode, currentPanel.mode == .input {
currentPanel.updateResults(peers)
return currentPanel
} else {
let panel = MentionChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, mode: .input, chatPresentationContext: chatPresentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(peers)
return panel
}
} else {
return nil
}
case let .commands(commands):
if !commands.commands.isEmpty || commands.hasShortcuts {
if let currentPanel = currentPanel as? CommandChatInputContextPanelNode {
currentPanel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query)
return currentPanel
} else {
let panel = CommandChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
panel.interfaceInteraction = interfaceInteraction
panel.updateResults(commands.commands, accountPeer: commands.accountPeer, hasShortcuts: commands.hasShortcuts, query: commands.query)
return panel
}
} else {
return nil
}
case .mentions:
return nil
case .commands:
return nil
case let .contextRequestResult(_, results):
if let results = results, (!results.results.isEmpty || results.switchPeer != nil || results.webView != nil) {
switch results.presentation {

View file

@ -461,6 +461,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
interfaceInteraction?.presentController(controller, nil)
})
panel.textInputAccessoryPanel = textInputAccessoryPanel
panel.textInputContextPanel = textInputContextPanel
panel.chatControllerInteraction = chatControllerInteraction
panel.interfaceInteraction = interfaceInteraction
panel.context = context

View file

@ -15,7 +15,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode {
private let reportButton: HighlightableButtonNode
private let separatorNode: ASDisplayNode
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)?
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool)?
private var presentationInterfaceState: ChatPresentationInterfaceState?
private var theme: PresentationTheme
@ -72,7 +72,7 @@ final class ChatMessageReportInputPanelNode: ChatInputPanelNode {
}
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
self.presentationInterfaceState = interfaceState

View file

@ -26,18 +26,20 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode {
var bottomInset: CGFloat
var additionalSideInsets: UIEdgeInsets
var maxHeight: CGFloat
var maxOverlayHeight: CGFloat
var isSecondary: Bool
var interfaceState: ChatPresentationInterfaceState
var metrics: LayoutMetrics
var isMediaInputExpanded: Bool
init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) {
init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) {
self.width = width
self.leftInset = leftInset
self.rightInset = rightInset
self.bottomInset = bottomInset
self.additionalSideInsets = additionalSideInsets
self.maxHeight = maxHeight
self.maxOverlayHeight = maxOverlayHeight
self.isSecondary = isSecondary
self.interfaceState = interfaceState
self.metrics = metrics
@ -72,8 +74,8 @@ final class ChatPremiumRequiredInputPanelNode: ChatInputPanelNode {
deinit {
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let currentLayout = self.currentLayout, currentLayout.params == params {
return currentLayout.height
}

View file

@ -10,31 +10,49 @@ import ChatPresentationInterfaceState
import TelegramPresentationData
import ChatInputPanelNode
import AccountContext
import GlassBackgroundComponent
import ComponentFlow
import ComponentDisplayAdapters
final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
private let backgroundView: GlassBackgroundView
private let buttonNode: HighlightTrackingButtonNode
private let textNode: ImmediateTextNode
private let tintTextNode: ImmediateTextNode
private let subtitleNode: ImmediateTextNode
private let tintSubtitleNode: ImmediateTextNode
private var iconView: UIImageView?
private var presentationInterfaceState: ChatPresentationInterfaceState?
override init() {
self.backgroundView = GlassBackgroundView()
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 2
self.textNode.textAlignment = .center
self.tintTextNode = ImmediateTextNode()
self.tintTextNode.maximumNumberOfLines = 2
self.tintTextNode.textAlignment = .center
self.subtitleNode = ImmediateTextNode()
self.subtitleNode.maximumNumberOfLines = 1
self.subtitleNode.textAlignment = .center
self.tintSubtitleNode = ImmediateTextNode()
self.tintSubtitleNode.maximumNumberOfLines = 1
self.tintSubtitleNode.textAlignment = .center
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.textNode)
self.addSubnode(self.subtitleNode)
self.backgroundView.contentView.addSubview(self.textNode.view)
self.backgroundView.maskContentView.addSubview(self.tintTextNode.view)
self.backgroundView.contentView.addSubview(self.subtitleNode.view)
self.backgroundView.maskContentView.addSubview(self.tintSubtitleNode.view)
self.view.addSubview(self.backgroundView)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
@ -63,7 +81,7 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
self.interfaceInteraction?.openBoostToUnrestrict()
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
self.presentationInterfaceState = interfaceState
}
@ -135,6 +153,9 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
}
self.buttonNode.isUserInteractionEnabled = isUserInteractionEnabled
self.tintTextNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(15.0), textColor: .black)
self.tintSubtitleNode.attributedText = NSAttributedString(string: self.subtitleNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: .black)
let panelHeight = defaultHeight(metrics: metrics)
let textSize = self.textNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: width - leftInset - rightInset - 8.0 * 2.0, height: panelHeight))
@ -166,13 +187,22 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
combinedHeight += subtitleSize.height + 2.0
}
let textFrame = CGRect(origin: CGPoint(x: originX, y: floor((panelHeight - combinedHeight) / 2.0)), size: textSize)
self.textNode.frame = textFrame
let subtitleFrame = CGRect(origin: CGPoint(x: leftInset + floor((width - leftInset - rightInset - subtitleSize.width) / 2.0), y: floor((panelHeight + combinedHeight) / 2.0) - subtitleSize.height), size: subtitleSize)
self.subtitleNode.frame = subtitleFrame
let combinedFrame = textFrame.union(subtitleFrame)
self.buttonNode.frame = combinedFrame.insetBy(dx: -8.0, dy: -12.0)
var combinedFrame = textFrame.union(subtitleFrame).insetBy(dx: -12.0, dy: -6.0)
combinedFrame.origin.y += 1.0
self.textNode.frame = textFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY)
self.tintTextNode.frame = self.textNode.frame
self.subtitleNode.frame = subtitleFrame.offsetBy(dx: -combinedFrame.minX, dy: -combinedFrame.minY)
self.tintSubtitleNode.frame = self.subtitleNode.frame
self.backgroundView.frame = combinedFrame
self.backgroundView.update(size: combinedFrame.size, cornerRadius: combinedFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
self.buttonNode.frame = combinedFrame
return panelHeight
}

View file

@ -1,224 +0,0 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import Postbox
import SwiftSignalKit
import TelegramNotices
import TelegramPresentationData
import ActivityIndicator
import ChatPresentationInterfaceState
import ChatInputPanelNode
private let labelFont = Font.regular(15.0)
final class ChatSearchInputPanelNode: ChatInputPanelNode {
private let upButton: HighlightableButtonNode
private let downButton: HighlightableButtonNode
private let calendarButton: HighlightableButtonNode
private let membersButton: HighlightableButtonNode
private let resultsButton: HighlightableButtonNode
private let measureResultsLabel: TextNode
private let activityIndicator: ActivityIndicator
private var presentationInterfaceState: ChatPresentationInterfaceState?
private let activityDisposable = MetaDisposable()
private var displayActivity = false
private var needsSearchResultsTooltip = true
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
override var interfaceInteraction: ChatPanelInterfaceInteraction? {
didSet {
if let statuses = self.interfaceInteraction?.statuses {
self.activityDisposable.set((combineLatest((statuses.searching |> deliverOnMainQueue), (statuses.loadingMessage |> deliverOnMainQueue))).startStrict(next: { [weak self] searching, loadingMessage in
let value = searching || loadingMessage == .generic
if let strongSelf = self, strongSelf.displayActivity != value {
strongSelf.displayActivity = value
strongSelf.activityIndicator.isHidden = !value
if let interfaceState = strongSelf.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout {
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
}
}
}))
} else {
self.activityDisposable.set(nil)
}
}
}
init(theme: PresentationTheme) {
self.upButton = HighlightableButtonNode(pointerStyle: .circle(36.0))
self.upButton.isEnabled = false
self.downButton = HighlightableButtonNode(pointerStyle: .circle(36.0))
self.downButton.isEnabled = false
self.calendarButton = HighlightableButtonNode()
self.membersButton = HighlightableButtonNode(pointerStyle: .circle(36.0))
self.measureResultsLabel = TextNode()
self.measureResultsLabel.displaysAsynchronously = false
self.resultsButton = HighlightableButtonNode(pointerStyle: .default)
self.activityIndicator = ActivityIndicator(type: .navigationAccent(theme.rootController.navigationBar.buttonColor))
self.activityIndicator.isHidden = true
super.init()
//self.addSubnode(self.upButton)
//self.addSubnode(self.downButton)
self.addSubnode(self.calendarButton)
self.addSubnode(self.membersButton)
self.addSubnode(self.resultsButton)
self.resultsButton.addSubnode(self.measureResultsLabel)
self.addSubnode(self.activityIndicator)
self.upButton.addTarget(self, action: #selector(self.upPressed), forControlEvents: [.touchUpInside])
self.downButton.addTarget(self, action: #selector(self.downPressed), forControlEvents: [.touchUpInside])
self.calendarButton.addTarget(self, action: #selector(self.calendarPressed), forControlEvents: [.touchUpInside])
self.membersButton.addTarget(self, action: #selector(self.membersPressed), forControlEvents: [.touchUpInside])
self.resultsButton.addTarget(self, action: #selector(self.resultsPressed), forControlEvents: [.touchUpInside])
}
deinit {
self.activityDisposable.dispose()
}
@objc func upPressed() {
self.interfaceInteraction?.navigateMessageSearch(.earlier)
guard self.needsSearchResultsTooltip, let context = self.context else {
return
}
let _ = (ApplicationSpecificNotice.getChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager)
|> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
guard let strongSelf = self else {
return
}
if counter >= 3 {
strongSelf.needsSearchResultsTooltip = false
} else if arc4random_uniform(4) == 1 {
strongSelf.needsSearchResultsTooltip = false
let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager).startStandalone()
strongSelf.interfaceInteraction?.displaySearchResultsTooltip(strongSelf.resultsButton, strongSelf.resultsButton.bounds)
}
})
}
@objc func downPressed() {
self.interfaceInteraction?.navigateMessageSearch(.later)
}
@objc func calendarPressed() {
self.interfaceInteraction?.openCalendarSearch()
}
@objc func membersPressed() {
self.interfaceInteraction?.toggleMembersSearch(true)
}
@objc func resultsPressed() {
self.interfaceInteraction?.openSearchResults()
if let context = self.context {
let _ = ApplicationSpecificNotice.incrementChatMessageSearchResultsTip(accountManager: context.sharedContext.accountManager, count: 4).startStandalone()
}
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
if self.presentationInterfaceState != interfaceState {
let themeUpdated = self.presentationInterfaceState?.theme !== interfaceState.theme
self.presentationInterfaceState = interfaceState
if themeUpdated {
self.upButton.setImage(PresentationResourcesChat.chatInputSearchPanelUpImage(interfaceState.theme), for: [.normal])
self.upButton.setImage(PresentationResourcesChat.chatInputSearchPanelUpDisabledImage(interfaceState.theme), for: [.disabled])
self.downButton.setImage(PresentationResourcesChat.chatInputSearchPanelDownImage(interfaceState.theme), for: [.normal])
self.downButton.setImage(PresentationResourcesChat.chatInputSearchPanelDownDisabledImage(interfaceState.theme), for: [.disabled])
self.calendarButton.setImage(PresentationResourcesChat.chatInputSearchPanelCalendarImage(interfaceState.theme), for: [])
self.membersButton.setImage(PresentationResourcesChat.chatInputSearchPanelMembersImage(interfaceState.theme), for: [])
}
}
let panelHeight: CGFloat
if case .regular = metrics.widthClass {
panelHeight = 49.0
} else {
panelHeight = 45.0
}
var width = width
if additionalSideInsets.right > 0.0 {
width -= additionalSideInsets.right
}
self.downButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 48.0, y: 0.0), size: CGSize(width: 40.0, height: panelHeight))
self.upButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 48.0 - 43.0, y: 0.0), size: CGSize(width: 40.0, height: panelHeight))
self.calendarButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 60.0, height: panelHeight))
self.membersButton.frame = CGRect(origin: CGPoint(x: leftInset + 43.0, y: 0.0), size: CGSize(width: 60.0, height: panelHeight))
var resultIndex: Int?
var resultCount: Int?
var resultsText: String?
if let results = interfaceState.search?.resultsState {
resultCount = results.messageIndices.count
let displayTotalCount = results.completed ? results.messageIndices.count : Int(results.totalCount)
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
let adjustedIndex = results.messageIndices.count - 1 - index
resultIndex = index
resultsText = interfaceState.strings.Items_NOfM("\(adjustedIndex + 1)", "\(displayTotalCount)").string
} else {
resultsText = interfaceState.strings.Conversation_SearchNoResults
}
}
self.upButton.isEnabled = resultIndex != nil && resultIndex != 0
self.downButton.isEnabled = resultIndex != nil && resultCount != nil && resultIndex != resultCount! - 1
self.calendarButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity
var canSearchMembers = false
if let search = interfaceState.search {
if case .everything = search.domain {
if let _ = interfaceState.renderedPeer?.peer as? TelegramGroup {
canSearchMembers = true
} else if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel, case .group = peer.info, !peer.isMonoForum {
canSearchMembers = true
}
} else {
canSearchMembers = false
}
}
self.membersButton.isHidden = (!(interfaceState.search?.query.isEmpty ?? true)) || self.displayActivity || !canSearchMembers
let resultsEnabled = (resultCount ?? 0) > 0
//self.resultsButton.setTitle(resultsText ?? "", with: labelFont, with: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, for: .normal)
self.resultsButton.isUserInteractionEnabled = resultsEnabled
let makeLabelLayout = TextNode.asyncLayout(self.measureResultsLabel)
let (labelSize, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: resultsText ?? "", font: labelFont, textColor: resultsEnabled ? interfaceState.theme.chat.inputPanel.panelControlAccentColor : interfaceState.theme.chat.inputPanel.primaryTextColor, paragraphAlignment: .left), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - leftInset - rightInset - 50.0, height: 100.0), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
let _ = labelApply()
var resultsOffset: CGFloat = 16.0
if !self.calendarButton.isHidden {
resultsOffset += 48.0
}
self.resultsButton.frame = CGRect(origin: CGPoint(x: leftInset + resultsOffset, y: floor((panelHeight - labelSize.size.height) / 2.0)), size: labelSize.size)
self.measureResultsLabel.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: labelSize.size)
let indicatorSize = self.activityIndicator.measure(CGSize(width: 22.0, height: 22.0))
self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - 41.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
return panelHeight
}
override func minimalHeight(interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics) -> CGFloat {
return defaultHeight(metrics: metrics)
}
}

View file

@ -27,18 +27,20 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
var bottomInset: CGFloat
var additionalSideInsets: UIEdgeInsets
var maxHeight: CGFloat
var maxOverlayHeight: CGFloat
var isSecondary: Bool
var interfaceState: ChatPresentationInterfaceState
var metrics: LayoutMetrics
var isMediaInputExpanded: Bool
init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) {
init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) {
self.width = width
self.leftInset = leftInset
self.rightInset = rightInset
self.bottomInset = bottomInset
self.additionalSideInsets = additionalSideInsets
self.maxHeight = maxHeight
self.maxOverlayHeight = maxOverlayHeight
self.isSecondary = isSecondary
self.interfaceState = interfaceState
self.metrics = metrics
@ -96,8 +98,8 @@ final class ChatTagSearchInputPanelNode: ChatInputPanelNode {
self.totalMessageCountDisposable?.dispose()
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
let params = Params(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
if let currentLayout = self.currentLayout, currentLayout.params == params {
return currentLayout.height
}

View file

@ -83,7 +83,7 @@ final class ChatUnblockInputPanelNode: ChatInputPanelNode {
self.interfaceInteraction?.unblockPeer()
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
self.presentationInterfaceState = interfaceState

View file

@ -16,6 +16,7 @@ import ChatInputContextPanelNode
import ChatListUI
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
private enum CommandChatInputContextPanelEntryStableId: Hashable {
case editShortcuts
@ -284,8 +285,8 @@ private func preparedTransition(from fromEntries: [CommandChatInputContextPanelE
}
final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private let listBackgroundView: UIView
private var currentEntries: [CommandChatInputContextPanelEntry]?
private var contentOffsetChangeTransition: ComponentTransition?
private var isAnimatingOut: Bool = false
@ -294,49 +295,43 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.accessibilityPageScrolledString = { row, count in
return strings.VoiceOver_ScrollStatus(row, count).string
}
self.listBackgroundView = UIView()
self.listBackgroundView.backgroundColor = theme.list.plainBackgroundColor
self.listBackgroundView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
self.listBackgroundView.layer.cornerRadius = 10.0
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.listBackgroundView)
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
if self.isAnimatingOut {
return
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
var topItemOffset: CGFloat = self.listView.bounds.height
var isFirst = true
self.listView.forEachItemNode { itemNode in
if isFirst {
isFirst = false
topItemOffset = itemNode.frame.minY
}
}
let transition: ComponentTransition = self.contentOffsetChangeTransition ?? .immediate
transition.setFrame(view: self.listBackgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: topItemOffset), size: CGSize(width: self.listView.bounds.width, height: self.listView.bounds.height + 1000.0)))
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
}
@ -431,8 +426,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
options.insert(.PreferSynchronousResourceLoading)
if firstTime {
self.contentOffsetChangeTransition = .immediate
self.listBackgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: self.listView.bounds.height), size: CGSize(width: self.listView.bounds.width, height: self.listView.bounds.height + 1000.0))
} else {
if transition.itemCountChanged {
options.insert(.AnimateTopItemPosition)
@ -443,9 +436,10 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@ -458,11 +452,11 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: strongSelf.listView.bounds.size.height - topItemOffset))
transition.animatePositionAdditive(layer: strongSelf.listBackgroundView.layer, offset: CGPoint(x: 0.0, y: strongSelf.listView.bounds.size.height - topItemOffset))
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
}
})
@ -471,7 +465,7 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat {
var minimumItemHeights: CGFloat = 0.0
if let currentEntries = self.currentEntries, !currentEntries.isEmpty {
let indexLimit = min(4, currentEntries.count - 1)
@ -498,17 +492,27 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
minimumItemHeights = floor(MentionChatInputPanelItemNode.itemHeight * 3.5)
}
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset, bottomInset)
self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))
self.backgroundView.update(
size: self.backgroundView.bounds.size,
cornerRadius: 20.0,
isDark: interfaceState.theme.overallDarkAppearance,
tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)),
transition: ComponentTransition(transition)
)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
insets.right = rightInset
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
@ -529,7 +533,6 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new)
@ -546,14 +549,14 @@ final class CommandChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let position = self.listView.layer.position
if let topItemOffset {
let offset = (self.listView.bounds.size.height - topItemOffset)
let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
self.listBackgroundView.layer.animatePosition(from: self.listBackgroundView.layer.position, to: CGPoint(x: self.listBackgroundView.layer.position.x, y: self.listBackgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
})
self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
completion()
}

View file

@ -86,7 +86,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
private var item: CommandChatInputPanelItem?
private let avatarNode: AvatarNode
private let textNode: TextNode
private let topSeparatorNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let arrowNode: ASButtonNode
@ -97,9 +96,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
self.avatarNode = AvatarNode(font: avatarFont)
self.textNode = TextNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
@ -113,7 +109,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.avatarNode)
@ -167,8 +162,6 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
strongSelf.item = item
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
strongSelf.arrowNode.setImage(iconImage, for: [])
@ -183,10 +176,8 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
let arrowSize = CGSize(width: 42.0, height: nodeLayout.contentSize.height)
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: nodeLayout.size.width - arrowSize.width - params.rightInset, y: 0.0), size: arrowSize)
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.separatorNode.isHidden = !mergedBottom
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
@ -205,7 +196,7 @@ final class CommandChatInputPanelItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
//self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {

View file

@ -13,6 +13,9 @@ import ChatPresentationInterfaceState
import ChatControllerInteraction
import ChatContextQuery
import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
private struct CommandMenuChatInputContextPanelEntryStableId: Hashable {
let command: PeerCommand
@ -61,6 +64,7 @@ private func preparedTransition(from fromEntries: [CommandMenuChatInputContextPa
}
final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private var currentEntries: [CommandMenuChatInputContextPanelEntry]?
@ -70,11 +74,13 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
private let disposable = MetaDisposable()
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, peerId: PeerId, chatPresentationContext: ChatPresentationContext) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.clipsToBounds = false
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.accessibilityPageScrolledString = { row, count in
@ -86,8 +92,28 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
self.disposable.set((context.engine.peers.peerCommands(id: peerId)
|> deliverOnMainQueue).startStrict(next: { [weak self] results in
if let strongSelf = self {
@ -178,9 +204,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@ -193,21 +220,20 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let position = strongSelf.listView.layer.position
strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset))
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView {
strongSelf.listView.position = position
}
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
}
})
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat {
let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 4.7)
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
@ -215,9 +241,10 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
self.validLayout = (size, leftInset, rightInset, bottomInset)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
insets.right = rightInset
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
@ -234,7 +261,6 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
self.prepareTransition(from: self.currentEntries, to: new)
@ -249,11 +275,14 @@ final class CommandMenuChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
if let topItemOffset {
let offset = (self.listView.bounds.size.height - topItemOffset)
let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
completion()
}

View file

@ -219,7 +219,6 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode {
strongSelf.item = item
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundNode.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
let _ = textApply()
@ -261,7 +260,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.backgroundNode.insertSubnode(self.highlightedBackgroundNode, at: 0)
//self.backgroundNode.insertSubnode(self.highlightedBackgroundNode, at: 0)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {

View file

@ -36,7 +36,7 @@ final class DeleteChatInputPanelNode: ChatInputPanelNode {
self.interfaceInteraction?.deleteChat()
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
self.presentationInterfaceState = interfaceState

View file

@ -14,6 +14,9 @@ import ChatPresentationInterfaceState
import ChatControllerInteraction
import ChatContextQuery
import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
private enum HashtagChatInputContextPanelEntryStableId: Hashable {
case generic
@ -77,6 +80,7 @@ private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelE
}
final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private var currentEntries: [HashtagChatInputContextPanelEntry]?
@ -89,10 +93,12 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)?
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.accessibilityPageScrolledString = { row, count in
@ -104,7 +110,27 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
}
func updateResults(_ results: [String], query: String, peer: EnginePeer?) {
@ -256,9 +282,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@ -271,32 +298,41 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let position = strongSelf.listView.layer.position
strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset))
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView {
strongSelf.listView.position = position
}
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
}
})
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat {
let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 3.5)
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset, bottomInset)
self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))
self.backgroundView.update(
size: self.backgroundView.bounds.size,
cornerRadius: 20.0,
isDark: interfaceState.theme.overallDarkAppearance,
tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)),
transition: ComponentTransition(transition)
)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size)
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
insets.right = rightInset
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
@ -313,7 +349,6 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
self.prepareTransition(from: self.currentEntries, to: new)
@ -329,10 +364,13 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
}
if let topItemOffset = topItemOffset {
let offset = (self.listView.bounds.size.height - topItemOffset)
let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
completion()
}

View file

@ -116,7 +116,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
private let titleNode: TextNode
private let textNode: TextNode
private let badgeNode: TextNode
private let topSeparatorNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
@ -141,9 +140,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
self.textNode = TextNode()
self.badgeNode = TextNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
@ -155,7 +151,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
@ -226,8 +221,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
strongSelf.badgeBackgroundLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
let _ = titleApply()
@ -250,7 +243,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
strongSelf.badgeBackgroundLayer.frame = badgeBackgroundFrame
}
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.separatorNode.isHidden = !mergedBottom
let iconSize = CGSize(width: 30.0, height: 30.0)
@ -273,7 +265,6 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
strongSelf.iconBackgroundLayer.isHidden = false
}
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset + textLeftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - textLeftInset, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
@ -305,7 +296,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
//self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {

View file

@ -378,12 +378,12 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let listHeight: CGFloat = 105.0
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - listHeight), size: CGSize(width: size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - bottomInset - 8.0 - listHeight), size: CGSize(width: size.width, height: UIScreenPixel)))
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: listHeight, height: size.width)
//transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - listHeight / 2.0))
transition.updatePosition(node: self.listView, position: CGPoint(x: size.width / 2.0, y: size.height - bottomInset - 8.0 - listHeight / 2.0))
var insets = UIEdgeInsets()
insets.top = leftInset

View file

@ -14,6 +14,9 @@ import ChatPresentationInterfaceState
import ChatControllerInteraction
import ChatContextQuery
import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
private struct MentionChatInputContextPanelEntry: Comparable, Identifiable {
let index: Int
@ -61,6 +64,7 @@ enum MentionChatInputContextPanelMode {
final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
let mode: MentionChatInputContextPanelMode
private let backgroundView: GlassBackgroundView
private let listView: ListView
private var currentEntries: [MentionChatInputContextPanelEntry]?
@ -73,10 +77,12 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, mode: MentionChatInputContextPanelMode, chatPresentationContext: ChatPresentationContext) {
self.mode = mode
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.accessibilityPageScrolledString = { row, count in
@ -86,13 +92,32 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
if mode == .search {
self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
}
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
}
func updateResults(_ results: [EnginePeer]) {
@ -202,9 +227,10 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: validLayout.0, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@ -217,22 +243,21 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
let position = strongSelf.listView.layer.position
strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset))
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView {
strongSelf.listView.position = position
}
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
}
})
}
}
private func topInsetForLayout(size: CGSize) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat) -> CGFloat {
let minimumItemHeights: CGFloat = floor(MentionChatInputPanelItemNode.itemHeight * 3.5)
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
@ -241,17 +266,26 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
if self.theme !== interfaceState.theme {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
if let currentEntries = self.currentEntries {
self.updateToEntries(entries: currentEntries, forceUpdate: true)
}
}
self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))
self.backgroundView.update(
size: self.backgroundView.bounds.size,
cornerRadius: 20.0,
isDark: interfaceState.theme.overallDarkAppearance,
tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)),
transition: ComponentTransition(transition)
)
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: size)
insets.top = topInsetForLayout(size: size, bottomInset: bottomInset)
insets.left = leftInset
insets.right = rightInset
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
@ -275,11 +309,14 @@ final class MentionChatInputContextPanelNode: ChatInputContextPanelNode {
}
}
if let topItemOffset = topItemOffset {
if let topItemOffset {
let offset = (self.listView.bounds.size.height - topItemOffset)
let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
completion()
}

View file

@ -97,7 +97,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
private let avatarNode: AvatarNode
private let textNode: TextNode
private let topSeparatorNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
@ -118,9 +117,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
self.avatarNode = AvatarNode(font: avatarFont)
self.textNode = TextNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
@ -132,7 +128,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.avatarNode)
@ -207,8 +202,6 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
}
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: EnginePeer(item.peer), emptyColor: item.presentationData.theme.list.mediaPlaceholderColor)
@ -218,10 +211,8 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 12.0, y: floor((nodeLayout.contentSize.height - 30.0) / 2.0)), size: CGSize(width: 30.0, height: 30.0))
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size)
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.separatorNode.isHidden = !mergedBottom
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: !item.inverted ? (nodeLayout.contentSize.height - UIScreenPixel) : 0.0), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
@ -259,7 +250,7 @@ final class MentionChatInputPanelItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
//self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {

View file

@ -183,6 +183,11 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
params.navigationController?.pushViewController(controller)
return true
case let .stickerPack(reference, previewIconFile):
var previewIconFile: TelegramMediaFile? = previewIconFile
if let file = previewIconFile, !file.isValidForDisplay(chatPeerId: params.message.id.peerId) {
previewIconFile = nil
}
let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], previewIconFile: previewIconFile, parentNavigationController: params.navigationController, sendSticker: params.sendSticker, sendEmoji: params.sendEmoji, actionPerformed: { actions in
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }

View file

@ -45,7 +45,7 @@ final class SecretChatHandshakeStatusInputPanelNode: ChatInputPanelNode {
self.interfaceInteraction?.unblockPeer()
}
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
if self.presentationInterfaceState != interfaceState {
self.presentationInterfaceState = interfaceState

View file

@ -11,6 +11,10 @@ import SwiftSignalKit
import ChatPresentationInterfaceState
import ChatControllerInteraction
import ChatInputContextPanelNode
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
import EdgeEffect
private enum VerticalChatContextResultsEntryStableId: Hashable {
case action
@ -123,7 +127,9 @@ private func preparedTransition(from fromEntries: [VerticalListContextResultsCha
}
final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContextPanelNode {
private let backgroundView: GlassBackgroundView
private let listView: ListView
private let listMaskView: UIImageView
private var currentExternalResults: ChatContextResultCollection?
private var currentProcessedResults: ChatContextResultCollection?
private var currentEntries: [VerticalListContextResultsChatInputContextPanelEntry]?
@ -135,10 +141,12 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
private var isLoadingMore: Bool = false
override init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, chatPresentationContext: ChatPresentationContext) {
self.backgroundView = GlassBackgroundView()
self.backgroundView.layer.anchorPoint = CGPoint()
self.listView = ListView()
self.listView.isOpaque = false
self.listView.stackFromBottom = true
self.listView.keepBottomItemOverscrollBackground = theme.list.plainBackgroundColor
self.listView.limitHitTestToNodes = true
self.listView.isHidden = true
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
@ -146,12 +154,17 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
return strings.VoiceOver_ScrollStatus(row, count).string
}
self.listMaskView = UIImageView()
super.init(context: context, theme: theme, strings: strings, fontSize: fontSize, chatPresentationContext: chatPresentationContext)
self.isOpaque = false
self.clipsToBounds = true
self.view.addSubview(self.backgroundView)
self.addSubnode(self.listView)
//self.view.addSubview(self.listMaskView)
self.listView.view.mask = self.listMaskView
self.listView.visibleBottomContentOffsetChanged = { [weak self] offset in
guard let strongSelf = self, !strongSelf.isLoadingMore, case let .known(value) = offset, value < 40.0 else {
@ -159,6 +172,25 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
}
strongSelf.loadMore()
}
self.backgroundView.isHidden = true
self.listView.visibleContentOffsetChanged = { [weak self] offset in
guard let self else {
return
}
var topOffset: CGFloat = 0.0
switch offset {
case let .known(offset):
topOffset = max(0.0, -offset + self.listView.insets.top)
case .unknown:
break
case .none:
break
}
self.backgroundView.isHidden = false
self.backgroundView.layer.position = CGPoint(x: 0.0, y: topOffset)
}
}
deinit {
@ -255,9 +287,10 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
}
var insets = UIEdgeInsets()
insets.top = topInsetForLayout(size: validLayout.0, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil)
insets.top = topInsetForLayout(size: validLayout.0, bottomInset: validLayout.3, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil)
insets.left = validLayout.1
insets.right = validLayout.2
insets.bottom = validLayout.3
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: self.listView.bounds.size, insets: insets, duration: 0.0, curve: .Default(duration: nil))
@ -270,39 +303,78 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
}
}
if let topItemOffset = topItemOffset {
let position = strongSelf.listView.layer.position
strongSelf.listView.position = CGPoint(x: position.x, y: position.y + (strongSelf.listView.bounds.size.height - topItemOffset))
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring).animateView {
strongSelf.listView.position = position
}
if let topItemOffset {
let offset = strongSelf.listView.bounds.size.height - topItemOffset
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
transition.animatePositionAdditive(layer: strongSelf.listView.layer, offset: CGPoint(x: 0.0, y: offset))
transition.animatePositionAdditive(layer: strongSelf.backgroundView.layer, offset: CGPoint(x: 0.0, y: offset))
}
strongSelf.listView.isHidden = false
}
})
}
}
private func topInsetForLayout(size: CGSize, hasSwitchPeer: Bool) -> CGFloat {
private func topInsetForLayout(size: CGSize, bottomInset: CGFloat, hasSwitchPeer: Bool) -> CGFloat {
var minimumItemHeights: CGFloat = floor(VerticalListContextResultsChatInputPanelItemNode.itemHeight * 3.5)
if hasSwitchPeer {
minimumItemHeights += VerticalListContextResultsChatInputPanelButtonItemNode.itemHeight(style: .regular)
}
return max(size.height - minimumItemHeights, 0.0)
return max(size.height - bottomInset - minimumItemHeights, 0.0)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) {
let hadValidLayout = self.validLayout != nil
self.validLayout = (size, leftInset, rightInset, bottomInset)
self.backgroundView.bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 32.0))
self.backgroundView.update(
size: self.backgroundView.bounds.size,
cornerRadius: 20.0,
isDark: interfaceState.theme.overallDarkAppearance,
tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)),
transition: ComponentTransition(transition)
)
var insets = UIEdgeInsets()
insets.top = self.topInsetForLayout(size: size, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil)
insets.top = self.topInsetForLayout(size: size, bottomInset: bottomInset, hasSwitchPeer: self.currentExternalResults?.switchPeer != nil || self.currentExternalResults?.webView != nil)
insets.left = leftInset
insets.right = rightInset
insets.bottom = bottomInset
transition.updateFrame(node: self.listView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let listMaskHeight: CGFloat = bottomInset + 1.0
if self.listMaskView.image?.size.height != listMaskHeight {
let baseGradientAlpha: CGFloat = 0.65
let numSteps = 8
let firstStep = 1
let firstLocation = 0.0
let colors: [UIColor] = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 0.0, alpha: 0.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 0.0, alpha: 1.0 - baseGradientAlpha * value)
}
}
let locations: [CGFloat] = (0 ..< numSteps).map { i in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
self.listMaskView.image = generateGradientImage(
size: CGSize(width: 8.0, height: listMaskHeight),
colors: colors,
locations: locations
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
}
transition.updateFrame(view: self.listMaskView, frame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve)
@ -317,7 +389,6 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
if self.theme !== interfaceState.theme, let currentProcessedResults = self.currentProcessedResults {
self.theme = interfaceState.theme
self.listView.keepBottomItemOverscrollBackground = self.theme.list.plainBackgroundColor
let new = self.currentEntries?.map({$0.withUpdatedTheme(interfaceState.theme)}) ?? []
prepareTransition(from: self.currentEntries, to: new, results: currentProcessedResults)
@ -332,11 +403,14 @@ final class VerticalListContextResultsChatInputContextPanelNode: ChatInputContex
}
}
if let topItemOffset = topItemOffset {
if let topItemOffset {
let offset = (self.listView.bounds.size.height - topItemOffset)
let position = self.listView.layer.position
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + (self.listView.bounds.size.height - topItemOffset)), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
self.listView.layer.animatePosition(from: position, to: CGPoint(x: position.x, y: position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
completion()
})
self.backgroundView.layer.animatePosition(from: self.backgroundView.layer.position, to: CGPoint(x: self.backgroundView.layer.position.x, y: self.backgroundView.layer.position.y + offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
} else {
completion()
}

View file

@ -161,13 +161,9 @@ final class VerticalListContextResultsChatInputPanelButtonItemNode: ListViewItem
let titleOffsetY: CGFloat
switch item.style {
case .regular:
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.separatorNode.isHidden = !mergedBottom
titleOffsetY = 2.0
case .round:
strongSelf.backgroundColor = nil
strongSelf.topSeparatorNode.isHidden = true
strongSelf.separatorNode.isHidden = !mergedBottom
titleOffsetY = 1.0
}

View file

@ -85,7 +85,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
private let iconImageNode: TransformImageNode
private let titleNode: TextNode
private let textNode: TextNode
private let topSeparatorNode: ASDisplayNode
private let separatorNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private var statusDisposable = MetaDisposable()
@ -100,9 +99,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
self.titleNode = TextNode()
self.textNode = TextNode()
self.topSeparatorNode = ASDisplayNode()
self.topSeparatorNode.isLayerBacked = true
self.separatorNode = ASDisplayNode()
self.separatorNode.isLayerBacked = true
@ -124,7 +120,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.topSeparatorNode)
self.addSubnode(self.separatorNode)
self.addSubnode(self.iconImageNode)
@ -285,8 +280,6 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
strongSelf.item = item
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.backgroundColor = item.theme.list.plainBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
let _ = titleApply()
@ -338,10 +331,8 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
}
}
strongSelf.topSeparatorNode.isHidden = mergedTop
strongSelf.separatorNode.isHidden = !mergedBottom
strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel))
@ -388,7 +379,7 @@ final class VerticalListContextResultsChatInputPanelItemNode: ListViewItemNode {
if highlighted {
self.highlightedBackgroundNode.alpha = 1.0
if self.highlightedBackgroundNode.supernode == nil {
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
//self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: self.separatorNode)
}
} else {
if self.highlightedBackgroundNode.supernode != nil {

View file

@ -1,5 +1,5 @@
{
"app": "12.0",
"app": "12.1",
"xcode": "16.2",
"bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217",
"macos": "15"