Update
This commit is contained in:
parent
0678d0ded0
commit
04765e0c36
67 changed files with 1405 additions and 1890 deletions
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,37 @@ public extension UIColor {
|
|||
return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha)
|
||||
}
|
||||
|
||||
func adjustedPerceivedBrightness(_ factor: CGFloat) -> UIColor {
|
||||
let f = max(0, factor)
|
||||
let base = self
|
||||
guard
|
||||
let cs = CGColorSpace(name: CGColorSpace.extendedSRGB),
|
||||
let cg = base.cgColor.converted(to: cs, intent: .defaultIntent, options: nil),
|
||||
let c = cg.components, c.count >= 3
|
||||
else { return base }
|
||||
|
||||
func toLin(_ x: CGFloat) -> CGFloat { x <= 0.04045 ? x/12.92 : pow((x+0.055)/1.055, 2.4) }
|
||||
func toSRGB(_ x: CGFloat) -> CGFloat { x <= 0.0031308 ? 12.92*x : 1.055*pow(x, 1/2.4) - 0.055 }
|
||||
func clamp(_ x: CGFloat) -> CGFloat { min(max(x, 0), 1) }
|
||||
|
||||
var r = toLin(c[0]), g = toLin(c[1]), b = toLin(c[2])
|
||||
if f >= 1 {
|
||||
// mix toward white: t = 1 - 1/f (so f=1 → t=0, f→∞ → t→1)
|
||||
let t = 1 - 1/f
|
||||
r = r + (1 - r) * t
|
||||
g = g + (1 - g) * t
|
||||
b = b + (1 - b) * t
|
||||
} else {
|
||||
// scale toward black
|
||||
r *= f; g *= f; b *= f
|
||||
}
|
||||
|
||||
return UIColor(red: clamp(toSRGB(r)),
|
||||
green: clamp(toSRGB(g)),
|
||||
blue: clamp(toSRGB(b)),
|
||||
alpha: cg.alpha)
|
||||
}
|
||||
|
||||
func withMultiplied(hue: CGFloat, saturation: CGFloat, brightness: CGFloat) -> UIColor {
|
||||
var hueValue: CGFloat = 0.0
|
||||
var saturationValue: CGFloat = 0.0
|
||||
|
|
|
|||
|
|
@ -1,299 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import StickerResources
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import UndoUI
|
||||
import TelegramPresentationData
|
||||
|
||||
public enum StickerPackPreviewControllerMode {
|
||||
case `default`
|
||||
case settings
|
||||
}
|
||||
|
||||
public final class StickerPackPreviewController: ViewController, StandalonePresentableController {
|
||||
private var controllerNode: StickerPackPreviewControllerNode {
|
||||
return self.displayNode as! StickerPackPreviewControllerNode
|
||||
}
|
||||
|
||||
private var animatedIn = false
|
||||
private var isDismissed = false
|
||||
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
private let context: AccountContext
|
||||
private let mode: StickerPackPreviewControllerMode
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
|
||||
private let stickerPack: StickerPackReference
|
||||
|
||||
private var stickerPackContentsValue: LoadedStickerPack?
|
||||
|
||||
private let stickerPackDisposable = MetaDisposable()
|
||||
private let stickerPackContents = Promise<LoadedStickerPack>()
|
||||
|
||||
private let stickerPackInstalledDisposable = MetaDisposable()
|
||||
private let stickerPackInstalled = Promise<Bool>()
|
||||
|
||||
private let openMentionDisposable = MetaDisposable()
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
public var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
if let sendSticker = self.sendSticker {
|
||||
self.controllerNode.sendSticker = { [weak self] file, sourceView, sourceRect in
|
||||
if sendSticker(file, sourceView, sourceRect) {
|
||||
self?.dismiss()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.controllerNode.sendSticker = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, stickerPack: StickerPackReference, mode: StickerPackPreviewControllerMode = .default, parentNavigationController: NavigationController?, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.parentNavigationController = parentNavigationController
|
||||
self.actionPerformed = actionPerformed
|
||||
|
||||
self.stickerPack = stickerPack
|
||||
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
self.stickerPackContents.set(context.engine.stickers.loadedStickerPack(reference: stickerPack, forceActualized: true))
|
||||
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerPackDisposable.dispose()
|
||||
self.stickerPackInstalledDisposable.dispose()
|
||||
self.openMentionDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
var openShareImpl: (() -> Void)?
|
||||
if self.mode == .settings {
|
||||
openShareImpl = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let stickerPackContentsValue = strongSelf.stickerPackContentsValue, case let .result(info, _, _) = stickerPackContentsValue, !info.shortName.isEmpty {
|
||||
let parentNavigationController = strongSelf.parentNavigationController
|
||||
let shareController = strongSelf.context.sharedContext.makeShareController(
|
||||
context: strongSelf.context,
|
||||
subject: .url("https://t.me/addstickers/\(info.shortName)"),
|
||||
forceExternal: true,
|
||||
shareStory: nil,
|
||||
enqueued: nil,
|
||||
actionCompleted: { [weak parentNavigationController] in
|
||||
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
)
|
||||
strongSelf.present(shareController, in: .window(.root))
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.displayNode = StickerPackPreviewControllerNode(context: self.context, presentationData: self.presentationData, openShare: openShareImpl, openMention: { [weak self] mention in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.openMentionDisposable.set((strongSelf.context.engine.peers.resolvePeerByName(name: mention, referrer: nil)
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
guard case let .result(result) = result else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(result)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<Peer?, NoError> in
|
||||
if let peer = peer {
|
||||
return .single(peer._asPeer())
|
||||
} else {
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let peer = peer, let parentNavigationController = strongSelf.parentNavigationController {
|
||||
strongSelf.dismiss()
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: parentNavigationController, context: strongSelf.context, chatLocation: .peer(EnginePeer(peer)), animated: true))
|
||||
}
|
||||
}))
|
||||
}, actionPerformed: self.actionPerformed)
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.dismissed?()
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
self.controllerNode.cancel = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
self.controllerNode.presentInGlobalOverlay = { [weak self] controller, arguments in
|
||||
self?.presentInGlobalOverlay(controller, with: arguments)
|
||||
}
|
||||
if let sendSticker = self.sendSticker {
|
||||
self.controllerNode.sendSticker = { [weak self] file, sourceNode, sourceRect in
|
||||
if sendSticker(file, sourceNode, sourceRect) {
|
||||
self?.dismiss()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
let account = self.context.account
|
||||
self.displayNodeDidLoad()
|
||||
self.stickerPackDisposable.set((combineLatest(self.stickerPackContents.get(), self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) |> take(1))
|
||||
|> mapToSignal { next, sharedData -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
|
||||
var stickerSettings = StickerSettings.defaultSettings
|
||||
if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings]?.get(StickerSettings.self) {
|
||||
stickerSettings = value
|
||||
}
|
||||
|
||||
switch next {
|
||||
case let .result(info, items, _):
|
||||
var preloadSignals: [Signal<Bool, NoError>] = []
|
||||
|
||||
let info = info._parse()
|
||||
if let thumbnail = info.thumbnail {
|
||||
let signal = Signal<Bool, NoError> { subscriber in
|
||||
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()
|
||||
let data = account.postbox.mediaBox.resourceData(thumbnail.resource, option: .incremental(waitUntilFetchStatus: false)).start(next: { data in
|
||||
if data.complete {
|
||||
subscriber.putNext(true)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putNext(false)
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
fetched.dispose()
|
||||
data.dispose()
|
||||
}
|
||||
}
|
||||
preloadSignals.append(signal)
|
||||
}
|
||||
|
||||
let topItems = items.prefix(16)
|
||||
for item in topItems {
|
||||
if item.file.isAnimatedSticker {
|
||||
let itemFile = item.file._parse()
|
||||
let signal = Signal<Bool, NoError> { subscriber in
|
||||
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: FileMediaReference.standalone(media: itemFile).resourceReference(itemFile.resource)).start()
|
||||
let data = account.postbox.mediaBox.resourceData(itemFile.resource).start()
|
||||
let dimensions = item.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fetchedRepresentation = chatMessageAnimatedStickerDatas(postbox: account.postbox, userLocation: .other, file: itemFile, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)), fetched: true, onlyFullSize: false, synchronousLoad: false).start(next: { next in
|
||||
let hasContent = next._0 != nil || next._1 != nil
|
||||
subscriber.putNext(hasContent)
|
||||
if hasContent {
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
fetched.dispose()
|
||||
data.dispose()
|
||||
fetchedRepresentation.dispose()
|
||||
}
|
||||
}
|
||||
preloadSignals.append(signal)
|
||||
}
|
||||
}
|
||||
return combineLatest(preloadSignals)
|
||||
|> map { values -> Bool in
|
||||
return !values.contains(false)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { loaded -> Signal<(LoadedStickerPack, StickerSettings), NoError> in
|
||||
if !loaded {
|
||||
return .single((.fetching, stickerSettings))
|
||||
} else {
|
||||
return .single((next, stickerSettings))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return .single((next, stickerSettings))
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] next in
|
||||
if let strongSelf = self {
|
||||
if case .none = next.0 {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
strongSelf.dismiss()
|
||||
} else {
|
||||
strongSelf.controllerNode.updateStickerPack(next.0, stickerSettings: next.1)
|
||||
strongSelf.stickerPackContentsValue = next.0
|
||||
}
|
||||
}
|
||||
}))
|
||||
self.ready.set(self.controllerNode.ready.get())
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
} else {
|
||||
return
|
||||
}
|
||||
self.acceptsFocusWhenInOverlay = false
|
||||
self.requestUpdateParameters()
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,704 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import ActivityIndicator
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import ContextUI
|
||||
import StickerPeekUI
|
||||
import AccountContext
|
||||
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let stickerItem: StickerPackItem
|
||||
|
||||
var stableId: MediaId {
|
||||
return self.stickerItem.file.fileId
|
||||
}
|
||||
|
||||
static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) -> StickerPackPreviewGridItem {
|
||||
return StickerPackPreviewGridItem(context: context, stickerItem: self.stickerItem, interaction: interaction, theme: theme, isPremium: false, isLocked: false, isEmpty: false, isEditable: false, isEditing: false)
|
||||
}
|
||||
}
|
||||
|
||||
private struct StickerPackPreviewGridTransaction {
|
||||
let deletions: [Int]
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
|
||||
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme) {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
|
||||
|
||||
self.deletions = deleteIndices
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme)) }
|
||||
}
|
||||
}
|
||||
|
||||
final class StickerPackPreviewControllerNode: ViewControllerTracingNode, ASScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let openShare: (() -> Void)?
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
|
||||
private let wrappingScrollNode: ASScrollNode
|
||||
private let cancelButtonNode: ASButtonNode
|
||||
|
||||
private let contentContainerNode: ASDisplayNode
|
||||
private let contentBackgroundNode: ASImageNode
|
||||
private let contentGridNode: GridNode
|
||||
private let installActionButtonNode: ASButtonNode
|
||||
private let installActionSeparatorNode: ASDisplayNode
|
||||
private let shareActionButtonNode: ASButtonNode
|
||||
private let shareActionSeparatorNode: ASDisplayNode
|
||||
private let contentTitleNode: ImmediateTextNode
|
||||
private let contentSeparatorNode: ASDisplayNode
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var interaction: StickerPackPreviewInteraction!
|
||||
|
||||
var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
var sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?
|
||||
private let actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?
|
||||
|
||||
let ready = Promise<Bool>()
|
||||
private var didSetReady = false
|
||||
|
||||
private var stickerPack: LoadedStickerPack?
|
||||
private var stickerPackUpdated = false
|
||||
private var stickerPackInitiallyInstalled : Bool?
|
||||
private var stickerSettings: StickerSettings?
|
||||
|
||||
private var currentItems: [StickerPackPreviewGridEntry] = []
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private weak var peekController: PeekController?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, openShare: (() -> Void)?, openMention: @escaping (String) -> Void, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)?) {
|
||||
self.context = context
|
||||
self.openShare = openShare
|
||||
self.presentationData = presentationData
|
||||
self.actionPerformed = actionPerformed
|
||||
|
||||
self.wrappingScrollNode = ASScrollNode()
|
||||
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
self.cancelButtonNode = ASButtonNode()
|
||||
self.cancelButtonNode.displaysAsynchronously = false
|
||||
|
||||
self.contentContainerNode = ASDisplayNode()
|
||||
self.contentContainerNode.isOpaque = false
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
|
||||
self.contentBackgroundNode = ASImageNode()
|
||||
self.contentBackgroundNode.displaysAsynchronously = false
|
||||
self.contentBackgroundNode.displayWithoutProcessing = true
|
||||
|
||||
self.contentGridNode = GridNode()
|
||||
|
||||
self.installActionButtonNode = HighlightTrackingButtonNode()
|
||||
self.installActionButtonNode.displaysAsynchronously = false
|
||||
self.installActionButtonNode.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.shareActionButtonNode = HighlightTrackingButtonNode()
|
||||
self.shareActionButtonNode.displaysAsynchronously = false
|
||||
self.shareActionButtonNode.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.contentTitleNode = ImmediateTextNode()
|
||||
self.contentTitleNode.displaysAsynchronously = false
|
||||
self.contentTitleNode.maximumNumberOfLines = 1
|
||||
|
||||
self.contentSeparatorNode = ASDisplayNode()
|
||||
self.contentSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.installActionSeparatorNode = ASDisplayNode()
|
||||
self.installActionSeparatorNode.isLayerBacked = true
|
||||
self.installActionSeparatorNode.displaysAsynchronously = false
|
||||
|
||||
self.shareActionSeparatorNode = ASDisplayNode()
|
||||
self.shareActionSeparatorNode.isLayerBacked = true
|
||||
self.shareActionSeparatorNode.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }, addPressed: {})
|
||||
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
self.addSubnode(self.dimNode)
|
||||
|
||||
self.wrappingScrollNode.view.delegate = self.wrappedScrollViewDelegate
|
||||
self.addSubnode(self.wrappingScrollNode)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.cancelButtonNode)
|
||||
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.installActionButtonNode.addTarget(self, action: #selector(self.installActionButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.shareActionButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.contentBackgroundNode)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||
self.contentContainerNode.addSubnode(self.contentGridNode)
|
||||
self.contentContainerNode.addSubnode(self.installActionSeparatorNode)
|
||||
self.contentContainerNode.addSubnode(self.installActionButtonNode)
|
||||
if openShare != nil {
|
||||
self.contentContainerNode.addSubnode(self.shareActionSeparatorNode)
|
||||
self.contentContainerNode.addSubnode(self.shareActionButtonNode)
|
||||
}
|
||||
self.wrappingScrollNode.addSubnode(self.contentTitleNode)
|
||||
self.wrappingScrollNode.addSubnode(self.contentSeparatorNode)
|
||||
|
||||
self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition)
|
||||
}
|
||||
|
||||
self.contentTitleNode.highlightAttributeAction = { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
self.contentTitleNode.tapAttributeAction = { attributes, _ in
|
||||
if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String, mention.count > 1 {
|
||||
openMention(String(mention[mention.index(after: mention.startIndex)...]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.contentGridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(UIView, CGRect, PeekControllerContent)?, NoError>? in
|
||||
if let strongSelf = self {
|
||||
if let itemNode = strongSelf.contentGridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem {
|
||||
let accountPeerId = strongSelf.context.account.peerId
|
||||
return combineLatest(
|
||||
strongSelf.context.engine.stickers.isStickerSaved(id: item.file.fileId),
|
||||
strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: accountPeerId)) |> map { peer -> Bool in
|
||||
var hasPremium = false
|
||||
if case let .user(user) = peer, user.isPremium {
|
||||
hasPremium = true
|
||||
}
|
||||
return hasPremium
|
||||
}
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { isStarred, hasPremium -> (UIView, CGRect, PeekControllerContent)? in
|
||||
if let strongSelf = self {
|
||||
var menuItems: [ContextMenuItem] = []
|
||||
if let stickerPack = strongSelf.stickerPack, case let .result(info, _, _) = stickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
if strongSelf.sendSticker != nil {
|
||||
menuItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.StickerPack_Send, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
if let strongSelf = self, let peekController = strongSelf.peekController {
|
||||
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
|
||||
let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), animationNode.view, animationNode.bounds)
|
||||
} else if let imageNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.imageNode {
|
||||
let _ = strongSelf.sendSticker?(.standalone(media: item.file._parse()), imageNode.view, imageNode.bounds)
|
||||
}
|
||||
}
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
menuItems.append(.action(ContextMenuActionItem(text: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, icon: { theme in generateTintedImage(image: isStarred ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.context.engine.stickers.toggleStickerSaved(file: item.file._parse(), saved: !isStarred).start(next: { result in
|
||||
|
||||
})
|
||||
}
|
||||
})))
|
||||
}
|
||||
return (itemNode.view, itemNode.bounds, StickerPreviewPeekContent(context: strongSelf.context, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, item: .pack(item.file._parse()), isLocked: item.file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: {
|
||||
|
||||
}))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, present: { [weak self] content, sourceView, sourceRect in
|
||||
if let strongSelf = self {
|
||||
let controller = PeekController(presentationData: strongSelf.presentationData, content: content, sourceView: {
|
||||
return (sourceView, sourceRect)
|
||||
})
|
||||
controller.visibilityUpdated = { [weak self] visible in
|
||||
if let strongSelf = self {
|
||||
strongSelf.contentGridNode.forceHidden = visible
|
||||
}
|
||||
}
|
||||
strongSelf.peekController = controller
|
||||
strongSelf.presentInGlobalOverlay?(controller, nil)
|
||||
return controller
|
||||
}
|
||||
return nil
|
||||
}, updateContent: { [weak self] content in
|
||||
if let strongSelf = self {
|
||||
var item: StickerPreviewPeekItem?
|
||||
if let content = content as? StickerPreviewPeekContent {
|
||||
item = content.item
|
||||
}
|
||||
strongSelf.updatePreviewingItem(item: item, animated: true)
|
||||
}
|
||||
}, activateBySingleTap: true))
|
||||
|
||||
self.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
|
||||
let theme = presentationData.theme
|
||||
let solidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let highlightedSolidBackground = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let halfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let highlightedHalfRoundedBackground = generateImage(CGSize(width: 32.0, height: 32.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(theme.actionSheet.opaqueItemHighlightedBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
|
||||
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 1)
|
||||
|
||||
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemBackgroundColor)
|
||||
let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor)
|
||||
|
||||
self.contentBackgroundNode.image = roundedBackground
|
||||
|
||||
self.cancelButtonNode.setBackgroundImage(roundedBackground, for: .normal)
|
||||
self.cancelButtonNode.setBackgroundImage(highlightedRoundedBackground, for: .highlighted)
|
||||
|
||||
if self.shareActionButtonNode.supernode != nil {
|
||||
self.installActionButtonNode.setBackgroundImage(solidBackground, for: .normal)
|
||||
self.installActionButtonNode.setBackgroundImage(highlightedSolidBackground, for: .highlighted)
|
||||
} else {
|
||||
self.installActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal)
|
||||
self.installActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
|
||||
}
|
||||
|
||||
self.shareActionButtonNode.setBackgroundImage(halfRoundedBackground, for: .normal)
|
||||
self.shareActionButtonNode.setBackgroundImage(highlightedHalfRoundedBackground, for: .highlighted)
|
||||
self.shareActionButtonNode.setTitle(presentationData.strings.Conversation_ContextMenuShare, with: Font.regular(20.0), with: presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
|
||||
self.contentSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
||||
self.installActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
||||
self.shareActionSeparatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor
|
||||
|
||||
self.cancelButtonNode.setTitle(presentationData.strings.Common_Cancel, with: Font.medium(20.0), with: presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
||||
|
||||
self.contentTitleNode.linkHighlightColor = presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
if let (layout, navigationBarHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var insets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
let hasShareButton = self.shareActionButtonNode.supernode != nil
|
||||
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
if insets.bottom > 0 {
|
||||
bottomInset -= 12.0
|
||||
}
|
||||
|
||||
let buttonHeight: CGFloat = 57.0
|
||||
let sectionSpacing: CGFloat = 8.0
|
||||
let titleAreaHeight: CGFloat = 51.0
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
|
||||
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
|
||||
transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: width, height: buttonHeight)))
|
||||
|
||||
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
|
||||
|
||||
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
|
||||
let contentFrame = contentContainerFrame.insetBy(dx: 12.0, dy: 0.0)
|
||||
|
||||
var transaction: StickerPackPreviewGridTransaction?
|
||||
|
||||
var itemCount = 0
|
||||
var animateIn = false
|
||||
|
||||
if let stickerPack = self.stickerPack {
|
||||
switch stickerPack {
|
||||
case .fetching, .none:
|
||||
if self.activityIndicator == nil {
|
||||
let activityIndicator = ActivityIndicator(type: ActivityIndicatorType.custom(self.presentationData.theme.actionSheet.controlAccentColor, 22.0, 2.0, false))
|
||||
self.activityIndicator = activityIndicator
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
case let .result(info, items, _):
|
||||
if let activityIndicator = self.activityIndicator {
|
||||
activityIndicator.removeFromSupernode()
|
||||
self.activityIndicator = nil
|
||||
}
|
||||
itemCount = items.count
|
||||
|
||||
var updatedItems: [StickerPackPreviewGridEntry] = []
|
||||
for item in items {
|
||||
updatedItems.append(StickerPackPreviewGridEntry(index: updatedItems.count, stickerItem: item))
|
||||
}
|
||||
|
||||
if self.currentItems.isEmpty && !updatedItems.isEmpty {
|
||||
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
|
||||
let font = Font.medium(20.0)
|
||||
self.contentTitleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: font, linkFont: font, boldFont: font, italicFont: font, boldItalicFont: font, fixedFont: font, blockQuoteFont: font, message: nil)
|
||||
animateIn = true
|
||||
}
|
||||
transaction = StickerPackPreviewGridTransaction(previousList: self.currentItems, list: updatedItems, context: self.context, interaction: self.interaction, theme: self.presentationData.theme)
|
||||
self.currentItems = updatedItems
|
||||
}
|
||||
}
|
||||
|
||||
let titleSize = self.contentTitleNode.updateLayout(CGSize(width: contentContainerFrame.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: contentContainerFrame.minX + floor((contentContainerFrame.size.width - titleSize.width) / 2.0), y: self.contentBackgroundNode.frame.minY + 15.0), size: titleSize)
|
||||
let deltaTitlePosition = CGPoint(x: titleFrame.midX - self.contentTitleNode.frame.midX, y: titleFrame.midY - self.contentTitleNode.frame.midY)
|
||||
self.contentTitleNode.frame = titleFrame
|
||||
transition.animatePosition(node: self.contentTitleNode, from: CGPoint(x: titleFrame.midX + deltaTitlePosition.x, y: titleFrame.midY + deltaTitlePosition.y))
|
||||
|
||||
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
|
||||
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentContainerFrame.minX, y: self.contentBackgroundNode.frame.minY + titleAreaHeight), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
let itemsPerRow = 4
|
||||
let itemWidth = floor(contentFrame.size.width / CGFloat(itemsPerRow))
|
||||
let rowCount = itemCount / itemsPerRow + (itemCount % itemsPerRow != 0 ? 1 : 0)
|
||||
|
||||
let minimallyRevealedRowCount: CGFloat = 3.5
|
||||
let initiallyRevealedRowCount = min(minimallyRevealedRowCount, CGFloat(rowCount))
|
||||
|
||||
let bottomGridInset = hasShareButton ? buttonHeight * 2.0 : buttonHeight
|
||||
let topInset = max(0.0, contentFrame.size.height - initiallyRevealedRowCount * itemWidth - titleAreaHeight - bottomGridInset)
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
|
||||
if let activityIndicator = self.activityIndicator {
|
||||
let indicatorSize = activityIndicator.calculateSizeThatFits(layout.size)
|
||||
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.width - indicatorSize.width) / 2.0), y: contentFrame.maxY - indicatorSize.height - 30.0), size: indicatorSize))
|
||||
}
|
||||
|
||||
let installButtonOffset = hasShareButton ? buttonHeight * 2.0 : buttonHeight
|
||||
transition.updateFrame(node: self.installActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.installActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - installButtonOffset - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
transition.updateFrame(node: self.shareActionButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight), size: CGSize(width: contentContainerFrame.size.width, height: buttonHeight)))
|
||||
transition.updateFrame(node: self.shareActionSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentContainerFrame.size.height - buttonHeight - UIScreenPixel), size: CGSize(width: contentContainerFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
let gridSize = CGSize(width: contentFrame.size.width, height: max(32.0, contentFrame.size.height - titleAreaHeight))
|
||||
|
||||
self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transaction?.deletions ?? [], insertItems: transaction?.insertions ?? [], updateItems: transaction?.updates ?? [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridSize, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: bottomGridInset, right: 0.0), preloadSize: 80.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
|
||||
transition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((contentContainerFrame.size.width - contentFrame.size.width) / 2.0), y: titleAreaHeight), size: gridSize))
|
||||
|
||||
if animateIn {
|
||||
self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.shareActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.shareActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
if let _ = self.stickerPack, self.stickerPackUpdated {
|
||||
self.dequeueUpdateStickerPack()
|
||||
}
|
||||
}
|
||||
|
||||
private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) {
|
||||
if let (layout, _) = self.containerLayout {
|
||||
var insets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
|
||||
var bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
if insets.bottom > 0 {
|
||||
bottomInset -= 12.0
|
||||
}
|
||||
|
||||
let buttonHeight: CGFloat = 57.0
|
||||
let sectionSpacing: CGFloat = 8.0
|
||||
let titleAreaHeight: CGFloat = 51.0
|
||||
|
||||
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 10.0 + layout.safeInsets.left)
|
||||
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
|
||||
let maximumContentHeight = layout.size.height - insets.top - bottomInset - buttonHeight - sectionSpacing
|
||||
let contentFrame = CGRect(origin: CGPoint(x: sideInset, y: insets.top), size: CGSize(width: width, height: maximumContentHeight))
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY - presentationLayout.contentOffset.y), size: contentFrame.size)
|
||||
if backgroundFrame.minY < contentFrame.minY {
|
||||
backgroundFrame.origin.y = contentFrame.minY
|
||||
}
|
||||
if backgroundFrame.maxY > contentFrame.maxY {
|
||||
backgroundFrame.size.height += contentFrame.maxY - backgroundFrame.maxY
|
||||
}
|
||||
if backgroundFrame.size.height < buttonHeight + 32.0 {
|
||||
backgroundFrame.origin.y -= buttonHeight + 32.0 - backgroundFrame.size.height
|
||||
backgroundFrame.size.height = buttonHeight + 32.0
|
||||
}
|
||||
var compactFrame = true
|
||||
if let stickerPack = self.stickerPack, case .result = stickerPack {
|
||||
compactFrame = false
|
||||
}
|
||||
if compactFrame {
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.maxY - buttonHeight - 32.0), size: CGSize(width: contentFrame.size.width, height: buttonHeight + 32.0))
|
||||
}
|
||||
let backgroundDeltaY = backgroundFrame.minY - self.contentBackgroundNode.frame.minY
|
||||
transition.updateFrame(node: self.contentBackgroundNode, frame: backgroundFrame)
|
||||
transition.animatePositionAdditive(node: self.contentGridNode, offset: CGPoint(x: 0.0, y: -backgroundDeltaY))
|
||||
|
||||
let titleSize = self.contentTitleNode.bounds.size
|
||||
let titleFrame = CGRect(origin: CGPoint(x: contentFrame.minX + floor((contentFrame.size.width - titleSize.width) / 2.0), y: backgroundFrame.minY + 15.0), size: titleSize)
|
||||
transition.updateFrame(node: self.contentTitleNode, frame: titleFrame)
|
||||
|
||||
transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: contentFrame.minX, y: backgroundFrame.minY + titleAreaHeight), size: CGSize(width: contentFrame.size.width, height: UIScreenPixel)))
|
||||
|
||||
if !compactFrame && CGFloat(0.0).isLessThanOrEqualTo(presentationLayout.contentOffset.y) {
|
||||
self.contentSeparatorNode.alpha = 1.0
|
||||
} else {
|
||||
self.contentSeparatorNode.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.cancelButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
self.cancel?()
|
||||
}
|
||||
|
||||
@objc func installActionButtonPressed() {
|
||||
if let stickerPack = self.stickerPack, let _ = self.stickerSettings {
|
||||
switch stickerPack {
|
||||
case let .result(info, items, installed):
|
||||
if installed {
|
||||
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] indexAndItems in
|
||||
guard let strongSelf = self, let (positionInList, _) = indexAndItems else {
|
||||
return
|
||||
}
|
||||
strongSelf.actionPerformed?(info._parse(), items, .remove(positionInList: positionInList))
|
||||
})
|
||||
} else {
|
||||
let parsedInfo = info._parse()
|
||||
let _ = self.context.engine.stickers.addStickerPackInteractively(info: parsedInfo, items: items).start()
|
||||
self.actionPerformed?(parsedInfo, items, .add)
|
||||
}
|
||||
self.cancelButtonPressed()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
|
||||
let offset: CGFloat = 510.0
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
let targetBounds = self.bounds
|
||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||
transition.animateView({
|
||||
self.bounds = targetBounds
|
||||
self.dimNode.position = dimPosition
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
var dimCompleted = false
|
||||
var offsetCompleted = false
|
||||
|
||||
let internalCompletion: () -> Void = { [weak self] in
|
||||
if let strongSelf = self, dimCompleted && offsetCompleted {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
dimCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
|
||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
offsetCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
func updateStickerPack(_ stickerPack: LoadedStickerPack, stickerSettings: StickerSettings) {
|
||||
self.stickerPack = stickerPack
|
||||
self.stickerSettings = stickerSettings
|
||||
self.stickerPackUpdated = true
|
||||
|
||||
self.interaction.playAnimatedStickers = self.context.sharedContext.energyUsageSettings.loopStickers
|
||||
|
||||
if let _ = self.containerLayout {
|
||||
self.dequeueUpdateStickerPack()
|
||||
}
|
||||
switch stickerPack {
|
||||
case .none, .fetching:
|
||||
self.installActionSeparatorNode.alpha = 0.0
|
||||
self.shareActionSeparatorNode.alpha = 0.0
|
||||
self.shareActionButtonNode.alpha = 0.0
|
||||
self.installActionButtonNode.alpha = 0.0
|
||||
self.installActionButtonNode.setTitle("", with: Font.medium(20.0), with: self.presentationData.theme.actionSheet.standardActionTextColor, for: .normal)
|
||||
case let .result(info, _, installed):
|
||||
if self.stickerPackInitiallyInstalled == nil {
|
||||
self.stickerPackInitiallyInstalled = installed
|
||||
}
|
||||
self.installActionSeparatorNode.alpha = 1.0
|
||||
self.shareActionSeparatorNode.alpha = 1.0
|
||||
self.shareActionButtonNode.alpha = 1.0
|
||||
self.installActionButtonNode.alpha = 1.0
|
||||
if installed {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveEmojiCount(info.count)
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count)
|
||||
}
|
||||
self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal)
|
||||
} else {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddStickerCount(info.count)
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddEmojiCount(info.count)
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_AddMaskCount(info.count)
|
||||
}
|
||||
self.installActionButtonNode.setTitle(text, with: Font.regular(20.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dequeueUpdateStickerPack() {
|
||||
if let (layout, navigationBarHeight) = self.containerLayout, let _ = self.stickerPack, self.stickerPackUpdated {
|
||||
self.stickerPackUpdated = false
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if self.didSetReady {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
self.ready.set(.single(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let result = self.installActionButtonNode.hitTest(self.installActionButtonNode.convert(point, from: self), with: event) {
|
||||
return result
|
||||
}
|
||||
else if self.shareActionButtonNode.supernode != nil, let result = self.shareActionButtonNode.hitTest(self.shareActionButtonNode.convert(point, from: self), with: event) {
|
||||
return result
|
||||
}
|
||||
if self.bounds.contains(point) {
|
||||
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) && !self.cancelButtonNode.bounds.contains(self.convert(point, to: self.cancelButtonNode)) {
|
||||
return self.dimNode.view
|
||||
}
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let additionalTopHeight = max(0.0, -contentOffset.y)
|
||||
|
||||
if additionalTopHeight >= 30.0 {
|
||||
self.cancelButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) {
|
||||
if self.interaction.previewedItem != item {
|
||||
self.interaction.previewedItem = item
|
||||
|
||||
self.contentGridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? StickerPackPreviewGridItemNode {
|
||||
itemNode.updatePreviewing(animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func sharePressed() {
|
||||
self.openShare?()
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,11 @@ import EmojiStatusComponent
|
|||
|
||||
private let maxStickersCount = 120
|
||||
|
||||
public enum StickerPackPreviewControllerMode {
|
||||
case `default`
|
||||
case settings
|
||||
}
|
||||
|
||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool, isEditing: Bool, isAdd: Bool)
|
||||
case add
|
||||
|
|
@ -2354,6 +2359,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
|||
self.controller = controller
|
||||
self.presentationData = controller.presentationData
|
||||
self.stickerPacks = stickerPacks
|
||||
|
||||
self.previewIconFile = previewIconFile
|
||||
self.selectedStickerPackIndex = initialSelectedStickerPackIndex
|
||||
self.modalProgressUpdated = modalProgressUpdated
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ?? [])
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -534,6 +534,9 @@ private class AdMessagesHistoryContextImpl {
|
|||
case let .peerColor(_, color, backgroundEmojiIdValue):
|
||||
nameColorIndex = color
|
||||
backgroundEmojiId = backgroundEmojiIdValue
|
||||
case .peerColorCollectible, .inputPeerColorCollectible:
|
||||
//TODO:release
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ swift_library(
|
|||
"//submodules/AccountContext",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import AccountContext
|
|||
import OldChannelsController
|
||||
import TooltipUI
|
||||
import TelegramNotices
|
||||
import GlassBackgroundComponent
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
private enum SubscriberAction: Equatable {
|
||||
case join
|
||||
|
|
@ -140,16 +143,22 @@ private func actionForPeer(context: AccountContext, peer: Peer, interfaceState:
|
|||
private let badgeFont = Font.regular(14.0)
|
||||
|
||||
public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
private let button: HighlightableButtonNode
|
||||
private let discussButton: HighlightableButtonNode
|
||||
private let discussButtonText: ImmediateTextNode
|
||||
private let badgeBackground: ASImageNode
|
||||
private let badgeText: ImmediateTextNode
|
||||
private let activityIndicator: UIActivityIndicatorView
|
||||
private let buttonBackgroundView: GlassBackgroundView
|
||||
private let button: HighlightableButton
|
||||
private let buttonTitle: ImmediateTextNode
|
||||
private let buttonTintTitle: ImmediateTextNode
|
||||
|
||||
private let helpButton: HighlightableButtonNode
|
||||
private let giftButton: HighlightableButtonNode
|
||||
private let suggestedPostButton: HighlightableButtonNode
|
||||
private let helpButtonBackgroundView: GlassBackgroundView
|
||||
private let helpButton: HighlightableButton
|
||||
private let helpButtonIconView: UIImageView
|
||||
|
||||
private let giftButtonBackgroundView: GlassBackgroundView
|
||||
private let giftButton: HighlightableButton
|
||||
private let giftButtonIconView: UIImageView
|
||||
|
||||
private let suggestedPostButtonBackgroundView: GlassBackgroundView
|
||||
private let suggestedPostButton: HighlightableButton
|
||||
private let suggestedPostButtonIconView: UIImageView
|
||||
|
||||
private var action: SubscriberAction?
|
||||
|
||||
|
|
@ -159,52 +168,53 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||
|
||||
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, Bool, LayoutMetrics)?
|
||||
private var layoutData: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, Bool, LayoutMetrics)?
|
||||
|
||||
public override init() {
|
||||
self.button = HighlightableButtonNode()
|
||||
self.discussButton = HighlightableButtonNode()
|
||||
self.activityIndicator = UIActivityIndicatorView(style: .medium)
|
||||
self.activityIndicator.isHidden = true
|
||||
self.button = HighlightableButton()
|
||||
self.buttonBackgroundView = GlassBackgroundView()
|
||||
self.buttonBackgroundView.isUserInteractionEnabled = false
|
||||
self.button.addSubview(self.buttonBackgroundView)
|
||||
self.buttonTitle = ImmediateTextNode()
|
||||
self.buttonTitle.isUserInteractionEnabled = false
|
||||
self.buttonTintTitle = ImmediateTextNode()
|
||||
self.buttonBackgroundView.contentView.addSubview(self.buttonTitle.view)
|
||||
self.buttonBackgroundView.maskContentView.addSubview(self.buttonTintTitle.view)
|
||||
|
||||
self.discussButtonText = ImmediateTextNode()
|
||||
self.discussButtonText.displaysAsynchronously = false
|
||||
|
||||
self.badgeBackground = ASImageNode()
|
||||
self.badgeBackground.displaysAsynchronously = false
|
||||
self.badgeBackground.displayWithoutProcessing = true
|
||||
self.badgeBackground.isHidden = true
|
||||
|
||||
self.badgeText = ImmediateTextNode()
|
||||
self.badgeText.displaysAsynchronously = false
|
||||
self.badgeText.isHidden = true
|
||||
|
||||
self.helpButton = HighlightableButtonNode()
|
||||
self.helpButton = HighlightableButton()
|
||||
self.helpButton.isHidden = true
|
||||
self.giftButton = HighlightableButtonNode()
|
||||
self.giftButton.isHidden = true
|
||||
self.suggestedPostButton = HighlightableButtonNode()
|
||||
self.suggestedPostButton.isHidden = true
|
||||
self.helpButtonBackgroundView = GlassBackgroundView()
|
||||
self.helpButtonBackgroundView.isUserInteractionEnabled = false
|
||||
self.helpButton.addSubview(self.helpButtonBackgroundView)
|
||||
self.helpButtonIconView = GlassBackgroundView.ContentImageView()
|
||||
self.helpButtonBackgroundView.contentView.addSubview(self.helpButtonIconView)
|
||||
|
||||
self.discussButton.addSubnode(self.discussButtonText)
|
||||
self.discussButton.addSubnode(self.badgeBackground)
|
||||
self.discussButton.addSubnode(self.badgeText)
|
||||
self.giftButton = HighlightableButton()
|
||||
self.giftButton.isHidden = true
|
||||
self.giftButtonBackgroundView = GlassBackgroundView()
|
||||
self.giftButtonBackgroundView.isUserInteractionEnabled = false
|
||||
self.giftButton.addSubview(self.giftButtonBackgroundView)
|
||||
self.giftButtonIconView = GlassBackgroundView.ContentImageView()
|
||||
self.giftButtonBackgroundView.contentView.addSubview(self.giftButtonIconView)
|
||||
|
||||
self.suggestedPostButton = HighlightableButton()
|
||||
self.suggestedPostButton.isHidden = true
|
||||
self.suggestedPostButtonBackgroundView = GlassBackgroundView()
|
||||
self.suggestedPostButtonBackgroundView.isUserInteractionEnabled = false
|
||||
self.suggestedPostButton.addSubview(self.suggestedPostButtonBackgroundView)
|
||||
self.suggestedPostButtonIconView = GlassBackgroundView.ContentImageView()
|
||||
self.suggestedPostButtonBackgroundView.contentView.addSubview(self.suggestedPostButtonIconView)
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.addSubnode(self.button)
|
||||
self.addSubnode(self.discussButton)
|
||||
self.view.addSubview(self.activityIndicator)
|
||||
self.addSubnode(self.helpButton)
|
||||
self.addSubnode(self.giftButton)
|
||||
self.addSubnode(self.suggestedPostButton)
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside)
|
||||
self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside)
|
||||
self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside)
|
||||
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), forControlEvents: .touchUpInside)
|
||||
self.view.addSubview(self.button)
|
||||
self.view.addSubview(self.helpButton)
|
||||
self.view.addSubview(self.giftButton)
|
||||
self.view.addSubview(self.suggestedPostButton)
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
self.helpButton.addTarget(self, action: #selector(self.helpPressed), for: .touchUpInside)
|
||||
self.giftButton.addTarget(self, action: #selector(self.giftPressed), for: .touchUpInside)
|
||||
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
@ -238,33 +248,14 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
|
||||
switch action {
|
||||
case .join, .joinGroup, .applyToJoin:
|
||||
var delayActivity = false
|
||||
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
|
||||
delayActivity = true
|
||||
}
|
||||
|
||||
if delayActivity {
|
||||
Queue.mainQueue().after(1.5) {
|
||||
if self.isJoining {
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.activityIndicator.isHidden = false
|
||||
self.activityIndicator.startAnimating()
|
||||
}
|
||||
|
||||
self.isJoining = true
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics) = self.layoutData, let presentationInterfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: presentationInterfaceState, metrics: metrics, force: true)
|
||||
}
|
||||
self.actionDisposable.set((context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peer.id, hash: nil)
|
||||
|> afterDisposed { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
strongSelf.activityIndicator.stopAnimating()
|
||||
strongSelf.isJoining = false
|
||||
}
|
||||
}
|
||||
|
|
@ -311,14 +302,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func discussPressed() {
|
||||
if let presentationInterfaceState = self.presentationInterfaceState, let peerDiscussionId = presentationInterfaceState.peerDiscussionId {
|
||||
self.interfaceInteraction?.navigateToChat(peerDiscussionId)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
|
||||
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
return self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, force: false)
|
||||
}
|
||||
|
||||
private var displayedGiftOrSuggestTooltip = false
|
||||
|
|
@ -349,7 +334,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
let _ = ApplicationSpecificNotice.incrementChannelSendGiftTooltip(accountManager: context.sharedContext.accountManager).start()
|
||||
|
||||
Queue.mainQueue().after(0.4, {
|
||||
let absoluteFrame = self.giftButton.view.convert(self.giftButton.bounds, to: parentController.view)
|
||||
let absoluteFrame = self.giftButton.convert(self.giftButton.bounds, to: parentController.view)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
@ -376,7 +361,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
let _ = ApplicationSpecificNotice.incrementChannelSuggestTooltip(accountManager: context.sharedContext.accountManager).start()
|
||||
|
||||
Queue.mainQueue().after(0.4, {
|
||||
let absoluteFrame = self.suggestedPostButton.view.convert(self.suggestedPostButton.bounds, to: parentController.view)
|
||||
let absoluteFrame = self.suggestedPostButton.convert(self.suggestedPostButton.bounds, to: parentController.view)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 11.0), size: CGSize())
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
|
@ -405,19 +390,23 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
})
|
||||
}
|
||||
|
||||
private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat {
|
||||
private func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, force: Bool) -> CGFloat {
|
||||
let isFirstTime = self.layoutData == nil
|
||||
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, isSecondary, metrics)
|
||||
self.layoutData = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, isSecondary, metrics)
|
||||
|
||||
if self.presentationInterfaceState != interfaceState || force {
|
||||
let previousState = self.presentationInterfaceState
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
if previousState?.theme !== interfaceState.theme {
|
||||
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
|
||||
self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
self.helpButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.helpButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
|
||||
|
||||
self.suggestedPostButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPost"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.suggestedPostButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
|
||||
|
||||
self.giftButtonIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.giftButtonIconView.tintColor = interfaceState.theme.chat.inputPanel.inputControlColor
|
||||
}
|
||||
|
||||
if let context = self.context, let peer = interfaceState.renderedPeer?.peer, previousState?.renderedPeer?.peer == nil || !peer.isEqual(previousState!.renderedPeer!.peer!) || previousState?.theme !== interfaceState.theme || previousState?.strings !== interfaceState.strings || previousState?.peerIsMuted != interfaceState.peerIsMuted || previousState?.pinnedMessage != interfaceState.pinnedMessage || force {
|
||||
|
|
@ -425,9 +414,11 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
if let action = actionForPeer(context: context, peer: peer, interfaceState: interfaceState, isJoining: self.isJoining, isMuted: interfaceState.peerIsMuted) {
|
||||
let previousAction = self.action
|
||||
self.action = action
|
||||
let (title, color) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
|
||||
let (title, _) = titleAndColorForAction(action, theme: interfaceState.theme, strings: interfaceState.strings)
|
||||
|
||||
var offset: CGFloat = 30.0
|
||||
let _ = previousAction
|
||||
|
||||
/*var offset: CGFloat = 30.0
|
||||
|
||||
if let previousAction = previousAction, [.join, .muteNotifications].contains(previousAction) && action == .unmuteNotifications || [.join, .unmuteNotifications].contains(previousAction) && action == .muteNotifications {
|
||||
if [.join, .muteNotifications].contains(previousAction) {
|
||||
|
|
@ -444,81 +435,95 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
|||
self.button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.button.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
self.button.setTitle(title, with: Font.regular(17.0), with: color, for: [])
|
||||
let titleColor: UIColor
|
||||
if case .join = self.action {
|
||||
titleColor = interfaceState.theme.chat.inputPanel.actionControlForegroundColor
|
||||
} else {
|
||||
titleColor = interfaceState.theme.chat.inputPanel.inputControlColor
|
||||
}
|
||||
self.buttonTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: titleColor)
|
||||
self.buttonTintTitle.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: .black)
|
||||
self.button.accessibilityLabel = title
|
||||
} else {
|
||||
self.action = nil
|
||||
}
|
||||
|
||||
self.discussButton.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
let panelHeight = defaultHeight(metrics: metrics)
|
||||
|
||||
if self.discussButton.isHidden {
|
||||
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable {
|
||||
if self.giftButton.isHidden && !isFirstTime {
|
||||
self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
self.giftButton.isHidden = false
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum)
|
||||
self.presentGiftOrSuggestTooltip()
|
||||
} else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = false
|
||||
self.presentGiftOrSuggestTooltip()
|
||||
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = false
|
||||
self.suggestedPostButton.isHidden = true
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
if let peer = interfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
if case let .broadcast(broadcastInfo) = peer.info, interfaceState.starGiftsAvailable {
|
||||
if self.giftButton.isHidden && !isFirstTime {
|
||||
self.giftButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.giftButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
self.giftButton.isHidden = false
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = !broadcastInfo.flags.contains(.hasMonoforum)
|
||||
self.presentGiftOrSuggestTooltip()
|
||||
} else if case let .broadcast(broadcastInfo) = peer.info, broadcastInfo.flags.contains(.hasMonoforum) {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = false
|
||||
self.presentGiftOrSuggestTooltip()
|
||||
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = false
|
||||
self.suggestedPostButton.isHidden = true
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
}
|
||||
if let action = self.action, action == .muteNotifications || action == .unmuteNotifications {
|
||||
let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0
|
||||
self.button.frame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: 0.0), size: CGSize(width: buttonWidth, height: panelHeight))
|
||||
} else {
|
||||
self.button.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: width - leftInset - rightInset, height: panelHeight))
|
||||
}
|
||||
self.giftButton.frame = CGRect(x: width - rightInset - panelHeight - 5.0, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
self.suggestedPostButton.frame = CGRect(x: leftInset + 5.0, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
|
||||
let availableWidth = min(600.0, width - leftInset - rightInset)
|
||||
let leftOffset = floor((width - availableWidth) / 2.0)
|
||||
self.button.frame = CGRect(origin: CGPoint(x: leftOffset, y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight))
|
||||
self.discussButton.frame = CGRect(origin: CGPoint(x: leftOffset + floor(availableWidth / 2.0), y: 0.0), size: CGSize(width: floor(availableWidth / 2.0), height: panelHeight))
|
||||
|
||||
let discussButtonSize = self.discussButton.bounds.size
|
||||
let discussTextSize = self.discussButtonText.updateLayout(discussButtonSize)
|
||||
self.discussButtonText.frame = CGRect(origin: CGPoint(x: floor((discussButtonSize.width - discussTextSize.width) / 2.0), y: floor((discussButtonSize.height - discussTextSize.height) / 2.0)), size: discussTextSize)
|
||||
|
||||
let badgeOffset = self.discussButtonText.frame.maxX + 5.0 - self.badgeBackground.frame.minX
|
||||
self.badgeBackground.frame = self.badgeBackground.frame.offsetBy(dx: badgeOffset, dy: 0.0)
|
||||
self.badgeText.frame = self.badgeText.frame.offsetBy(dx: badgeOffset, dy: 0.0)
|
||||
}
|
||||
|
||||
let indicatorSize = self.activityIndicator.bounds.size
|
||||
self.activityIndicator.frame = CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width - 12.0, y: floor((panelHeight - indicatorSize.height) / 2.0)), size: indicatorSize)
|
||||
let buttonTitleSize = self.buttonTitle.updateLayout(CGSize(width: width, height: panelHeight))
|
||||
let _ = self.buttonTintTitle.updateLayout(CGSize(width: width, height: panelHeight))
|
||||
let buttonSize = CGSize(width: buttonTitleSize.width + 16.0 * 2.0, height: 40.0)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonSize.width) / 2.0), y: floor((panelHeight - buttonSize.height) * 0.5)), size: buttonSize)
|
||||
transition.updateFrame(view: self.button, frame: buttonFrame)
|
||||
transition.updateFrame(view: self.buttonBackgroundView, frame: CGRect(origin: CGPoint(), size: buttonFrame.size))
|
||||
let buttonTintColor: GlassBackgroundView.TintColor
|
||||
if case .join = self.action {
|
||||
buttonTintColor = .init(kind: .custom, color: interfaceState.theme.chat.inputPanel.actionControlFillColor)
|
||||
} else {
|
||||
buttonTintColor = .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
|
||||
}
|
||||
self.buttonBackgroundView.update(size: buttonFrame.size, cornerRadius: buttonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: buttonTintColor, transition: ComponentTransition(transition))
|
||||
self.buttonTitle.frame = CGRect(origin: CGPoint(x: floor((buttonFrame.width - buttonTitleSize.width) * 0.5), y: floor((buttonFrame.height - buttonTitleSize.height) * 0.5)), size: buttonTitleSize)
|
||||
self.buttonTintTitle.frame = self.buttonTitle.frame
|
||||
|
||||
let giftButtonFrame = CGRect(x: width - rightInset - 40.0 - 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
|
||||
transition.updateFrame(view: self.giftButton, frame: giftButtonFrame)
|
||||
if let image = self.giftButtonIconView.image {
|
||||
transition.updateFrame(view: self.giftButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: giftButtonFrame.size)))
|
||||
}
|
||||
transition.updateFrame(view: self.giftButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: giftButtonFrame.size))
|
||||
self.giftButtonBackgroundView.update(size: giftButtonFrame.size, cornerRadius: giftButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
|
||||
|
||||
let helpButtonFrame = CGRect(x: width - rightInset - 8.0 - 40.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
|
||||
transition.updateFrame(view: self.helpButton, frame: helpButtonFrame)
|
||||
if let image = self.helpButtonIconView.image {
|
||||
transition.updateFrame(view: self.helpButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: helpButtonFrame.size)))
|
||||
}
|
||||
transition.updateFrame(view: self.helpButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: helpButtonFrame.size))
|
||||
self.helpButtonBackgroundView.update(size: helpButtonFrame.size, cornerRadius: helpButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
|
||||
|
||||
let suggestedPostButtonFrame = CGRect(x: leftInset + 8.0, y: floor((panelHeight - 40.0) * 0.5), width: 40.0, height: 40.0)
|
||||
transition.updateFrame(view: self.suggestedPostButton, frame: suggestedPostButtonFrame)
|
||||
if let image = self.suggestedPostButtonIconView.image {
|
||||
transition.updateFrame(view: self.suggestedPostButtonIconView, frame: image.size.centered(in: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size)))
|
||||
}
|
||||
transition.updateFrame(view: self.suggestedPostButtonBackgroundView, frame: CGRect(origin: CGPoint(), size: suggestedPostButtonFrame.size))
|
||||
self.suggestedPostButtonBackgroundView.update(size: suggestedPostButtonFrame.size, cornerRadius: suggestedPostButtonFrame.height * 0.5, isDark: interfaceState.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: interfaceState.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7)), transition: ComponentTransition(transition))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatInputAutocompletePanel",
|
||||
module_name = "ChatInputAutocompletePanel",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import GlassBackgroundComponent
|
||||
|
||||
public final class ChatInputAutocompletePanelEnvironment: Equatable {
|
||||
public let theme: PresentationTheme
|
||||
public let strings: PresentationStrings
|
||||
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||
public let dateTimeFormat: PresentationDateTimeFormat
|
||||
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
nameDisplayOrder: PresentationPersonNameOrder,
|
||||
dateTimeFormat: PresentationDateTimeFormat
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatInputAutocompletePanelEnvironment, rhs: ChatInputAutocompletePanelEnvironment) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.nameDisplayOrder != rhs.nameDisplayOrder {
|
||||
return false
|
||||
}
|
||||
if lhs.dateTimeFormat != rhs.dateTimeFormat {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ChatInputAutocompletePanelView: UIView {
|
||||
var contentTintView: UIView { get }
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ open class ChatInputPanelNode: ASDisplayNode {
|
|||
open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||
|
||||
private let reactionOverlayContainer: ChatMessageSelectionInputPanelNodeViewForOverlayContent
|
||||
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
|
||||
private var validLayout: (width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, metrics: LayoutMetrics, isSecondary: Bool, isMediaInputExpanded: Bool)?
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||
private var actions: ChatAvailableMessageActions?
|
||||
|
||||
|
|
@ -167,8 +167,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||
|
||||
if self.selectedMessages.isEmpty {
|
||||
self.actions = nil
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
self.canDeleteMessagesDisposable.set(nil)
|
||||
} else if let context = self.context {
|
||||
|
|
@ -176,8 +176,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||
|> deliverOnMainQueue).startStrict(next: { [weak self] actions in
|
||||
if let strongSelf = self {
|
||||
strongSelf.actions = actions
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight: maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = strongSelf.validLayout, let interfaceState = strongSelf.presentationInterfaceState {
|
||||
let _ = strongSelf.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: .immediate, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -348,13 +348,13 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||
}
|
||||
|
||||
private func update(transition: ContainedViewLayoutTransition) {
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
if let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded) = self.validLayout, let interfaceState = self.presentationInterfaceState {
|
||||
let _ = self.updateLayout(width: width, leftInset: leftInset, rightInset: rightInset, bottomInset: bottomInset, additionalSideInsets: additionalSideInsets, maxHeight: maxHeight, maxOverlayHeight: maxOverlayHeight, isSecondary: isSecondary, transition: transition, interfaceState: interfaceState, metrics: metrics, isMediaInputExpanded: isMediaInputExpanded)
|
||||
}
|
||||
}
|
||||
|
||||
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
override public func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, additionalSideInsets: UIEdgeInsets, maxHeight: CGFloat, maxOverlayHeight: CGFloat, isSecondary: Bool, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, metrics: LayoutMetrics, isMediaInputExpanded: Bool) -> CGFloat {
|
||||
self.validLayout = (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded)
|
||||
|
||||
let panelHeight = defaultHeight(metrics: metrics)
|
||||
|
||||
|
|
|
|||
|
|
@ -981,10 +981,13 @@ final class OverscrollContentsComponent: Component {
|
|||
let titleSize = self.titleNode.updateLayout(CGSize(width: availableSize.width - 32.0, height: 100.0))
|
||||
let titleBackgroundSize = CGSize(width: titleSize.width + 18.0, height: titleSize.height + 8.0)
|
||||
let titleBackgroundFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleBackgroundSize.width) / 2.0), y: fullHeight - titleBackgroundSize.height - 8.0), size: titleBackgroundSize)
|
||||
self.titleBackgroundNode.frame = titleBackgroundFrame
|
||||
self.titleBackgroundNode.position = titleBackgroundFrame.center
|
||||
self.titleBackgroundNode.bounds = CGRect(origin: CGPoint(), size: titleBackgroundFrame.size)
|
||||
self.titleBackgroundNode.update(rect: titleBackgroundFrame.offsetBy(dx: component.absoluteRect.minX, dy: component.absoluteRect.minY), within: component.absoluteSize, color: component.backgroundColor, wallpaperNode: component.wallpaperNode, transition: .immediate)
|
||||
self.titleBackgroundNode.cornerRadius = min(titleBackgroundFrame.width, titleBackgroundFrame.height) / 2.0
|
||||
self.titleNode.frame = titleSize.centered(in: titleBackgroundFrame)
|
||||
let titleFrame = titleSize.centered(in: titleBackgroundFrame)
|
||||
self.titleNode.position = titleFrame.center
|
||||
self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
|
||||
let backgroundClippingFrame = CGRect(origin: CGPoint(x: floor(-backgroundWidth / 2.0), y: -fullHeight), size: CGSize(width: backgroundWidth, height: isFullyExpanded ? backgroundWidth : fullHeight))
|
||||
self.backgroundClippingNode.cornerRadius = isFolderMask ? 10.0 : backgroundWidth / 2.0
|
||||
|
|
@ -1003,7 +1006,7 @@ final class OverscrollContentsComponent: Component {
|
|||
let transformTransition: ContainedViewLayoutTransition
|
||||
if self.isFullyExpanded != isFullyExpanded {
|
||||
self.isFullyExpanded = isFullyExpanded
|
||||
transformTransition = .animated(duration: 0.12, curve: .easeInOut)
|
||||
transformTransition = .animated(duration: 0.18, curve: .easeInOut)
|
||||
|
||||
if isFullyExpanded {
|
||||
func animateBounce(layer: CALayer) {
|
||||
|
|
@ -1067,8 +1070,11 @@ final class OverscrollContentsComponent: Component {
|
|||
|
||||
transformTransition.updateSublayerTransformOffset(layer: self.avatarOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))
|
||||
transformTransition.updateSublayerTransformOffset(layer: self.arrowOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? -(fullHeight - backgroundWidth) : 0.0))
|
||||
|
||||
transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : 20.0))
|
||||
|
||||
transformTransition.updateSublayerTransformOffset(layer: self.titleOffsetContainer.layer, offset: CGPoint(x: 0.0, y: isFullyExpanded ? 0.0 : (titleBackgroundSize.height + 50.0)))
|
||||
transformTransition.updateTransformScale(layer: self.titleBackgroundNode.layer, scale: isFullyExpanded ? 1.0 : 0.001)
|
||||
transformTransition.updateTransformScale(layer: self.titleNode.layer, scale: isFullyExpanded ? 1.0 : 0.001)
|
||||
|
||||
transformTransition.updateSublayerTransformScale(node: self.avatarExtraScalingContainer, scale: isFullyExpanded ? 1.0 : ((backgroundWidth - avatarInset * 2.0) / backgroundWidth))
|
||||
|
||||
|
|
@ -1209,50 +1215,3 @@ public final class ChatOverscrollControl: CombinedComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class ChatInputPanelOverscrollNode: ASDisplayNode {
|
||||
public let text: NSAttributedString
|
||||
public let priority: Int
|
||||
private let titleNode: ImmediateTextNodeWithEntities
|
||||
|
||||
public init(context: AccountContext, text: NSAttributedString, color: UIColor, priority: Int) {
|
||||
self.text = text
|
||||
self.priority = priority
|
||||
self.titleNode = ImmediateTextNodeWithEntities()
|
||||
|
||||
super.init()
|
||||
|
||||
let attributedText = NSMutableAttributedString(string: text.string)
|
||||
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
|
||||
attributedText.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: text.length))
|
||||
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
|
||||
for (key, value) in attributes {
|
||||
if key == ChatTextInputAttributes.bold {
|
||||
attributedText.addAttribute(.font, value: Font.bold(14.0), range: range)
|
||||
} else if key == ChatTextInputAttributes.italic {
|
||||
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||
} else if key == ChatTextInputAttributes.monospace {
|
||||
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||
} else {
|
||||
attributedText.addAttribute(key, value: value, range: range)
|
||||
}
|
||||
}
|
||||
})
|
||||
self.titleNode.attributedText = attributedText
|
||||
self.titleNode.visibility = true
|
||||
self.titleNode.arguments = TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: color.withMultipliedAlpha(0.1),
|
||||
attemptSynchronous: true
|
||||
)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
|
||||
public func update(size: CGSize) {
|
||||
let titleSize = self.titleNode.updateLayout(size)
|
||||
self.titleNode.frame = titleSize.centered(in: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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] = []
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"app": "12.0",
|
||||
"app": "12.1",
|
||||
"xcode": "16.2",
|
||||
"bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217",
|
||||
"macos": "15"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue