Swiftgram/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift
2024-01-19 22:23:10 +04:00

225 lines
12 KiB
Swift

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